2
0
Fork 0

Compare commits

...

4 commits

Author SHA1 Message Date
Carlos Galindo
1f182df473 feat: alpine packages feed reader (beta) 2026-05-31 00:57:33 +02:00
Carlos Galindo
800c4c2ee3 FeedReader: do not update etag on error response 2026-05-31 00:56:03 +02:00
Carlos Galindo
8e7fcc6ed2 update packages watched 2026-05-31 00:53:35 +02:00
Carlos Galindo
11c3103e3b FeedReader: now able to post multiple issues on a new version 2026-05-31 00:51:47 +02:00
2 changed files with 72 additions and 41 deletions

View file

@ -14,19 +14,40 @@ from issue_generator import FeedReader, GithubReader, GithubTagReader, PIPYReade
TIMEOUT TIMEOUT
import _secrets import _secrets
class AlpinePackageReader(FeedReader):
'''Custom feed reader for Alpine packages'''
def __init__(self, package: str, targets: IssuePoster):
super().__init__(package, "https://gitlab.alpinelinux.org/alpine/aports/-/commits/master.atom", targets)
self.regex = r'community/([^:]+): upgrade to (.+)'
self.package = package
def is_valid_item(self, entry) -> bool:
match = re.search(self.regex, entry.title)
return match and match.groups(1) == self.package
def entry_get_version(self, entry: dict[str, Any]) -> tuple[str, str]:
match = re.search(self.regex, entry.title)
if match and match.groups(1) == self.package:
return (match.groups(2),) * 2
raise RuntimeError(f"Checking version for package {self.package} in invalid entry titled: {entry.title}")
def entry_get_link(self, entry: dict[str, Any]) -> str:
return entry.id
class UDSClientReader(FeedReader): class UDSClientReader(FeedReader):
'''Custom feed reader for UDSClient, whose version number appears in a .js file.''' '''Custom feed reader for UDSClient, whose version number appears in a .js file.'''
def __init__(self, target: IssuePoster): def __init__(self, targets: IssuePoster):
'''Creates a new UDSClient Reader.''' '''Creates a new UDSClient Reader.'''
super().__init__("udsclient", "https://polilabs.upv.es/uds/utility/uds.js", target) super().__init__("udsclient", "https://polilabs.upv.es/uds/utility/uds.js", targets)
def read_feed(self) -> bool | None: def read_feed(self) -> bool | None:
'''Checks for a new version of udsclient, by checking a .js file.''' '''Checks for a new version of udsclient, by checking a .js file.'''
get = requests.get(self.url, timeout = TIMEOUT) get = requests.get(self.url, timeout = TIMEOUT)
if not self.target.check_req(get, self.name): for target in self.targets:
return None if not target.check_req(get, self.name):
return None
match = re.search(r"[/a-zA-Z_-]+udsclient\d+-(\d+\.\d+\.\d+)\.tar\.gz", get.text) match = re.search(r"[/a-zA-Z_-]+udsclient\d+-(\d+\.\d+\.\d+)\.tar\.gz", get.text)
if not match: if not match:
return None return None
@ -55,7 +76,7 @@ class NCAppReader(GithubReader):
'''All GitHub releases readers that must alert Forgejo's `archpkgs`.''' '''All GitHub releases readers that must alert Forgejo's `archpkgs`.'''
def __init__(self, app, project): def __init__(self, app, project):
super().__init__(name = app, project = project, super().__init__(name = app, project = project,
target = CGJForgejoPoster("archpkgs/" + app)) targets = CGJForgejoPoster("archpkgs/" + app))
# Issue Posters # Issue Posters
@ -70,20 +91,18 @@ FEED_READERS = [
# Name FeedType Project TargetProject # Name FeedType Project TargetProject
################################ Software used in MIST (Gitlab) ################################### ################################ Software used in MIST (Gitlab) ###################################
# LanguageTool GHTags languagetool-org/languagetool 36 (sysadmin/boira) # LanguageTool GHTags languagetool-org/languagetool 36 (sysadmin/boira)
GithubTagReader(name = "LanguageTool", project = "languagetool-org/languagetool", GithubTagReader(name = "LanguageTool", project = "languagetool-org/languagetool",
target = GITLAB_BOIRA_CARGAJI), targets = GITLAB_BOIRA_CARGAJI),
# meshcentral GHReleases Ylianst/MeshCentral 36 (sysadmin/boira) # meshcentral GHReleases Ylianst/MeshCentral 36 (sysadmin/boira)
GithubReader(name = "meshcentral", project = "Ylianst/MeshCentral", # meshcentral GHReleases Ylianst/MeshCentral archpkgs/meshcentral
target = GITLAB_BOIRA_CARGAJI), GithubReader(name = "meshcentral", project = "Ylianst/MeshCentral",
targets = [GITLAB_BOIRA_CARGAJI, CGJForgejoPoster("archpkgs/meshcentral")]),
# python3-snakes PIPY snakes 37 (packages/python3-snakes) # python3-snakes PIPY snakes 37 (packages/python3-snakes)
PIPYReader(name = "python3-snakes", package = "snakes", PIPYReader(name = "python3-snakes", package = "snakes",
target = GITLAB_SNAKES_CARGAJI), targets = GITLAB_SNAKES_CARGAJI),
################################ Software that I package (Forgejo) ################################ ################################ Software that I package (Forgejo) ################################
# pdfbooklet GHReleases Averell7/PdfBooklet archpkgs/pdfbooklet # pdfbooklet GHReleases Averell7/PdfBooklet archpkgs/pdfbooklet
NCAppReader(app = "pdfbooklet", project = "Averell7/PdfBooklet"), NCAppReader(app = "pdfbooklet", project = "Averell7/PdfBooklet"),
# meshcentral GHReleases Ylianst/MeshCentral archpkgs/meshcentral
GithubReader(name = "meshcentral", project = "Ylianst/MeshCentral",
target = CGJForgejoPoster("archpkgs/meshcentral")),
# nc-cospend GHReleases eneiluj/cospend-nc archpkgs/nextcloud-app-cospend # nc-cospend GHReleases eneiluj/cospend-nc archpkgs/nextcloud-app-cospend
NCAppReader(app = "nextcloud-app-cospend", project = "eneiluj/cospend-nc"), NCAppReader(app = "nextcloud-app-cospend", project = "eneiluj/cospend-nc"),
# nc-f_autotagging GHReleases # nc-f_autotagging GHReleases
@ -94,32 +113,41 @@ FEED_READERS = [
NCAppReader(app = "nextcloud-app-forms", project = "nextcloud/forms"), NCAppReader(app = "nextcloud-app-forms", project = "nextcloud/forms"),
# nc-maps GHReleases nextcloud/maps archpkgs/nextcloud-app-maps # nc-maps GHReleases nextcloud/maps archpkgs/nextcloud-app-maps
NCAppReader(app = "nextcloud-app-maps", project = "nextcloud/maps"), NCAppReader(app = "nextcloud-app-maps", project = "nextcloud/maps"),
# nc-music GHReleases owncloud/music archpkgs/nextcloud-app-music # nc-music GHReleases nc-music/music archpkgs/nextcloud-app-music
NCAppReader(app = "nextcloud-app-music", project = "owncloud/music"), NCAppReader(app = "nextcloud-app-music", project = "nc-music/music"),
# nc-onlyoffice GHReleases ONLYOFFICE/onlyoffice-nextcloud archpkgs/nextcloud-app-onlyoffice # nc-onlyoffice GHReleases ONLYOFFICE/onlyoffice-nextcloud archpkgs/nextcloud-app-onlyoffice
NCAppReader(app = "nextcloud-app-onlyoffice", project = "ONLYOFFICE/onlyoffice-nextcloud"), # NCAppReader(app = "nextcloud-app-onlyoffice", project = "ONLYOFFICE/onlyoffice-nextcloud"),
# nc-phonetrack GHReleases julien-nc/phonetrack archpkgs/nextcloud-app-phonetrack
NCAppReader(app = "nextcloud-app-phonetrack", project = "julien-nc/phonetrack"),
# nc-polls GHReleases nextcloud/polls archpkgs/nextcloud-app-polls # nc-polls GHReleases nextcloud/polls archpkgs/nextcloud-app-polls
NCAppReader(app = "nextcloud-app-polls", project = "nextcloud/polls"), NCAppReader(app = "nextcloud-app-polls", project = "nextcloud/polls"),
# nc-socialsharing GHReleases nextcloud/socialsharing archpkgs/nextcloud-app-socialsharing # nc-socialsharing GHReleases nextcloud/socialsharing archpkgs/nextcloud-app-socialsharing
NCAppReader(app = "nextcloud-app-socialsharing", project = "nextcloud/socialsharing"), NCAppReader(app = "nextcloud-app-socialsharing", project = "nextcloud/socialsharing"),
# udsclient Custom --- archpkgs/udsclient # udsclient Custom --- archpkgs/udsclient
UDSClientReader(target = CGJForgejoPoster(project="archpkgs/udsclient")), UDSClientReader(targets = CGJForgejoPoster(project="archpkgs/udsclient")),
# vigil GHReleases valeriansaliou/vigil archpkgs/vigil # vigil GHReleases valeriansaliou/vigil archpkgs/vigil
NCAppReader(app = "vigil", project = "valeriansaliou/vigil"), NCAppReader(app = "vigil", project = "valeriansaliou/vigil"),
# vigil-local GHReleases valeriansaliou/vigil-local archpkgs/vigil-local # vigil-local GHReleases valeriansaliou/vigil-local archpkgs/vigil-local
NCAppReader(app = "vigil-local", project = "valeriansaliou/vigil-local"), NCAppReader(app = "vigil-local", project = "valeriansaliou/vigil-local"),
# yourls GHReleases YOURLS/YOURLS archpkgs/yourls # yourls GHReleases YOURLS/YOURLS archpkgs/yourls
NCAppReader(app = "yourls", project = "YOURLS/YOURLS"), NCAppReader(app = "yourls", project = "YOURLS/YOURLS"),
################################ Critical software in servers ################################
# zfs
GithubReader(name = "zfs-dkms", project = "openzfs/zfs",
targets = CGJForgejoPoster("kauron/solaris-packages")),
# yay GHReleases Jguer/yay aur?
GithubReader(name = "yay", project = "Jguer/yay",
targets = CGJForgejoPoster("kauron/solaris-packages")),
# qbittorrent-alpine
AlpinePackageReader(package = "qbittorrent",
targets = CGJForgejoPoster("kauron/solaris-packages")),
] ]
## PENDING: ## PENDING:
# Software that I use exposed to the Internet # Software that I use exposed to the Internet
# Name FeedType Project # Name FeedType Project
# gad GHReleases brianreumere/gandi-automatic-dns aur? # gad GHReleases brianreumere/gandi-automatic-dns aur?
# nextcloud GHReleases nextcloud/server pacman?
# peertube GHReleases Chocobozzz/PeerTube aur? # peertube GHReleases Chocobozzz/PeerTube aur?
# vaultwarden GHReleases dani-garcia/vaultwarden pacman?
# wallabag GHReleases wallabag/wallabag pacman?
# yay GHReleases Jguer/yay aur?
# Others? # Others?
# duplicati ??? # duplicati ???

