From f60427abd79c49650445d94cc53369b40ab09965 Mon Sep 17 00:00:00 2001 From: LVL1024 <70866179+LVL1024@users.noreply.github.com> Date: Tue, 26 May 2026 16:11:33 +0000 Subject: [PATCH 1/2] feat: support Steam News patch notes and fix syntax duplication --- src/changelogs/constants.py | 4 + src/changelogs/fetch_changelogs.py | 260 +++++++++++++++++++++++++++-- src/changelogs/link_targets.json | 2 +- 3 files changed, 252 insertions(+), 14 deletions(-) diff --git a/src/changelogs/constants.py b/src/changelogs/constants.py index 57c1a2ae..c221ca95 100644 --- a/src/changelogs/constants.py +++ b/src/changelogs/constants.py @@ -1 +1,5 @@ CHANGELOG_RSS_URL = 'https://forums.playdeadlock.com/forums/changelog.10/index.rss' + +STEAM_APPID = '1422450' +# count=20 covers the rolling window; maxlength=0 means return full content +STEAM_NEWS_API_URL = f'https://api.steampowered.com/ISteamNews/GetNewsForApp/v2/' f'?appid={STEAM_APPID}&count=20&maxlength=0&format=json' diff --git a/src/changelogs/fetch_changelogs.py b/src/changelogs/fetch_changelogs.py index 0899c1c7..c5da1c17 100644 --- a/src/changelogs/fetch_changelogs.py +++ b/src/changelogs/fetch_changelogs.py @@ -1,4 +1,7 @@ import os +import re +import json +import mwclient from os import listdir from os.path import isfile, join from loguru import logger @@ -8,12 +11,10 @@ from urllib.parse import urlparse from utils import file_utils, json_utils from typing import TypedDict -from .constants import CHANGELOG_RSS_URL +from .constants import CHANGELOG_RSS_URL, STEAM_NEWS_API_URL from collections import defaultdict -from datetime import datetime +from datetime import datetime, timezone from zoneinfo import ZoneInfo -import re -import mwclient class ChangelogConfig(TypedDict): @@ -23,6 +24,7 @@ class ChangelogConfig(TypedDict): """ forum_id: str + steam_gid: str # Steam News article GID (new field, None for forum-only entries) date: str link: str @@ -48,9 +50,103 @@ class ChangelogString(TypedDict): changelog_string: str +# --------------------------------------------------------------------------- +# BBCode -> plain-text helpers for Steam News content +# --------------------------------------------------------------------------- + +# Section headers: [h1]Foo[/h1] or [h2]Foo[/h2] -> [ Foo ] +_BBCODE_HEADER = re.compile(r'\[h[1-6]\](.*?)\[/h[1-6]\]', re.IGNORECASE | re.DOTALL) +# List items: [*]text (inside [list][/list]) +_BBCODE_LIST_ITEM = re.compile(r'\[\*\](.*?)(?=\[\*\]|\[/list\]|$)', re.IGNORECASE | re.DOTALL) +_BBCODE_LIST_WRAPPER = re.compile(r'\[/?list[^\]]*\]', re.IGNORECASE) +# Simple inline tags: [b], [i], [u], [s], [/b] ... etc. +_BBCODE_INLINE = re.compile(r'\[/?(b|i|u|s|strike|code|quote)[^\]]*\]', re.IGNORECASE) +# URL tags: [url=...]text[/url] or [url]...[/url] +_BBCODE_URL = re.compile(r'\[url=[^\]]*\](.*?)\[/url\]', re.IGNORECASE | re.DOTALL) +_BBCODE_URL_BARE = re.compile(r'\[url\](.*?)\[/url\]', re.IGNORECASE | re.DOTALL) +# Any remaining bracketed tags +_BBCODE_REMAINING = re.compile(r'\[[^\]]+\]') + + +def _clean_steam_content(raw: str) -> str: + """ + Convert Steam News BBCode content to the plain-text changelog format + the rest of the bot expects: + + [ Section Header ] + - Bullet line + - Bullet line + + Valve's Deadlock patch notes are mostly already plain text, but this + handles any BBCode markup they may introduce. + """ + text = raw + + # Section headers + text = _BBCODE_HEADER.sub(lambda m: f'\n[ {m.group(1).strip()} ]', text) + + # URL tags -- keep visible text, drop URL + text = _BBCODE_URL.sub(r'\1', text) + text = _BBCODE_URL_BARE.sub(r'\1', text) + + # Convert [*] list items to hyphen bullets + def _list_item(m): + content = m.group(1).strip() + return f'\n- {content}' if content else '' + + text = _BBCODE_LIST_ITEM.sub(_list_item, text) + text = _BBCODE_LIST_WRAPPER.sub('', text) + + # Strip remaining simple inline tags + text = _BBCODE_INLINE.sub('', text) + + # Drop any unknown bracketed tags that remain + text = _BBCODE_REMAINING.sub('', text) + + # Normalise whitespace: collapse 3+ blank lines into 2 + text = re.sub(r'\n{3,}', '\n\n', text) + + return text.strip() + + +# Structural check helpers +_SECTION_HEADER = re.compile(r'^\s*\[.+\]\s*$', re.MULTILINE) +_BULLET_LINE = re.compile(r'^\s*-\s+\S', re.MULTILINE) + + +def _looks_like_patch_notes(text: str) -> bool: + has_header = bool(_SECTION_HEADER.search(text)) + bullet_count = len(_BULLET_LINE.findall(text)) + return has_header and bullet_count >= 3 + + +def _strip_wiki_syntax(text: str) -> str: + """ + Strips wiki formatting (links, templates, bold/italics) from text + to ensure it is in a raw state before being processed or saved. + """ + if not text: + return text + + # Strip Icon templates: {{HeroIcon|Name}} -> Name + text = re.sub(r'\{\{(?:Hero|Item|Ability)Icon\|([^}]+)\}\}', r'\1', text) + + # Strip PageRef templates: {{PageRef|Name|alt_name=text}} -> text + text = re.sub(r'\{\{PageRef\|([^|}]+)(?:\|alt_name=([^}]+))?\}\}', lambda m: m.group(2) or m.group(1), text) + + # Strip wiki links: [[Page|text]] -> text, [[Page]] -> Page + text = re.sub(r'\[\[(?:[^|\]]+\|)?([^\]]+)\]\]', r'\1', text) + + # Strip bold/italics + text = re.sub(r"'{2,}", '', text) + + return text + + class ChangelogFetcher: """ - Fetches changelogs from the deadlock forums and game files and parses them into a dictionary + Fetches changelogs from the deadlock forums, Steam News, and game files, + then parses them into a unified dictionary keyed by date (YYYY-MM-DD). """ def __init__(self, update_existing, input_dir, output_dir, herolab_patch_notes_path): @@ -63,6 +159,7 @@ def __init__(self, update_existing, input_dir, output_dir, herolab_patch_notes_p self.INPUT_DIR = input_dir self.OUTPUT_DIR = output_dir self.RSS_URL = CHANGELOG_RSS_URL + self.STEAM_NEWS_API_URL = STEAM_NEWS_API_URL # Safely derive base url (e.g., https://forums.playdeadlock.com) parsed_url = urlparse(self.RSS_URL) @@ -125,9 +222,8 @@ def _get_wiki_content(self, date_key: str) -> str: if match: notes_content = match.group(1).strip() - # Only strip specific icons the bot auto-generates. - # This turns {{HeroIcon|Hero}} -> Hero, so the parser can re-wrap it later. - notes_content = re.sub(r'\{\{(?:Hero|Item|Ability)Icon\|([^}]+)\}\}', r'\1', notes_content) + # Strip all wiki syntax so we only work with raw text + notes_content = _strip_wiki_syntax(notes_content) logger.debug(f'Found existing wiki page for {date_key}') return notes_content @@ -155,7 +251,8 @@ def _get_patch_section(self, header: str, full_text: str) -> str: def run(self): self.load_localization() - self.fetch_forum_changelogs() + self.fetch_steam_changelogs() # Steam first -- it's the current primary source + self.fetch_forum_changelogs() # Forum fills in gaps / older entries self.get_gamefile_changelogs() self.changelogs_to_file() @@ -197,6 +294,121 @@ def changelogs_to_file(self): # Save decoupled hotfixes for the uploader json_utils.write(f'{self.OUTPUT_DIR}/changelogs/hotfixes.json', self.hotfixes) + # ------------------------------------------------------------------ + # Steam News fetching + # ------------------------------------------------------------------ + + def fetch_steam_changelogs(self): + """ + Fetch patch notes posted to the Steam News hub and integrate them + into the changelog dictionary. + + Steam is now the primary source for Deadlock patch notes. Each + article is stored under a date-keyed changelog_id (YYYY-MM-DD), + exactly like forum entries. The article's GID is saved as + ``steam_gid`` in the config so callers can identify the source. + + Entries are skipped if: + - A local .txt file already exists for that date (already processed). + - The entry is not a gameplay/patch-note post (filtered by feedname + and title keywords). + """ + logger.trace('Fetching Steam News changelogs') + + try: + with request.urlopen(self.STEAM_NEWS_API_URL, timeout=15) as resp: + data = json.loads(resp.read().decode('utf-8')) + except Exception as e: + logger.error(f'Failed to fetch Steam News feed: {e}') + return + + items = data.get('appnews', {}).get('newsitems', []) + if not items: + logger.warning('Steam News feed returned no items') + return + + target_tz = ZoneInfo('US/Pacific') + + for item in items: + # Only process official Steam announcements, not third-party feeds + feedname = item.get('feedname', '') + if feedname != 'steam_community_announcements': + logger.trace(f"Skipping Steam item with feedname '{feedname}'") + continue + + title: str = item.get('title', '') + gid: str = item.get('gid', '') + url: str = item.get('url', '') + unix_ts: int = item.get('date', 0) + + # Convert Unix timestamp to US/Pacific date + try: + dt_utc = datetime.fromtimestamp(unix_ts, tz=timezone.utc) + dt_valve = dt_utc.astimezone(target_tz) + date_key = dt_valve.strftime('%Y-%m-%d') + except Exception as e: + logger.warning(f"Could not parse date for Steam item '{title}': {e}") + continue + + changelog_id = date_key + + # Skip if we already have a processed local file for this date + local_path = os.path.join(self.INPUT_DIR, 'changelogs/raw', f'{changelog_id}.txt') + if os.path.exists(local_path): + logger.trace(f'Local file already exists for {changelog_id}, skipping Steam fetch') + # Still update steam_gid in the config if it's missing + existing_config = self.changelog_configs.get(changelog_id, {}) + if existing_config and not existing_config.get('steam_gid'): + existing_config['steam_gid'] = gid + self.changelog_configs[changelog_id] = existing_config + continue + + raw_content: str = item.get('contents', '') + if not raw_content: + logger.warning(f"Steam item '{title}' has empty content, skipping") + continue + + clean_text = _clean_steam_content(raw_content) + + if not clean_text: + logger.warning(f"Steam item '{title}' produced empty content after cleaning, skipping") + continue + + if not _looks_like_patch_notes(clean_text): + logger.debug(f"Skipping Steam item '{title}' (no patch note structure found)") + continue + + existing_config = self.changelog_configs.get(changelog_id) + + if existing_config: + # A config already exists (possibly from a previous forum fetch or + # a previous run). Only overwrite content if we have no text yet. + if changelog_id not in self.changelogs or not self.changelogs[changelog_id]: + logger.info(f'Backfilling Steam content for existing config {changelog_id}') + self.changelogs[changelog_id] = clean_text + # Always record the GID so we know it came from Steam + existing_config['steam_gid'] = gid + if not existing_config.get('link'): + existing_config['link'] = url + self.changelog_configs[changelog_id] = existing_config + else: + # Brand-new entry sourced from Steam + logger.info(f'New Steam changelog for {changelog_id}: "{title}"') + self.changelogs[changelog_id] = clean_text + self.changelog_configs[changelog_id] = { + 'forum_id': None, + 'steam_gid': gid, + 'date': date_key, + 'link': url, + 'is_hero_lab': False, + } + + logger.success(f'Steam News fetch complete ({len(items)} items scanned)') + + # ------------------------------------------------------------------ + # Forum fetching (unchanged from original, kept as fallback) + # ------------------------------------------------------------------ + def _fetch_update_html(self, link): """ Fetches the HTML content of a forum link and extracts posts grouped by date. @@ -331,6 +543,7 @@ def get_gamefile_changelogs(self): if raw_changelog_id not in self.changelog_configs: self.changelog_configs[raw_changelog_id] = { 'forum_id': None, + 'steam_gid': None, 'date': date, 'link': None, 'is_hero_lab': True, @@ -381,7 +594,13 @@ def _localize(self, key): return value def fetch_forum_changelogs(self): - """download rss feed from changelog forum and save all available entries""" + """ + Download RSS feed from the changelog forum and save all available entries. + + Forum entries are now treated as a secondary source: they fill in dates + that Steam News did not cover (older patches, or dates where no Steam + post matched the filter). + """ logger.trace('Parsing Changelog RSS feed') # fetches 20 most recent entries feed = feedparser.parse(self.RSS_URL) @@ -434,7 +653,15 @@ def fetch_forum_changelogs(self): local_path = os.path.join(self.INPUT_DIR, 'changelogs/raw', f'{changelog_id}.txt') local_content = file_utils.read(local_path) if os.path.exists(local_path) else '' - # Check for wiki page (manual pages created by editors) + # If Steam already populated this date, skip forum content entirely + # unless the Steam entry has no content (shouldn't happen but be safe) + steam_already_populated = ( + changelog_id in self.changelogs and self.changelogs[changelog_id] and self.changelog_configs.get(changelog_id, {}).get('steam_gid') + ) + if steam_already_populated: + logger.trace(f'Steam content already present for {changelog_id}, skipping forum fetch') + continue + wiki_content = self._get_wiki_content(changelog_id) if not local_content else '' existing_config = self.changelog_configs.get(changelog_id) @@ -445,7 +672,7 @@ def fetch_forum_changelogs(self): # Find the main entry if existing_config: for e in entries: - if e['version'] == existing_config['forum_id']: + if e['version'] == existing_config.get('forum_id'): main_entry = e break for e in entries: @@ -459,7 +686,6 @@ def fetch_forum_changelogs(self): # Determine base content and whether to append if local_content: # Local file exists - use it as base, don't append to file - # (will still check for hotfixes to append to wiki later) current_text = local_content elif wiki_content and main_entry: # Wiki page exists but no local file - append forum content to wiki @@ -471,6 +697,11 @@ def fetch_forum_changelogs(self): # No existing content - use forum as base current_text = main_entry['text'] if main_entry else (self.changelogs.get(changelog_id, '')) + # IMPORTANT: Strip any existing wiki syntax from current_text. + # This repairs corrupted raw files from previous runs and ensures + # the formatter only applies wiki syntax exactly once. + current_text = _strip_wiki_syntax(current_text) + old_text = local_content or wiki_content or '' # Helper to get next patch number @@ -491,6 +722,8 @@ def get_next_patch_num(txt): # Skip if this content is already in final_text normalized_entry = re.sub(r'^- ', '* ', entry_text, flags=re.MULTILINE) normalized_final = re.sub(r'^- ', '* ', final_text, flags=re.MULTILINE) + normalized_final = re.sub(r'\[\[(?:[^|\]]+\|)?([^\]]+)\]\]', r'\1', normalized_final) + normalized_final = re.sub(r'\{\{[^|]+\|([^}]+)\}\}', r'\1', normalized_final) if normalized_entry not in normalized_final: patch_num = get_next_patch_num(final_text) final_text += f'\n\n=== Patch {patch_num} ===\n\n{entry_text}' @@ -530,6 +763,7 @@ def get_next_patch_num(txt): self.changelog_configs[changelog_id] = { 'forum_id': forum_id, + 'steam_gid': existing_config.get('steam_gid') if existing_config else None, 'date': date_key, 'link': link, 'is_hero_lab': False, diff --git a/src/changelogs/link_targets.json b/src/changelogs/link_targets.json index 7c51bb77..1b192ffc 100644 --- a/src/changelogs/link_targets.json +++ b/src/changelogs/link_targets.json @@ -1,5 +1,5 @@ { - "Urn": ["Urn", "urn", "Soul Urn"], + "Soul Urn": ["Urn", "urn", "Soul Urn"], "Rejuvenator": ["Rejuvenator", "Rejuv", "rejuv"], "Patron": ["Patron", "patron"], "Weakened Patron": ["Weakened Patron"], From cfadf590631c7833588e1fecd8b63ceb5b8d89a5 Mon Sep 17 00:00:00 2001 From: Deadbot0 Date: Tue, 26 May 2026 16:21:01 +0000 Subject: [PATCH 2/2] [skip ci] chore: updated input data --- input-data/changelogs/changelog_configs.json | 56 ++++++++++++++++++-- input-data/changelogs/raw/2026-03-10.txt | 12 ++--- 2 files changed, 59 insertions(+), 9 deletions(-) diff --git a/input-data/changelogs/changelog_configs.json b/input-data/changelogs/changelog_configs.json index d433e5de..0914e0e3 100644 --- a/input-data/changelogs/changelog_configs.json +++ b/input-data/changelogs/changelog_configs.json @@ -487,180 +487,210 @@ }, "2025-05-19": { "forum_id": "65381", + "steam_gid": null, "date": "2025-05-19", "link": "https://forums.playdeadlock.com/threads/05-19-2025-update.65381/", "is_hero_lab": false }, "2025-05-21": { "forum_id": "65674", + "steam_gid": null, "date": "2025-05-21", "link": "https://forums.playdeadlock.com/threads/05-21-2025-update.65674/", "is_hero_lab": false }, "2025-05-27": { "forum_id": "66453", + "steam_gid": null, "date": "2025-05-27", "link": "https://forums.playdeadlock.com/threads/05-27-2025-update.66453/", "is_hero_lab": false }, "2025-06-17": { "forum_id": "68401", + "steam_gid": null, "date": "2025-06-17", "link": "https://forums.playdeadlock.com/threads/06-17-2025-update.68401/", "is_hero_lab": false }, "2025-07-04": { "forum_id": "70156", + "steam_gid": null, "date": "2025-07-04", "link": "https://forums.playdeadlock.com/threads/07-04-2025-update.70156/", "is_hero_lab": false }, "2025-07-29": { "forum_id": "72760", + "steam_gid": null, "date": "2025-07-29", "link": "https://forums.playdeadlock.com/threads/07-29-2025-update.72760/", "is_hero_lab": false }, "2025-08-01": { "forum_id": "72760", + "steam_gid": null, "date": "2025-08-01", "link": "https://forums.playdeadlock.com/threads/07-29-2025-update.72760/post-139997", "is_hero_lab": false }, "2025-08-08": { "forum_id": "72760", + "steam_gid": null, "date": "2025-08-08", "link": "https://forums.playdeadlock.com/threads/07-29-2025-update.72760/post-141214", "is_hero_lab": false }, "2025-08-18": { "forum_id": "75046", + "steam_gid": null, "date": "2025-08-18", "link": "https://forums.playdeadlock.com/threads/08-18-2025-update.75046/", "is_hero_lab": false }, "2025-08-22": { "forum_id": "75046", + "steam_gid": null, "date": "2025-08-22", "link": "https://forums.playdeadlock.com/threads/08-18-2025-update.75046/post-145970", "is_hero_lab": false }, "2025-08-28": { "forum_id": "75046", + "steam_gid": null, "date": "2025-08-28", "link": "https://forums.playdeadlock.com/threads/08-18-2025-update.75046/post-149166", "is_hero_lab": false }, "2025-08-29": { "forum_id": "75046", + "steam_gid": null, "date": "2025-08-29", "link": "https://forums.playdeadlock.com/threads/08-18-2025-update.75046/post-149696", "is_hero_lab": false }, "2025-09-04": { "forum_id": "80693", + "steam_gid": null, "date": "2025-09-04", "link": "https://forums.playdeadlock.com/threads/09-04-2025-update.80693/", "is_hero_lab": false }, "2025-10-02": { "forum_id": "84332", + "steam_gid": null, "date": "2025-10-02", "link": "https://forums.playdeadlock.com/threads/10-02-2025-update.84332/", "is_hero_lab": false }, "2025-10-03": { "forum_id": "84332", + "steam_gid": null, "date": "2025-10-03", "link": "https://forums.playdeadlock.com/threads/10-02-2025-update.84332/post-159495", "is_hero_lab": false }, "2025-10-05": { "forum_id": "84332", + "steam_gid": null, "date": "2025-10-05", "link": "https://forums.playdeadlock.com/threads/10-02-2025-update.84332/post-160063", "is_hero_lab": false }, "2025-10-24": { "forum_id": "87198", + "steam_gid": null, "date": "2025-10-24", "link": "https://forums.playdeadlock.com/threads/10-24-2025-update.87198/", "is_hero_lab": false }, "2025-11-21": { "forum_id": "90383", + "steam_gid": null, "date": "2025-11-21", "link": "https://forums.playdeadlock.com/threads/11-21-2025-update.90383/", "is_hero_lab": false }, "2025-11-22": { "forum_id": "90383", + "steam_gid": null, "date": "2025-11-22", "link": "https://forums.playdeadlock.com/threads/11-21-2025-update.90383/post-171913", "is_hero_lab": false }, "2025-11-23": { "forum_id": "90383", + "steam_gid": null, "date": "2025-11-23", "link": "https://forums.playdeadlock.com/threads/11-21-2025-update.90383/post-172057", "is_hero_lab": false }, "2025-11-27": { "forum_id": "90383", + "steam_gid": null, "date": "2025-11-27", "link": "https://forums.playdeadlock.com/threads/11-21-2025-update.90383/post-173347", "is_hero_lab": false }, "2025-12-03": { "forum_id": "90383", + "steam_gid": null, "date": "2025-12-03", "link": "https://forums.playdeadlock.com/threads/11-21-2025-update.90383/post-174987", "is_hero_lab": false }, "2025-12-16": { "forum_id": "93983", + "steam_gid": null, "date": "2025-12-16", "link": "https://forums.playdeadlock.com/threads/12-16-2025-update.93983/", "is_hero_lab": false }, "2025-12-17": { "forum_id": "93983", + "steam_gid": null, "date": "2025-12-17", "link": "https://forums.playdeadlock.com/threads/12-16-2025-update.93983/post-178389", "is_hero_lab": false }, "2025-12-29": { "forum_id": "95233", + "steam_gid": null, "date": "2025-12-29", "link": "https://forums.playdeadlock.com/threads/12-29-2025-update.95233/", "is_hero_lab": false }, "2025-12-30": { "forum_id": "95233", + "steam_gid": null, "date": "2025-12-30", "link": "https://forums.playdeadlock.com/threads/12-29-2025-update.95233/post-180763", "is_hero_lab": false }, "2026-01-20": { "forum_id": "95233", + "steam_gid": null, "date": "2026-01-20", "link": "https://forums.playdeadlock.com/threads/12-29-2025-update.95233/post-185824", "is_hero_lab": false }, "2026-01-30": { "forum_id": "102822", + "steam_gid": null, "date": "2026-01-30", "link": "https://forums.playdeadlock.com/threads/01-30-2026-update.102822/", "is_hero_lab": false }, "2026-02-02": { "forum_id": "102822", + "steam_gid": null, "date": "2026-02-02", "link": "https://forums.playdeadlock.com/threads/01-30-2026-update.102822/post-197062", "is_hero_lab": false }, "2026-02-04": { "forum_id": "102822", + "steam_gid": null, "date": "2026-02-04", "link": "https://forums.playdeadlock.com/threads/01-30-2026-update.102822/post-198653", "is_hero_lab": false @@ -669,16 +699,19 @@ "forum_id": "102822", "date": "2026-02-12", "link": "https://forums.playdeadlock.com/threads/01-30-2026-update.102822/post-204182", - "is_hero_lab": false + "is_hero_lab": false, + "steam_gid": "1824459501608168" }, "2026-02-13": { "forum_id": "102822", + "steam_gid": null, "date": "2026-02-13", "link": "https://forums.playdeadlock.com/threads/01-30-2026-update.102822/post-205106", "is_hero_lab": false }, "2026-02-17": { "forum_id": "102822", + "steam_gid": null, "date": "2026-02-17", "link": "https://forums.playdeadlock.com/threads/01-30-2026-update.102822/post-207811", "is_hero_lab": false @@ -687,40 +720,47 @@ "forum_id": "114328", "date": "2026-03-06", "link": "https://forums.playdeadlock.com/threads/03-06-2026-update.114328/", - "is_hero_lab": false + "is_hero_lab": false, + "steam_gid": "1826362059925616" }, "2026-03-07": { "forum_id": "114328", + "steam_gid": null, "date": "2026-03-07", "link": "https://forums.playdeadlock.com/threads/03-06-2026-update.114328/post-217955", "is_hero_lab": false }, "2026-03-10": { "forum_id": "114328", + "steam_gid": null, "date": "2026-03-10", "link": "https://forums.playdeadlock.com/threads/03-06-2026-update.114328/post-220287", "is_hero_lab": false }, "2026-03-21": { "forum_id": "114328", + "steam_gid": null, "date": "2026-03-21", "link": "https://forums.playdeadlock.com/threads/03-06-2026-update.114328/post-226950", "is_hero_lab": false }, "2026-03-25": { "forum_id": "121766", + "steam_gid": null, "date": "2026-03-25", "link": "https://forums.playdeadlock.com/threads/03-25-2026-update.121766/", "is_hero_lab": false }, "2026-03-30": { "forum_id": "121766", + "steam_gid": null, "date": "2026-03-30", "link": "https://forums.playdeadlock.com/threads/03-25-2026-update.121766/post-232903", "is_hero_lab": false }, "2026-04-10": { "forum_id": "125825", + "steam_gid": null, "date": "2026-04-10", "link": "https://forums.playdeadlock.com/threads/04-10-2026-update.125825/", "is_hero_lab": false @@ -729,10 +769,12 @@ "forum_id": "129989", "date": "2026-04-30", "link": "https://forums.playdeadlock.com/threads/04-30-2026-update.129989/", - "is_hero_lab": false + "is_hero_lab": false, + "steam_gid": "1831432155569981" }, "2026-05-01": { "forum_id": "129989", + "steam_gid": null, "date": "2026-05-01", "link": "https://forums.playdeadlock.com/threads/04-30-2026-update.129989/post-246437", "is_hero_lab": false @@ -741,6 +783,14 @@ "forum_id": "135477", "date": "2026-05-22", "link": "https://forums.playdeadlock.com/threads/05-22-2026-update.135477/", + "is_hero_lab": false, + "steam_gid": "1833334318572828" + }, + "2026-05-25": { + "forum_id": "135477", + "steam_gid": null, + "date": "2026-05-25", + "link": "https://forums.playdeadlock.com/threads/05-22-2026-update.135477/post-259788", "is_hero_lab": false }, "2026-05-25": { diff --git a/input-data/changelogs/raw/2026-03-10.txt b/input-data/changelogs/raw/2026-03-10.txt index 6013ab77..70c2acaa 100644 --- a/input-data/changelogs/raw/2026-03-10.txt +++ b/input-data/changelogs/raw/2026-03-10.txt @@ -1,7 +1,7 @@ -* Base [[Guardian|Guardians]] HP reduced from 5500 to 4000 -* [[Zipline]] can now be captured a little bit more forward towards the enemy base -* The minimum always captured [[Zipline|zipline]] distance in your base is now reduced a little bit inwards -* Super [[Trooper|troopers]] (when a lane [[Shrine|shrine]] is down) now have 15% less bounty +* Base Guardians HP reduced from 5500 to 4000 +* Zipline can now be captured a little bit more forward towards the enemy base +* The minimum always captured zipline distance in your base is now reduced a little bit inwards +* Super troopers (when a lane shrine is down) now have 15% less bounty * Hero base health increased by 40 * Hero health increased growth by +4 and 8% @@ -76,7 +76,7 @@ * Victor: Pain Battery T3 reduced from 18% Missing Health to 15% * Victor: Jumpstart T3 spirit scaling reduced from 0.8 to 0.6 -* Victor: Aura of Suffering can now be activated on the [[Zipline|zipline]] +* Victor: Aura of Suffering can now be activated on the zipline * Victor: Aura of Suffering radius reduced from 10m to 9.5m * Victor: Aura of Suffering Base and T2 Max DPS and scaling reduced by 10% * Victor: Shocking Reanimation cooldown increased from 190s to 210s @@ -92,7 +92,7 @@ * Wraith: Card Trick cooldown increased from 0.5s to 0.6s * Wraith: Card Trick Joker no longer bounces to other targets -* Wraith: Card Trick gaining charges (AP or [[Shop|shop]]) no longer automatically builds them up +* Wraith: Card Trick gaining charges (AP or shop) no longer automatically builds them up * Wraith: Card Trick can now be alt casted to fire from the inverse order * Wraith: Card Trick T3 Spade bonus reduced from +50% to +40% * Wraith: Card Trick T3 Heart healing scale reduced from +0.75 to +0.5