打算写一个导出账号内全部已归档贴纸包的脚本,被Telegram的messages.getArchivedStickers()方法折磨了几个月后,我可以确信这是服务器端的问题,而不是客户端的方法定义有问题。

TG最多可以添加200个贴纸包,当超过之后最老的贴纸包会被自动归档,我TG用了几年了,一看,嗯,3000多个已归档贴纸包,就想导出一下,然后就被硬控了,具体怎么硬控我不想说了,反正循环遍历休息各种逻辑都尝试过了还是没戏,总之下面是报告。

https://core.telegram.org/method/messages.getArchivedStickers
依据官方文档,messages.getArchivedStickers()方法允许用户通过分页方式浏览其已归档的贴纸包列表,该方法接受偏移量ID offset_id 和单次获取数量限制 limit 两个参数,旨在实现标准的分页浏览机制。
其工作流程如下:
客户端发送offset_id=0,服务器返回第1页贴纸包;
接着客户端获取第1页结果中最后一个贴纸包的ID,并将其作为下一次请求的offset_id,从而获取第二页数据。如此循环,直至服务器返回空列表。
然而,实际情况却是:服务器仅正确响应了offset_id=0首次请求和第1页结果中最后一个贴纸包的ID的第二次请求。对于所有后续的、携带了有效offset_id的请求,服务器完全无视该参数,再次返回第1页的数据。这种行为使任何超过2页数据量的归档贴纸包都无法通过此API访问。

以Telethon为例,使用GetArchivedStickersRequest()方法,脚本如下:
import json
import asyncio
from telethon.sync import TelegramClient
from telethon.tl.functions.messages import GetArchivedStickersRequest

API_ID = '数据删除'
API_HASH = '数据删除'
SESSION_NAME = 'account'
JSON = 'sticker.json'

async def main():
    async with TelegramClient(SESSION_NAME, API_ID, API_HASH) as client:
        try:
            initial_request = await client(GetArchivedStickersRequest(offset_id=0, limit=1, masks=False))
            total_count = initial_request.count
        except:
            return

        seen_ids = set()
        all_archived_sets = []
        offset_id = 0
        is_stuck = False

        while len(seen_ids) < total_count:
            count_before = len(seen_ids)
            print(f"DEBUG: offset_id = {offset_id}")
            try:
                result = await client(GetArchivedStickersRequest(offset_id=offset_id, limit=100, masks=False))
            except:
                break
            if not result.sets:
                break

            new_items = 0
            for s in result.sets:
                if s.set.id not in seen_ids:
                    seen_ids.add(s.set.id)
                    all_archived_sets.append(s.set)
                    new_items += 1

            print(f"新增 {new_items} 个")

            if new_items == 0 and len(result.sets) > 0:
                print("API开始返回重复数据 终止")
                is_stuck = True
                break

            offset_id = result.sets[-1].set.id

        print(f"最终获取 {len(all_archived_sets)} / {total_count} 个贴纸包")
        with open(JSON, 'w', encoding='utf-8') as f:
            json.dump(
                [{'id': s.id, 'access_hash': s.access_hash, 'title': s.title, 'short_name': s.short_name}
                 for s in all_archived_sets],
                f, indent=4, ensure_ascii=False
            )

if __name__ == '__main__':
    asyncio.run(main())


输出:
zgqinc@B850M-PLUS:/mnt/f/test/telethon$ python3 acv_sticker.py
DEBUG: offset_id = 0
新增 40 个
DEBUG: offset_id = 591378...
新增 37 个
DEBUG: offset_id = 747964...
新增 0 个
API开始返回重复数据 终止
最终获取 77 / 3410 个贴纸包


使用基于TDLib实现的Telegram X查阅已归档贴纸包页面,依然返回77个贴纸包。不仅如此,Unigram以及官方客户端都只能获取少于80个的数据。

基于以上证据,我们可以得出结论:
这不是 messages.getArchivedStickers()方法定义的问题。因为该方法在前两页的请求中工作得非常完美,证明其参数设计是正确的。TDLib和Telethon都在以正确的方式使用这个方法。
这是服务器端实现的bug。问题的根源在于,当处理拥有超过100个数据的账户时,服务器的后端逻辑在处理到第三页的分页请求时出现了错误。它没能正确解析offset_id参数来定位到数据列表的相应位置,而是错误地重置或返回了初始位置的数据。

打个比方:
想象你在用某APP浏览文章,看完前2页,翻页,又出现了第1页。

那很搞笑了,我看看有什么办法可以让官方修修这个问题。
 
 
Back to Top