twitterbridge: decide new vs old by status id, not feed position

RSSHub's guid is the timeline entry's own id (a retweet's id), so ids follow
the order the account posted, while feed order and pubDate follow the
original tweet. A fresh retweet of an old post can sort to the bottom; it is
now posted, and only ids older than every known one are treated as deletion
slide-ins. !leaks sorts by id too.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
esefandClaude Opus 5.5 committed 2026-10-06 21:29:08 +03:00
1 parent f8b3501637
commit 2af2adc780
2 files changed
+31 -12

No files matched your search

+3 -1
View File
@@ -3,7 +3,7 @@ import aiohttp
import feedparser
from discord.ext import commands
from twitterbridge.rss import embed_link
from twitterbridge.rss import embed_link, tweet_id
REQUEST_TIMEOUT = 15
@@ -51,6 +51,8 @@ class TwitterLeaks(commands.Cog):
return await ctx.reply(msg[:2000])
response = "**Latest 5 Leaks:**\n"
# the feed isn't chronological; status ids are
entries = sorted(entries, key=lambda e: tweet_id(e.get('id') or e.get('link', '')) or 0, reverse=True)
for entry in entries[:5]:
response += f"\n{embed_link(entry.get('link', ''))}\n"
+28 -11
View File
@@ -43,6 +43,14 @@ def _guid(entry) -> str:
return entry.get('id') or entry.get('link', '')
def tweet_id(guid: str) -> int | None:
"""Status id from a guid. RSSHub uses the timeline entry's own id (a retweet's
id, not the original's), so ids grow in the order the account posted them,
unlike the feed order and pubDate (the original tweet's date)."""
tail = guid.rstrip('/').rsplit('/', 1)[-1]
return int(tail) if tail.isdigit() else None
class TwitterRSSForwarder:
"""Polls the Twitter RSS feed and forwards new items to Discord webhooks.
@@ -164,7 +172,7 @@ class TwitterRSSForwarder:
return
seen_set = set(seen)
known = [i for i, g in enumerate(ids) if g in seen_set]
known = [g for g in ids if g in seen_set]
if not known:
# nothing in the feed is familiar (long outage or a feed change):
# re-baseline instead of flooding the channel with the whole page
@@ -174,19 +182,28 @@ class TwitterRSSForwarder:
self._save_state()
return
# Unseen entries above the lowest known one are new (the feed isn't strictly
# chronological, so they can sit below newer known tweets). Unseen entries
# below every known one are old tweets that slid in when something above
# was deleted, so they are only remembered.
lowest_known = known[-1]
new = [e for e in entries[:lowest_known] if _guid(e) not in seen_set]
tail = [g for g in ids[lowest_known:] if g not in seen_set]
if tail:
seen.extend(tail)
# Unseen entries newer than the oldest known one in this feed are new, wherever
# the feed puts them. Unseen entries older than every known one are old tweets
# that slid in when something was deleted, so they are only remembered.
known_ids = [i for i in map(tweet_id, known) if i is not None]
oldest_known = min(known_ids) if known_ids else None
new, slid_in = [], []
for e in entries:
g = _guid(e)
if g in seen_set:
continue
tid = tweet_id(g)
if tid is not None and oldest_known is not None and tid < oldest_known:
slid_in.append(g)
else:
new.append(e)
if slid_in:
seen.extend(slid_in)
del seen[:-SEEN_LIMIT]
self._save_state()
for entry in reversed(new): # oldest first
new.sort(key=lambda e: tweet_id(_guid(e)) or 0)
for entry in new: # oldest first
if not await self._post(name, webhook, entry):
break # leave the rest unseen; retried next poll
seen.append(_guid(entry))