View file

@ -157,15 +157,19 @@ class ForgejoPoster(IssuePoster):
class FeedReader: class FeedReader:
'''Class to read an RSS/Atom feed and post an issue if a new version is found.''' '''Class to read an RSS/Atom feed and post an issue if a new version is found.'''
def __init__(self, name: str, url: str, target: IssuePoster): def __init__(self, name: str, url: str, targets: list[IssuePoster]):
'''Create a new feed reader''' '''Create a new feed reader'''
self.name = name self.name = name
self.url = url self.url = url
self.target = target self.targets = targets if type(targets) == list else [targets]
self.version_file = CONFIG_DIR + self.name self.version_file = CONFIG_DIR + self.name
self.etag_file = CONFIG_DIR + self.name + ".etag" self.etag_file = CONFIG_DIR + self.name + ".etag"
self.beta_strings = [ "nightly", "beta", "alpha", "rc", "pr" ] self.beta_strings = [ "nightly", "beta", "alpha", "rc", "pr" ]
def is_valid_item(self, entry) -> bool:
version = self.entry_get_version(entry)[0]
return all([beta not in version for beta in self.beta_strings])
def first_item(self) -> dict[str, Any] | None | int: def first_item(self) -> dict[str, Any] | None | int:
'''Get the first item of the feed (newest)''' '''Get the first item of the feed (newest)'''
if os.path.isfile(self.etag_file): if os.path.isfile(self.etag_file):
@ -173,7 +177,7 @@ class FeedReader:
etag = file.readline() etag = file.readline()
else: etag = None else: etag = None
feed = feedparser.parse(self.url, etag=etag) feed = feedparser.parse(self.url, etag=etag)
if feed.etag and feed.etag != etag: if feed.etag and feed.etag != etag and feed.status // 100 in [2, 3]:
if not os.path.isdir(CONFIG_DIR): if not os.path.isdir(CONFIG_DIR):
os.mkdir(CONFIG_DIR) os.mkdir(CONFIG_DIR)
with open(self.etag_file, mode='w', encoding="UTF-8") as file: with open(self.etag_file, mode='w', encoding="UTF-8") as file:
@ -183,12 +187,7 @@ class FeedReader:
if len(feed.entries) == 0: if len(feed.entries) == 0:
return None return None
for entry in feed.entries: for entry in feed.entries:
skip = False if self.is_valid_item(entry):
for beta in self.beta_strings:
if beta in self.entry_get_version(entry)[0]:
skip = True
break
if not skip:
return entry return entry
return None return None
@ -223,10 +222,14 @@ class FeedReader:
if match: if match:
return False return False
# Match 2: with repository issues # Match 2: with repository issues
if not self.target.issue_exists(self.name, version): issues_posted = True
# Post the issue for target in self.targets:
if not self.target.post_issue(self.name, version, link): if not target.issue_exists(self.name, version):
return False # Post the issue
if not target.post_issue(self.name, version, link):
issues_posted = False
if not issues_posted:
return False
# Save to disk # Save to disk
if not os.path.isdir(CONFIG_DIR): if not os.path.isdir(CONFIG_DIR):
os.makedirs(CONFIG_DIR) os.makedirs(CONFIG_DIR)
@ -238,9 +241,9 @@ class FeedReader:
class PIPYReader(FeedReader): class PIPYReader(FeedReader):
'''Reader specialized in the PIPY repository.''' '''Reader specialized in the PIPY repository.'''
def __init__(self, name: str, package: str, target: IssuePoster): def __init__(self, name: str, package: str, targets: IssuePoster):
'''Create a new PIPY reader for the given package.''' '''Create a new PIPY reader for the given package.'''
super().__init__(name, f"https://pypi.org/rss/project/{package}/releases.xml", target) super().__init__(name, f"https://pypi.org/rss/project/{package}/releases.xml", targets)
def entry_get_version(self, entry: dict[str, Any]) -> tuple[str, str]: def entry_get_version(self, entry: dict[str, Any]) -> tuple[str, str]:
return entry.title, entry.title return entry.title, entry.title
@ -252,9 +255,9 @@ class PIPYReader(FeedReader):
class GithubTagReader(FeedReader): class GithubTagReader(FeedReader):
'''Reader specialized in GitHub Tags Atom feed.''' '''Reader specialized in GitHub Tags Atom feed.'''
def __init__(self, name: str, project: str, target: IssuePoster): def __init__(self, name: str, project: str, targets: IssuePoster):
'''Create a new GitHub Tags reader for the given project.''' '''Create a new GitHub Tags reader for the given project.'''
super().__init__(name, f"https://github.com/{project}/tags.atom", target) super().__init__(name, f"https://github.com/{project}/tags.atom", targets)
def entry_get_version(self, entry: dict[str, Any]) -> tuple[str, str]: def entry_get_version(self, entry: dict[str, Any]) -> tuple[str, str]:
return entry.title, str(entry.id) return entry.title, str(entry.id)
@ -266,8 +269,8 @@ class GithubTagReader(FeedReader):
class GithubReader(GithubTagReader): class GithubReader(GithubTagReader):
'''Reader specialized in GitHub releases' Atom feed.''' '''Reader specialized in GitHub releases' Atom feed.'''
def __init__(self, name: str, project: str, target: IssuePoster): def __init__(self, name: str, project: str, targets: IssuePoster):
super().__init__(name, project, target) super().__init__(name, project, targets)
self.url = f"https://github.com/{project}/releases.atom" self.url = f"https://github.com/{project}/releases.atom"