diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 6e94c2c2e786173cc275e215e2a7d7d7fffa266c..b623caf2098f61aac7a7ed0efc4a8f3a1f077b9b 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,6 +1,19 @@ # See https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/ci/templates/Python.gitlab-ci.yml +stages: + - .pre + - lemmy + - build + - test + - deploy + - .post + include: + - local: .gitlab/build-year.yml + inputs: + year: 2026 + rules: + - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH - local: .gitlab/build-year.yml inputs: year: 2025 @@ -12,6 +25,18 @@ include: rules: - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH +lemmy fetcher: + image: python:3-alpine + stage: lemmy + rules: + - if: $CI_PIPELINE_SOURCE == "schedule" + script: + - python ./tools/lemmy_fetcher.py + cache: + key: lemmy-fetcher + paths: + - .fetcher + build: image: python:3-alpine stage: build @@ -20,12 +45,14 @@ build: script: - python ./tools/merge_data.py ./entries/2024 ./atlas2024.json - python ./tools/merge_data.py ./entries/2025 ./atlas2025.json + - python ./tools/merge_data.py ./entries/2026 ./atlas2026.json artifacts: expire_in: "1 hour" expose_as: "atlas_json" paths: - "atlas2024.json" - "atlas2025.json" + - "atlas2026.json" comment: image: alpine @@ -41,6 +68,7 @@ comment: - build before_script: - apk add curl jq git + - git fetch origin main script: - | # 📝 Build MR Comment Body diff --git a/entries/2026/.keep b/entries/2026/.keep new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/tools/lemmy_fetcher.py b/tools/lemmy_fetcher.py index 7080f183a25400c8afc235d164ebdf477a207add..68352de35f245b93da5c77cf01b5ce1ccf73ddad 100644 --- a/tools/lemmy_fetcher.py +++ b/tools/lemmy_fetcher.py @@ -8,6 +8,16 @@ from lib.gitlab_forge import GitlabForge import hashlib +# cached in gitlab-ci +STATE_FOLDER = ".fetcher/" + +LEMMY_HOST = "toast.ooo" + +GIT_CONFIG = [ + "-c", "user.name=Atlas Bot [CI]", + "-c", "user.email=canvas-atlas@sc07.com" +] + EDITIONS = { "2024": { "community": "2024lemmycanvasatlas", @@ -17,12 +27,42 @@ "community": "2025fedicanvasatlas", "entry_folder": "entries/2025/" }, + "2026": { + "community": "2026canvasatlas", + "entry_folder": "entries/2026/" + }, "test": { "community": "testatlas", "entry_folder": "entries/2025/" } } +ENDPOINTS = { + "login": "https://" + LEMMY_HOST + "/api/v3/user/login", + "logout": "https://" + LEMMY_HOST + "/api/v3/user/logout", + "list_posts": "https://" + LEMMY_HOST + "/api/v3/post/list", + "comment": "https://" + LEMMY_HOST + "/api/v3/comment" +} + +JWT = "" + +def login(): + global JWT + login_req = requests.post(ENDPOINTS["login"], json={ + "username_or_email": os.environ.get("LEMMY_USERNAME"), + "password": os.environ.get("LEMMY_PASSWORD"), + "totp_2fa_token": "", + }) + + assert login_req.status_code == 200 + JWT = login_req.json()["jwt"] + +def logout(): + global JWT + requests.post(ENDPOINTS["logout"], headers={ + "Authorization": "Bearer " + JWT + }) + class LemmyFetcher: def __init__(self, edition): assert edition in EDITIONS @@ -31,8 +71,9 @@ def __init__(self, edition): self.forge = GitlabForge() self.forge.from_env() self.read_ids = set() - os.makedirs("processed_posts", exist_ok = True) - os.makedirs("failed_posts", exist_ok = True) + + self.prepare_dirs() + for filename in os.listdir("processed_posts"): self.read_ids.add(filename) @@ -40,15 +81,10 @@ def __init__(self, edition): for filename in os.listdir("failed_posts"): self.failed_ids_hashes.add(filename) - login_req = requests.post("https://toast.ooo/api/v3/user/login", json={ - "username_or_email": os.environ.get("LEMMY_USERNAME"), - "password": os.environ.get("LEMMY_PASSWORD"), - "totp_2fa_token": "", - }) - print(login_req.content) - assert login_req.status_code == 200 - self.lemmy_jwt = login_req.json()["jwt"] - + def prepare_dirs(): + os.makedirs(STATE_FOLDER, exist_ok=True) + os.makedirs(STATE_FOLDER + "processed_posts", exist_ok=True) + os.makedirs(STATE_FOLDER + "failed_posts", exist_ok=True) def is_post_failed(self, post_id, body): entry_key = str(post_id) + "-" + self.hash_body(body) @@ -138,7 +174,7 @@ def process_post(self, post_body, make_pr): if make_pr: if success: subprocess.check_call(["git", "add", EDITIONS[self.edition]["entry_folder"]]) - subprocess.check_call(["git", "-c", "user.name=lemmy_canvas_bot", "-c", "user.email=lemmy_canvas_bot@mariusdavid.fr", "commit", "-m", "Add submission from Lemmy post " + str(post_id)]) + subprocess.check_call(["git", *GIT_CONFIG, "commit", "-m", "Add submission from Lemmy post " + str(post_id)]) subprocess.check_call(["git", "push", "--force", "origin", new_branch_name]) pr_url = self.forge.does_pr_already_exist(new_branch_name) if pr_url == False: @@ -155,18 +191,18 @@ def process_post(self, post_body, make_pr): f.close() def send_comment(self, post_id, comment): - req = requests.post("https://toast.ooo/api/v3/comment", json={ + req = requests.post(ENDPOINTS["comment"], json={ "content": comment, "post_id": post_id, }, headers = { - "Authorization": "Bearer " + self.lemmy_jwt + "Authorization": "Bearer " + JWT }) print(req.content) assert req.status_code == 200 print("Commented on post " + str(post_id)) def crawl_latest(self): - req = requests.get("https://toast.ooo/api/v3/post/list", params={ + req = requests.get(ENDPOINTS["list_posts"], params={ "community_name": EDITIONS[self.edition]["community"], "type_": "All", "sort": "New", @@ -177,7 +213,7 @@ def crawl_latest(self): data = req.json() - print("Fetched " + str(len(data["posts"])) + " post fetched"); + print("Fetched " + str(len(data["posts"])) + " posts"); for post in data["posts"]: try: self.process_post(post["post"], True) @@ -186,9 +222,15 @@ def crawl_latest(self): print(traceback.format_exc()) subprocess.check_call(["git", "checkout", "main"]) +login() + +lm2026 = LemmyFetcher("2026") +lm2026.crawl_latest() lm2025 = LemmyFetcher("2025") lm2025.crawl_latest() lm2024 = LemmyFetcher("2024") lm2024.crawl_latest() + +logout() \ No newline at end of file diff --git a/tools/pre_process.py b/tools/pre_process.py index 84268d9de9c7b545ddc6f0b2b8352feff8e51d83..65646e6c1ba443db2a3533c956318a96dd73d998 100644 --- a/tools/pre_process.py +++ b/tools/pre_process.py @@ -13,7 +13,7 @@ def process_edition(year, community): .replace("{YEAR}", year) \ .replace("{COMMUNITY}", community) - for known_year in ["2025", "2024"]: + for known_year in ["2026", "2025", "2024"]: if known_year == year: index = index.replace("{" + known_year + "ONLYCOMMENTSTART}", "") @@ -32,6 +32,8 @@ def process_edition(year, community): process_edition("2024", "2024lemmycanvasatlas") elif sys.argv[1] == "2025": process_edition("2025", "2025fedicanvasatlas") +elif sys.argv[1] == "2026": + process_edition("2026", "2026canvasatlas") else: print("Unrecognizeable edition: " + sys.argv[1]) exit(-1) diff --git a/web/_img/fedicanvas2026/final.png b/web/_img/fedicanvas2026/final.png new file mode 100644 index 0000000000000000000000000000000000000000..fe3c62eb59a980118e2437810a05c44d49901b63 Binary files /dev/null and b/web/_img/fedicanvas2026/final.png differ diff --git a/web/_js/config-2026.js b/web/_js/config-2026.js new file mode 100644 index 0000000000000000000000000000000000000000..242acfc2009d1ecc803e177816601870e7c7ff26 --- /dev/null +++ b/web/_js/config-2026.js @@ -0,0 +1,262 @@ +// This script only applies to this instance of the Atlas. +// Please also check code indicated with "@instanceonly" outside this file. +// TODO: Avoid having instance-only code inside the main scripts to make updating easier. + +const prodDomain = "2026.canvas-atlas.fediverse.events"; +window.prodDomain = prodDomain; + +const instanceId = "canvas2026"; +window.instanceId = instanceId; + +const instanceSubreddit = null; +window.instanceSubreddit = instanceSubreddit; + +const instanceRepo = null; +window.instanceRepo = instanceRepo; + +const pageTitle = "2026 Fediverse Canvas Atlas"; +window.pageTitle = pageTitle; + +const canvasSize = { + x: 500, + y: 500, +}; +window.canvasSize = canvasSize; + +const canvasOffset = { + x: 0, + y: 0, +}; +window.canvasOffset = canvasOffset; + +const canvasCenter = { + x: canvasSize.x / 2 + canvasOffset.x, + y: canvasSize.y / 2 + canvasOffset.y, +}; +window.canvasCenter = canvasCenter; + +const variationsConfig = { + default: { + name: "canvas", + code: "", + default: 48, + drawablePeriods: [1, 48], + drawableRegions: [ + [ + [0, 0], + [0, 0, 499, 499], + ], + ], + versions: [ + { + timestamp: "blank", + url: "./_img/fedicanvas2025/canvas-1752285642.png", + }, + { + timestamp: 1752289199, + url: "./_img/fedicanvas2025/canvas-1752289199.png", + }, + { + timestamp: 1752292797, + url: "./_img/fedicanvas2025/canvas-1752292797.png", + }, + { + timestamp: 1752296399, + url: "./_img/fedicanvas2025/canvas-1752296399.png", + }, + { + timestamp: 1752299999, + url: "./_img/fedicanvas2025/canvas-1752299999.png", + }, + { + timestamp: 1752303597, + url: "./_img/fedicanvas2025/canvas-1752303597.png", + }, + { + timestamp: 1752307199, + url: "./_img/fedicanvas2025/canvas-1752307199.png", + }, + { + timestamp: 1752310797, + url: "./_img/fedicanvas2025/canvas-1752310797.png", + }, + { + timestamp: 1752314399, + url: "./_img/fedicanvas2025/canvas-1752314399.png", + }, + { + timestamp: 1752317999, + url: "./_img/fedicanvas2025/canvas-1752317999.png", + }, + { + timestamp: 1752321599, + url: "./_img/fedicanvas2025/canvas-1752321599.png", + }, + { + timestamp: 1752325199, + url: "./_img/fedicanvas2025/canvas-1752325199.png", + }, + { + timestamp: 1752328799, + url: "./_img/fedicanvas2025/canvas-1752328799.png", + }, + { + timestamp: 1752332399, + url: "./_img/fedicanvas2025/canvas-1752332399.png", + }, + { + timestamp: 1752335999, + url: "./_img/fedicanvas2025/canvas-1752335999.png", + }, + { + timestamp: 1752339599, + url: "./_img/fedicanvas2025/canvas-1752339599.png", + }, + { + timestamp: 1752343199, + url: "./_img/fedicanvas2025/canvas-1752343199.png", + }, + { + timestamp: 1752346799, + url: "./_img/fedicanvas2025/canvas-1752346799.png", + }, + { + timestamp: 1752350399, + url: "./_img/fedicanvas2025/canvas-1752350399.png", + }, + { + timestamp: 1752353999, + url: "./_img/fedicanvas2025/canvas-1752353999.png", + }, + { + timestamp: 1752357598, + url: "./_img/fedicanvas2025/canvas-1752357598.png", + }, + { + timestamp: 1752361199, + url: "./_img/fedicanvas2025/canvas-1752361199.png", + }, + { + timestamp: 1752364799, + url: "./_img/fedicanvas2025/canvas-1752364799.png", + }, + { + timestamp: 1752368399, + url: "./_img/fedicanvas2025/canvas-1752368399.png", + }, + { + timestamp: 1752371999, + url: "./_img/fedicanvas2025/canvas-1752371999.png", + }, + { + timestamp: 1752375597, + url: "./_img/fedicanvas2025/canvas-1752375597.png", + }, + { + timestamp: 1752379199, + url: "./_img/fedicanvas2025/canvas-1752379199.png", + }, + { + timestamp: 1752382799, + url: "./_img/fedicanvas2025/canvas-1752382799.png", + }, + { + timestamp: 1752386399, + url: "./_img/fedicanvas2025/canvas-1752386399.png", + }, + { + timestamp: 1752389999, + url: "./_img/fedicanvas2025/canvas-1752389999.png", + }, + { + timestamp: 1752393599, + url: "./_img/fedicanvas2025/canvas-1752393599.png", + }, + { + timestamp: 1752397199, + url: "./_img/fedicanvas2025/canvas-1752397199.png", + }, + { + timestamp: 1752400799, + url: "./_img/fedicanvas2025/canvas-1752400799.png", + }, + { + timestamp: 1752404399, + url: "./_img/fedicanvas2025/canvas-1752404399.png", + }, + { + timestamp: 1752407998, + url: "./_img/fedicanvas2025/canvas-1752407998.png", + }, + { + timestamp: 1752411599, + url: "./_img/fedicanvas2025/canvas-1752411599.png", + }, + { + timestamp: 1752415199, + url: "./_img/fedicanvas2025/canvas-1752415199.png", + }, + { + timestamp: 1752418799, + url: "./_img/fedicanvas2025/canvas-1752418799.png", + }, + { + timestamp: 1752422399, + url: "./_img/fedicanvas2025/canvas-1752422399.png", + }, + { + timestamp: 1752425999, + url: "./_img/fedicanvas2025/canvas-1752425999.png", + }, + { + timestamp: 1752429594, + url: "./_img/fedicanvas2025/canvas-1752429594.png", + }, + { + timestamp: 1752433199, + url: "./_img/fedicanvas2025/canvas-1752433199.png", + }, + { + timestamp: 1752436799, + url: "./_img/fedicanvas2025/canvas-1752436799.png", + }, + { + timestamp: 1752440399, + url: "./_img/fedicanvas2025/canvas-1752440399.png", + }, + { + timestamp: 1752443999, + url: "./_img/fedicanvas2025/canvas-1752443999.png", + }, + { + timestamp: 1752447596, + url: "./_img/fedicanvas2025/canvas-1752447596.png", + }, + { + timestamp: 1752451198, + url: "./_img/fedicanvas2025/canvas-1752451198.png", + }, + { + timestamp: 1752454799, + url: "./_img/fedicanvas2025/canvas-1752454799.png", + }, + { + timestamp: "final", + url: "./_img/fedicanvas2026/final.png", + }, + ], + icon: '', + }, +}; +window.variationsConfig = variationsConfig; + +let defaultVariation = "default"; +window.defaultVariation = defaultVariation; + +let defaultPeriod = variationsConfig[defaultVariation].default; +window.defaultPeriod = defaultPeriod; + +const useNumericalId = true; +window.useNumericalId = useNumericalId; + +window.defaultAtlasURL = "./atlas/2026.json"; diff --git a/web/_js/config-common.js b/web/_js/config-common.js index f67aed7157ce88ced53f81af4872bfcdf132eb42..1b4a68b655f97ca3d00d5555d940616cf9317346 100644 --- a/web/_js/config-common.js +++ b/web/_js/config-common.js @@ -116,6 +116,7 @@ console.info( %cCopyright (c) 2017 Roland Rytz Copyright (c) 2023 Place Atlas Initiative and contributors Copyright (c) 2025 Fediverse Canvas Atlas contributors +Copyright (c) 2026 Fediverse Canvas Atlas contributors Licensed under AGPL-3.0 (https://2023.place-atlas.stefanocoding.me/license.txt) https://sc07.dev/fediverse.events/canvas-atlas @@ -123,5 +124,5 @@ https://sc07.dev/fediverse.events/canvas-atlas To get the image of the canvas, use downloadCanvas(). `, "font-size: 150%; line-height: 150%", - "" + "", ); diff --git a/web/index_template.html b/web/index_template.html index ef7952dcb117fdd473c6c65e6c91e87c89bf2f14..9e469e596f394fd2bcd057c08d7fa7787a59c956 100644 --- a/web/index_template.html +++ b/web/index_template.html @@ -49,20 +49,20 @@
- {2025ONLYCOMMENTSTART}{NOT2026ONLYCOMMENTEND}