diff --git a/data/changelog.json b/data/changelog.json index a1e7302..d88b914 100644 --- a/data/changelog.json +++ b/data/changelog.json @@ -1,1062 +1,6 @@ { "last_updated": "2026-06-17T23:01:35.840Z", "roster_hash": "ea698a2677a18790dbd13b07b0ca63bd023efb3c561b531f230c0511f6f370d9", - "prev_roster_snapshot": { - "app_name": "Undaunted: Relentless", - "realm": "EU-Draenor", - "region": "eu", - "mplus_weekly_minimum": 4, - "mplus_minimum_key_level": 10, - "primary_raid_difficulty": "heroic", - "tracking_start_date": "2026-05-20", - "mplus_seasons": [ - { - "season_id": "midnight-s1", - "label": "Midnight Season 1", - "start_date": "2026-03-24", - "end_date": null, - "dungeon_count": 8, - "dungeons": [ - "Windrunner Spire", - "Maisara Caverns", - "Magisters' Terrace", - "Nexus-Point Xenas", - "Algeth'ar Academy", - "Seat of the Triumvirate", - "Skyreach", - "Pit of Saron" - ] - } - ], - "raid_difficulties": [ - "heroic", - "mythic" - ], - "wcl_expansion_id": 7, - "wcl_zone_ids": [ - 46, - 47, - 50 - ], - "zone_combination": { - "base_id": 46, - "extra_ids": [ - 50 - ], - "start_date": "2026-06-17", - "label": "VS / DR / MQD / SF" - }, - "raid_schedule": { - "timezone": "Europe/Paris", - "sessions": [ - { - "day": "monday", - "start": "20:30", - "end": "23:30", - "grace_minutes": 30 - }, - { - "day": "wednesday", - "start": "20:30", - "end": "23:30", - "grace_minutes": 30 - } - ], - "safe_pug_windows": [ - { - "day": "tuesday", - "start": "00:00", - "end": "23:59" - }, - { - "day": "wednesday", - "start": "00:00", - "end": "05:59" - } - ] - }, - "raid_difficulty_status": { - "raid-46": { - "heroic": "progression", - "mythic": "progression", - "mythic_start_date": "2026-06-08" - } - }, - "players": [ - { - "raider_id": "ad297730-db58-4d5d-87d9-2774ba988f2b", - "display_name": "Howl", - "status": "active", - "tracking_start_date": "2026-05-20", - "team_designation": "main", - "membership_history": [ - { - "event": "joined", - "date": "2026-05-20", - "note": "Founding member" - } - ], - "characters": [ - { - "name": "Hxwl", - "realm": "Draenor", - "class": "Shaman", - "specs": [ - { - "spec": "Elemental", - "role": "dps", - "primary": true, - "wcl_active": true - }, - { - "spec": "Restoration", - "role": "healer", - "primary": false, - "wcl_active": false - } - ], - "active": true - } - ], - "role_history": [ - { - "role": "dps", - "class": "Shaman", - "spec": "Elemental", - "character": "Hxwl", - "from": "2026-05-20", - "to": null - } - ] - }, - { - "raider_id": "6b6181f7-636d-4614-b37f-be5231d540af", - "display_name": "Raem", - "status": "active", - "tracking_start_date": "2026-05-20", - "team_designation": "main", - "membership_history": [ - { - "event": "joined", - "date": "2026-05-20", - "note": "Founding member" - } - ], - "characters": [ - { - "name": "Raem", - "realm": "Doomhammer", - "class": "Priest", - "specs": [ - { - "spec": "Shadow", - "role": "dps", - "primary": true, - "wcl_active": true - }, - { - "spec": "Discipline", - "role": "healer", - "primary": false, - "wcl_active": true - } - ], - "active": true - } - ], - "role_history": [ - { - "role": "dps", - "class": "Priest", - "spec": "Shadow", - "character": "Raem", - "from": "2026-05-20", - "to": null - } - ] - }, - { - "raider_id": "07726c95-9cec-4f33-ae20-1b4e73d1365a", - "display_name": "Fluke", - "status": "active", - "tracking_start_date": "2026-05-20", - "team_designation": "main", - "membership_history": [ - { - "event": "joined", - "date": "2026-05-20", - "note": "Founding member" - } - ], - "characters": [ - { - "name": "Flukey", - "realm": "Draenor", - "class": "Paladin", - "specs": [ - { - "spec": "Retribution", - "role": "dps", - "primary": true, - "wcl_active": true - }, - { - "spec": "Protection", - "role": "tank", - "primary": false, - "wcl_active": true - } - ], - "active": true - } - ], - "role_history": [ - { - "role": "dps", - "class": "Paladin", - "spec": "Retribution", - "character": "Flukey", - "from": "2026-05-20", - "to": null - } - ] - }, - { - "raider_id": "56a6cc30-3287-47ca-bfae-bb3d9afca3c0", - "display_name": "Osena", - "status": "active", - "tracking_start_date": "2026-05-20", - "team_designation": "main", - "membership_history": [ - { - "event": "joined", - "date": "2026-05-20", - "note": "Founding member" - } - ], - "characters": [ - { - "name": "Eyeballshurt", - "realm": "Draenor", - "class": "DemonHunter", - "specs": [ - { - "spec": "Havoc", - "role": "dps", - "primary": true, - "wcl_active": true - }, - { - "spec": "Vengeance", - "role": "tank", - "primary": false, - "wcl_active": true - } - ], - "active": true - } - ], - "role_history": [ - { - "role": "dps", - "class": "DemonHunter", - "spec": "Havoc", - "character": "Eyeballshurt", - "from": "2026-05-20", - "to": null - } - ] - }, - { - "raider_id": "77d3af03-b9b3-4097-b5ad-4c8d5a536966", - "display_name": "Zya", - "status": "active", - "tracking_start_date": "2026-05-20", - "team_designation": "main", - "membership_history": [ - { - "event": "joined", - "date": "2026-05-20", - "note": "Founding member" - } - ], - "characters": [ - { - "name": "Zyálayna", - "realm": "Draenor", - "class": "Hunter", - "spec": "Survival", - "role": "dps", - "active": true - } - ], - "role_history": [ - { - "role": "dps", - "class": "Hunter", - "spec": "Survival", - "character": "Zyálayna", - "from": "2026-05-20", - "to": null - } - ] - }, - { - "raider_id": "0bede844-3d15-48b3-ad62-471ea652a874", - "display_name": "Sleipner", - "status": "active", - "tracking_start_date": "2026-05-20", - "team_designation": "main", - "membership_history": [ - { - "event": "joined", - "date": "2026-05-20", - "note": "Applied on 7th May - Guildmaster" - } - ], - "characters": [ - { - "name": "Sleipner", - "realm": "SteamwheedleCartel", - "class": "DeathKnight", - "spec": "Blood", - "role": "tank", - "active": true - } - ], - "role_history": [ - { - "role": "tank", - "class": "DeathKnight", - "spec": "Blood", - "character": "Sleipner", - "from": "2026-05-20", - "to": null - } - ] - }, - { - "raider_id": "7b772b7b-9e9b-4ac3-b68b-cedf0fc36dff", - "display_name": "Tickle", - "status": "active", - "tracking_start_date": "2026-05-20", - "team_designation": "main", - "membership_history": [ - { - "event": "joined", - "date": "2026-05-20", - "note": "Founding member" - } - ], - "characters": [ - { - "name": "Ticklefist", - "realm": "Doomhammer", - "class": "Monk", - "spec": "Mistweaver", - "role": "healer", - "active": true - } - ], - "role_history": [ - { - "role": "healer", - "class": "Monk", - "spec": "Mistweaver", - "character": "Ticklefist", - "from": "2026-05-20", - "to": null - } - ] - }, - { - "raider_id": "7ad5ac04-e4cb-4ebd-bee2-240b4a9275c9", - "display_name": "Holgers", - "status": "active", - "tracking_start_date": "2026-05-20", - "team_designation": "main", - "membership_history": [ - { - "event": "joined", - "date": "2026-05-20", - "note": "Applied on 13th May 2026" - } - ], - "characters": [ - { - "name": "Holgshame", - "realm": "Draenor", - "class": "Shaman", - "spec": "Restoration", - "role": "healer", - "active": true - } - ], - "role_history": [ - { - "role": "healer", - "class": "Shaman", - "spec": "Restoration", - "character": "Holgshame", - "from": "2026-05-20", - "to": null - } - ] - }, - { - "raider_id": "8a1c5c47-c359-49ba-ad7d-96f6707e0162", - "display_name": "Qryso", - "status": "active", - "tracking_start_date": "2026-05-20", - "team_designation": "main", - "membership_history": [ - { - "event": "joined", - "date": "2026-05-20", - "note": "Applied on 12th May 2026" - } - ], - "characters": [ - { - "name": "Qrysocorax", - "realm": "ArgentDawn", - "class": "Druid", - "spec": "Balance", - "role": "dps", - "active": true - } - ], - "role_history": [ - { - "role": "dps", - "class": "Druid", - "spec": "Balance", - "character": "Qrysocorax", - "from": "2026-05-20", - "to": null - } - ] - }, - { - "raider_id": "3752b0c0-364e-440f-a08b-78330ed4239c", - "display_name": "Thomas", - "status": "active", - "tracking_start_date": "2026-05-20", - "team_designation": "alt", - "membership_history": [ - { - "event": "joined", - "date": "2026-05-20", - "note": "Applied on 12th May 2026" - } - ], - "characters": [ - { - "name": "Thômâtschô", - "realm": "Antonidas", - "class": "Warrior", - "spec": "Fury", - "role": "dps", - "active": true - } - ], - "role_history": [ - { - "role": "dps", - "class": "Warrior", - "spec": "Fury", - "character": "Thômâtschô", - "from": "2026-05-20", - "to": null - } - ] - }, - { - "raider_id": "185f2e13-823b-42bb-9387-fb439efc132e", - "display_name": "Fanghui", - "status": "active", - "tracking_start_date": "2026-05-20", - "team_designation": "main", - "membership_history": [ - { - "event": "joined", - "date": "2026-05-20", - "note": "Applied on 13th May 2026" - } - ], - "characters": [ - { - "name": "Fanghui", - "realm": "Draenor", - "class": "Mage", - "spec": "Frost", - "role": "dps", - "active": true - } - ], - "role_history": [ - { - "role": "dps", - "class": "Mage", - "spec": "Frost", - "character": "Fanghui", - "from": "2026-05-20", - "to": null - } - ] - }, - { - "raider_id": "9b9364da-6525-4693-80f2-e6a28e995b4f", - "display_name": "Gary", - "status": "active", - "tracking_start_date": "2026-05-20", - "team_designation": "alt", - "membership_history": [ - { - "event": "joined", - "date": "2026-05-20", - "note": "Applied on 18th May 2026" - } - ], - "characters": [ - { - "name": "Gaymeron", - "realm": "Draenor", - "class": "Druid", - "specs": [ - { - "spec": "Guardian", - "role": "tank", - "primary": true, - "wcl_active": true - }, - { - "spec": "Restoration", - "role": "healer", - "primary": false, - "wcl_active": false - }, - { - "spec": "Feral", - "role": "dps", - "primary": false, - "wcl_active": false - } - ], - "active": true - } - ], - "role_history": [ - { - "role": "tank", - "class": "Druid", - "spec": "Guardian", - "character": "Gaymeron", - "from": "2026-05-20", - "to": null - } - ] - }, - { - "raider_id": "80dc248a-fb7b-4eef-8faa-5c274f415963", - "display_name": "Buzz", - "status": "inactive", - "tracking_start_date": "2026-05-25", - "team_designation": "alt", - "membership_history": [ - { - "event": "joined", - "date": "2026-05-25", - "note": "Fearless main" - }, - { - "event": "left", - "date": "2026-06-17" - } - ], - "characters": [ - { - "name": "Starfoullah", - "realm": "Draenor", - "class": "Druid", - "specs": [ - { - "spec": "Restoration", - "role": "healer", - "primary": true, - "wcl_active": true - }, - { - "spec": "Feral", - "role": "dps", - "primary": false, - "wcl_active": false - } - ], - "active": true - } - ], - "role_history": [ - { - "role": "healer", - "class": "Druid", - "spec": "Restoration", - "character": "Starfoullah", - "from": "2026-05-25", - "to": "2026-06-17" - } - ] - }, - { - "raider_id": "dcd66ae4-99d3-4970-a071-e0af1d9d479c", - "display_name": "Quillet", - "status": "active", - "tracking_start_date": "2026-05-25", - "team_designation": "main", - "membership_history": [ - { - "event": "joined", - "date": "2026-05-25", - "note": "" - } - ], - "characters": [ - { - "name": "Quillet", - "realm": "Tarren Mill", - "class": "Shaman", - "specs": [ - { - "spec": "Elemental", - "role": "dps", - "primary": true, - "wcl_active": true - }, - { - "spec": "Restoration", - "role": "healer", - "primary": false, - "wcl_active": false - } - ], - "active": true - } - ], - "role_history": [ - { - "role": "dps", - "class": "Shaman", - "spec": "Elemental", - "character": "Quillet", - "from": "2026-05-25", - "to": null - } - ] - }, - { - "raider_id": "f479633a-cbd8-4a19-9e73-7bb92bdb03fa", - "display_name": "Apo", - "status": "active", - "tracking_start_date": "2026-06-05", - "team_designation": "main", - "membership_history": [ - { - "event": "joined", - "date": "2026-06-05", - "note": "Applied on 5th June 2026" - } - ], - "characters": [ - { - "name": "Apocalypseki", - "realm": "Draenor", - "class": "DeathKnight", - "specs": [ - { - "spec": "Frost", - "role": "dps", - "primary": true, - "wcl_active": true - }, - { - "spec": "Unholy", - "role": "dps", - "primary": false, - "wcl_active": false - } - ], - "active": true - } - ], - "role_history": [ - { - "role": "dps", - "class": "DeathKnight", - "spec": "Frost", - "character": "Apocalypseki", - "from": "2026-06-05", - "to": null - } - ] - }, - { - "raider_id": "47dab7cd-7045-46a0-a845-776217d26d5b", - "display_name": "Kazashi", - "status": "active", - "tracking_start_date": "2026-06-05", - "team_designation": "alt", - "membership_history": [ - { - "event": "joined", - "date": "2026-06-05", - "note": "Applied on 5th June 2026" - } - ], - "characters": [ - { - "name": "Kazashi", - "realm": "Draenor", - "class": "Rogue", - "spec": "Assassination", - "role": "dps", - "active": true - } - ], - "role_history": [ - { - "role": "dps", - "class": "Rogue", - "spec": "Assassination", - "character": "Kazashi", - "from": "2026-06-05", - "to": null - } - ] - }, - { - "raider_id": "f551f254-b145-4991-8cf1-970db0cab24e", - "display_name": "Lucifron", - "status": "active", - "tracking_start_date": "2026-06-05", - "team_designation": "main", - "membership_history": [ - { - "event": "joined", - "date": "2026-06-05", - "note": "Applied on 5th June 2026" - } - ], - "characters": [ - { - "name": "Lucífron", - "realm": "Draenor", - "class": "Warlock", - "spec": "Demonology", - "role": "dps", - "active": true - } - ], - "role_history": [ - { - "role": "dps", - "class": "Warlock", - "spec": "Demonology", - "character": "Lucífron", - "from": "2026-06-05", - "to": null - } - ] - }, - { - "raider_id": "ee9fde20-2aaf-49e2-8fad-e67ef3a68f8a", - "display_name": "Barvoid", - "status": "active", - "tracking_start_date": "2026-06-05", - "team_designation": "main", - "membership_history": [ - { - "event": "joined", - "date": "2026-06-05", - "note": "Applied on 5th June 2026" - } - ], - "characters": [ - { - "name": "Barvoid", - "realm": "Draenor", - "class": "Priest", - "spec": "Shadow", - "role": "dps", - "active": false - }, - { - "name": "Barhumevo", - "realm": "Draenor", - "class": "Evoker", - "spec": "Augmentation", - "role": "dps", - "active": true - } - ], - "role_history": [ - { - "role": "dps", - "class": "Priest", - "spec": "Shadow", - "character": "Barvoid", - "from": "2026-06-05", - "to": "2026-06-17" - }, - { - "role": "dps", - "class": "Evoker", - "spec": "Augmentation", - "character": "Barhumevo", - "from": "2026-06-17", - "to": null - } - ] - }, - { - "raider_id": "82bdcf06-92f4-490d-bee7-02e7d74fdc0d", - "display_name": "Vumera", - "status": "active", - "tracking_start_date": "2026-06-10", - "team_designation": "alt", - "membership_history": [ - { - "event": "joined", - "date": "2026-06-10", - "note": "Joined 10th June 2026" - } - ], - "characters": [ - { - "name": "Vumera", - "realm": "Draenor", - "class": "Shaman", - "spec": "Restoration", - "role": "healer", - "active": true - } - ], - "role_history": [ - { - "role": "healer", - "class": "Shaman", - "spec": "Restoration", - "character": "Vumera", - "from": "2026-06-10", - "to": null - } - ] - }, - { - "raider_id": "5f6b9abe-1dd7-45e5-9fac-56b893f8ffd5", - "display_name": "Imû", - "status": "active", - "tracking_start_date": "2026-06-10", - "team_designation": "main", - "membership_history": [ - { - "event": "joined", - "date": "2026-06-10", - "note": "Joined 10th June 2026" - } - ], - "characters": [ - { - "name": "Imû", - "realm": "Draenor", - "class": "Mage", - "spec": "Frost", - "role": "dps", - "active": true - } - ], - "role_history": [ - { - "role": "dps", - "class": "Mage", - "spec": "Frost", - "character": "Imû", - "from": "2026-06-10", - "to": null - } - ] - }, - { - "raider_id": "02a5f0e3-4d74-443f-9be6-a83af426174b", - "display_name": "Lilputz", - "status": "active", - "tracking_start_date": "2026-06-16", - "team_designation": "main", - "membership_history": [ - { - "event": "joined", - "date": "2026-06-16", - "note": "Applied on 16th June 2026" - } - ], - "characters": [ - { - "name": "Lilputz", - "realm": "ChamberOfAspects", - "class": "Mage", - "spec": "Frost", - "role": "dps", - "active": true - } - ], - "role_history": [ - { - "role": "dps", - "class": "Mage", - "spec": "Frost", - "character": "Lilputz", - "from": "2026-06-16", - "to": null - } - ] - }, - { - "raider_id": "87752f7d-2d52-4797-83ef-dc0a34827d0a", - "display_name": "Stormlòck", - "status": "active", - "tracking_start_date": "2026-06-16", - "team_designation": "main", - "membership_history": [ - { - "event": "joined", - "date": "2026-06-16", - "note": "Applied on 16th June 2026" - } - ], - "characters": [ - { - "name": "Stormlòck", - "realm": "ChamberOfAspects", - "class": "Warlock", - "spec": "Demonology", - "role": "dps", - "active": true - } - ], - "role_history": [ - { - "role": "dps", - "class": "Warlock", - "spec": "Demonology", - "character": "Stormlòck", - "from": "2026-06-16", - "to": null - } - ] - }, - { - "raider_id": "ce94fbb6-cf99-46be-bc77-3abd7b2df01b", - "display_name": "Hydradead", - "status": "active", - "tracking_start_date": "2026-06-16", - "team_designation": "main", - "membership_history": [ - { - "event": "joined", - "date": "2026-06-16", - "note": "Applied on 16th June 2026" - } - ], - "characters": [ - { - "name": "Hydradead", - "realm": "ChamberOfAspects", - "class": "DeathKnight", - "spec": "Unholy", - "role": "dps", - "active": true - } - ], - "role_history": [ - { - "role": "dps", - "class": "DeathKnight", - "spec": "Unholy", - "character": "Hydradead", - "from": "2026-06-16", - "to": null - } - ] - }, - { - "raider_id": "f6ec9fd2-163f-490b-ad10-eaf274ef73cd", - "display_name": "Mydrui", - "status": "active", - "tracking_start_date": "2026-06-17", - "team_designation": "main", - "membership_history": [ - { - "event": "joined", - "date": "2026-06-17", - "note": "Transferred from Fearless on 17th June 2026" - } - ], - "characters": [ - { - "name": "Mydrui", - "realm": "Draenor", - "class": "Druid", - "spec": "Balance", - "role": "dps", - "active": true - } - ], - "role_history": [ - { - "role": "dps", - "class": "Druid", - "spec": "Balance", - "character": "Mydrui", - "from": "2026-06-17", - "to": null - } - ] - }, - { - "raider_id": "6b3ea430-aa85-41a8-b4df-2f83c6f7bc35", - "display_name": "Xelorith", - "status": "active", - "tracking_start_date": "2026-06-17", - "team_designation": "main", - "membership_history": [ - { - "event": "joined", - "date": "2026-06-17", - "note": "Joined 17th June 2026" - } - ], - "characters": [ - { - "name": "Xelorith", - "realm": "Draenor", - "class": "Warlock", - "spec": "Demonology", - "role": "dps", - "active": true - } - ], - "role_history": [ - { - "role": "dps", - "class": "Warlock", - "spec": "Demonology", - "character": "Xelorith", - "from": "2026-06-17", - "to": null - } - ] - } - ] - }, "entries": [ { "id": "f1a00001-0000-0000-0000-000000000001", @@ -1122,7 +66,7 @@ "event": "joined", "raider_id": "77d3af03-b9b3-4097-b5ad-4c8d5a536966", "display_name": "Zya", - "character": "Zyálayna", + "character": "Zy\u00e1layna", "class": "Hunter", "spec": "Survival", "role": "dps", @@ -1192,7 +136,7 @@ "event": "joined", "raider_id": "3752b0c0-364e-440f-a08b-78330ed4239c", "display_name": "Thomas", - "character": "Thômâtschô", + "character": "Th\u00f4m\u00e2tsch\u00f4", "class": "Warrior", "spec": "Fury", "role": "dps", @@ -1290,7 +234,7 @@ "event": "joined", "raider_id": "f551f254-b145-4991-8cf1-970db0cab24e", "display_name": "Lucifron", - "character": "Lucífron", + "character": "Luc\u00edfron", "class": "Warlock", "spec": "Demonology", "role": "dps", @@ -1331,8 +275,8 @@ "team": "main", "event": "joined", "raider_id": "5f6b9abe-1dd7-45e5-9fac-56b893f8ffd5", - "display_name": "Imû", - "character": "Imû", + "display_name": "Im\u00fb", + "character": "Im\u00fb", "class": "Mage", "spec": "Frost", "role": "dps", @@ -1359,8 +303,8 @@ "team": "main", "event": "joined", "raider_id": "87752f7d-2d52-4797-83ef-dc0a34827d0a", - "display_name": "Stormlòck", - "character": "Stormlòck", + "display_name": "Storml\u00f2ck", + "character": "Storml\u00f2ck", "class": "Warlock", "spec": "Demonology", "role": "dps", diff --git a/scripts/fetch.ts b/scripts/fetch.ts index 4caebd4..f20193e 100644 --- a/scripts/fetch.ts +++ b/scripts/fetch.ts @@ -15,8 +15,9 @@ * 11. Generate changelog entries by diffing current vs. previous roster. */ +import { execSync } from 'node:child_process'; import { createHash } from 'node:crypto'; -import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'; +import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from 'node:fs'; import { dirname, join } from 'node:path'; import { fileURLToPath } from 'node:url'; import { classifyKill, formatLocalTime, getIsoWeekForTimestamp } from '../src/lib/utils/lockout.js'; @@ -73,11 +74,32 @@ function loadJsonOr(relPath, fallback) { return JSON.parse(readFileSync(full, 'utf-8')); } +/** Load the most-recent weeks/*.json for a season, or null if none exist. */ +function loadLatestWeek(seasonRelPath) { + const weeksDir = join(dataDir, seasonRelPath, 'weeks'); + if (!existsSync(weeksDir)) return null; + const files = readdirSync(weeksDir) + .filter((f) => f.endsWith('.json')) + .sort(); + if (!files.length) return null; + return loadJsonOr(`${seasonRelPath}/weeks/${files[files.length - 1]}`, null); +} + /** SHA-256 hash of an object (deterministic JSON). */ function hashRoster(roster) { return createHash('sha256').update(JSON.stringify(roster)).digest('hex'); } +/** Load roster.json from the previous git commit, or null if unavailable. */ +function loadPrevRosterFromGit() { + try { + const raw = execSync('git show HEAD:data/roster.json', { encoding: 'utf-8' }); + return JSON.parse(raw); + } catch { + return null; + } +} + // ── Main ────────────────────────────────────────────────────────────────────── async function main() { @@ -95,7 +117,6 @@ async function main() { const changelogFile = loadJsonOr('changelog.json', { last_updated: null, roster_hash: null, - prev_roster_snapshot: null, entries: [], }); @@ -206,23 +227,22 @@ async function main() { // ── 10. Changelog ───────────────────────────────────────────────────────── const currentHash = hashRoster(roster); const prevHash = changelogFile.roster_hash; - const prevRoster = changelogFile.prev_roster_snapshot ?? null; if (currentHash !== prevHash) { console.log('[fetch] Roster changed — generating changelog entries…'); + const prevRoster = loadPrevRosterFromGit(); const newEntries = generateChangelogEntries(roster, prevRoster, fetchedAt, currentWeek); console.log(`[fetch] Generated ${newEntries.length} changelog entry/entries.`); const updatedChangelog = { last_updated: fetchedAt, roster_hash: currentHash, - prev_roster_snapshot: roster, entries: [...changelogFile.entries, ...newEntries], }; writeJson('changelog.json', updatedChangelog); } else { - changelogFile.last_updated = fetchedAt; - writeJson('changelog.json', changelogFile); + const { prev_roster_snapshot: _, ...rest } = changelogFile; + writeJson('changelog.json', { ...rest, last_updated: fetchedAt }); } console.log(`[fetch] Run complete. Week: ${currentWeek}`); @@ -241,7 +261,7 @@ async function processMplusSeason({ const { season_id } = season; const prefix = `seasons/${season_id}`; - const existingMplusSnapshot = loadJsonOr(`${prefix}/snapshot.json`, null); + const existingMplusSnapshot = loadLatestWeek(prefix); const prevMplusSnapByRaider = new Map( (existingMplusSnapshot?.raiders ?? []).map((r) => [r.raider_id, r]), ); @@ -348,7 +368,6 @@ async function processMplusSeason({ const weeklyData = { season_id, week: currentWeek, fetched_at: fetchedAt, raiders }; writeJson(`${prefix}/weeks/${currentWeek}.json`, weeklyData); - writeJson(`${prefix}/snapshot.json`, weeklyData); console.log(`[fetch] M+ season ${season_id}: wrote ${raiders.length} raider(s).`); } @@ -356,24 +375,17 @@ async function processMplusSeason({ // ── Spec helpers ────────────────────────────────────────────────────────────── function getPrimarySpecName(char) { - if (char.specs?.length) { - return (char.specs.find((s) => s.primary) ?? char.specs[0]).spec; - } - return char.spec ?? null; + if (!char.specs?.length) return null; + return (char.specs.find((s) => s.primary) ?? char.specs[0]).spec; } function getPrimarySpecRole(char) { - if (char.specs?.length) { - return (char.specs.find((s) => s.primary) ?? char.specs[0]).role; - } - return char.role ?? 'dps'; + if (!char.specs?.length) return 'dps'; + return (char.specs.find((s) => s.primary) ?? char.specs[0]).role; } function getCharOffspecs(char) { - if (char.specs?.length) { - return char.specs.filter((s) => !s.primary && s.wcl_active); - } - return []; + return (char.specs ?? []).filter((s) => !s.primary && s.wcl_active); } // ── Lockout helpers ──────────────────────────────────────────────────────────── @@ -468,8 +480,8 @@ async function processRaidZone({ const bossNames = new Map((zone.encounters ?? []).map((e) => [e.id, e.name])); const difficulties = roster.raid_difficulties ?? ['heroic', 'mythic']; - // Read existing snapshot to detect character switches on this run - const existingRaidSnapshot = loadJsonOr(`${prefix}/snapshot.json`, null); + // Read existing latest week to detect character switches on this run + const existingRaidSnapshot = loadLatestWeek(prefix); const prevRaidSnapByRaider = new Map( (existingRaidSnapshot?.raiders ?? []).map((r) => [r.raider_id, r]), ); @@ -981,7 +993,6 @@ async function processRaidZone({ } writeJson(`${prefix}/weeks/${currentWeek}.json`, weeklyData); - writeJson(`${prefix}/snapshot.json`, weeklyData); console.log(`[fetch] Raid zone ${zone.name}: wrote ${raiders.length} raider(s).`); }