nonebot-plugin-roblox-search 1.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (21) hide show
  1. nonebot_plugin_roblox_search-1.0.0/LICENSE +21 -0
  2. nonebot_plugin_roblox_search-1.0.0/PKG-INFO +52 -0
  3. nonebot_plugin_roblox_search-1.0.0/README.md +31 -0
  4. nonebot_plugin_roblox_search-1.0.0/nonebot_plugin_roblox_search/__init__.py +51 -0
  5. nonebot_plugin_roblox_search-1.0.0/nonebot_plugin_roblox_search/roblox_game_id_search.py +117 -0
  6. nonebot_plugin_roblox_search-1.0.0/nonebot_plugin_roblox_search/roblox_game_name_search.py +106 -0
  7. nonebot_plugin_roblox_search-1.0.0/nonebot_plugin_roblox_search/roblox_get_followers.py +102 -0
  8. nonebot_plugin_roblox_search-1.0.0/nonebot_plugin_roblox_search/roblox_get_followings.py +102 -0
  9. nonebot_plugin_roblox_search-1.0.0/nonebot_plugin_roblox_search/roblox_get_friends.py +102 -0
  10. nonebot_plugin_roblox_search-1.0.0/nonebot_plugin_roblox_search/roblox_group_id_search.py +113 -0
  11. nonebot_plugin_roblox_search-1.0.0/nonebot_plugin_roblox_search/roblox_group_name_search.py +107 -0
  12. nonebot_plugin_roblox_search-1.0.0/nonebot_plugin_roblox_search/roblox_menu.py +24 -0
  13. nonebot_plugin_roblox_search-1.0.0/nonebot_plugin_roblox_search/roblox_query.py +208 -0
  14. nonebot_plugin_roblox_search-1.0.0/nonebot_plugin_roblox_search/roblox_user_id_search.py +164 -0
  15. nonebot_plugin_roblox_search-1.0.0/nonebot_plugin_roblox_search.egg-info/PKG-INFO +52 -0
  16. nonebot_plugin_roblox_search-1.0.0/nonebot_plugin_roblox_search.egg-info/SOURCES.txt +19 -0
  17. nonebot_plugin_roblox_search-1.0.0/nonebot_plugin_roblox_search.egg-info/dependency_links.txt +1 -0
  18. nonebot_plugin_roblox_search-1.0.0/nonebot_plugin_roblox_search.egg-info/requires.txt +3 -0
  19. nonebot_plugin_roblox_search-1.0.0/nonebot_plugin_roblox_search.egg-info/top_level.txt +1 -0
  20. nonebot_plugin_roblox_search-1.0.0/pyproject.toml +44 -0
  21. nonebot_plugin_roblox_search-1.0.0/setup.cfg +4 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 MrMao
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,52 @@
1
+ Metadata-Version: 2.4
2
+ Name: nonebot-plugin-roblox-search
3
+ Version: 1.0.0
4
+ Summary: Roblox平台综合查询插件,支持用户/游戏/群组/好友粉丝列表查询
5
+ Author-email: MrMao <admin@maoyyds.cn>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/maoyyds-cn/nonebot-plugin-roblox-search
8
+ Project-URL: Repository, https://github.com/maoyyds-cn/nonebot-plugin-roblox-search.git
9
+ Keywords: nonebot2,roblox,onebot-v11,查询,游戏
10
+ Classifier: Programming Language :: Python :: 3.10
11
+ Classifier: Programming Language :: Python :: 3.11
12
+ Classifier: Programming Language :: Python :: 3.12
13
+ Classifier: Operating System :: OS Independent
14
+ Requires-Python: >=3.10
15
+ Description-Content-Type: text/markdown
16
+ License-File: LICENSE
17
+ Requires-Dist: nonebot2>=2.2.0
18
+ Requires-Dist: nonebot-adapter-onebot>=2.4.0
19
+ Requires-Dist: python-dateutil>=2.8.2
20
+ Dynamic: license-file
21
+
22
+ # nonebot-plugin-roblox-search
23
+ NoneBot2 Roblox 全功能查询插件,基于系统curl请求,无需aiohttp,低环境依赖
24
+
25
+ ## 功能清单
26
+ - `/菜单`:查看全部指令
27
+ - `/用户名搜索 [用户名]`:通过用户名查询用户完整资料、历史曾用名、会员、群组、在线状态
28
+ - `/用户ID搜索 [数字ID]`:直接UID查询用户
29
+ - `/群组名搜索 [群组名]`:模糊搜索群组并展示详情
30
+ - `/群组ID搜索 [数字ID]`:群组ID精准查询、职位列表
31
+ - `/游戏名搜索 [游戏名]`:搜索游戏、在线人数、访问量
32
+ - `/游戏ID搜索 [数字ID]`:游戏详情+公开服务器列表
33
+ - `/获取好友列表 [用户ID]`:读取用户前10位好友,区分互关
34
+ - `/获取粉丝列表 [用户ID]`:读取前10位粉丝
35
+ - `/获取关注列表 [用户ID]`:读取前10位关注
36
+
37
+ ## 特性
38
+ 1. 使用系统curl异步调用,不依赖aiohttp,规避服务器Python环境冲突
39
+ 2. 多层异常捕获,单个接口失效不崩溃整条查询
40
+ 3. 自动截断超长文本,适配QQ消息长度限制
41
+ 4. 自带查询耗时日志,方便排错
42
+ 5. 兼容Linux/Windows/Mac(需系统内置curl)
43
+
44
+ ## 前置依赖
45
+ 1. Python >=3.10
46
+ 2. NoneBot2 + OneBot V11适配器
47
+ 3. 系统安装curl(Linux默认自带,Windows需安装Git Bash或单独curl)
48
+
49
+ ## 安装方式
50
+ ### 使用nb-cli安装(推荐)
51
+ ```bash
52
+ nb plugin install nonebot-plugin-roblox-search
@@ -0,0 +1,31 @@
1
+ # nonebot-plugin-roblox-search
2
+ NoneBot2 Roblox 全功能查询插件,基于系统curl请求,无需aiohttp,低环境依赖
3
+
4
+ ## 功能清单
5
+ - `/菜单`:查看全部指令
6
+ - `/用户名搜索 [用户名]`:通过用户名查询用户完整资料、历史曾用名、会员、群组、在线状态
7
+ - `/用户ID搜索 [数字ID]`:直接UID查询用户
8
+ - `/群组名搜索 [群组名]`:模糊搜索群组并展示详情
9
+ - `/群组ID搜索 [数字ID]`:群组ID精准查询、职位列表
10
+ - `/游戏名搜索 [游戏名]`:搜索游戏、在线人数、访问量
11
+ - `/游戏ID搜索 [数字ID]`:游戏详情+公开服务器列表
12
+ - `/获取好友列表 [用户ID]`:读取用户前10位好友,区分互关
13
+ - `/获取粉丝列表 [用户ID]`:读取前10位粉丝
14
+ - `/获取关注列表 [用户ID]`:读取前10位关注
15
+
16
+ ## 特性
17
+ 1. 使用系统curl异步调用,不依赖aiohttp,规避服务器Python环境冲突
18
+ 2. 多层异常捕获,单个接口失效不崩溃整条查询
19
+ 3. 自动截断超长文本,适配QQ消息长度限制
20
+ 4. 自带查询耗时日志,方便排错
21
+ 5. 兼容Linux/Windows/Mac(需系统内置curl)
22
+
23
+ ## 前置依赖
24
+ 1. Python >=3.10
25
+ 2. NoneBot2 + OneBot V11适配器
26
+ 3. 系统安装curl(Linux默认自带,Windows需安装Git Bash或单独curl)
27
+
28
+ ## 安装方式
29
+ ### 使用nb-cli安装(推荐)
30
+ ```bash
31
+ nb plugin install nonebot-plugin-roblox-search
@@ -0,0 +1,51 @@
1
+ # nonebot_plugin_roblox_search/__init__.py
2
+ from nonebot.plugin import PluginMetadata
3
+
4
+ # 导入全部功能模块,原有roblox文件零修改
5
+ from . import (
6
+ roblox_menu,
7
+ roblox_query,
8
+ roblox_user_id_search,
9
+ roblox_group_id_search,
10
+ roblox_group_name_search,
11
+ roblox_game_id_search,
12
+ roblox_game_name_search,
13
+ roblox_get_friends,
14
+ roblox_get_followers,
15
+ roblox_get_followings,
16
+ )
17
+
18
+ # 插件元信息(NoneBot2 标准PluginMetadata)
19
+ __plugin_meta__ = PluginMetadata(
20
+ name="Roblox查询插件",
21
+ description="Roblox平台综合查询工具,支持用户/游戏/群组、好友粉丝关注列表查询",
22
+ usage="""
23
+ 发送 /menu 查看全部可用指令
24
+ /用户名搜索 [用户名] 根据用户名查询用户
25
+ /用户ID搜索 [数字ID] 根据ID查询用户完整资料
26
+ /游戏名搜索 [游戏名] 模糊搜索游戏
27
+ /游戏ID搜索 [数字ID] 查询游戏详情+公开服务器
28
+ /群组名搜索 [群组名] 模糊搜索群组
29
+ /群组ID搜索 [数字ID] 查询群组详情与职位
30
+ /获取好友列表 [用户ID] 获取前10位好友
31
+ /获取粉丝列表 [用户ID] 获取前10位粉丝
32
+ /获取关注列表 [用户ID] 获取前10位关注
33
+ """.strip(),
34
+ type="application",
35
+ homepage="https://github.com/maoyyds-cn/nonebot-plugin-roblox-search",
36
+ supported_adapters={"nonebot.adapters.onebot.v11"},
37
+ )
38
+
39
+ # 导出模块,NoneBot自动加载所有命令
40
+ __all__ = [
41
+ "roblox_menu",
42
+ "roblox_query",
43
+ "roblox_user_id_search",
44
+ "roblox_group_id_search",
45
+ "roblox_group_name_search",
46
+ "roblox_game_id_search",
47
+ "roblox_game_name_search",
48
+ "roblox_get_friends",
49
+ "roblox_get_followers",
50
+ "roblox_get_followings",
51
+ ]
@@ -0,0 +1,117 @@
1
+ import asyncio
2
+ import json
3
+ import traceback
4
+ import time
5
+ from nonebot import on_keyword
6
+ from nonebot.adapters.onebot.v11 import Event, Message, MessageSegment
7
+ from nonebot.exception import ActionFailed
8
+
9
+ HEADERS = [
10
+ "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
11
+ ]
12
+ TIMEOUT = 30
13
+
14
+ # 触发关键词:/游戏ID搜索
15
+ roblox_game_id_search = on_keyword(["/游戏ID搜索","游戏ID搜索"], priority=5, block=True)
16
+
17
+ async def curl_request(method, url, data=None, headers=None):
18
+ cmd = ["curl", "-s", "-X", method, "--max-time", str(TIMEOUT)]
19
+ if data:
20
+ cmd += ["-H", "Content-Type: application/json", "-d", json.dumps(data)]
21
+ for h in (headers or HEADERS):
22
+ cmd += ["-H", h]
23
+ cmd.append(url)
24
+
25
+ process = await asyncio.create_subprocess_exec(
26
+ *cmd,
27
+ stdout=asyncio.subprocess.PIPE,
28
+ stderr=asyncio.subprocess.PIPE
29
+ )
30
+ stdout, stderr = await process.communicate()
31
+ if process.returncode != 0:
32
+ raise Exception(f"curl 失败: {stderr.decode()}")
33
+ return json.loads(stdout.decode()) if stdout else {}
34
+
35
+ async def get_game_detail(game_id):
36
+ """根据游戏ID获取详情"""
37
+ url = f"https://games.roblox.com/v1/games?universeIds={game_id}"
38
+ data = await curl_request("GET", url)
39
+ return data.get("data", [{}])[0]
40
+
41
+ async def get_game_servers(game_id):
42
+ """获取游戏服务器列表(前3个)"""
43
+ url = f"https://games.roblox.com/v1/games/{game_id}/servers/Public?limit=3"
44
+ try:
45
+ data = await curl_request("GET", url)
46
+ return data.get("data", [])
47
+ except Exception:
48
+ return []
49
+
50
+ @roblox_game_id_search.handle()
51
+ async def handle_game_id_search(event: Event):
52
+ # 提取游戏ID
53
+ raw_text = str(event.get_message()).strip()
54
+ game_id_str = raw_text.replace("/游戏ID搜索", "").strip()
55
+
56
+ if not game_id_str or not game_id_str.isdigit():
57
+ await roblox_game_id_search.finish("请输入有效的Roblox游戏ID(纯数字),例:/游戏ID搜索 123456789")
58
+ game_id = int(game_id_str)
59
+
60
+ await roblox_game_id_search.send("稍等,正在查询游戏信息...")
61
+ total_start = time.time()
62
+
63
+ try:
64
+ # 并发请求数据
65
+ tasks = [get_game_detail(game_id), get_game_servers(game_id)]
66
+ results = await asyncio.gather(*tasks, return_exceptions=True)
67
+ game_detail, game_servers = results
68
+
69
+ # 异常兜底
70
+ if isinstance(game_detail, Exception) or not game_detail:
71
+ await roblox_game_id_search.finish("未找到该游戏ID对应的信息!")
72
+ if isinstance(game_servers, Exception):
73
+ game_servers = []
74
+
75
+ # 解析信息
76
+ name = game_detail.get("name", "未知")
77
+ creator = game_detail.get("creator", {}).get("name", "未知")
78
+ play_count = game_detail.get("playing", 0)
79
+ visit_count = game_detail.get("visits", 0)
80
+ max_players = game_detail.get("maxPlayers", 0)
81
+ description = game_detail.get("description", "").strip() or "无简介"
82
+ created = game_detail.get("created", "").split("T")[0] if game_detail.get("created") else "未知"
83
+
84
+ # 服务器列表
85
+ server_text = ""
86
+ if game_servers:
87
+ for idx, server in enumerate(game_servers):
88
+ server_id = server.get("id", "未知")
89
+ player_count = server.get("playing", 0)
90
+ ping = server.get("ping", 0)
91
+ server_text += f"{idx+1}) 服务器ID:{server_id}\n 在线人数:{player_count}/{max_players} | 延迟:{ping}ms\n"
92
+ else:
93
+ server_text = "暂无公开服务器信息"
94
+
95
+ # 组装输出
96
+ output = (
97
+ f"🎮 Roblox 游戏ID查询结果\n"
98
+ f"🆔 游戏ID:{game_id}\n"
99
+ f"📛 游戏名:{name}\n"
100
+ f"📅 创建时间:{created}\n"
101
+ f"👨‍💻 开发者:{creator}\n"
102
+ f"👥 当前在线:{play_count}\n"
103
+ f"📊 总访问量:{visit_count}\n"
104
+ f"🔢 最大人数:{max_players}\n\n"
105
+ f"📝 简介:\n{description[:300]}{'......' if len(description)>300 else ''}\n\n"
106
+ f"🌐 公开服务器(前3个):\n{server_text}"
107
+ )
108
+
109
+ await roblox_game_id_search.finish(output)
110
+
111
+ except ActionFailed:
112
+ raise
113
+ except Exception as e:
114
+ print("[游戏ID搜索错误]", traceback.format_exc())
115
+ await roblox_game_id_search.finish(f"查询失败:{str(e)}")
116
+ finally:
117
+ print(f"[DEBUG] 游戏ID搜索耗时: {time.time()-total_start:.2f}s")
@@ -0,0 +1,106 @@
1
+ import asyncio
2
+ import json
3
+ import traceback
4
+ import time
5
+ from nonebot import on_keyword
6
+ from nonebot.adapters.onebot.v11 import Event, Message, MessageSegment
7
+ from nonebot.exception import ActionFailed
8
+
9
+ HEADERS = [
10
+ "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
11
+ ]
12
+ TIMEOUT = 30
13
+
14
+ # 触发关键词:/游戏名搜索
15
+ roblox_game_name_search = on_keyword(["/游戏名搜索","游戏名搜索"], priority=5, block=True)
16
+
17
+ async def curl_request(method, url, data=None, headers=None):
18
+ cmd = ["curl", "-s", "-X", method, "--max-time", str(TIMEOUT)]
19
+ if data:
20
+ cmd += ["-H", "Content-Type: application/json", "-d", json.dumps(data)]
21
+ for h in (headers or HEADERS):
22
+ cmd += ["-H", h]
23
+ cmd.append(url)
24
+
25
+ process = await asyncio.create_subprocess_exec(
26
+ *cmd,
27
+ stdout=asyncio.subprocess.PIPE,
28
+ stderr=asyncio.subprocess.PIPE
29
+ )
30
+ stdout, stderr = await process.communicate()
31
+ if process.returncode != 0:
32
+ raise Exception(f"curl 失败: {stderr.decode()}")
33
+ return json.loads(stdout.decode()) if stdout else {}
34
+
35
+ async def search_game_by_name(game_name):
36
+ """根据游戏名搜索"""
37
+ url = f"https://games.roblox.com/v1/games/list?keyword={game_name}&limit=5"
38
+ try:
39
+ data = await curl_request("GET", url)
40
+ return data.get("data", [])
41
+ except Exception:
42
+ return []
43
+
44
+ async def get_game_detail(game_id):
45
+ """获取游戏详情"""
46
+ url = f"https://games.roblox.com/v1/games?universeIds={game_id}"
47
+ try:
48
+ data = await curl_request("GET", url)
49
+ return data.get("data", [{}])[0]
50
+ except Exception:
51
+ return {}
52
+
53
+ @roblox_game_name_search.handle()
54
+ async def handle_game_name_search(event: Event):
55
+ # 提取游戏名
56
+ raw_text = str(event.get_message()).strip()
57
+ game_name = raw_text.replace("/游戏名搜索", "").strip()
58
+
59
+ if not game_name:
60
+ await roblox_game_name_search.finish("请输入Roblox游戏名,例:/游戏名搜索 Adopt Me")
61
+
62
+ await roblox_game_name_search.send("稍等,正在搜索游戏...")
63
+ total_start = time.time()
64
+
65
+ try:
66
+ # 搜索游戏
67
+ game_list = await search_game_by_name(game_name)
68
+ if not game_list:
69
+ await roblox_game_name_search.finish("未找到匹配的游戏!")
70
+
71
+ # 批量获取游戏详情
72
+ tasks = [get_game_detail(g.get("universeId", 0)) for g in game_list]
73
+ game_details = await asyncio.gather(*tasks, return_exceptions=True)
74
+
75
+ # 组装结果
76
+ output = f"🎮 游戏名搜索结果(共{len(game_list)}个):\n\n"
77
+ for idx, (game, detail) in enumerate(zip(game_list, game_details)):
78
+ game_id = game.get("universeId", 0)
79
+ name = game.get("name", "未知")
80
+ creator = game.get("creator", {}).get("name", "未知")
81
+
82
+ if isinstance(detail, Exception):
83
+ detail = {}
84
+ play_count = detail.get("playing", 0)
85
+ visit_count = detail.get("visits", 0)
86
+ description = detail.get("description", "").strip()[:100] or "无简介"
87
+
88
+ output += (
89
+ f"【{idx+1}】\n"
90
+ f"游戏名:{name}\n"
91
+ f"游戏ID:{game_id}\n"
92
+ f"开发者:{creator}\n"
93
+ f"当前在线:{play_count}\n"
94
+ f"总访问量:{visit_count}\n"
95
+ f"简介:{description}\n\n"
96
+ )
97
+
98
+ await roblox_game_name_search.finish(output.strip())
99
+
100
+ except ActionFailed:
101
+ raise
102
+ except Exception as e:
103
+ print("[游戏名搜索错误]", traceback.format_exc())
104
+ await roblox_game_name_search.finish(f"搜索失败:{str(e)}")
105
+ finally:
106
+ print(f"[DEBUG] 游戏名搜索耗时: {time.time()-total_start:.2f}s")
@@ -0,0 +1,102 @@
1
+ import asyncio
2
+ import json
3
+ import traceback
4
+ import time
5
+ from nonebot import on_keyword
6
+ from nonebot.adapters.onebot.v11 import Event, Message, MessageSegment
7
+ from nonebot.exception import ActionFailed
8
+
9
+ HEADERS = [
10
+ "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
11
+ ]
12
+ TIMEOUT = 30
13
+
14
+ # 触发关键词:/获取粉丝列表
15
+ roblox_get_followers = on_keyword(["/获取粉丝列表","获取粉丝列表"], priority=5, block=True)
16
+
17
+ async def curl_request(method, url, data=None, headers=None):
18
+ cmd = ["curl", "-s", "-X", method, "--max-time", str(TIMEOUT)]
19
+ if data:
20
+ cmd += ["-H", "Content-Type: application/json", "-d", json.dumps(data)]
21
+ for h in (headers or HEADERS):
22
+ cmd += ["-H", h]
23
+ cmd.append(url)
24
+
25
+ process = await asyncio.create_subprocess_exec(
26
+ *cmd,
27
+ stdout=asyncio.subprocess.PIPE,
28
+ stderr=asyncio.subprocess.PIPE
29
+ )
30
+ stdout, stderr = await process.communicate()
31
+ if process.returncode != 0:
32
+ raise Exception(f"curl 失败: {stderr.decode()}")
33
+ return json.loads(stdout.decode()) if stdout else {}
34
+
35
+ async def get_follower_list(uid):
36
+ """获取用户粉丝列表"""
37
+ url = f"https://friends.roblox.com/v1/users/{uid}/followers?limit=10" # 最多10个
38
+ try:
39
+ data = await curl_request("GET", url)
40
+ return data.get("data", [])
41
+ except Exception:
42
+ return []
43
+
44
+ async def get_user_basic_info(uids):
45
+ """批量获取用户基础信息"""
46
+ url = "https://users.roblox.com/v1/users/usernames"
47
+ payload = {"userIds": uids, "excludeBannedUsers": False}
48
+ try:
49
+ data = await curl_request("POST", url, data=payload)
50
+ return {item["id"]: item for item in data.get("data", [])}
51
+ except Exception:
52
+ return {}
53
+
54
+ @roblox_get_followers.handle()
55
+ async def handle_get_followers(event: Event):
56
+ # 提取用户ID
57
+ raw_text = str(event.get_message()).strip()
58
+ uid_str = raw_text.replace("/获取粉丝列表", "").strip()
59
+
60
+ if not uid_str or not uid_str.isdigit():
61
+ await roblox_get_followers.finish("请输入有效的Roblox用户ID(纯数字),例:/获取粉丝列表 123456789")
62
+ uid = int(uid_str)
63
+
64
+ await roblox_get_followers.send("稍等,正在获取粉丝列表...")
65
+ total_start = time.time()
66
+
67
+ try:
68
+ # 获取粉丝列表
69
+ follower_list = await get_follower_list(uid)
70
+ if not follower_list:
71
+ await roblox_get_followers.finish("该用户暂无粉丝,或无法获取粉丝列表!")
72
+
73
+ # 批量获取粉丝基础信息
74
+ follower_uids = [f.get("id", 0) for f in follower_list]
75
+ follower_info_map = await get_user_basic_info(follower_uids)
76
+
77
+ # 组装结果
78
+ output = f"🌟 用户ID {uid} 的粉丝列表(共{len(follower_list)}个):\n\n"
79
+ for idx, follower in enumerate(follower_list):
80
+ follower_id = follower.get("id", 0)
81
+ follower_info = follower_info_map.get(follower_id, {})
82
+ username = follower_info.get("name", "未知")
83
+ display_name = follower_info.get("displayName", "未知")
84
+ followed_at = follower.get("created", "").split("T")[0] if follower.get("created") else "未知时间"
85
+
86
+ output += (
87
+ f"【{idx+1}】\n"
88
+ f"用户ID:{follower_id}\n"
89
+ f"用户名:{username}\n"
90
+ f"展示名:{display_name}\n"
91
+ f"关注时间:{followed_at}\n\n"
92
+ )
93
+
94
+ await roblox_get_followers.finish(output.strip())
95
+
96
+ except ActionFailed:
97
+ raise
98
+ except Exception as e:
99
+ print("[获取粉丝列表错误]", traceback.format_exc())
100
+ await roblox_get_followers.finish(f"获取失败:{str(e)}")
101
+ finally:
102
+ print(f"[DEBUG] 获取粉丝列表耗时: {time.time()-total_start:.2f}s")
@@ -0,0 +1,102 @@
1
+ import asyncio
2
+ import json
3
+ import traceback
4
+ import time
5
+ from nonebot import on_keyword
6
+ from nonebot.adapters.onebot.v11 import Event, Message, MessageSegment
7
+ from nonebot.exception import ActionFailed
8
+
9
+ HEADERS = [
10
+ "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
11
+ ]
12
+ TIMEOUT = 30
13
+
14
+ # 触发关键词:/获取关注列表
15
+ roblox_get_followings = on_keyword(["/获取关注列表","获取关注列表"], priority=5, block=True)
16
+
17
+ async def curl_request(method, url, data=None, headers=None):
18
+ cmd = ["curl", "-s", "-X", method, "--max-time", str(TIMEOUT)]
19
+ if data:
20
+ cmd += ["-H", "Content-Type: application/json", "-d", json.dumps(data)]
21
+ for h in (headers or HEADERS):
22
+ cmd += ["-H", h]
23
+ cmd.append(url)
24
+
25
+ process = await asyncio.create_subprocess_exec(
26
+ *cmd,
27
+ stdout=asyncio.subprocess.PIPE,
28
+ stderr=asyncio.subprocess.PIPE
29
+ )
30
+ stdout, stderr = await process.communicate()
31
+ if process.returncode != 0:
32
+ raise Exception(f"curl 失败: {stderr.decode()}")
33
+ return json.loads(stdout.decode()) if stdout else {}
34
+
35
+ async def get_following_list(uid):
36
+ """获取用户关注列表"""
37
+ url = f"https://friends.roblox.com/v1/users/{uid}/followings?limit=10" # 最多10个
38
+ try:
39
+ data = await curl_request("GET", url)
40
+ return data.get("data", [])
41
+ except Exception:
42
+ return []
43
+
44
+ async def get_user_basic_info(uids):
45
+ """批量获取用户基础信息"""
46
+ url = "https://users.roblox.com/v1/users/usernames"
47
+ payload = {"userIds": uids, "excludeBannedUsers": False}
48
+ try:
49
+ data = await curl_request("POST", url, data=payload)
50
+ return {item["id"]: item for item in data.get("data", [])}
51
+ except Exception:
52
+ return {}
53
+
54
+ @roblox_get_followings.handle()
55
+ async def handle_get_followings(event: Event):
56
+ # 提取用户ID
57
+ raw_text = str(event.get_message()).strip()
58
+ uid_str = raw_text.replace("/获取关注列表", "").strip()
59
+
60
+ if not uid_str or not uid_str.isdigit():
61
+ await roblox_get_followings.finish("请输入有效的Roblox用户ID(纯数字),例:/获取关注列表 123456789")
62
+ uid = int(uid_str)
63
+
64
+ await roblox_get_followings.send("稍等,正在获取关注列表...")
65
+ total_start = time.time()
66
+
67
+ try:
68
+ # 获取关注列表
69
+ following_list = await get_following_list(uid)
70
+ if not following_list:
71
+ await roblox_get_followings.finish("该用户暂无关注,或无法获取关注列表!")
72
+
73
+ # 批量获取关注用户基础信息
74
+ following_uids = [f.get("id", 0) for f in following_list]
75
+ following_info_map = await get_user_basic_info(following_uids)
76
+
77
+ # 组装结果
78
+ output = f"🔍 用户ID {uid} 的关注列表(共{len(following_list)}个):\n\n"
79
+ for idx, following in enumerate(following_list):
80
+ following_id = following.get("id", 0)
81
+ following_info = following_info_map.get(following_id, {})
82
+ username = following_info.get("name", "未知")
83
+ display_name = following_info.get("displayName", "未知")
84
+ followed_at = following.get("created", "").split("T")[0] if following.get("created") else "未知时间"
85
+
86
+ output += (
87
+ f"【{idx+1}】\n"
88
+ f"用户ID:{following_id}\n"
89
+ f"用户名:{username}\n"
90
+ f"展示名:{display_name}\n"
91
+ f"关注时间:{followed_at}\n\n"
92
+ )
93
+
94
+ await roblox_get_followings.finish(output.strip())
95
+
96
+ except ActionFailed:
97
+ raise
98
+ except Exception as e:
99
+ print("[获取关注列表错误]", traceback.format_exc())
100
+ await roblox_get_followings.finish(f"获取失败:{str(e)}")
101
+ finally:
102
+ print(f"[DEBUG] 获取关注列表耗时: {time.time()-total_start:.2f}s")