├── .gitignore ├── .idea ├── .gitignore ├── inspectionProfiles │ └── profiles_settings.xml ├── misc.xml ├── modules.xml ├── teelebot-master.iml └── vcs.xml ├── README.md ├── plugins ├── Menu │ ├── Menu.py │ ├── Menu_screenshot.png │ ├── __init__.py │ └── config.ini ├── __init__.py ├── add_code │ ├── __init__.py │ └── add_code.py ├── deluser │ ├── __init__.py │ ├── deluser.py │ └── usertext.json ├── invite_code │ ├── __init__.py │ ├── code.txt │ ├── invite_code.py │ └── usertext.json ├── len_invite │ ├── __init__.py │ └── len_invite.py └── updatacode │ ├── __init__.py │ ├── dlercloud.py │ └── updatacode.py ├── setup.py ├── supervisor.conf └── teelebot ├── __init__.py ├── __main__.py ├── handler.py ├── logger.py ├── plugins ├── About │ ├── About.py │ ├── __init__.py │ └── icon.png ├── Chat │ ├── Chat.py │ ├── __init__.py │ └── hello.ogg ├── Hello │ ├── Hello.py │ ├── __init__.py │ └── helloworld.png ├── Menu │ ├── Menu.py │ └── __init__.py ├── PluginCTL │ ├── PluginCTL.py │ └── __init__.py ├── Schedule │ ├── Schedule.py │ └── __init__.py ├── Uptime │ ├── Uptime.py │ └── __init__.py └── __init__.py ├── polling.py ├── request.py ├── schedule.py ├── teelebot.py ├── version.py └── webhook.py /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__/ 2 | info.txt 3 | test.py -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /workspace.xml -------------------------------------------------------------------------------- /.idea/inspectionProfiles/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/teelebot-master.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## 框架更新了我还没同步过来 大的结构会有变化 三天之内全部修改完 我说的 耶稣也拦不住我(更新完了) 2 | 3 | ## 新手安装教程请看 4 | https://southcat.net/2526/ 5 | 6 | ## 插件必看 7 | 喵帕斯的已经没了 自动补码是喵帕斯的 需要dlercloud的请把updatacode目录下的dlercloud.py文件名修改为updatacode.py覆盖掉源文件,需要其他的自己修改或是可以找我 8 | 我有时间就做一个 马上放假了时间多的是 9 | 10 | ## 第一次开发有问题及时反馈 11 | email:admin@southcat.net 12 | 13 | 博客:[南猫](https://southcat.net) 14 | 15 | 基于[teelebot](https://github.com/plutobell/teelebot)开源项目开发。 16 | 17 | ## 增加功能 18 | 19 | 1.邀请码自动发码模块 并且限制领取数量(邀请码添加在invite_code目录下的code.txt 一行一个) 20 | 21 | 2.邀请码数量统计,如果邀请码用完自动给管理员发消息(需自行修改len_invite.py里面的发送id) 22 | 23 | 3.补码模块已经开发 指令`/add_code邀请码` 请注意一行一码因为太菜要求比较严格 可能效果不是很理想,请等我再去学两天python再回来改 24 | 25 | 4.删除用户信息模块,删除后用户可以再次获取邀请码指令`/del用户id`目前只能删除单个用户 26 | 27 | 5.updatacode模块,目前仅支持从喵帕斯进行获取,会自动抓取邀请码页面前两页的邀请码,并和之前的数据进行对比,然后写入code,后续会支持更多网站 28 | 理论上所有和喵帕斯同模板的都可以使用,请在updatacode/updatacode.py 文件夹内填入你的账号密码 29 | 30 | 6.很遗憾喵关门了 在plguins/updatacode文件夹下更新了dlercloud的自动更新模块,需要使用的话备份原文件,将文件名修改为updatacode.py即可,理论上喵帕斯的补码支持所有同模板的网站只需要更改里面的网站即可,接下来会针对所有有邀请码模块的网站开发补码模块,或是你们也可以从邮箱或是tg发给我网站,我尽量进行适配(放假闲的) 31 | ## 开发计划 32 | 1.增加管理员添加邀请码模块 33 | 34 | 2.多类邀请码模块支持 35 | 36 | 3.代码优化,目前存在大量多余的代码 37 | 38 | 4.添加白名单,白名单用户支持无限获取 39 | 40 | 环境要求 41 | Python版本 42 | teelebot 只支持 Python3.x,不支持Python2.x。 43 | 44 | 本项目在 Python 3.5 及以上版本测试通过。 45 | 46 | 安装 47 | pip install teelebot 48 | 升级 49 | pip install teelebot --upgrade 50 | 使用 51 | 一行命令启动 (Polling Mode) 52 | teelebot -c/--config -k/--key -r/--root 53 | 此命令会自动生成在Polling模式下适用的配置文件,但仍需手动配置插件路径。 54 | 55 | 一、运行模式 56 | teelebot 支持以 Webhook 模式和 Polling 模式运行。生产环境推荐使用 Webhook 模式,而 Polling 则仅用于开发环境。 57 | 58 | 1、Webhook 模式 59 | 要以 Webhook 模式运行,请将配置文件字段 webhook 设置为 True ,此模式涉及的配置文件字段如下: 60 | 61 | [config] 62 | webhook=True 63 | self_signed=False 64 | cert_key=your private cert path 65 | cert_pub=your public cert path 66 | server_address=your server ip address or domain 67 | server_port=your server port 68 | local_address=webhook local address 69 | local_port=webhook local port 70 | self_signed 用于设置是否使用自签名证书,而 cert_key 和 cert_pub 则是你的证书路径(绝对路径),server_address 为你的服务器公网IP, server_port 为服务器的端口(目前 telegram 官方仅支持 443, 80, 88, 8443),local_address 为Webhook 本地监听地址, local_port 为 Webhook 本地运行的端口。 71 | 72 | 推荐搭配 nginx 使用,自签名证书生成请参考:Generating a self-signed certificate pair (PEM) 73 | 74 | 2、Polling 模式 75 | 要以 Polling 模式运行,只需要保证配置文件 webhook 字段为 False 即可。此模式最基本的配置文件如下: 76 | 77 | [config] 78 | key=bot key 79 | pool_size=40 80 | webhook=False 81 | root_id=your user id 82 | debug=False 83 | plugin_dir=your plugin dir 84 | 二、运行 85 | 任意路径打开终端,输入以下命令: 86 | 87 | 对于使用程序配置文件默认路径的: 88 | 89 | 输入teelebot 回车,正常情况下你应该能看见屏幕提示机器人开始运行。 90 | 91 | 对于命令行手动指定配置文件路径的: 92 | 93 | 输入teelebot -c/--config 回车,正常情况下你应该能看见屏幕提示机器人开始运行。(更多指令请通过 -h/--help 查看) 94 | 95 | 可配合supervisor使用。 96 | 97 | 三、配置文件 98 | 完整的配置文件如下所示: 99 | 100 | [config] 101 | key=bot key 102 | plugin_dir=your plugin dir 103 | pool_size=40 //the thread pool size, default 40, range(1, 101) 104 | webhook=False 105 | self_signed=False //Optional while webhook is False 106 | cert_key=your private cert path //Optional while webhook is False 107 | cert_pub=your public cert path //Optional while webhook is False 108 | server_ip=your server ip address //Optional while webhook is False 109 | server_port=your server port //Optional while webhook is False 110 | local_address=webhook local address //Optional while webhook is False 111 | local_port=webhook local port //Optional while webhook is False 112 | root_id=your user id 113 | debug=False 114 | drop_pending_updates=False 115 | local_api_server=local api server address //[Optional] 116 | 在 1.13.0 及以上版本,支持自动生成配置文件。(默认为Polling模式) 117 | 118 | 1.在命令行未指定配置文件路径的情况下,会在默认配置文件路径下不存在配置文件时自动生成配置文件 config.cfg。 119 | 120 | 在Linux下,会自动在用户目录下创建文件夹 .teelebot ,并生成配置文件 config.cfg 121 | 122 | 在Windows下,则会在 C:\Users\ 目录下创建文件夹 .teelebot ,并生成配置文件 config.cfg 123 | 124 | 2.指定配置文件 125 | 126 | Linux 和 Windows 都可在命令行通过参数手动指定配置文件路径,命令格式: 127 | 128 | teelebot -c/--config 129 | 路径必须为绝对路径,此情况下也会在指定路径上不存在配置文件时自动生成配置文件 ,配置文件命名由指定的路径决定。 130 | 131 | Tip: 自动生成的配置文件未设置这几个字段值:key、root_id、plugin_dir,key 和 root_id 为必须,但我们仍然可以通过命令行设置他们: 132 | 133 | teelebot -c/--config -k/--key -r/--root 134 | 使用以上命令会以Polling模式运行框架,而无需困扰于处理配置文件。 135 | 136 | 之后请手动设置 plugin_dir 。 137 | 138 | 插件开发指南 (以 Hello 插件为例) BETA 0.8 139 | 一、插件结构 140 | 一个完整的 teelebot 插件应当呈现为一个文件夹,即一个Python包,以 Hello 插件为例,最基本的目录结构如下: 141 | 142 | Hello/ 143 | ./__init__.py 144 | ./Hello.py 145 | ./Hello_screenshot.png 146 | ./readme.md 147 | ./requirement.txt 148 | 二、规则 149 | 命名 150 | 在构建teelebot插件中应当遵守的规则是:每个插件目录下应当存在一个与插件同名的.py 文件,比如插件 Hello 中的 Hello.py 文件,并且此文件中必须存在作为插件入口的同名函数,以插件 Hello 为例: 151 | 152 | #file Hello/Hello.py 153 | 154 | # -*- coding:utf-8 -*- 155 | 156 | def Hello(bot, message): 157 | pass 158 | 函数 Hello() 即为插件的入口函数,参数 bot 为Bot接口库实例化对象,参数 message 用于接收消息数据。 159 | 160 | 资源路径 161 | 若要打开某个插件目录下的文件资源,需要使用的路径应当遵循以下的格式: 162 | 163 | bot.path_converter(bot.plugin_dir + "/") 164 | 方法 path_converter 根据操作系统转换路径格式。 165 | 166 | 三、自定义触发指令 167 | 插件指令 168 | 插件的触发指令可不同于插件名,允许自定义。以插件 Hello 为例,触发指令为 /helloworld 而不是 Hello。 169 | 170 | 修改插件目录下的 __init__.py 文件设置触发指令: 171 | 172 | #file Hello/__init__.py 173 | 174 | #/helloworld 175 | #Hello World插件例子 176 | 第一行为触发指令,默认以 / 作为前缀;第二行为插件简介。 177 | 178 | 不用作插件的特殊情况 179 | 通常情况下,位于 plugins 目录下的所有包都将被识别为插件并自动加载到 teelebot 中。但在某些情况下,存在并不用作插件而只是多个插件共用包的情况,若想该包不被 teelebot 加载,请将触发指令设置为 ~~ 。以 tools 共用包为例, __init__.py 文件内容如下: 180 | 181 | #fille tools/__init__.py 182 | 183 | #~~ 184 | #tools 包的简介 185 | 建议用作插件的包名遵守 Pascal命名法,即每个单词的首字母大写;而不用做插件的包名使用全小写的包名,每个单词之间以_ 分隔。以区分 插件包 和 非插件包 : 186 | 187 | - plugins 188 | - Menu #插件包 189 | - tools #非插件包 190 | 四、插件模板创建工具 191 | 在 v1.9.20_dev 及以上版本,可以通过命令行指令一键创建插件模板。 192 | 193 | teelebot -p/--plugin 194 | 该指令会使用框架配置文件(config.cfg)中的插件路径作为所创建插件模板的存放路径。 195 | 196 | 五、周期性任务 197 | 在 v1.11.1 及以上版本,可以创建周期性任务,功能类似循环定时器。 198 | 199 | 可获得的方法: 200 | 201 | schedule.add : 添加任务 202 | schedule.delete : 移除任务 203 | schedule.find : 查找任务 204 | schedule.clear : 清空任务池 205 | schedule.status : 查看任务池状态 206 | 例: 207 | 208 | ok, uid = bot.schedule.add(gap, event, (bot, )) 209 | ok, uid = bot.schedule.delete(uid) 210 | ok, uid = bot.schedule.find(uid) 211 | ok, uid = bot.schedule.clear() 212 | ok, uid = bot.schedule.status() 213 | 周期性任务池的大小为全局线程池的三分之一 ,线程池大小则可通过配置文件指定。 214 | 1.克隆或点击下载本项目到本地,保证本机安装有`Python3.x`版本和包`requests` ; 215 | 216 | 217 | 218 | 2.`config.cfg` 配置文件 219 | 220 | 配置文件格式: 221 | 222 | ```python 223 | [config] 224 | key=your key 225 | pool_size=40 //the thread pool size, default 40, range(1, 101) 226 | webhook=False 227 | cert_pub=your public certificate dir //Optional while webhook is False 228 | server_ip=your server ip address //Optional while webhook is False 229 | server_port=your server port //Optional while webhook is False 230 | local_address=webhook local address //Optional while webhook is False 231 | local_port=webhook local port //Optional while webhook is False 232 | root=your user id 233 | debug=False 234 | timeout=60 235 | plugin_dir=your plugin dir //[Optional] 236 | ``` 237 | 238 | * Linux 239 | 240 | 在 `/root` 目录下创建文件夹 `.teelebot` ,并在其内新建配置文件 `config.cfg` ,按照上面的格式填写配置文件 241 | 242 | * Windows 243 | 244 | 在 `C:\Users\` 目录下创建文件夹 `.teelebot` ,并在其内新建配置文件 `config.cfg` ,按照上面的格式填写配置文件 245 | 246 | * 指定配置文件 247 | 248 | Linux 和 Windows 都可在命令行通过参数手动指定配置文件路径,命令格式: 249 | 250 | ``` 251 | python -m teelebot -c/-C 252 | ``` 253 | 254 | 路径必须为绝对路径。 255 | 256 | 257 | 258 | 3.运行 259 | 260 | 终端下进入teelebot文件夹所在目录。 261 | 262 | * 对于使用程序配置文件默认路径的: 263 | 264 | 输入`python -m teelebot` 回车,正常情况下你应该能看见屏幕提示机器人开始运行。 265 | 266 | * 对于命令行手动指定配置文件路径的: 267 | 268 | 输入`python -m teelebot -c/-C ` 回车,正常情况下你应该能看见屏幕提示机器人开始运行。 269 | 270 | 271 | 272 | #### 三、Pip安装运行 273 | 274 | ##### 安装 ##### 275 | 276 | * 确保本机Python环境拥有pip包管理工具。 277 | 278 | * 在本项目Releases页面下载包文件。 279 | 280 | * 本机命令行进入包文件所在目录,执行: 281 | 282 | ``` 283 | pip install 284 | 285 | or 286 | 287 | pip3 install 288 | ``` 289 | 290 | 由于API未封装完毕,暂未上传至 `PyPI` ,故不能在线安装,望谅解。 291 | 292 | ##### 运行 ##### 293 | 294 | 任意路径打开终端,输入以下命令: 295 | 296 | - 对于使用程序配置文件默认路径的: 297 | 298 | 输入`teelebot` 回车,正常情况下你应该能看见屏幕提示机器人开始运行。 299 | 300 | - 对于命令行手动指定配置文件路径的: 301 | 302 | 输入`teelebot -c/-C ` 回车,正常情况下你应该能看见屏幕提示机器人开始运行。 303 | 304 | 305 | 306 | 可配合`supervisor`使用。 307 | 308 | 309 | 310 | 311 | 312 | ## 插件开发指南 (以 Hello 插件为例) BETA 0.6 313 | 314 | #### 一、插件结构 315 | 316 | 一个完整的 `teelebot` 插件应当呈现为一个文件夹,即一个Python包,以 `Hello` 插件为例,最基本的目录结构如下: 317 | 318 | ```Python 319 | Hello/ 320 | ./__init__.py 321 | ./Hello.py 322 | ./Hello_screenshot.png 323 | ./readme.md 324 | ``` 325 | 326 | #### 二、规则 327 | 328 | ##### 命名 329 | 330 | 在构建teelebot插件中应当遵守的规则是:每个插件目录下应当存在一个与插件同名的`.py` 文件,比如插件 `Hello ` 中的 `Hello.py` 文件,并且此文件中必须存在作为插件入口的同名函数,以插件 `Hello` 为例: 331 | 332 | ```python 333 | #file Hello/Hello.py 334 | 335 | # -*- coding:utf-8 -*- 336 | 337 | def Hello(bot, message): 338 | pass 339 | ``` 340 | 341 | 函数 `Hello()` 即为插件的入口函数,参数 `bot` 为Bot接口库实例化对象,参数 `message` 用于接收消息数据。 342 | 343 | 344 | 345 | 346 | 347 | ##### 资源路径 348 | 349 | 若要打开某个插件目录下的文件资源,需要使用的路径应当遵循以下的格式: 350 | 351 | ```python 352 | bot.plugin_dir + "/" 353 | ``` 354 | 355 | #### 三、自定义触发指令 356 | 357 | ##### 插件指令 358 | 359 | 插件的触发指令可不同于插件名,允许自定义。以插件 `Hello` 为例,触发指令为 `/helloworld` 而不是 `Hello`。 360 | 361 | 修改插件目录下的 `__init__.py` 文件设置触发指令: 362 | 363 | ```python 364 | #file Hello/__init__.py 365 | 366 | #/helloworld 367 | #Hello World插件例子 368 | ``` 369 | 370 | 第一行为触发指令,默认以 `/` 作为前缀;第二行为插件简介。 371 | 372 | 373 | 374 | ##### 不用作插件的特殊情况 375 | 376 | 通常情况下,位于 `plugins` 目录下的所有包都将被识别为插件并自动加载到 `teelebot` 中。但在某些情况下,存在并不用作插件而只是多个插件共用包的情况,若想该包不被 `teelebot` 加载,请将触发指令设置为 `~~` 。以 `tools` 共用包为例, `__init__.py` 文件内容如下: 377 | 378 | ```python 379 | #fille tools/__init__.py 380 | 381 | #~~ 382 | #tools 包的简介 383 | ``` 384 | 385 | 建议用作插件的包名遵守 `Pascal命名法`,即每个单词的首字母大写;而不用做插件的包名使用全小写的包名,每个单词之间以`_` 分隔。以区分 `插件包` 和 `非插件包` : 386 | 387 | ```python 388 | - plugins 389 | - Menu #插件包 390 | - tools #非插件包 391 | ``` 392 | -------------------------------------------------------------------------------- /plugins/Menu/Menu.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | ''' 3 | creation time: 2019-8-15 4 | last_modify: 2020-11-16 5 | ''' 6 | import os 7 | 8 | def Menu(bot, message): 9 | chat_id = message["chat"]["id"] 10 | message_id = message["message_id"] 11 | chat_type = message["chat"]["type"] 12 | 13 | prefix = "start" 14 | 15 | plugin_bridge = bot.plugin_bridge 16 | plugin_dir = bot.plugin_dir 17 | plugin_list = list(plugin_bridge.keys()) 18 | 19 | if chat_type != "private" and "/pluginctl" in plugin_bridge.values() and plugin_bridge["PluginCTL"] == "/pluginctl": 20 | if os.path.exists(bot.path_converter(plugin_dir + "PluginCTL/db/" + str(chat_id) + ".db")): 21 | with open(bot.path_converter(plugin_dir + "PluginCTL/db/" + str(chat_id) + ".db"), "r") as f: 22 | plugin_setting = f.read().strip() 23 | plugin_list_off = plugin_setting.split(',') 24 | plugin_list_temp = [] 25 | for plugin in plugin_bridge.keys(): 26 | if plugin not in plugin_list_off: 27 | plugin_list_temp.append(plugin) 28 | plugin_list = plugin_list_temp 29 | 30 | plugin_count = len(plugin_list) 31 | page_size = 5 32 | page_total = int((plugin_count + page_size - 1) / page_size) # 总页数=(总数+每页数量-1)/每页数量 33 | page_callback_command = "/" + prefix + "page?page=" 34 | 35 | if not os.path.exists(bot.path_converter(plugin_dir + "Menu/config.ini")): 36 | first_btn = ["交流群组", "https://t.me/teelebot_chat"] 37 | last_btn = ["项目地址", "https://github.com/plutobell/teelebot"] 38 | else: 39 | with open(bot.path_converter(plugin_dir + "Menu/config.ini"), 'r') as g: 40 | first_btn = g.readline().strip().split(',') 41 | last_btn = g.readline().strip().split(',') 42 | 43 | wait_time = plugin_count * 7 44 | 45 | if "reply_markup" in message.keys(): 46 | click_user_id = message["click_user"]["id"] 47 | from_user_id = message["reply_to_message"]["from"]["id"] 48 | callback_query_data = message["callback_query_data"] 49 | 50 | if callback_query_data[:len(page_callback_command)] == page_callback_command: 51 | if click_user_id == from_user_id: 52 | page = int(callback_query_data.split('=')[1]) 53 | page, menu_str = menu_text(bot, plugin_dir=plugin_dir, page=page, page_total=page_total, page_size=page_size, plugin_list=plugin_list) 54 | previous_page = page - 1 55 | if previous_page < 1: 56 | previous_page = 1 57 | next_page = page + 1 58 | if next_page > page_total: 59 | next_page = page_total 60 | 61 | if page_total == 1: 62 | inlineKeyboard = [ 63 | [ 64 | {"text": first_btn[0], "url": first_btn[1]}, 65 | {"text": last_btn[0], "url": last_btn[1]}, 66 | ] 67 | ] 68 | elif page == 1: 69 | inlineKeyboard = [ 70 | [ 71 | {"text": first_btn[0], "url": first_btn[1]}, 72 | {"text": "下一页", "callback_data": page_callback_command + str(page+1)}, 73 | ] 74 | ] 75 | elif page == page_total: 76 | inlineKeyboard = [ 77 | [ 78 | {"text": "上一页", "callback_data": page_callback_command + str(page-1)}, 79 | {"text": last_btn[0], "url": last_btn[1]}, 80 | ] 81 | ] 82 | else: 83 | inlineKeyboard = [ 84 | [ 85 | {"text": "上一页", "callback_data": page_callback_command + str(previous_page)}, 86 | {"text": "下一页", "callback_data": page_callback_command + str(next_page)}, 87 | ] 88 | ] 89 | reply_markup = { 90 | "inline_keyboard": inlineKeyboard 91 | } 92 | status = bot.editMessageText(chat_id=chat_id, message_id=message_id, text=menu_str, parse_mode="HTML", reply_markup=reply_markup) 93 | status = bot.answerCallbackQuery(message["callback_query_id"]) 94 | else: 95 | status = bot.answerCallbackQuery(message["callback_query_id"], text="点啥点,关你啥事?", show_alert=bool("true")) 96 | else: 97 | page = 1 98 | if page_total == 1: 99 | inlineKeyboard = [ 100 | [ 101 | {"text": first_btn[0], "url": first_btn[1]}, 102 | {"text": last_btn[0], "url": last_btn[1]}, 103 | ] 104 | ] 105 | else: 106 | inlineKeyboard = [ 107 | [ 108 | {"text": first_btn[0], "url": first_btn[1]}, 109 | {"text": "下一页", "callback_data": page_callback_command + str(page+1)}, 110 | ] 111 | ] 112 | reply_markup = { 113 | "inline_keyboard": inlineKeyboard 114 | } 115 | 116 | page, menu_str = menu_text(bot=bot, plugin_dir=plugin_dir, page=page, page_total=page_total, page_size=page_size, plugin_list=plugin_list) 117 | 118 | status = bot.sendChatAction(chat_id, "typing") 119 | status = bot.sendMessage(chat_id=chat_id, text=menu_str, parse_mode="HTML", reply_to_message_id=message_id, reply_markup=reply_markup) 120 | 121 | bot.message_deletor(wait_time, message["chat"]["id"], status["message_id"]) 122 | 123 | def menu_text(bot, plugin_dir, page, page_total, page_size, plugin_list): 124 | VERSION = bot.version 125 | if page < 1: 126 | page = 1 127 | elif page > page_total: 128 | page = page_total 129 | 130 | if page >=1 and page <= page_total: 131 | menu_str = "" 132 | plugin_range = range(page*page_size-page_size, page*page_size-1+1) 133 | for i, plugin in enumerate(plugin_list): #(now_page*page_size-page_size,now_page*page_size-1) 134 | if i in plugin_range: 135 | with open(bot.path_converter(plugin_dir + plugin + r"/__init__.py"), encoding="utf-8") as f: 136 | line_1 = "" 137 | line_2 = "" 138 | for i in range(2): 139 | if i == 0: 140 | line_1 = f.readline().strip()[1:] 141 | elif i == 1: 142 | line_2 = f.readline().strip()[1:] 143 | menu_str += "" + line_1 + " - " + line_2 + "\n\n" 144 | menu_str = "插件列表 [" + str(page) + "/" + str(page_total) + "]\n\n" + menu_str + "\nv" + VERSION + "" 145 | 146 | return page, menu_str -------------------------------------------------------------------------------- /plugins/Menu/Menu_screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Everless321/telebot-invitecode/9ebbf77e85620485574b7bebd61d247e9a1d6612/plugins/Menu/Menu_screenshot.png -------------------------------------------------------------------------------- /plugins/Menu/__init__.py: -------------------------------------------------------------------------------- 1 | #/start 2 | #Robot 插件列表 -------------------------------------------------------------------------------- /plugins/Menu/config.ini: -------------------------------------------------------------------------------- 1 | 南猫,https://southcat.net 2 | 开源地址,https://github.com/southcat/telebot-invitecode 3 | 4 | // inclues telegram links and ordinary links -------------------------------------------------------------------------------- /plugins/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Everless321/telebot-invitecode/9ebbf77e85620485574b7bebd61d247e9a1d6612/plugins/__init__.py -------------------------------------------------------------------------------- /plugins/add_code/__init__.py: -------------------------------------------------------------------------------- 1 | #/add_code 2 | #邀请码添加模块 请一行一码 -------------------------------------------------------------------------------- /plugins/add_code/add_code.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | import os 3 | import json 4 | global last_line 5 | global chongfu 6 | def add_code(bot, message): 7 | hx = "\n" 8 | chongfu = 0 9 | if str(message["from"]["id"]) == bot.root_id: 10 | with open(bot.plugin_dir + "add_code/__init__.py", encoding="utf-8") as f: 11 | h = f.readline()[1:] 12 | with open(bot.plugin_dir + 'invite_code/usertext.json', 'r') as f1: 13 | userjson = json.load(f1) 14 | if len(message["text"]) < len(h): 15 | status = bot.sendChatAction(message["chat"]["id"], "typing") 16 | status = bot.sendMessage(message["chat"]["id"], "添加失败,请输入邀请码", "HTML") 17 | return False 18 | code = message["text"][len(h) - 1:] + "\n" 19 | with open(bot.plugin_dir + 'invite_code/code.txt', 'r') as fp: 20 | lines = fp.readlines() 21 | last_line = lines[-1] 22 | if hx not in last_line: 23 | f = open(bot.plugin_dir + 'invite_code/code.txt', 'a+') 24 | f.write(hx) 25 | f.write(code) 26 | f.close() 27 | status = bot.sendMessage(message["chat"]["id"], "添加完成,当前邀请码剩余"+str(lentj(bot=bot)), "HTML") 28 | else: 29 | f = open(bot.plugin_dir + 'invite_code/code.txt', 'a+') 30 | f.write(code) 31 | f.close() 32 | status = bot.sendMessage(message["chat"]["id"], "添加完成,当前邀请码剩余"+str(lentj(bot=bot)), "HTML") 33 | else: 34 | status = bot.sendChatAction(message["chat"]["id"], "typing") 35 | status = bot.sendMessage(message["chat"]["id"], "添加失败,您没有权限添加", "HTML") 36 | def lentj(bot): 37 | count = 0 38 | for index, line in enumerate(open(bot.plugin_dir + 'invite_code/code.txt', 'r')): 39 | count += 1 40 | return count 41 | -------------------------------------------------------------------------------- /plugins/deluser/__init__.py: -------------------------------------------------------------------------------- 1 | #/del 2 | #用于删除以获取邀请码的用户信息 /del用户ID -------------------------------------------------------------------------------- /plugins/deluser/deluser.py: -------------------------------------------------------------------------------- 1 | import json 2 | import os 3 | def deluser(bot,message): 4 | hx = "\n" 5 | if str(message["from"]["id"]) == bot.root_id: 6 | with open(bot.plugin_dir + "deluser/__init__.py", encoding="utf-8") as f: 7 | h = f.readline()[1:] 8 | if len(message["text"]) < len(h): 9 | status = bot.sendChatAction(message["chat"]["id"], "typing") 10 | status = bot.sendMessage(message["chat"]["id"], "删除失败,请输入用户id", "HTML") 11 | return False 12 | userid = message["text"][len(h) - 1:] 13 | with open(bot.plugin_dir + 'invite_code/usertext.json', 'r') as f: 14 | userjson = json.load(f) 15 | if userid in userjson: 16 | del userjson[userid] 17 | with open(bot.plugin_dir + 'invite_code/usertext.json', 'w') as f1: 18 | json.dump(userjson, f1) 19 | status = bot.sendChatAction(message["chat"]["id"], "typing") 20 | status = bot.sendMessage(message["chat"]["id"], "删除用户信息成功", "HTML") 21 | else: 22 | status = bot.sendChatAction(message["chat"]["id"], "typing") 23 | status = bot.sendMessage(message["chat"]["id"], "该用户不存在", "HTML") 24 | else: 25 | status = bot.sendChatAction(message["chat"]["id"], "typing") 26 | status = bot.sendMessage(message["chat"]["id"], "删除失败,您没有权限", "HTML") 27 | 28 | 29 | -------------------------------------------------------------------------------- /plugins/deluser/usertext.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Everless321/telebot-invitecode/9ebbf77e85620485574b7bebd61d247e9a1d6612/plugins/deluser/usertext.json -------------------------------------------------------------------------------- /plugins/invite_code/__init__.py: -------------------------------------------------------------------------------- 1 | #/code 2 | #邀请码自动发码模块 -------------------------------------------------------------------------------- /plugins/invite_code/code.txt: -------------------------------------------------------------------------------- 1 | 123123 2 | -------------------------------------------------------------------------------- /plugins/invite_code/invite_code.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | 3 | import os 4 | import json 5 | tips = "有问题联系@southcat解决" 6 | def invite_code(bot, message): 7 | ''' 8 | 通过读取usertext.json文件 来对比用户是否获取过,如果没有则从code.txt读取第一行的邀请码发送给用户,并删除这个邀请码。 9 | 邀请码为0时自动发送给管理员。 10 | ''' 11 | sysl = lentj(bot=bot) #sysl=剩余数量 12 | with open(bot.plugin_dir + 'invite_code/usertext.json','r') as f: #打开文件给f 13 | userjson = json.load(f) #json.load读取f赋值给userjson 14 | status = bot.sendChatAction(message["chat"]["id"], "typing") #显示回复状态 15 | if yanzheng2(bot,message["from"]["id"]) == 0: #判断用户是否获取过 16 | status = bot.sendChatAction(message["chat"]["id"], "typing") #显示发送状态 17 | bot.sendMessage(message["chat"]["id"], "您已经获取过了哦", "HTML")#发送提示 18 | bot.sendMessage(message["chat"]["id"], tips, "HTML") #发送预设的提示 19 | else: 20 | if sysl == 0: #判断数量是否为0 比较容易理解 21 | #status = bot.sendMessage(512466300, "邀请码不足", "HTML") #邀请码不足提醒模块 如需开启请取消注释并将512466300修改为你的id 22 | status = bot.sendMessage(message["chat"]["id"], "邀请码数量不足请等待补充", "HTML") #发送信息 23 | else: 24 | invite_code1 = code(bot=bot) #函数赋值 25 | status = bot.sendChatAction(message["chat"]["id"], "typing") #显示发送状态 26 | bot.sendMessage(message["chat"]["id"], invite_code1, "HTML") #发送邀请码 27 | bot.sendMessage(message["chat"]["id"], tips, "HTML") #发送预设的提示 28 | userid = message["from"]["id"] #获取用户id 29 | userjson[userid] = invite_code1 #写入字典 30 | with open(bot.plugin_dir + 'invite_code/usertext.json','w') as f1: #打开文件 31 | json.dump(userjson,f1) #将修改过的字典写入文件 32 | 33 | 34 | def code(bot): #获取验证吗 35 | file = open(bot.plugin_dir + 'invite_code/code.txt','r') 36 | a = file.readline() 37 | lines = (i for i in open(bot.plugin_dir + 'invite_code/code.txt', 'r') if a not in i) 38 | f = open(bot.plugin_dir + 'invite_code/test_new.txt', 'w', encoding="utf-8") 39 | f.writelines(lines) 40 | f.close() 41 | os.rename(bot.plugin_dir + 'invite_code/code.txt', bot.plugin_dir + 'invite_code/test.bak') 42 | os.rename(bot.plugin_dir + 'invite_code/test_new.txt', bot.plugin_dir + 'invite_code/code.txt') 43 | os.remove(bot.plugin_dir + 'invite_code/test.bak') 44 | file.close() 45 | return a 46 | 47 | def lentj(bot): #验证码剩余数量统计 48 | count = 0 49 | for index, line in enumerate(open(bot.plugin_dir + 'invite_code/code.txt', 'r')): 50 | count += 1 51 | return count 52 | 53 | def yanzheng2(bot,user_id): 54 | with open(bot.plugin_dir + 'invite_code/usertext.json', 'r') as f: 55 | userjson = json.load(f) 56 | if str(user_id) in userjson.keys(): 57 | return 0 58 | else: 59 | return 1 -------------------------------------------------------------------------------- /plugins/invite_code/usertext.json: -------------------------------------------------------------------------------- 1 | {"123123": "123123123","123123asd": "123123123"} -------------------------------------------------------------------------------- /plugins/len_invite/__init__.py: -------------------------------------------------------------------------------- 1 | #/len_invite 2 | #统计邀请码数量 -------------------------------------------------------------------------------- /plugins/len_invite/len_invite.py: -------------------------------------------------------------------------------- 1 | import os 2 | import json 3 | def len_invite(bot,message): 4 | count_mps = lentj(bot=bot) 5 | with open(bot.plugin_dir + 'invite_code/usertext.json','r') as f: #打开文件给f 6 | userjson = json.load(f) 7 | status = bot.sendMessage(message["chat"]["id"], "邀请码剩余:"+str(lentj(bot=bot))+"\n"+"已发放用户:"+str(len(userjson)), "HTML") 8 | if count_mps == 0 : 9 | status= bot.sendMessage(512466300,"邀请码不足","HTML") 10 | 11 | def lentj(bot): 12 | count = 0 13 | for index, line in enumerate(open(bot.plugin_dir + 'invite_code/code.txt', 'r')): 14 | count += 1 15 | return count -------------------------------------------------------------------------------- /plugins/updatacode/__init__.py: -------------------------------------------------------------------------------- 1 | #/updata 2 | #用于更新邀请码 请打开upadtacode.py填入你的账号信息 -------------------------------------------------------------------------------- /plugins/updatacode/dlercloud.py: -------------------------------------------------------------------------------- 1 | import requests 2 | # import re 3 | import json 4 | from bs4 import BeautifulSoup 5 | 6 | global codelist1 7 | codelist1 = [] 8 | url = ["https://dlercloud.com/user/invite", "https://dlercloud.com/user/invite?page=2"] 9 | 10 | chongfu = 0 11 | 12 | 13 | def updatacode(bot,message): 14 | if str(message["from"]["id"]) == bot.root_id: 15 | for abc in url: 16 | a = shuaxin(bot,abc) 17 | if a == 1: 18 | status = bot.sendChatAction(message["chat"]["id"], "typing") 19 | bot.sendMessage(message["chat"]["id"], "更新失败,cookie失效或是遇到防火墙", "HTML") 20 | else: 21 | status = bot.sendChatAction(message["chat"]["id"], "typing") 22 | bot.sendMessage(message["chat"]["id"], "更新成功", "HTML") 23 | file = open(bot.plugin_dir + 'invite_code/code.txt', 'w+') 24 | for code1 in codelist1: 25 | if code1 not in file: 26 | file.write(str(code1)) 27 | else: 28 | print(code1+"存在") 29 | pass 30 | codelist1.clear() 31 | else: 32 | status = bot.sendChatAction(message["chat"]["id"], "typing") 33 | bot.sendMessage(message["chat"]["id"], "您没有权限哦", "HTML") 34 | status = bot.sendChatAction(message["chat"]["id"], "typing") 35 | bot.sendMessage(message["chat"]["id"], "当前剩余数量:" + str(lentj(bot=bot)), "HTML") 36 | 37 | 38 | def shuaxin(bot,urls): 39 | header = { 40 | 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Safari/537.36'} 41 | data = { 42 | "email": "", 43 | "passwd": "", 44 | } 45 | # 通过session模拟登录,每次请求带着session 46 | url1 = 'https://dlercloud.com/auth/login' 47 | sess = requests.Session() 48 | f = sess.post(url1, data=data, headers=header) 49 | # print(f.text) 50 | e = sess.get(url=urls, headers=header) 51 | html = e.text 52 | # print(html) 53 | soup = BeautifulSoup(html, 'lxml') 54 | codelist = soup.find_all('a', class_='copy-text') 55 | # print(codelist) 56 | code = [] 57 | for i in codelist: 58 | # print(i['data-clipboard-text']) 59 | if i['data-clipboard-text'] == 'https://dlercloud.com/auth/register?affid=56029': 60 | pass 61 | else: 62 | code.append(i['data-clipboard-text']) 63 | if len(code) <= 1: 64 | return 1 65 | else: 66 | n = '\n' 67 | http = 'https://dlercloud.com' 68 | for c in code: 69 | if http + c + n in codelist1: 70 | print(c + "存在") 71 | pass 72 | else: 73 | codelist1.append(http + c + n) 74 | return 0 75 | 76 | 77 | def lentj(bot): 78 | count = 0 79 | for index, line in enumerate(open(bot.plugin_dir + 'invite_code/code.txt', 'r')): 80 | count += 1 81 | return count -------------------------------------------------------------------------------- /plugins/updatacode/updatacode.py: -------------------------------------------------------------------------------- 1 | import requests 2 | # import re 3 | import json 4 | from bs4 import BeautifulSoup 5 | 6 | global codelist1 7 | codelist1 = [] 8 | url = ["https://xn--i2ru8q2qg.com/user/invite", "https://xn--i2ru8q2qg.com/user/invite?page=2"] 9 | 10 | chongfu = 0 11 | 12 | 13 | def updatacode(bot,message): 14 | if str(message["from"]["id"]) == bot.root_id: 15 | for abc in url: 16 | a = shuaxin(bot,abc) 17 | if a == 1: 18 | status = bot.sendChatAction(message["chat"]["id"], "typing") 19 | bot.sendMessage(message["chat"]["id"], "更新失败,cookie失效或是遇到防火墙", "HTML") 20 | else: 21 | status = bot.sendChatAction(message["chat"]["id"], "typing") 22 | bot.sendMessage(message["chat"]["id"], "更新成功", "HTML") 23 | file = open(bot.plugin_dir + 'invite_code/code.txt', 'w+') 24 | for code1 in codelist1: 25 | if code1 not in file: 26 | file.write(str(code1)) 27 | else: 28 | print(code1+"存在") 29 | pass 30 | codelist1.clear() 31 | else: 32 | status = bot.sendChatAction(message["chat"]["id"], "typing") 33 | bot.sendMessage(message["chat"]["id"], "您没有权限哦", "HTML") 34 | status = bot.sendChatAction(message["chat"]["id"], "typing") 35 | bot.sendMessage(message["chat"]["id"], "当前剩余数量:" + str(lentj(bot=bot)), "HTML") 36 | 37 | 38 | def shuaxin(bot,urls): 39 | header = { 40 | 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Safari/537.36'} 41 | data = { 42 | "email": "", 43 | "passwd": "", 44 | } 45 | # 通过session模拟登录,每次请求带着session 46 | url1 = 'https://xn--i2ru8q2qg.com/auth/login' 47 | sess = requests.Session() 48 | f = sess.post(url1, data=data, headers=header) 49 | # print(f.text) 50 | e = sess.get(urls, headers=header) 51 | html = e.text 52 | soup = BeautifulSoup(html, 'lxml') 53 | codelist = soup.find_all('a', target='_blank') 54 | # print(codelist) 55 | code = [] 56 | cloudflare = 'https://xn--i2ru8q2qg.comhttps://www.cloudflare.com/5xx-error-landing?utm_source=iuam' 57 | for i in codelist: 58 | code.append(i.get('href')) 59 | if len(code) <= 1: 60 | return 1 61 | else: 62 | n = '\n' 63 | http = 'https://xn--i2ru8q2qg.com' 64 | with open(bot.plugin_dir + 'invite_code/usertext.json') as f1: 65 | userjson = json.load(f1) 66 | for c in code: 67 | if http + c + n in userjson.values() or http + c + n in codelist1: 68 | # print(c + "存在") 69 | pass 70 | else: 71 | codelist1.append(http + c + n) 72 | return 0 73 | 74 | 75 | def lentj(bot): 76 | count = 0 77 | for index, line in enumerate(open(bot.plugin_dir + 'invite_code/code.txt', 'r')): 78 | count += 1 79 | return count 80 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import sys 2 | sys.path.append("./teelebot") 3 | 4 | from setuptools import setup, find_packages, Extension 5 | from distutils.core import setup, Extension 6 | from version import( 7 | __author__, 8 | __email__, 9 | __blog__, 10 | __description__, 11 | __version__ 12 | ) 13 | 14 | with open('README.md', "r", encoding="utf-8") as README_md: 15 | README = README_md.read() 16 | 17 | setup( 18 | name='teelebot', 19 | version=__version__, 20 | description=__description__, 21 | keywords=' '.join([ 22 | 'teelebot', 23 | 'telegram bot', 24 | 'telegram bot api', 25 | "telegram" 26 | ]), 27 | classifiers=[ 28 | "Programming Language :: Python :: 3", 29 | "Operating System :: OS Independent", 30 | "License :: OSI Approved :: GNU General Public License v3 (GPLv3)", 31 | ], 32 | url=__blog__, 33 | author=__author__, 34 | author_email=__email__, 35 | long_description=README, 36 | long_description_content_type="text/markdown", 37 | license='GPLv3', 38 | packages=find_packages(exclude=['plugins', 'plugins.*', 'test', 'test.*']), 39 | package_data={ 40 | 'teelebot':['README.md'], 41 | 'teelebot':['LICENSE'], 42 | 'teelebot':[ 43 | 'plugins/Chat/hello.ogg', 44 | 'plugins/Hello/helloworld.png', 45 | 'plugins/About/icon.png' 46 | ], 47 | }, 48 | python_requires='>=3.5', 49 | install_requires=['requests'], 50 | entry_points={ 51 | 'console_scripts': [ 52 | 'teelebot=teelebot:main', 53 | ] 54 | }, 55 | zip_safe=True 56 | ) -------------------------------------------------------------------------------- /supervisor.conf: -------------------------------------------------------------------------------- 1 | [program:teelebot] 2 | directory=/home/teelebot/ 3 | command=python3 -m teelebot -c config.cfg 4 | autostart=true 5 | autorestart=true 6 | startsecs=2 7 | stopsignal=INT 8 | redirect_stderr=true 9 | -------------------------------------------------------------------------------- /teelebot/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | """ 3 | @creation date: 2019-8-23 4 | @last modify: 2020-11-23 5 | """ 6 | import os 7 | import requests 8 | from requests.packages.urllib3.exceptions import InsecureRequestWarning 9 | from .polling import _runUpdates 10 | from .webhook import _runWebhook 11 | from .teelebot import Bot 12 | 13 | requests.packages.urllib3.disable_warnings(InsecureRequestWarning) 14 | 15 | name = "teelebot" 16 | __all__ = ['Bot'] 17 | 18 | bot = Bot() 19 | VERSION = bot.version 20 | 21 | if bot._local_api_server != "False": 22 | api_server = "Local" 23 | else: 24 | api_server = "Remote" 25 | 26 | 27 | def main(): 28 | print(" * Self-checking...", end="\r") 29 | req = requests.post(url=bot._url + "getWebhookInfo", verify=False) 30 | if not req.json().get("ok"): 31 | if (req.json().get("error_code") == 401 and 32 | req.json().get("description") == "Unauthorized"): 33 | print("\nif you already logout the bot from the cloud Bot API server,please wait at least 10 minutes and try again.") 34 | else: 35 | print("\nfailed to get running mode!") 36 | os._exit(0) 37 | 38 | status = req.json().get("result") 39 | pending_update_count = status["pending_update_count"] 40 | 41 | if bot._webhook: 42 | protocol = "https://" 43 | if bot._local_api_server != "False": 44 | protocol = "http://" 45 | url = protocol + str(bot._server_address + ":" + str( 46 | bot._server_port) + "/bot" + str(bot._key)) 47 | if (bot._drop_pending_updates == True and pending_update_count != 0) \ 48 | or (status["url"] != url) or (status["has_custom_certificate"] != bot._self_signed)\ 49 | or status["max_connections"] != int(bot._pool_size): 50 | if bot._self_signed: 51 | status = bot.setWebhook( 52 | url=url, 53 | certificate=bot._cert_pub, 54 | max_connections=bot._pool_size, 55 | drop_pending_updates=bot._drop_pending_updates 56 | ) 57 | else: 58 | status = bot.setWebhook( 59 | url=url, 60 | max_connections=bot._pool_size, 61 | drop_pending_updates=bot._drop_pending_updates 62 | ) 63 | if not status: 64 | print("\nfailed to set Webhook!") 65 | os._exit(0) 66 | 67 | print(" * The teelebot starts running", 68 | "\n * Version : v" + VERSION, 69 | "\n * Mode : Webhook", 70 | "\n * Thread : " + str(bot._pool_size), 71 | "\n * Server : " + api_server + "\n") 72 | _runWebhook(bot=bot, 73 | host=bot._local_address,port=int(bot._local_port)) 74 | 75 | else: 76 | if status["url"] != "" or status["has_custom_certificate"]: 77 | status = bot.deleteWebhook() 78 | if not status: 79 | print("\nfailed to set getUpdates!") 80 | os._exit(0) 81 | 82 | print(" * The teelebot starts running", 83 | "\n * Version : v" + VERSION, 84 | "\n * Mode : Polling", 85 | "\n * Thread : " + str(bot._pool_size), 86 | "\n * Server : " + api_server + "\n") 87 | if bot._drop_pending_updates == True and \ 88 | pending_update_count != 0: 89 | results = bot.getUpdates() 90 | messages = bot._washUpdates(results) 91 | _runUpdates(bot=bot) 92 | -------------------------------------------------------------------------------- /teelebot/__main__.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | ''' 3 | @creation date: 2019-8-23 4 | @last modify: 2020-6-25 5 | ''' 6 | 7 | if __name__ == "__main__": 8 | import teelebot 9 | teelebot.main() -------------------------------------------------------------------------------- /teelebot/handler.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | ''' 3 | @creation date: 2019-8-23 4 | @last modify: 2020-11-23 5 | ''' 6 | import configparser 7 | import argparse 8 | import os 9 | import sys 10 | import shutil 11 | import requests 12 | from requests.packages.urllib3.exceptions import InsecureRequestWarning 13 | from pathlib import Path 14 | from .version import __author__, __github__, __version__ 15 | 16 | requests.packages.urllib3.disable_warnings(InsecureRequestWarning) 17 | 18 | cloud_api_server = "https://api.telegram.org/" 19 | 20 | parser = argparse.ArgumentParser(description="teelebot console command list") 21 | parser.add_argument("-c", "--config", type=str, 22 | help="specify the configuration file") 23 | parser.add_argument("-k", "--key", type=str, 24 | help="Specify the bot key") 25 | parser.add_argument("-r", "--root", type=str, 26 | help="Specify the root user id") 27 | parser.add_argument("-p", "--plugin", type=str, 28 | help="create a plugin template") 29 | parser.add_argument("-L", "--logout", 30 | help="use it to log out from the cloud Bot API server before launching the bot locally.", 31 | action="store_true") 32 | parser.add_argument("-C", "--close", 33 | help="use it to close the bot instance before moving it from one local server to another.", 34 | action="store_true") 35 | parser.add_argument( 36 | "-d", "--debug", help="run teelebot in debug mode", action="store_true") 37 | parser.add_argument( 38 | "-v", "--version", help="view the current version of teelebot", action="store_true") 39 | args = parser.parse_args() 40 | 41 | if len(sys.argv) == 2 and args.version: 42 | print("\nVersion: " + __version__) 43 | print("Author: " + __author__) 44 | print("Project: " + __github__) 45 | os._exit(0) 46 | 47 | 48 | def _config(): 49 | ''' 50 | 获取bot配置信息及初始化 51 | ''' 52 | config = {} 53 | 54 | if len(sys.argv) == 3 and args.config: 55 | config_dir = os.path.abspath(str(Path(args.config))) 56 | else: 57 | config_dir = str(Path(os.path.abspath( 58 | os.path.expanduser('~')) + "/.teelebot/config.cfg")) 59 | (path, filename) = os.path.split(config_dir) 60 | (filename_, extension) = os.path.splitext(filename) 61 | if extension != ".cfg": 62 | print("only support configuration files with .cfg suffix.") 63 | os._exit(0) 64 | if not os.path.exists(str(Path(path))): 65 | os.makedirs(str(Path(path))) 66 | if not os.path.exists(str(Path(config_dir))): 67 | print("the configuration file does not exist.") 68 | key = "" 69 | if args.key: 70 | key = args.key 71 | root = "" 72 | if args.root: 73 | root = args.root 74 | with open(config_dir, "w") as conf_file: 75 | conf_file.writelines([ 76 | "[config]" + "\n", 77 | "key=" + str(key) + "\n", 78 | "root_id=" + str(root) + "\n", 79 | "plugin_dir=" + "\n", 80 | "pool_size=40" + "\n", 81 | "debug=False" + "\n", 82 | "local_api_server=False" + "\n", 83 | "drop_pending_updates=False" + "\n", 84 | "webhook=False" + "\n", 85 | "self_signed=False" + "\n", 86 | "cert_key=" + "\n", 87 | "cert_pub=" + "\n", 88 | "server_address=" + "\n", 89 | "server_port=" + "\n", 90 | "local_address=" + "\n", 91 | "local_port=" 92 | ]) 93 | print("the configuration file has been created automatically.") 94 | print("configuration file path: " + str(config_dir)) 95 | if not args.key or not args.root: 96 | print("please modify the relevant parameters and restart the teelebot.") 97 | os._exit(0) 98 | # else: 99 | # print("\n") 100 | 101 | conf = configparser.ConfigParser() 102 | conf.read(config_dir) 103 | options = conf.options("config") 104 | 105 | if args.debug: 106 | conf.set("config", "debug", str(True)) 107 | if args.key: 108 | conf.set("config", "key", str(args.key)) 109 | if args.root: 110 | conf.set("config", "root_id", str(args.root)) 111 | 112 | if args.debug: 113 | default_args = ["key", "webhook", "root_id", "debug"] 114 | else: 115 | default_args = ["key", "webhook", "root_id"] 116 | for default_arg in default_args: 117 | if default_arg not in options: 118 | print("the configuration file is missing necessary parameters.", 119 | "\nnecessary parameters:" + default_args) 120 | os._exit(0) 121 | 122 | for option in options: 123 | config[str(option)] = conf.get("config", option) 124 | 125 | none_count = 0 126 | for default_arg in default_args: 127 | if config[default_arg] == "" or\ 128 | config[default_arg] == None: 129 | none_count += 1 130 | print("field " + default_arg + " is not set in configuration file.") 131 | if none_count != 0: 132 | os._exit(0) 133 | 134 | if any(["version" in config.keys(), "author" in config.keys()]): 135 | print("error in configuration file.") 136 | os._exit(0) 137 | 138 | if config["webhook"] == "True": 139 | webhook_args = ["self_signed", 140 | "server_address", "server_port", 141 | "local_address", "local_port", 142 | "cert_pub", "cert_key"] 143 | for w in webhook_args: 144 | if w not in config.keys(): 145 | print("please check if the following fields exist in the configuration file: \n" + 146 | "cert_pub cert_key self_signed server_address server_port local_address local_port") 147 | os._exit(0) 148 | 149 | plugin_dir_in_config = False 150 | if "plugin_dir" in config.keys(): 151 | if config["plugin_dir"] == "" or config["plugin_dir"] == None: 152 | plugin_dir = str(Path(os.path.dirname(os.path.abspath(__file__)) + r"/plugins/")) + os.sep 153 | else: 154 | plugin_dir = str(Path(os.path.abspath(config["plugin_dir"]))) + os.sep 155 | plugin_dir_in_config = True 156 | else: 157 | plugin_dir = str(Path(os.path.dirname(os.path.abspath(__file__)) + r"/plugins/")) + os.sep 158 | 159 | if os.path.exists(str(Path(os.path.dirname( 160 | os.path.abspath(__file__)) + r"/__pycache__"))): 161 | shutil.rmtree(str(Path(os.path.dirname( 162 | os.path.abspath(__file__)) + r"/__pycache__"))) 163 | 164 | if not os.path.isdir(plugin_dir): # 插件目录检测 165 | # os.makedirs(plugin_dir) 166 | os.mkdir(plugin_dir) 167 | with open(str(Path(plugin_dir + "__init__.py")), "w") as f: 168 | pass 169 | elif not os.path.exists(str(Path(plugin_dir + "__init__.py"))): 170 | with open(str(Path(plugin_dir + "__init__.py")), "w") as f: 171 | pass 172 | 173 | if args.plugin and plugin_dir_in_config: #插件模板创建 174 | plugin_name = args.plugin 175 | if not os.path.exists(str(Path(plugin_dir + plugin_name))): 176 | os.mkdir(str(Path(plugin_dir + plugin_name))) 177 | if not os.path.exists(str(Path(plugin_dir + plugin_name + os.sep + plugin_name + ".py"))): 178 | with open(str(Path(plugin_dir + plugin_name + os.sep + plugin_name + ".py")), "w") as enter: 179 | enter.writelines([ 180 | "# -*- coding:utf-8 -*-\n", 181 | "\n", 182 | "def " + plugin_name + "(bot, message):\n", 183 | "\n" + \ 184 | " # root_id = bot.root_id\n" + \ 185 | " # bot_id = bot.bot_id\n" + \ 186 | " # author = bot.author\n" + \ 187 | " # version = bot.version\n" + \ 188 | " # plugin_dir = bot.plugin_dir\n" + \ 189 | " # plugin_bridge = bot.plugin_bridge\n" + \ 190 | " # uptime = bot.uptime\n" + \ 191 | " # response_times = bot.response_times\n" + \ 192 | " # response_chats = bot.response_chats\n" + \ 193 | " # response_users = bot.response_users\n" + \ 194 | "\n" + \ 195 | ' chat_id = message["chat"]["id"]\n' + \ 196 | ' user_id = message["from"]["id"]\n' + \ 197 | ' message_id = message["message_id"]\n' + \ 198 | "\n" + \ 199 | ' message_type = message["message_type"]\n' + \ 200 | ' chat_type = message["chat"]["type"]\n' + \ 201 | "\n" + \ 202 | ' prefix = "/' + plugin_name.lower() + '"\n' + \ 203 | "\n\n" + \ 204 | " # Write your plugin code below" 205 | ]) 206 | if not os.path.exists(str(Path(plugin_dir + plugin_name + os.sep + "__init__.py"))): 207 | with open(str(Path(plugin_dir + plugin_name + os.sep + "__init__.py")), "w") as init: 208 | init.writelines([ 209 | "#/" + plugin_name.lower() + "\n", 210 | "#" + plugin_name + " Plugin\n" 211 | ]) 212 | if not os.path.exists(str(Path(plugin_dir + plugin_name + os.sep + "readme.md"))): 213 | with open(str(Path(plugin_dir + plugin_name + os.sep + "readme.md")), "w") as readme: 214 | readme.writelines([ 215 | "# " + plugin_name + " #\n" 216 | ]) 217 | if not os.path.exists(str(Path(plugin_dir + plugin_name + os.sep + "requirement.txt"))): 218 | with open(str(Path(plugin_dir + plugin_name + os.sep + "requirement.txt")), "w") as requirement: 219 | pass 220 | 221 | print("plugin " + plugin_name + " was created successfully.") 222 | else: 223 | print("plugin " + plugin_name + " already exists.") 224 | os._exit(0) 225 | elif args.plugin and not plugin_dir_in_config: 226 | print("the plugin_dir is not set in the configuration file.") 227 | os._exit(0) 228 | 229 | if "pool_size" in config.keys(): 230 | if int(config["pool_size"]) < 1 or int(config["pool_size"]) > 100: 231 | print("thread pool size is out of range (1-100).") 232 | os._exit(0) 233 | else: 234 | config["pool_size"] = "40" 235 | 236 | if "local_api_server" in config.keys(): 237 | local_api_server = config["local_api_server"] 238 | if (local_api_server == None or 239 | local_api_server == "" or 240 | local_api_server == "False" or 241 | len(local_api_server) < 7): 242 | config["local_api_server"] = "False" 243 | else: 244 | if "https://" in local_api_server: 245 | print("local api server address not support https.") 246 | os._exit(0) 247 | if "http://" not in local_api_server: 248 | print("local api server address incorrect.") 249 | os._exit(0) 250 | if "telegram.org" in local_api_server: 251 | print("local api server address incorrect.") 252 | os._exit(0) 253 | if local_api_server[len(local_api_server)-1] != "/": 254 | local_api_server += "/" 255 | config["local_api_server"] = local_api_server 256 | else: 257 | config["local_api_server"] = "False" 258 | 259 | if "self_signed" in config.keys(): 260 | if config["self_signed"] == "True": 261 | config["self_signed"] = True 262 | elif config["self_signed"] == "False": 263 | config["self_signed"] = False 264 | else: 265 | print("The self_signed field value in the configuration file is wrong.") 266 | os._exit(0) 267 | else: 268 | config["self_signed"] = False 269 | 270 | if "drop_pending_updates" in config.keys(): 271 | if config["drop_pending_updates"] == "True": 272 | config["drop_pending_updates"] = True 273 | elif config["drop_pending_updates"] == "False": 274 | config["drop_pending_updates"] = False 275 | else: 276 | print("The drop_pending_updates field value in the configuration file is wrong.") 277 | os._exit(0) 278 | else: 279 | config["drop_pending_updates"] = False 280 | 281 | if config["debug"] == "True": 282 | config["debug"] = True 283 | elif config["debug"] == "False": 284 | config["debug"] = False 285 | else: 286 | print("The debug field value in the configuration file is wrong.") 287 | os._exit(0) 288 | 289 | if config["webhook"] == "True": 290 | config["webhook"] = True 291 | elif config["webhook"] == "False": 292 | config["webhook"] = False 293 | else: 294 | print("The webhook field value in the configuration file is wrong.") 295 | os._exit(0) 296 | 297 | config["author"] = __author__ 298 | config["version"] = __version__ 299 | config["plugin_dir"] = plugin_dir 300 | config["plugin_bridge"] = _bridge(config["plugin_dir"]) 301 | config["plugin_info"] = _plugin_info( 302 | config["plugin_bridge"].keys(), config["plugin_dir"]) 303 | config["cloud_api_server"] = cloud_api_server 304 | 305 | if args.debug: 306 | config["debug"] = True 307 | 308 | # print(config) 309 | return config 310 | 311 | def _bridge(plugin_dir): 312 | ''' 313 | 获取插件和指令的映射 314 | ''' 315 | plugin_bridge = {} 316 | plugin_list = [] 317 | 318 | plugin_lis = os.listdir(plugin_dir) 319 | for plugi in plugin_lis: 320 | if os.path.isdir(str(Path(plugin_dir + plugi))) and plugi != "__pycache__" and plugi[0] != '.': 321 | plugin_list.append(plugi) 322 | for plugin in plugin_list: 323 | with open(str(Path(plugin_dir + plugin + r"/__init__.py")), encoding="utf-8") as f: 324 | row_one = f.readline().strip()[1:] 325 | if row_one != "~~": # Hidden plugin 326 | plugin_bridge[plugin] = row_one 327 | 328 | # print(plugin_bridge) 329 | return plugin_bridge 330 | 331 | def _plugin_info(plugin_list, plugin_dir): 332 | ''' 333 | 获取插件修改状态 334 | ''' 335 | plugin_info = {} 336 | for plugin in plugin_list: 337 | mtime = os.stat(str(Path(plugin_dir + plugin + "/" + plugin + ".py"))).st_mtime 338 | plugin_info[plugin] = mtime 339 | 340 | return plugin_info 341 | 342 | 343 | if args.close and args.logout: 344 | print("only one of logout and close can be used at the same time.") 345 | os._exit(0) 346 | 347 | elif args.logout and not args.close: 348 | config = _config() 349 | logout_url = cloud_api_server + "bot" + config["key"] + "/logOut" 350 | try: 351 | req = requests.post(url=logout_url, verify=False) 352 | except: 353 | print("error request the cloud Bot API server.") 354 | os._exit(0) 355 | if req.json().get("ok"): 356 | print("successfully log out from the cloud Bot API server.") 357 | elif not req.json().get("ok"): 358 | print("error log out from the cloud Bot API server.") 359 | if (req.json().get("error_code") == 401 and 360 | req.json().get("description") == "Unauthorized"): 361 | print("if you already logout the bot from the cloud Bot API server,please wait at least 10 minutes and try again.") 362 | os._exit(0) 363 | 364 | elif args.close and not args.logout: 365 | config = _config() 366 | if config["local_api_server"] == "False": 367 | print("close can only be used when local_api_server is configured.") 368 | os._exit(0) 369 | 370 | close_url = config["local_api_server"] + "bot" + config["key"] + "/close" 371 | try: 372 | req = requests.post(url=close_url, verify=False) 373 | except: 374 | print("error request the the local API server.") 375 | os._exit(0) 376 | if req.json().get("ok"): 377 | print("successfully close from the local API server.") 378 | elif not req.json().get("ok"): 379 | print("error close from the local API server.") 380 | if req.json().get("error_code") == 429: 381 | print("too many requests, please retry after " + str(req.json().get("parameters")["retry_after"]) + " seconds.") 382 | os._exit(0) 383 | 384 | 385 | -------------------------------------------------------------------------------- /teelebot/logger.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | ''' 3 | @creation date: 2019-11-15 4 | @last modify: 2020-11-18 5 | ''' 6 | import logging as __logging 7 | 8 | __logging.basicConfig(level=__logging.DEBUG, 9 | datefmt='%Y/%m/%d %H:%M:%S', 10 | format='%(asctime)s - %(levelname)s - %(message)s') 11 | __logging.getLogger("urllib3").setLevel(__logging.WARNING) 12 | 13 | _logger = __logging.getLogger(__name__) -------------------------------------------------------------------------------- /teelebot/plugins/About/About.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | import os 3 | 4 | def About(bot, message): 5 | chat_id = message["chat"]["id"] 6 | message_id = message["message_id"] 7 | text = message["text"] 8 | bot_id = bot.bot_id 9 | prefix = "about" 10 | 11 | plugin_dir = bot.plugin_dir 12 | VERSION = bot.version 13 | 14 | if not os.path.exists(bot.path_converter(plugin_dir + "About/config.ini")): 15 | first_btn = ["交流群组", "https://t.me/teelebot_chat"] 16 | last_btn = ["项目地址", "https://github.com/plutobell/teelebot"] 17 | else: 18 | with open(bot.path_converter(plugin_dir + "About/config.ini"), 'r') as g: 19 | first_btn = g.readline().strip().split(',') 20 | last_btn = g.readline().strip().split(',') 21 | 22 | if text[1:len(prefix)+1] == prefix: 23 | inlineKeyboard = [ 24 | [ 25 | {"text": first_btn[0], "url": first_btn[1]}, 26 | {"text": last_btn[0], "url": last_btn[1]}, 27 | ] 28 | ] 29 | reply_markup = { 30 | "inline_keyboard": inlineKeyboard 31 | } 32 | status = bot.sendChatAction(chat_id, "typing") 33 | msg = "此 Bot 基于 teelebot 框架 v" + VERSION + "\n\n" +\ 34 | "teelebot 是基于 Telegram Bot API 的 Bot 框架,具有插件系统,扩展方便。\n\n" 35 | 36 | req = bot.getUserProfilePhotos(user_id=str(bot_id), limit=1) 37 | if req.get("photos", "notphotos") != "notphotos": 38 | bot_icon = req.get("photos")[0][0]["file_id"] 39 | if type(bot_icon) == str and len(bot_icon) > 50: 40 | photo = bot_icon 41 | else: 42 | with open(bot.path_converter(plugin_dir + "About/icon.png"), "rb") as p: 43 | photo = p.read() 44 | else: 45 | with open(bot.path_converter(plugin_dir + "About/icon.png"), "rb") as p: 46 | photo = p.read() 47 | 48 | status = bot.sendPhoto(chat_id=chat_id, photo=photo, caption=msg, parse_mode="HTML", reply_to_message_id=message_id, reply_markup=reply_markup) 49 | bot.message_deletor(15, chat_id, status["message_id"]) 50 | else: 51 | status = bot.sendChatAction(chat_id, "typing") 52 | status = bot.sendMessage(chat_id=chat_id, text="指令格式错误,请检查!", parse_mode="HTML", reply_to_message_id=message_id) 53 | bot.message_deletor(15, chat_id, status["message_id"]) 54 | 55 | -------------------------------------------------------------------------------- /teelebot/plugins/About/__init__.py: -------------------------------------------------------------------------------- 1 | #/about 2 | #About 关于 -------------------------------------------------------------------------------- /teelebot/plugins/About/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Everless321/telebot-invitecode/9ebbf77e85620485574b7bebd61d247e9a1d6612/teelebot/plugins/About/icon.png -------------------------------------------------------------------------------- /teelebot/plugins/Chat/Chat.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | import requests 3 | import urllib.parse as ubp 4 | requests.adapters.DEFAULT_RETRIES = 5 5 | 6 | def Chat(bot, message): 7 | plugin_dir = bot.plugin_dir 8 | 9 | url = "http://api.qingyunke.com/api.php?key=free&appid=0&msg=" 10 | hello = ("你好", "nihao", "hello", "Hello", 11 | "HELLO", "hi", "Hi", "HI", 12 | "早上好", "上午好", "下午好", "晚上好", "中午好", 13 | "good morning", "Good morning", "good afternoom", 14 | "Good afternoom", "good evening", "Good evening") 15 | if message["text"][1:] in hello: 16 | status = bot.sendChatAction(message["chat"]["id"], "typing") 17 | status = bot.sendVoice(message["chat"]["id"], voice=bot.path_converter(plugin_dir + "Chat/hello.ogg"), 18 | reply_to_message_id=message["message_id"]) 19 | else: 20 | try: 21 | with requests.post(url + ubp.quote(message["text"][1:])) as req: #urlencode编码 22 | req.keep_alive = False 23 | req.encoding = "utf-8" 24 | if not req.status_code == requests.codes.ok: 25 | status = bot.sendChatAction(message["chat"]["id"], "typing") 26 | status = bot.sendMessage(chat_id=message["chat"]["id"], text="接口调用失败!", 27 | parse_mode="HTML", reply_to_message_id=message["message_id"]) 28 | bot.message_deletor(15, status["chat"]["id"], status["message_id"]) 29 | else: 30 | try: 31 | msg = str(req.json().get("content").replace("{br}", "\n").replace("菲菲", "小埋")) 32 | if "{face:" in msg: 33 | msg = msg.split("}")[1] 34 | except: 35 | msg = "出错了." 36 | status = bot.sendChatAction(message["chat"]["id"], "typing") 37 | status = bot.sendMessage(message["chat"]["id"],text=msg, 38 | parse_mode="HTML", reply_to_message_id=message["message_id"]) 39 | except Exception as e: 40 | print(e) 41 | 42 | 43 | def timer_func(bot, chat_id, message_id): 44 | status = bot.deleteMessage(chat_id=chat_id, message_id=message_id) -------------------------------------------------------------------------------- /teelebot/plugins/Chat/__init__.py: -------------------------------------------------------------------------------- 1 | #@ 2 | #Chat插件 人工智障在线陪聊 -------------------------------------------------------------------------------- /teelebot/plugins/Chat/hello.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Everless321/telebot-invitecode/9ebbf77e85620485574b7bebd61d247e9a1d6612/teelebot/plugins/Chat/hello.ogg -------------------------------------------------------------------------------- /teelebot/plugins/Hello/Hello.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | 3 | def Hello(bot, message): 4 | #print("你好,世界!") 5 | bot.sendChatAction(message["chat"]["id"], "typing") 6 | bot.sendPhoto(message["chat"]["id"], bot.path_converter(bot.plugin_dir + "Hello/helloworld.png"), reply_to_message_id=message["message_id"]) -------------------------------------------------------------------------------- /teelebot/plugins/Hello/__init__.py: -------------------------------------------------------------------------------- 1 | #/helloworld 2 | #Hello World插件例子 -------------------------------------------------------------------------------- /teelebot/plugins/Hello/helloworld.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Everless321/telebot-invitecode/9ebbf77e85620485574b7bebd61d247e9a1d6612/teelebot/plugins/Hello/helloworld.png -------------------------------------------------------------------------------- /teelebot/plugins/Menu/Menu.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | ''' 3 | creation time: 2019-8-15 4 | last_modify: 2020-11-16 5 | ''' 6 | import os 7 | 8 | def Menu(bot, message): 9 | chat_id = message["chat"]["id"] 10 | message_id = message["message_id"] 11 | chat_type = message["chat"]["type"] 12 | 13 | prefix = "start" 14 | 15 | plugin_bridge = bot.plugin_bridge 16 | plugin_dir = bot.plugin_dir 17 | plugin_list = list(plugin_bridge.keys()) 18 | 19 | if chat_type != "private" and "/pluginctl" in plugin_bridge.values() and plugin_bridge["PluginCTL"] == "/pluginctl": 20 | if os.path.exists(bot.path_converter(plugin_dir + "PluginCTL/db/" + str(chat_id) + ".db")): 21 | with open(bot.path_converter(plugin_dir + "PluginCTL/db/" + str(chat_id) + ".db"), "r") as f: 22 | plugin_setting = f.read().strip() 23 | plugin_list_off = plugin_setting.split(',') 24 | plugin_list_temp = [] 25 | for plugin in plugin_bridge.keys(): 26 | if plugin not in plugin_list_off: 27 | plugin_list_temp.append(plugin) 28 | plugin_list = plugin_list_temp 29 | 30 | plugin_count = len(plugin_list) 31 | page_size = 5 32 | page_total = int((plugin_count + page_size - 1) / page_size) # 总页数=(总数+每页数量-1)/每页数量 33 | page_callback_command = "/" + prefix + "page?page=" 34 | 35 | if not os.path.exists(bot.path_converter(plugin_dir + "Menu/config.ini")): 36 | first_btn = ["交流群组", "https://t.me/teelebot_chat"] 37 | last_btn = ["项目地址", "https://github.com/plutobell/teelebot"] 38 | else: 39 | with open(bot.path_converter(plugin_dir + "Menu/config.ini"), 'r') as g: 40 | first_btn = g.readline().strip().split(',') 41 | last_btn = g.readline().strip().split(',') 42 | 43 | wait_time = plugin_count * 7 44 | 45 | if "reply_markup" in message.keys(): 46 | click_user_id = message["click_user"]["id"] 47 | from_user_id = message["reply_to_message"]["from"]["id"] 48 | callback_query_data = message["callback_query_data"] 49 | 50 | if callback_query_data[:len(page_callback_command)] == page_callback_command: 51 | if click_user_id == from_user_id: 52 | page = int(callback_query_data.split('=')[1]) 53 | page, menu_str = menu_text(bot, plugin_dir=plugin_dir, page=page, page_total=page_total, page_size=page_size, plugin_list=plugin_list) 54 | previous_page = page - 1 55 | if previous_page < 1: 56 | previous_page = 1 57 | next_page = page + 1 58 | if next_page > page_total: 59 | next_page = page_total 60 | 61 | if page_total == 1: 62 | inlineKeyboard = [ 63 | [ 64 | {"text": first_btn[0], "url": first_btn[1]}, 65 | {"text": last_btn[0], "url": last_btn[1]}, 66 | ] 67 | ] 68 | elif page == 1: 69 | inlineKeyboard = [ 70 | [ 71 | {"text": first_btn[0], "url": first_btn[1]}, 72 | {"text": "下一页", "callback_data": page_callback_command + str(page+1)}, 73 | ] 74 | ] 75 | elif page == page_total: 76 | inlineKeyboard = [ 77 | [ 78 | {"text": "上一页", "callback_data": page_callback_command + str(page-1)}, 79 | {"text": last_btn[0], "url": last_btn[1]}, 80 | ] 81 | ] 82 | else: 83 | inlineKeyboard = [ 84 | [ 85 | {"text": "上一页", "callback_data": page_callback_command + str(previous_page)}, 86 | {"text": "下一页", "callback_data": page_callback_command + str(next_page)}, 87 | ] 88 | ] 89 | reply_markup = { 90 | "inline_keyboard": inlineKeyboard 91 | } 92 | status = bot.editMessageText(chat_id=chat_id, message_id=message_id, text=menu_str, parse_mode="HTML", reply_markup=reply_markup) 93 | status = bot.answerCallbackQuery(message["callback_query_id"]) 94 | else: 95 | status = bot.answerCallbackQuery(message["callback_query_id"], text="点啥点,关你啥事?", show_alert=bool("true")) 96 | else: 97 | page = 1 98 | if page_total == 1: 99 | inlineKeyboard = [ 100 | [ 101 | {"text": first_btn[0], "url": first_btn[1]}, 102 | {"text": last_btn[0], "url": last_btn[1]}, 103 | ] 104 | ] 105 | else: 106 | inlineKeyboard = [ 107 | [ 108 | {"text": first_btn[0], "url": first_btn[1]}, 109 | {"text": "下一页", "callback_data": page_callback_command + str(page+1)}, 110 | ] 111 | ] 112 | reply_markup = { 113 | "inline_keyboard": inlineKeyboard 114 | } 115 | 116 | page, menu_str = menu_text(bot=bot, plugin_dir=plugin_dir, page=page, page_total=page_total, page_size=page_size, plugin_list=plugin_list) 117 | 118 | status = bot.sendChatAction(chat_id, "typing") 119 | status = bot.sendMessage(chat_id=chat_id, text=menu_str, parse_mode="HTML", reply_to_message_id=message_id, reply_markup=reply_markup) 120 | 121 | bot.message_deletor(wait_time, message["chat"]["id"], status["message_id"]) 122 | 123 | def menu_text(bot, plugin_dir, page, page_total, page_size, plugin_list): 124 | VERSION = bot.version 125 | if page < 1: 126 | page = 1 127 | elif page > page_total: 128 | page = page_total 129 | 130 | if page >=1 and page <= page_total: 131 | menu_str = "" 132 | plugin_range = range(page*page_size-page_size, page*page_size-1+1) 133 | for i, plugin in enumerate(plugin_list): #(now_page*page_size-page_size,now_page*page_size-1) 134 | if i in plugin_range: 135 | with open(bot.path_converter(plugin_dir + plugin + r"/__init__.py"), encoding="utf-8") as f: 136 | line_1 = "" 137 | line_2 = "" 138 | for i in range(2): 139 | if i == 0: 140 | line_1 = f.readline().strip()[1:] 141 | elif i == 1: 142 | line_2 = f.readline().strip()[1:] 143 | menu_str += "" + line_1 + " - " + line_2 + "\n\n" 144 | menu_str = "插件列表 [" + str(page) + "/" + str(page_total) + "]\n\n" + menu_str + "\nv" + VERSION + "" 145 | 146 | return page, menu_str 147 | -------------------------------------------------------------------------------- /teelebot/plugins/Menu/__init__.py: -------------------------------------------------------------------------------- 1 | #/start 2 | #Robot 插件列表 -------------------------------------------------------------------------------- /teelebot/plugins/PluginCTL/PluginCTL.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | ''' 3 | creation time: 2020-6-15 4 | last_modify: 2020-11-28 5 | ''' 6 | import os 7 | from threading import Lock 8 | 9 | 10 | lock = Lock() 11 | 12 | def PluginCTL(bot, message): 13 | message_id = message["message_id"] 14 | chat_id = message["chat"]["id"] 15 | user_id = message["from"]["id"] 16 | text = message["text"] 17 | prefix = "pluginctl" 18 | 19 | plugin_dir = bot.plugin_dir 20 | root_id = bot.root_id 21 | plugin_bridge = bot.plugin_bridge 22 | 23 | if not os.path.exists(bot.path_converter(plugin_dir + "PluginCTL/db/")): 24 | os.mkdir(bot.path_converter(plugin_dir + "PluginCTL/db/")) 25 | 26 | if message["chat"]["type"] != "private" and not os.path.exists(bot.path_converter(plugin_dir + "PluginCTL/db/" + str(chat_id) + ".db")): 27 | with open(bot.path_converter(plugin_dir + "PluginCTL/db/" + str(chat_id) + ".db"), "w") as f: 28 | pass 29 | 30 | command = { 31 | "/pluginctlshow": "show", 32 | "/pluginctlon": "on", 33 | "/pluginctloff": "off" 34 | } 35 | count = 0 36 | for c in command.keys(): 37 | if c in str(text): 38 | count += 1 39 | 40 | if message["chat"]["type"] != "private": 41 | admins = administrators(bot=bot, chat_id=chat_id) 42 | if str(root_id) not in admins: 43 | admins.append(str(root_id)) #root permission 44 | 45 | if message["chat"]["type"] == "private" and text[1:len(prefix)+1] == prefix: #判断是否为私人对话 46 | status = bot.sendChatAction(chat_id, "typing") 47 | status = bot.sendMessage(chat_id, "抱歉,该指令不支持私人会话.", parse_mode="text", reply_to_message_id=message_id) 48 | bot.message_deletor(15, chat_id, status["message_id"]) 49 | elif text[1:len(prefix)+1] == prefix and count == 0: 50 | status = bot.sendChatAction(chat_id, "typing") 51 | msg = "PluginCTL 插件功能\n\n" +\ 52 | "/pluginctlshow - 展示插件开启状态 \n" +\ 53 | "/pluginctlon - 启用插件。格式:/pluginctlon接要启用的插件名,以空格分隔 \n" +\ 54 | "/pluginctloff - 禁用插件。格式:/pluginctloff接要禁用的插件名,以空格分隔 \n" +\ 55 | "/pluginctlon all - 启用所有插件 \n" +\ 56 | "/pluginctloff all - 禁用所有插件,但必须的插件将被保留 \n" +\ 57 | "\n同时操作多个插件请用英文逗号分隔\n" 58 | status = bot.sendMessage(chat_id=chat_id, text=msg, parse_mode="HTML", reply_to_message_id=message["message_id"]) 59 | bot.message_deletor(30, chat_id, status["message_id"]) 60 | elif "reply_markup" in message.keys(): 61 | click_user_id = message["click_user"]["id"] 62 | from_user_id = message["reply_to_message"]["from"]["id"] 63 | callback_query_data = message["callback_query_data"] 64 | 65 | pluginctlsho_on_page = "/" + prefix + "showonpage" 66 | pluginctlsho_off_page = "/" + prefix + "showoffpage" 67 | 68 | plugin_dict = bot.plugin_bridge 69 | with open(bot.path_converter(plugin_dir + "PluginCTL/db/" + str(chat_id) + ".db"), "r") as f: 70 | plugin_setting = f.read().strip() 71 | plugin_list_off = plugin_setting.split(',') 72 | plugin_list_on = {} 73 | for plugin in plugin_bridge.keys(): 74 | if plugin not in plugin_list_off: 75 | plugin_list_on[plugin] = plugin_bridge[plugin] 76 | for key, val in plugin_list_on.items(): #dict.keys()不可修改! 77 | if val == "" or val == " ": 78 | plugin_list_on[key] = "nil" 79 | 80 | if callback_query_data == pluginctlsho_on_page: 81 | inlineKeyboard = [ 82 | [ 83 | {"text": "禁用的", "callback_data": "/pluginctlshowoffpage"}, 84 | ] 85 | ] 86 | reply_markup = { 87 | "inline_keyboard": inlineKeyboard 88 | } 89 | 90 | if click_user_id == from_user_id: 91 | for key, val in plugin_list_on.items(): #dict.keys()不可修改! 92 | if val == "" or val == " ": 93 | plugin_list_on[key] = "nil" 94 | msg_on = "启用的插件 \n\n" 95 | for i, on in enumerate(plugin_list_on): 96 | msg_on += " [" + str(i+1) + "] " + str(on) + " " + str(plugin_list_on[on]) + "\n" 97 | msg_on += "\nnil 代表指令为空" 98 | status = bot.editMessageText(chat_id=chat_id, message_id=message_id, text=msg_on + "\n", parse_mode="HTML", reply_markup=reply_markup) 99 | status = bot.answerCallbackQuery(message["callback_query_id"]) 100 | else: 101 | status = bot.answerCallbackQuery(message["callback_query_id"], text="点啥点,关你啥事?", show_alert=bool("true")) 102 | elif callback_query_data == pluginctlsho_off_page: 103 | inlineKeyboard = [ 104 | [ 105 | {"text": "启用的", "callback_data": "/pluginctlshowonpage"}, 106 | ] 107 | ] 108 | reply_markup = { 109 | "inline_keyboard": inlineKeyboard 110 | } 111 | 112 | if click_user_id == from_user_id: 113 | msg_off = "禁用的插件 \n\n" 114 | for key, val in plugin_bridge.items(): #dict.keys()不可修改! 115 | if val == "" or val == " ": 116 | plugin_bridge[key] = "nil" 117 | for i, pluo in enumerate(plugin_list_off): 118 | if pluo == "" or pluo == " ": 119 | del plugin_list_off[i] 120 | if len(plugin_list_off) == 0: 121 | msg_off += "无\n" 122 | else: 123 | for i, off in enumerate(plugin_list_off): 124 | msg_off += " [" + str(i+1) + "] " + str(off) + " " + str(plugin_bridge[off]) + "\n" 125 | msg_off += "\nnil 代表指令为空" 126 | status = bot.editMessageText(chat_id=chat_id, message_id=message_id, text=msg_off + "\n", parse_mode="HTML", reply_markup=reply_markup) 127 | status = bot.answerCallbackQuery(message["callback_query_id"]) 128 | else: 129 | status = bot.answerCallbackQuery(message["callback_query_id"], text="点啥点,关你啥事?", show_alert=bool("true")) 130 | 131 | elif count > 0: 132 | if str(user_id) not in admins: 133 | status = bot.sendChatAction(chat_id, "typing") 134 | status = bot.sendMessage(chat_id=chat_id, text="抱歉,您无权操作.", parse_mode="HTML", reply_to_message_id=message_id) 135 | bot.message_deletor(15, chat_id, status["message_id"]) 136 | elif text[1:len(prefix + command["/pluginctlshow"])+1] == prefix + command["/pluginctlshow"]: 137 | inlineKeyboard = [ 138 | [ 139 | {"text": "禁用的", "callback_data": "/pluginctlshowoffpage"}, 140 | ] 141 | ] 142 | reply_markup = { 143 | "inline_keyboard": inlineKeyboard 144 | } 145 | 146 | plugin_dict = bot.plugin_bridge 147 | with open(bot.path_converter(plugin_dir + "PluginCTL/db/" + str(chat_id) + ".db"), "r") as f: 148 | plugin_setting = f.read().strip() 149 | plugin_list_off = plugin_setting.split(',') 150 | plugin_list_on = {} 151 | for plugin in plugin_bridge.keys(): 152 | if plugin not in plugin_list_off: 153 | plugin_list_on[plugin] = plugin_bridge[plugin] 154 | for key, val in plugin_list_on.items(): #dict.keys()不可修改! 155 | if val == "" or val == " ": 156 | plugin_list_on[key] = "nil" 157 | msg_on = "启用的插件 \n\n" 158 | for i, on in enumerate(plugin_list_on): 159 | msg_on += " [" + str(i+1) + "] " + str(on) + " " + str(plugin_list_on[on]) + "\n" 160 | msg_on += "\nnil 代表指令为空" 161 | status = bot.sendChatAction(chat_id, "typing") 162 | status = bot.sendMessage(chat_id=chat_id, text=msg_on + "\n", parse_mode="HTML", reply_to_message_id=message_id, reply_markup=reply_markup) 163 | bot.message_deletor(60, chat_id, status["message_id"]) 164 | elif text[1:len(prefix + command["/pluginctlon"])+1] == prefix + command["/pluginctlon"]: 165 | plugin_list = list(plugin_bridge.keys()) 166 | if len(text.split(' ')) == 2: 167 | plug_set = text.split(' ')[1] 168 | for p in plug_set.split(','): 169 | if p == "nil": 170 | p = '' 171 | if p not in plugin_list: 172 | if '' in plugin_list and p == ' ': 173 | continue 174 | if p == "all": 175 | continue 176 | msg = "插件 " + str(p) + " 不存在,请重试." 177 | status = bot.sendChatAction(chat_id, "typing") 178 | status = bot.sendMessage(chat_id=chat_id, text=msg, parse_mode="HTML", reply_to_message_id=message_id) 179 | bot.message_deletor(15, chat_id, status["message_id"]) 180 | return False 181 | if plug_set == "all": 182 | lock.acquire() 183 | with open(bot.path_converter(plugin_dir + "PluginCTL/db/" + str(chat_id) + ".db"), "w") as f: 184 | f.write('') 185 | lock.release() 186 | status = bot.sendChatAction(chat_id, "typing") 187 | status = bot.sendMessage(chat_id=chat_id, text="已启用全部插件。", parse_mode="HTML", reply_to_message_id=message_id) 188 | bot.message_deletor(15, chat_id, status["message_id"]) 189 | return False 190 | elif len(plug_set.split(',')) >= 2: 191 | with open(bot.path_converter(plugin_dir + "PluginCTL/db/" + str(chat_id) + ".db"), "r") as f: 192 | plugin_setting = f.read().strip() 193 | plugin_list_off = plugin_setting.split(',') 194 | for i, plug_s in enumerate(plug_set.split(',')): 195 | if plug_s in plugin_list_off: 196 | for i, p in enumerate(plugin_list_off): 197 | if p == plug_s: 198 | del plugin_list_off[i] 199 | lock.acquire() 200 | with open(bot.path_converter(plugin_dir + "PluginCTL/db/" + str(chat_id) + ".db"), "w") as f: 201 | f.write(','.join(plugin_list_off)) 202 | lock.release() 203 | else: 204 | plug_set = plug_set.strip() 205 | with open(bot.path_converter(plugin_dir + "PluginCTL/db/" + str(chat_id) + ".db"), "r") as f: 206 | plugin_setting = f.read().strip() 207 | plugin_list_off = plugin_setting.split(',') 208 | if plug_set in plugin_list_off: 209 | for i, p in enumerate(plugin_list_off): 210 | if p == plug_set: 211 | del plugin_list_off[i] 212 | lock.acquire() 213 | with open(bot.path_converter(plugin_dir + "PluginCTL/db/" + str(chat_id) + ".db"), "w") as f: 214 | f.write(','.join(plugin_list_off)) 215 | lock.release() 216 | status = bot.sendChatAction(chat_id, "typing") 217 | status = bot.sendMessage(chat_id=chat_id, text="启用成功!", parse_mode="HTML", reply_to_message_id=message_id) 218 | bot.message_deletor(15, chat_id, status["message_id"]) 219 | else: 220 | status = bot.sendChatAction(chat_id, "typing") 221 | status = bot.sendMessage(chat_id=chat_id, text="指令错误,请检查.", parse_mode="HTML", reply_to_message_id=message_id) 222 | bot.message_deletor(15, chat_id, status["message_id"]) 223 | 224 | elif text[1:len(prefix + command["/pluginctloff"])+1] == prefix + command["/pluginctloff"]: 225 | default_plugin = ["Menu", "About", "PluginCTL", "Uptime", "Schedule"] 226 | plugin_list = list(plugin_bridge.keys()) 227 | if len(text.split(' ')) == 2: 228 | plug_set = text.split(' ')[1] 229 | for p in plug_set.split(','): 230 | if p not in plugin_list: 231 | if p == ' ' or p == '': 232 | continue 233 | if p == "all": 234 | continue 235 | msg = "插件 " + str(p) + " 不存在,请重试." 236 | status = bot.sendChatAction(chat_id, "typing") 237 | status = bot.sendMessage(chat_id=chat_id, text=msg, parse_mode="HTML", reply_to_message_id=message_id) 238 | bot.message_deletor(15, chat_id, status["message_id"]) 239 | return False 240 | if type(plug_set) == str and plug_set == "all": 241 | plugin_list_alloff = [] 242 | for i, p in enumerate(plugin_list): 243 | if p == "" or p == " ": 244 | p = "nil" 245 | if p not in default_plugin: 246 | plugin_list_alloff.append(p) 247 | lock.acquire() 248 | with open(bot.path_converter(plugin_dir + "PluginCTL/db/" + str(chat_id) + ".db"), "w") as f: 249 | f.write(','.join(plugin_list_alloff)) 250 | lock.release() 251 | status = bot.sendChatAction(chat_id, "typing") 252 | status = bot.sendMessage(chat_id=chat_id, text="已禁用全部插件,\n但必须的插件仍被保留。", parse_mode="HTML", reply_to_message_id=message_id) 253 | bot.message_deletor(15, chat_id, status["message_id"]) 254 | return False 255 | elif len(plug_set.split(',')) >= 2: 256 | for i, p in enumerate(plug_set.split(',')): 257 | if p in default_plugin: 258 | status = bot.sendChatAction(chat_id, "typing") 259 | msg = "插件 " + str(p) + " 不支持禁用." 260 | status = bot.sendMessage(chat_id=chat_id, text=msg, parse_mode="HTML", reply_to_message_id=message_id) 261 | bot.message_deletor(15, chat_id, status["message_id"]) 262 | return False 263 | with open(bot.path_converter(plugin_dir + "PluginCTL/db/" + str(chat_id) + ".db"), "r") as f: 264 | plugin_setting = f.read().strip() 265 | plugin_list_off = plugin_setting.split(',') 266 | for i, plug_s in enumerate(plug_set.split(',')): 267 | if plug_s not in plugin_list_off: 268 | plugin_list_off.append(plug_s) 269 | lock.acquire() 270 | with open(bot.path_converter(plugin_dir + "PluginCTL/db/" + str(chat_id) + ".db"), "w") as f: 271 | f.write(','.join(plugin_list_off)) 272 | lock.release() 273 | else: 274 | plug_set = plug_set.strip() 275 | for i, p in enumerate(plug_set.split(',')): 276 | if p in default_plugin: 277 | status = bot.sendChatAction(chat_id, "typing") 278 | msg = "插件 " + str(p) + " 不支持禁用." 279 | status = bot.sendMessage(chat_id=chat_id, text=msg, parse_mode="HTML", reply_to_message_id=message_id) 280 | bot.message_deletor(15, chat_id, status["message_id"]) 281 | return False 282 | with open(bot.path_converter(plugin_dir + "PluginCTL/db/" + str(chat_id) + ".db"), "r") as f: 283 | plugin_setting = f.read().strip() 284 | plugin_list_off = plugin_setting.split(',') 285 | if plug_set not in plugin_list_off: 286 | plugin_list_off.append(plug_set) 287 | lock.acquire() 288 | with open(bot.path_converter(plugin_dir + "PluginCTL/db/" + str(chat_id) + ".db"), "w") as f: 289 | f.write(','.join(plugin_list_off)) 290 | lock.release() 291 | status = bot.sendChatAction(chat_id, "typing") 292 | status = bot.sendMessage(chat_id=chat_id, text="禁用成功!", parse_mode="HTML", reply_to_message_id=message_id) 293 | bot.message_deletor(15, chat_id, status["message_id"]) 294 | else: 295 | status = bot.sendChatAction(chat_id, "typing") 296 | status = bot.sendMessage(chat_id=chat_id, text="指令错误,请检查.", parse_mode="HTML", reply_to_message_id=message_id) 297 | bot.message_deletor(15, chat_id, status["message_id"]) 298 | 299 | 300 | else: 301 | status = bot.sendChatAction(chat_id, "typing") 302 | status = bot.sendMessage(chat_id=chat_id, text="指令错误,请检查.", parse_mode="HTML", reply_to_message_id=message_id) 303 | bot.message_deletor(15, chat_id, status["message_id"]) 304 | 305 | 306 | 307 | def administrators(bot, chat_id): 308 | admins = [] 309 | results = bot.getChatAdministrators(chat_id=chat_id) 310 | if results != False: 311 | for result in results: 312 | if str(result["user"]["is_bot"]) == "False": 313 | admins.append(str(result["user"]["id"])) 314 | else: 315 | admins = False 316 | 317 | return admins 318 | -------------------------------------------------------------------------------- /teelebot/plugins/PluginCTL/__init__.py: -------------------------------------------------------------------------------- 1 | #/pluginctl 2 | #PluginCTL插件,控制插件开关 -------------------------------------------------------------------------------- /teelebot/plugins/Schedule/Schedule.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | ''' 3 | creation time: 2020-11-11 4 | last_modify: 2020-11-14 5 | ''' 6 | import time 7 | 8 | def Schedule(bot, message): 9 | chat_id = message["chat"]["id"] 10 | user_id = message["from"]["id"] 11 | message_id = message["message_id"] 12 | text = message["text"] 13 | 14 | root_id = bot.root_id 15 | 16 | gaps = { 17 | "1s": 1, 18 | "2s": 2, 19 | "5s": 5, 20 | "10s": 10, 21 | "15s": 15, 22 | "30s": 30, 23 | "45s": 45, 24 | 25 | "1m": 60, 26 | "2m": 120, 27 | "5m": 300, 28 | "10m": 600, 29 | "15m": 900, 30 | "30m": 1800, 31 | "45m": 2700, 32 | 33 | "1h": 3600, 34 | "2h": 7200, 35 | "4h": 10800, 36 | "6h": 21600, 37 | "8h": 28800, 38 | "10h": 36000, 39 | "12h": 43200, 40 | 41 | "1d": 86400, 42 | "3d": 259200, 43 | "5d": 432000, 44 | "7d": 604800, 45 | "10d": 864000, 46 | "15d": 1296000, 47 | "20d": 1728000, 48 | "30d": 2592000 49 | } 50 | 51 | prefix = "/sched" 52 | command = { #命令注册 53 | "/schedadd": "add", 54 | "/scheddel": "del", 55 | "/schedfind": "find", 56 | "/schedclear": "clear", 57 | "/schedstatus": "status" 58 | } 59 | count = 0 60 | for c in command.keys(): 61 | if c in str(text): 62 | count += 1 63 | 64 | if text.split(" ")[0] != prefix and prefix in text and str(user_id) != root_id: 65 | status = bot.sendMessage(chat_id, text="无权限", parse_mode="HTML", 66 | reply_to_message_id=message_id) 67 | bot.message_deletor(15, status["chat"]["id"], status["message_id"]) 68 | return 69 | 70 | if text[:len(prefix)] == prefix and count == 0: 71 | msg = "Schedule 插件功能" + "\n\n" + \ 72 | "/schedadd 添加任务 格式:指令+空格+周期+消息" + "\n" + \ 73 | "/scheddel 移除任务 格式:指令+空格+标识" + "\n" + \ 74 | "/schedfind 查找任务 格式:指令+空格+标识" + "\n" + \ 75 | "/schedclear 移除所有任务" + "\n" + \ 76 | "/schedstatus 查看队列信息" + "\n\n" + \ 77 | "支持的周期指令:1s 2s 5s 10s 15s 30s 45s | "+ \ 78 | "1m 2m 5m 10m 15m 30m 45m | " + \ 79 | "1h 2h 4h 6h 8h 10h 12h | " + \ 80 | "1d 3d 5d 7d 10d 15d 20d 30d" + "" 81 | status = bot.sendMessage(chat_id, text=msg, parse_mode="HTML", 82 | reply_to_message_id=message_id) 83 | bot.message_deletor(60, status["chat"]["id"], status["message_id"]) 84 | 85 | elif text[:len(prefix + "add")] == prefix + "add": 86 | if len(text.split(" ")) == 3: 87 | msg = "" 88 | gap_key = str(text.split(" ")[1]) 89 | if gap_key not in gaps.keys(): 90 | msg = "错误的周期,支持的周期指令: \n\n" + \ 91 | "1s 2s 5s 10s 15s 30s 45s \n" + \ 92 | "1m 2m 5m 10m 15m 30m 45m \n" + \ 93 | "1h 2h 4h 6h 8h 10h 12h \n" + \ 94 | "1d 3d 5d 7d 10d 15d 20d 30d" + "" 95 | status = bot.sendMessage(chat_id, text=msg, parse_mode="HTML", 96 | reply_to_message_id=message_id) 97 | bot.message_deletor(30, status["chat"]["id"], status["message_id"]) 98 | return 99 | 100 | gap = gaps[gap_key] 101 | gap_key = gap_key.replace("s", "秒").replace("m", "分钟").replace("h", "小时").replace("d", "天") 102 | msg = str(text.split(" ")[2]) + "\n\n" + "此消息为定时发送,周期" + str(gap_key) + "" 103 | ok, uid = bot.schedule.add(gap, event, (bot, message["chat"]["id"], msg, "HTML")) 104 | timestamp = time.strftime('%Y/%m/%d %H:%M:%S',time.localtime(time.time())) 105 | if ok: 106 | msg = "任务已加入队列\n\n" + \ 107 | "周期: " + gap_key + "\n" + \ 108 | "目标: " + str(chat_id) + "\n" + \ 109 | "标识: " + str(uid) + "\n" + \ 110 | "时间: " + str(timestamp) + "\n\n" + \ 111 | "此消息将在60秒后销毁,请尽快保存标识\n" 112 | else: 113 | msg = "" 114 | if uid == "Full": 115 | msg = "队列已满" 116 | else: 117 | msg = "遇到错误 \n\n " + uid + "" 118 | status = bot.sendMessage(chat_id, text=msg, parse_mode="HTML", 119 | reply_to_message_id=message_id) 120 | bot.message_deletor(60, status["chat"]["id"], status["message_id"]) 121 | else: 122 | status = bot.sendMessage(chat_id, 123 | text="指令格式错误 (e.g.: " + prefix + "add gap text)", 124 | parse_mode="HTML", reply_to_message_id=message_id) 125 | bot.message_deletor(30, status["chat"]["id"], status["message_id"]) 126 | 127 | elif text[:len(prefix + "del")] == prefix + "del": 128 | if len(text.split(" ")) == 2: 129 | msg = "" 130 | uid = str(text.split(" ")[1]) 131 | ok, uid = bot.schedule.delete(uid) 132 | if ok: 133 | msg = "移除了任务 " + str(uid) + "" 134 | else: 135 | if uid == "Empty": 136 | msg = "队列为空" 137 | elif uid == "NotFound": 138 | msg = "任务未找到" 139 | status = bot.sendMessage(chat_id, text=msg, 140 | parse_mode="HTML", reply_to_message_id=message_id) 141 | bot.message_deletor(30, status["chat"]["id"], status["message_id"]) 142 | else: 143 | status = bot.sendMessage(chat_id, 144 | text="指令格式错误 (e.g.: " + prefix + "del uid)", 145 | parse_mode="HTML", reply_to_message_id=message_id) 146 | bot.message_deletor(30, status["chat"]["id"], status["message_id"]) 147 | 148 | elif text[:len(prefix + "find")] == prefix + "find": 149 | if len(text.split(" ")) == 2: 150 | msg = "" 151 | uid = str(text.split(" ")[1]) 152 | ok, uid = bot.schedule.find(uid) 153 | if ok: 154 | msg = "任务存在于队列中" 155 | else: 156 | if uid == "Empty": 157 | msg = "队列为空" 158 | elif uid == "NotFound": 159 | msg = "任务未找到" 160 | status = bot.sendMessage(chat_id, text=msg, 161 | parse_mode="HTML", reply_to_message_id=message_id) 162 | bot.message_deletor(30, status["chat"]["id"], status["message_id"]) 163 | else: 164 | status = bot.sendMessage(chat_id, 165 | text="指令格式错误 (e.g.: " + prefix + "del uid)", 166 | parse_mode="HTML", reply_to_message_id=message_id) 167 | bot.message_deletor(30, status["chat"]["id"], status["message_id"]) 168 | 169 | elif text[:len(prefix + "clear")] == prefix + "clear": 170 | msg = "" 171 | ok, msgg = bot.schedule.clear() 172 | if ok: 173 | msg = "已清空队列" 174 | else: 175 | if msgg == "Empty": 176 | msg = "队列为空" 177 | elif msgg != "Cleared": 178 | msg = "遇到错误 \n\n " + msgg + "" 179 | 180 | status = bot.sendMessage(chat_id, text=msg, 181 | parse_mode="HTML", reply_to_message_id=message_id) 182 | bot.message_deletor(30, status["chat"]["id"], status["message_id"]) 183 | 184 | elif text[:len(prefix + "status")] == prefix + "status": 185 | msg = "" 186 | ok, result = bot.schedule.status() 187 | if ok: 188 | msg = "使用: " + str(result["used"]) + "\n" + \ 189 | "空闲: " + str(result["free"]) + "\n" + \ 190 | "容量: " + str(result["size"]) + "\n" 191 | else: 192 | msg = "遇到错误 \n\n " + result["exception"] + "" 193 | status = bot.sendMessage(chat_id, text=msg, 194 | parse_mode="HTML", reply_to_message_id=message_id) 195 | bot.message_deletor(30, status["chat"]["id"], status["message_id"]) 196 | 197 | 198 | 199 | def event(bot, chat_id, msg, parse_mode): 200 | status = bot.sendMessage(chat_id=chat_id, text=msg, parse_mode="HTML") 201 | 202 | -------------------------------------------------------------------------------- /teelebot/plugins/Schedule/__init__.py: -------------------------------------------------------------------------------- 1 | #/sched 2 | #Schedule插件 周期性执行特定任务,目前仅支持文本类消息 3 | -------------------------------------------------------------------------------- /teelebot/plugins/Uptime/Uptime.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | ''' 3 | creation time: 2020-6-26 4 | last_modify: 2020-11-18 5 | ''' 6 | import os 7 | from datetime import timedelta 8 | 9 | def Uptime(bot, message): 10 | 11 | chat_id = message["chat"]["id"] 12 | message_id = message["message_id"] 13 | text = message["text"] 14 | plugin_dir = bot.plugin_dir 15 | VERSION = bot.version 16 | prefix = "uptime" 17 | 18 | if not os.path.exists(bot.path_converter(plugin_dir + "Uptime/config.ini")): 19 | detail_links = None 20 | else: 21 | with open(bot.path_converter(plugin_dir + "Uptime/config.ini"), 'r') as f: 22 | detail_links = f.readline().strip() 23 | 24 | if text[1:len(prefix)+1] == prefix: 25 | time_second = bot.uptime 26 | time_format = timedelta(seconds=time_second) 27 | response_times = bot.response_times 28 | response_chats = len(bot.response_chats) 29 | response_users = len(bot.response_users) 30 | status = bot.sendChatAction(chat_id, "typing") 31 | inlineKeyboard = [ 32 | [ 33 | {"text": "详细信息", "url": detail_links} 34 | ] 35 | ] 36 | if detail_links is not None: 37 | reply_markup = { 38 | "inline_keyboard": inlineKeyboard 39 | } 40 | else: 41 | reply_markup = None 42 | msg = "感谢您的关心 ( ̄ε  ̄) \n\n我已经运行 " + str(time_second) + " 秒\n" +\ 43 | "即:" + str(time_format) + "\n\n" +\ 44 | "在此期间:\n" +\ 45 | "响应指令 " + str(response_times) + " 次\n" +\ 46 | "服务群组 " + str(response_chats) + " 个\n" +\ 47 | "服务用户 " + str(response_users) + " 名\n\n" +\ 48 | "v" + str(VERSION) + "" 49 | 50 | status = bot.sendMessage(chat_id=chat_id, text=msg, parse_mode="HTML", reply_to_message_id=message_id, reply_markup=reply_markup) 51 | bot.message_deletor(15, chat_id, status["message_id"]) -------------------------------------------------------------------------------- /teelebot/plugins/Uptime/__init__.py: -------------------------------------------------------------------------------- 1 | #/uptime 2 | # 查看Bot运行状态 -------------------------------------------------------------------------------- /teelebot/plugins/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Everless321/telebot-invitecode/9ebbf77e85620485574b7bebd61d247e9a1d6612/teelebot/plugins/__init__.py -------------------------------------------------------------------------------- /teelebot/polling.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | ''' 3 | @creation date: 2020-6-23 4 | @last modify: 2020-11-23 5 | ''' 6 | import time 7 | import sys 8 | 9 | 10 | def _runUpdates(bot): 11 | plugin_bridge = bot.plugin_bridge 12 | plugin_list = plugin_bridge.keys() 13 | try: 14 | while True: 15 | results = bot.getUpdates() # 获取消息队列messages 16 | messages = bot._washUpdates(results) 17 | if messages is None or not messages: 18 | continue 19 | for message in messages: # 获取单条消息message 20 | bot._pluginRun(bot, message) 21 | except KeyboardInterrupt: 22 | sys.exit("Bot Exit.") # 退出存在问题,待修复 23 | -------------------------------------------------------------------------------- /teelebot/request.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | ''' 3 | @creation date: 2019-11-15 4 | @last modify: 2020-11-23 5 | ''' 6 | import os 7 | 8 | import requests 9 | import inspect 10 | from .logger import _logger 11 | from traceback import extract_stack 12 | from requests.packages.urllib3.exceptions import InsecureRequestWarning 13 | 14 | requests.packages.urllib3.disable_warnings(InsecureRequestWarning) 15 | 16 | 17 | class _Request(object): 18 | """ 19 | 接口请求类 20 | """ 21 | def __init__(self, thread_pool_size, url, debug=False): 22 | self.__url = url 23 | self.__debug = debug 24 | self.__session = self.__connection_session( 25 | pool_connections=thread_pool_size, 26 | pool_maxsize=thread_pool_size * 2 27 | ) 28 | 29 | def __del__(self): 30 | self.__session.close() 31 | 32 | def __connection_session(self, pool_connections=10, pool_maxsize=10, max_retries=5): 33 | """ 34 | 连接池 35 | """ 36 | session = requests.Session() 37 | session.verify = False 38 | 39 | adapter = requests.adapters.HTTPAdapter(pool_connections=pool_connections, 40 | pool_maxsize=pool_maxsize, max_retries=max_retries) 41 | session.mount('http://', adapter) 42 | session.mount('https://', adapter) 43 | 44 | return session 45 | 46 | def __debug_info(self, result): 47 | """ 48 | debug模式 49 | """ 50 | if self.__debug and not result.get("ok"): 51 | os.system("") # "玄学"解决Windows下颜色显示失效的问题... 52 | stack_info = extract_stack() 53 | if len(stack_info) > 8: # 插件内 54 | _logger.debug("\033[1;31m" + \ 55 | "Request failed" + " - " + \ 56 | "From:" + stack_info[-3][2] + " - " + \ 57 | "Path:" + stack_info[5][0] + " - " + \ 58 | "Line:" + str(stack_info[5][1]) + " - " + \ 59 | "Method:" + stack_info[6][2] + " - " + \ 60 | "Result:" + str(result) + \ 61 | "\033[0m") 62 | elif len(stack_info) > 3: # 外部调用 63 | _logger.debug("\033[1;31m" + \ 64 | "Request failed" + " - " + \ 65 | "From:" + stack_info[0][0] + " - " + \ 66 | "Path:" + stack_info[1][0] + " - " + \ 67 | "Line:" + str(stack_info[0][1]) + " - " + \ 68 | "Method:" + stack_info[1][2] + " - " + \ 69 | "Result:" + str(result) + \ 70 | "\033[0m") 71 | 72 | def post(self, addr): 73 | try: 74 | with self.__session.post(self.__url + addr) as req: 75 | self.__debug_info(req.json()) 76 | if req.json().get("ok"): 77 | return req.json().get("result") 78 | elif not req.json().get("ok"): 79 | return req.json().get("ok") 80 | except: 81 | return False 82 | 83 | def postFile(self, addr, file_data): 84 | try: 85 | with self.__session.post(self.__url + addr, files=file_data) as req: 86 | self.__debug_info(req.json()) 87 | if req.json().get("ok"): 88 | return req.json().get("result") 89 | elif not req.json().get("ok"): 90 | return req.json().get("ok") 91 | except: 92 | return False 93 | 94 | def postJson(self, addr, json): 95 | try: 96 | with self.__session.get(self.__url + addr, json=json) as req: 97 | self.__debug_info(req.json()) 98 | if req.json().get("ok"): 99 | return req.json().get("result") 100 | elif not req.json().get("ok"): 101 | return req.json().get("ok") 102 | except: 103 | return False 104 | 105 | def get(self, addr): 106 | try: 107 | with self.__session.get(self.__url + addr) as req: 108 | self.__debug_info(req.json()) 109 | if req.json().get("ok"): 110 | return req.json().get("result") 111 | elif not req.json().get("ok"): 112 | return req.json().get("ok") 113 | except: 114 | return False 115 | 116 | 117 | -------------------------------------------------------------------------------- /teelebot/schedule.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | ''' 3 | @creation date: 2019-11-15 4 | @last modify: 2020-11-18 5 | ''' 6 | import threading 7 | from uuid import uuid4 8 | 9 | class _Schedule(object): 10 | """ 11 | 周期性任务类 12 | """ 13 | def __init__(self, queue_size): 14 | self.__queue_size = queue_size 15 | self.__queue_mutex = threading.Lock() 16 | self.__queue = {} 17 | 18 | def __create(self, gap, func, args): 19 | class RepeatingTimer(threading.Timer): 20 | def run(self): 21 | while not self.finished.is_set(): 22 | self.function(*self.args, **self.kwargs) 23 | self.finished.wait(self.interval) 24 | try: 25 | t = RepeatingTimer(gap, func, args) 26 | t.setDaemon(True) 27 | return True, t 28 | except Exception as e: 29 | print(e) 30 | return False, str(e) 31 | 32 | def add(self, gap, func, args): 33 | """ 34 | 添加周期性任务 35 | """ 36 | def __short_uuid(): 37 | uuidChars = ("a", "b", "c", "d", "e", "f", 38 | "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", 39 | "t", "u", "v", "w", "x", "y", "z", "0", "1", "2", "3", "4", "5", 40 | "6", "7", "8", "9", "A", "B", "C", "D", "E", "F", "G", "H", "I", 41 | "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", 42 | "W", "X", "Y", "Z") 43 | uuid = str(uuid4().hex) 44 | uid = '' 45 | for i in range(0,8): 46 | sub = uuid[i * 4: i * 4 + 4] 47 | x = int(sub,16) 48 | uid += uuidChars[x % 0x3E] 49 | return uid 50 | 51 | if len(self.__queue) == self.__queue_size: 52 | return False, "Full" 53 | 54 | ok, t = self.__create(gap, func, args) 55 | if ok: 56 | t.start() 57 | uid = __short_uuid() 58 | with self.__queue_mutex: 59 | self.__queue[uid] = t 60 | 61 | return True, uid 62 | else: 63 | return False, t 64 | 65 | def status(self): 66 | """ 67 | 获取周期性任务池的使用情况 68 | """ 69 | try: 70 | used = len(self.__queue) 71 | free = self.__queue_size - used 72 | size = self.__queue_size 73 | 74 | result = { 75 | "used": used, 76 | "free": free, 77 | "size": size 78 | } 79 | return True, result 80 | except Exception as e: 81 | return False, {"exception": e} 82 | 83 | def find(self, uid): 84 | """ 85 | 查找周期性任务 86 | """ 87 | if len(self.__queue) <= 0: 88 | return False, "Empty" 89 | 90 | if str(uid) in self.__queue.keys(): 91 | return True, str(uid) 92 | else: 93 | return False, "NotFound" 94 | 95 | def delete(self, uid): 96 | """ 97 | 移除周期性任务 98 | """ 99 | if len(self.__queue) <= 0: 100 | return False, "Empty" 101 | 102 | if str(uid) in self.__queue.keys(): 103 | self.__queue[str(uid)].cancel() 104 | with self.__queue_mutex: 105 | self.__queue.pop(str(uid)) 106 | 107 | return True, str(uid) 108 | else: 109 | return False, "NotFound" 110 | 111 | def clear(self): 112 | """ 113 | 移除所有周期性任务 114 | """ 115 | if len(self.__queue) == 0: 116 | return False, "Empty" 117 | else: 118 | try: 119 | for uid in list(self.__queue.keys()): 120 | self.__queue[str(uid)].cancel() 121 | 122 | with self.__queue_mutex: 123 | self.__queue.clear() 124 | 125 | return True, "Cleared" 126 | except Exception as e: 127 | return False, str(e) 128 | -------------------------------------------------------------------------------- /teelebot/teelebot.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | """ 3 | @description:基于Telegram Bot Api 的机器人框架 4 | @creation date: 2019-8-13 5 | @last modify: 2020-11-27 6 | @author: Pluto (github:plutobell) 7 | @version: 1.14.1 8 | """ 9 | import inspect 10 | import time 11 | import sys 12 | import os 13 | import json 14 | import shutil 15 | import importlib 16 | import threading 17 | 18 | from pathlib import Path 19 | from urllib.parse import quote 20 | from concurrent.futures import ThreadPoolExecutor 21 | 22 | from .handler import _config, _bridge, _plugin_info 23 | from .logger import _logger 24 | from .schedule import _Schedule 25 | from .request import _Request 26 | 27 | 28 | class Bot(object): 29 | """机器人的基类""" 30 | 31 | def __init__(self, key=""): 32 | config = _config() 33 | 34 | if key != "": 35 | self._key = key 36 | elif key == "": 37 | self._key = config["key"] 38 | 39 | self._cloud_api_server = config["cloud_api_server"] 40 | self._local_api_server = config["local_api_server"] 41 | if self._local_api_server != "False": 42 | self._basic_url = config["local_api_server"] 43 | else: 44 | self._basic_url = self._cloud_api_server 45 | self._url = self._basic_url + r"bot" + self._key + r"/" 46 | 47 | self._webhook = config["webhook"] 48 | if self._webhook: 49 | self._self_signed = config["self_signed"] 50 | self._cert_key = config["cert_key"] 51 | self._cert_pub = config["cert_pub"] 52 | self._server_address = config["server_address"] 53 | self._server_port = config["server_port"] 54 | self._local_address = config["local_address"] 55 | self._local_port = config["local_port"] 56 | self._offset = 0 57 | self._timeout = 60 58 | self._debug = config["debug"] 59 | self._pool_size = config["pool_size"] 60 | self._drop_pending_updates = config["drop_pending_updates"] 61 | 62 | self.__root_id = config["root_id"] 63 | self.__bot_id = self._key.split(":")[0] 64 | self.__AUTHOR = config["author"] 65 | self.__VERSION = config["version"] 66 | self.__plugin_dir = config["plugin_dir"] 67 | self.__plugin_bridge = config["plugin_bridge"] 68 | self.__start_time = int(time.time()) 69 | self.__response_times = 0 70 | self.__response_chats = [] 71 | self.__response_users = [] 72 | 73 | thread_pool_size = round(int(self._pool_size) * 2 / 3) 74 | schedule_queue_size = int(self._pool_size) - thread_pool_size 75 | self.request = _Request(thread_pool_size, self._url, self._debug) 76 | self.schedule = _Schedule(schedule_queue_size) 77 | 78 | self.__thread_pool = ThreadPoolExecutor( 79 | max_workers=thread_pool_size) 80 | self.__timer_thread_pool = ThreadPoolExecutor( 81 | max_workers=int(self._pool_size) * 5) 82 | 83 | self.__plugin_info = config["plugin_info"] 84 | 85 | del config 86 | del thread_pool_size 87 | del schedule_queue_size 88 | 89 | def __del__(self): 90 | self.__thread_pool.shutdown(wait=True) 91 | self.__timer_thread_pool.shutdown(wait=True) 92 | del self.request 93 | del self.schedule 94 | 95 | # teelebot method 96 | def __threadpool_exception(self, fur): 97 | """ 98 | 线程池异常回调 99 | """ 100 | if fur.exception() is not None: 101 | _logger.debug("EXCEPTION" + " - " + str(fur.result())) 102 | 103 | def __import_module(self, plugin_name): 104 | """ 105 | 动态导入模块 106 | """ 107 | sys.path.append(self.path_converter(self.__plugin_dir + plugin_name + os.sep)) 108 | Module = importlib.import_module(plugin_name) # 模块检测 109 | 110 | return Module 111 | 112 | def __update_plugin(self, plugin_name): 113 | """ 114 | 热更新插件 115 | """ 116 | plugin_uri = self.path_converter( 117 | self.__plugin_dir + plugin_name + os.sep + plugin_name + ".py") 118 | now_mtime = os.stat(plugin_uri).st_mtime 119 | # print(now_mtime, self.__plugin_info[plugin_name]) 120 | if now_mtime != self.__plugin_info[plugin_name]: # 插件热更新 121 | if os.path.exists(self.path_converter(self.__plugin_dir + plugin_name + r"/__pycache__")): 122 | shutil.rmtree(self.path_converter(self.__plugin_dir + plugin_name + r"/__pycache__")) 123 | self.__plugin_info[plugin_name] = now_mtime 124 | Module = self.__import_module(plugin_name) 125 | importlib.reload(Module) 126 | _logger.info("The plugin " + plugin_name + " has been updated") 127 | 128 | def __load_plugin(self, now_plugin_bridge, now_plugin_info): 129 | """ 130 | 动态装载插件 131 | """ 132 | for plugin in list(now_plugin_bridge.keys()): 133 | if plugin not in list(self.__plugin_bridge.keys()): 134 | _logger.info("The plugin " + plugin + " has been installed") 135 | self.__plugin_info[plugin] = now_plugin_info[plugin] 136 | for plugin in list(self.__plugin_bridge.keys()): 137 | if plugin not in list(now_plugin_bridge.keys()): 138 | _logger.info("The plugin " + plugin + " has been uninstalled") 139 | self.__plugin_info.pop(plugin) 140 | 141 | self.__plugin_bridge = now_plugin_bridge 142 | 143 | def __control_plugin(self, plugin_bridge, chat_type, chat_id): 144 | if chat_type != "private" and "PluginCTL" in plugin_bridge.keys() \ 145 | and plugin_bridge["PluginCTL"] == "/pluginctl": 146 | if os.path.exists(self.path_converter(self.__plugin_dir + "PluginCTL/db/" + str(chat_id) + ".db")): 147 | with open(self.path_converter(self.__plugin_dir + "PluginCTL/db/" + str(chat_id) + ".db"), "r") as f: 148 | plugin_setting = f.read().strip() 149 | plugin_list_off = plugin_setting.split(',') 150 | plugin_bridge_temp = {} 151 | for plugin in list(plugin_bridge.keys()): 152 | if plugin not in plugin_list_off: 153 | plugin_bridge_temp[plugin] = plugin_bridge[plugin] 154 | plugin_bridge = plugin_bridge_temp 155 | 156 | return plugin_bridge 157 | 158 | def __mark_message_for_pluginRun(self, message): 159 | if "callback_query_id" in message.keys(): # callback query 160 | message["message_type"] = "callback_query_data" 161 | message_type = "callback_query_data" 162 | elif ("new_chat_members" in message.keys()) or ("left_chat_member" in message.keys()): 163 | message["message_type"] = "text" 164 | message_type = "text" 165 | message["text"] = "" # default prefix of command 166 | elif "photo" in message.keys(): 167 | message["message_type"] = "photo" 168 | message_type = "message_type" 169 | elif "sticker" in message.keys(): 170 | message["message_type"] = "sticker" 171 | message_type = "message_type" 172 | elif "video" in message.keys(): 173 | message["message_type"] = "video" 174 | message_type = "message_type" 175 | elif "audio" in message.keys(): 176 | message["message_type"] = "audio" 177 | message_type = "message_type" 178 | elif "document" in message.keys(): 179 | message["message_type"] = "document" 180 | message_type = "message_type" 181 | elif "text" in message.keys(): 182 | message["message_type"] = "text" 183 | message_type = "text" 184 | elif "caption" in message.keys(): 185 | message["message_type"] = "caption" 186 | message_type = "caption" 187 | elif "query" in message.keys(): 188 | message["message_type"] = "query" 189 | message_type = "query" 190 | else: 191 | message["message_type"] = "unknown" 192 | message_type = "unknown" 193 | 194 | return message_type, message 195 | 196 | def __logging_for_pluginRun(self, message, plugin): 197 | title = "" # INFO日志 198 | user_name = "" 199 | 200 | if message["chat"]["type"] == "private": 201 | if "first_name" in message["chat"].keys(): 202 | title += message["chat"]["first_name"] 203 | if "last_name" in message["chat"].keys(): 204 | if "first_name" in message["chat"].keys(): 205 | title += " " + message["chat"]["last_name"] 206 | else: 207 | title += message["chat"]["last_name"] 208 | elif "title" in message["chat"].keys(): 209 | title = message["chat"]["title"] 210 | if "reply_markup" in message.keys() and \ 211 | message["message_type"] == "callback_query_data": 212 | from_id = message["click_user"]["id"] 213 | if "first_name" in message["click_user"].keys(): 214 | user_name += message["click_user"]["first_name"] 215 | if "last_name" in message["click_user"].keys(): 216 | if "first_name" in message["click_user"].keys(): 217 | user_name += " " + message["click_user"]["last_name"] 218 | else: 219 | user_name += message["chat"]["last_name"] 220 | else: 221 | from_id = message["from"]["id"] 222 | if "first_name" in message["from"].keys(): 223 | user_name += message["from"]["first_name"] 224 | if "last_name" in message["from"].keys(): 225 | if "first_name" in message["from"].keys(): 226 | user_name += " " + message["from"]["last_name"] 227 | else: 228 | user_name += message["from"]["last_name"] 229 | 230 | if message["message_type"] == "unknown": 231 | _logger.info( 232 | "From:" + title + "(" + str(message["chat"]["id"]) + ") - " + \ 233 | "User:" + user_name + "(" + str(from_id) + ") - " + \ 234 | "Plugin: " + "" + " - " + \ 235 | "Type:" + message["message_type"]) 236 | else: 237 | _logger.info( 238 | "From:" + title + "(" + str(message["chat"]["id"]) + ") - " + \ 239 | "User:" + user_name + "(" + str(from_id) + ") - " + \ 240 | "Plugin: " + str(plugin) + " - " + \ 241 | "Type:" + message["message_type"]) 242 | 243 | def _pluginRun(self, bot, message): 244 | """ 245 | 运行插件 246 | """ 247 | if message is None: 248 | return 249 | 250 | now_plugin_bridge = _bridge(self.__plugin_dir) 251 | now_plugin_info = _plugin_info(now_plugin_bridge.keys(), self.__plugin_dir) 252 | 253 | if now_plugin_bridge != self.__plugin_bridge: # 动态装载插件 254 | self.__load_plugin(now_plugin_bridge, now_plugin_info) 255 | 256 | if len(now_plugin_info) != len(self.__plugin_info) or \ 257 | now_plugin_info != self.__plugin_info: # 动态更新插件信息 258 | for plugin_name in list(self.__plugin_bridge.keys()): 259 | self.__update_plugin(plugin_name) #热更新插件 260 | 261 | if len(self.__plugin_bridge) == 0: 262 | os.system("") 263 | _logger.warn("\033[1;31mNo plugins installed\033[0m") 264 | 265 | plugin_bridge = self.__control_plugin( # pluginctl控制 266 | self.__plugin_bridge, message["chat"]["type"], message["chat"]["id"]) 267 | 268 | message_type = "" 269 | message_type, message = self.__mark_message_for_pluginRun(message) # 分类标记消息 270 | 271 | if message_type == "unknown": 272 | self.__logging_for_pluginRun(message, "unknown") 273 | return 274 | 275 | for plugin, command in plugin_bridge.items(): 276 | if message.get(message_type)[:len(command)] == command: 277 | module = self.__import_module(plugin) 278 | pluginFunc = getattr(module, plugin) 279 | fur = self.__thread_pool.submit(pluginFunc, bot, message) 280 | fur.add_done_callback(self.__threadpool_exception) 281 | 282 | self.__response_times += 1 283 | 284 | if message["chat"]["type"] != "private" and \ 285 | message["chat"]["id"] not in self.__response_chats: 286 | self.__response_chats.append(message["chat"]["id"]) 287 | if message["from"]["id"] not in self.__response_users: 288 | self.__response_users.append(message["from"]["id"]) 289 | 290 | self.__logging_for_pluginRun(message, plugin) 291 | 292 | def _washUpdates(self, results): 293 | """ 294 | 清洗消息队列 295 | results应当是一个列表 296 | """ 297 | if not results: 298 | return False 299 | elif len(results) < 1: 300 | return None 301 | update_ids = [] 302 | messages = [] 303 | for result in results: 304 | if "update_id" not in result.keys(): 305 | return None 306 | update_ids.append(result["update_id"]) 307 | query_or_message = "" 308 | if result.get("inline_query"): 309 | query_or_message = "inline_query" 310 | elif result.get("callback_query"): 311 | query_or_message = "callback_query" 312 | elif result.get("message"): 313 | query_or_message = "message" 314 | update_ids.append(result.get("update_id")) 315 | 316 | if query_or_message == "callback_query": 317 | callback_query = result.get(query_or_message).get("message") 318 | callback_query["click_user"] = result.get(query_or_message)[ 319 | "from"] 320 | callback_query["callback_query_id"] = result.get( 321 | query_or_message).get("id") 322 | callback_query["callback_query_data"] = result.get( 323 | query_or_message).get("data") 324 | messages.append(callback_query) 325 | else: 326 | messages.append(result.get(query_or_message)) 327 | if len(update_ids) >= 1: 328 | self._offset = max(update_ids) + 1 329 | return messages 330 | else: 331 | return None 332 | 333 | def message_deletor(self, time_gap, chat_id, message_id): 334 | """ 335 | 定时删除一条消息,时间范围:[0, 900],单位秒 336 | """ 337 | if time_gap < 0 or time_gap > 900: 338 | return "time_gap_error" 339 | else: 340 | def message_deletor_func(time_gap, chat_id, message_id): 341 | time.sleep(int(time_gap)) 342 | self.deleteMessage(chat_id=chat_id, message_id=message_id) 343 | 344 | if time_gap == 0: 345 | message_deletor_func(chat_id, message_id) 346 | else: 347 | fur = self.__timer_thread_pool.submit( 348 | message_deletor_func, time_gap, chat_id, message_id) 349 | fur.add_done_callback(self.__threadpool_exception) 350 | 351 | return "ok" 352 | 353 | def timer(self, time_gap, func, args): 354 | """ 355 | 单次定时器,时间范围:[0, 900],单位秒 356 | """ 357 | if time_gap < 0 or time_gap > 900: 358 | return "time_gap_error" 359 | elif type(args) is not tuple: 360 | return "args_must_be_tuple" 361 | else: 362 | def timer_func(time_gap, func, args): 363 | time.sleep(int(time_gap)) 364 | func(*args) 365 | 366 | if time_gap == 0: 367 | func(args) 368 | else: 369 | fur = self.__timer_thread_pool.submit( 370 | timer_func, time_gap, func, args) 371 | fur.add_done_callback(self.__threadpool_exception) 372 | 373 | return "ok" 374 | 375 | def path_converter(self, path): 376 | """ 377 | 根据操作系统转换URI 378 | """ 379 | 380 | path = str(Path(path)) 381 | 382 | return path 383 | 384 | @property 385 | def plugin_bridge(self): 386 | """ 387 | 获取插件桥 388 | """ 389 | 390 | return self.__plugin_bridge 391 | 392 | @property 393 | def plugin_dir(self): 394 | """ 395 | 获取插件路径 396 | """ 397 | 398 | return self.__plugin_dir 399 | 400 | @property 401 | def version(self): 402 | """ 403 | 获取框架版本号 404 | """ 405 | 406 | return self.__VERSION 407 | 408 | @property 409 | def author(self): 410 | """ 411 | 作者信息 412 | """ 413 | 414 | return self.__AUTHOR 415 | 416 | @property 417 | def root_id(self): 418 | """ 419 | 获取root用户ID 420 | """ 421 | 422 | return self.__root_id 423 | 424 | @property 425 | def bot_id(self): 426 | """ 427 | 获取Bot的ID 428 | """ 429 | 430 | return self.__bot_id 431 | 432 | @property 433 | def uptime(self): 434 | """ 435 | 获取框架的持续运行时间(单位为秒) 436 | """ 437 | second = int(time.time()) - self.__start_time 438 | 439 | return second 440 | 441 | @property 442 | def response_times(self): 443 | """ 444 | 获取框架启动后响应指令的统计次数 445 | """ 446 | return self.__response_times 447 | 448 | @property 449 | def response_chats(self): 450 | """ 451 | 获取框架启动后响应的所有群组ID 452 | """ 453 | return self.__response_chats 454 | 455 | @property 456 | def response_users(self): 457 | """ 458 | 获取框架启动后响应的所有用户ID 459 | """ 460 | return self.__response_users 461 | 462 | def getChatCreator(self, chat_id): 463 | """ 464 | 获取群组创建者信息 465 | """ 466 | if str(chat_id)[0] == "-": 467 | req = self.getChatAdministrators(str(chat_id)) 468 | if req: 469 | creator = [] 470 | for i, user in enumerate(req): 471 | if user["status"] == "creator": 472 | creator.append(req[i]) 473 | if len(creator) == 1: 474 | return creator[0] 475 | else: 476 | return False 477 | else: 478 | return False 479 | 480 | def getFileDownloadPath(self, file_id): 481 | """ 482 | 生成文件下载链接 483 | 注意:下载链接包含Bot Key 484 | """ 485 | req = self.getFile(file_id=file_id) 486 | if req: 487 | file_path = req["file_path"] 488 | if (self._local_api_server != "False" and 489 | "telegram.org" not in self._basic_url): 490 | return file_path 491 | else: 492 | file_download_path = self._basic_url + "file/bot" + self._key + r"/" + file_path 493 | return file_download_path 494 | else: 495 | return False 496 | 497 | # Getting updates 498 | def getUpdates(self, limit=100, allowed_updates=None): 499 | """ 500 | 获取消息队列 501 | """ 502 | command = inspect.stack()[0].function 503 | addr = command + "?offset=" + str(self._offset) + \ 504 | "&limit=" + str(limit) + "&timeout=" + str(self._timeout) 505 | 506 | if allowed_updates is not None: 507 | return self.request.postJson(addr, allowed_updates) 508 | else: 509 | return self.request.get(addr) 510 | 511 | def setWebhook(self, url, certificate=None, ip_address=None, 512 | max_connections=None, allowed_updates=None, drop_pending_updates=None): 513 | """ 514 | 设置Webhook 515 | Ports currently supported for Webhooks: 443, 80, 88, 8443. 516 | """ 517 | command = inspect.stack()[0].function 518 | addr = command + "?url=" + str(url) 519 | if ip_address is not None: 520 | addr += "&ip_address=" + str(ip_address) 521 | if max_connections is not None: 522 | addr += "&max_connections=" + str(max_connections) 523 | if allowed_updates is not None: 524 | addr += "&allowed_updates=" + str(allowed_updates) 525 | if drop_pending_updates is not None: 526 | addr += "&drop_pending_updates=" + str(drop_pending_updates) 527 | 528 | file_data = None 529 | if certificate is not None: 530 | if type(certificate) == bytes: 531 | file_data = {"certificate": certificate} 532 | else: 533 | file_data = {"certificate": open(certificate, 'rb')} 534 | 535 | if file_data is None: 536 | return self.request.post(addr) 537 | else: 538 | return self.request.postFile(addr, file_data) 539 | 540 | def deleteWebhook(self, drop_pending_updates=None): 541 | """ 542 | 删除设置的Webhook 543 | """ 544 | command = inspect.stack()[0].function 545 | addr = command 546 | if drop_pending_updates is not None: 547 | addr += "?drop_pending_updates=" + str(drop_pending_updates) 548 | return self.request.post(addr) 549 | 550 | def getWebhookInfo(self): 551 | """ 552 | 获取当前的Webhook状态 553 | """ 554 | command = inspect.stack()[0].function 555 | addr = command 556 | return self.request.post(addr) 557 | 558 | # Available methods 559 | 560 | def getMe(self): 561 | """ 562 | 获取机器人基本信息 563 | """ 564 | command = inspect.stack()[0].function 565 | addr = command + "?" + "offset=" + \ 566 | str(self._offset) + "&timeout=" + str(self._timeout) 567 | return self.request.post(addr) 568 | 569 | def getFile(self, file_id): 570 | """ 571 | 获取文件信息 572 | """ 573 | command = inspect.stack()[0].function 574 | addr = command + "?file_id=" + file_id 575 | return self.request.post(addr) 576 | 577 | def logOut(self): 578 | """ 579 | 在本地启动机器人之前,使用此方法从云Bot API服务器注销。 580 | """ 581 | command = inspect.stack()[0].function 582 | addr = command 583 | 584 | return self.request.post(addr) 585 | 586 | def close(self): 587 | """ 588 | 在将bot实例从一个本地服务器移动到另一个本地服务器之前 589 | 使用此方法关闭它 590 | """ 591 | command = inspect.stack()[0].function 592 | addr = command 593 | 594 | return self.request.post(addr) 595 | 596 | def sendMessage(self, chat_id, text, parse_mode="Text", reply_to_message_id=None, 597 | reply_markup=None, disable_web_page_preview=None, entities=None, 598 | allow_sending_without_reply=None): 599 | """ 600 | 发送文本消息 601 | """ 602 | command = inspect.stack()[0].function 603 | addr = command + "?chat_id=" + str(chat_id) + "&text=" + quote(text) 604 | if parse_mode in ("Markdown", "MarkdownV2", "HTML"): 605 | addr += "&parse_mode=" + parse_mode 606 | if reply_to_message_id is not None: 607 | addr += "&reply_to_message_id=" + str(reply_to_message_id) 608 | if reply_markup is not None: 609 | addr += "&reply_markup=" + json.dumps(reply_markup) 610 | if disable_web_page_preview is not None: 611 | addr += "&disable_web_page_preview=" + str(disable_web_page_preview) 612 | if entities is not None: 613 | addr += "&entities=" + json.dumps(entities) 614 | if allow_sending_without_reply is not None: 615 | addr += "&allow_sending_without_reply=" + str(allow_sending_without_reply) 616 | 617 | return self.request.post(addr) 618 | 619 | def sendVoice(self, chat_id, voice, caption=None, parse_mode="Text", reply_to_message_id=None, 620 | reply_markup=None, allow_sending_without_reply=None, caption_entities=None): 621 | """ 622 | 发送音频消息 .ogg 623 | """ 624 | command = inspect.stack()[0].function 625 | if voice[:7] == "http://" or voice[:7] == "https:/": 626 | file_data = None 627 | addr = command + "?chat_id=" + str(chat_id) + "&voice=" + voice 628 | elif type(voice) == bytes: 629 | file_data = {"voice": voice} 630 | addr = command + "?chat_id=" + str(chat_id) 631 | elif type(voice) == str and '.' not in voice: 632 | file_data = None 633 | addr = command + "?chat_id=" + str(chat_id) + "&voice=" + voice 634 | else: 635 | file_data = {"voice": open(voice, 'rb')} 636 | addr = command + "?chat_id=" + str(chat_id) 637 | 638 | if caption is not None: 639 | addr += "&caption=" + quote(caption) 640 | if parse_mode in ("Markdown", "MarkdownV2", "HTML"): 641 | addr += "&parse_mode" + parse_mode 642 | if reply_to_message_id is not None: 643 | addr += "&reply_to_message_id=" + str(reply_to_message_id) 644 | if reply_markup is not None: 645 | addr += "&reply_markup=" + json.dumps(reply_markup) 646 | if allow_sending_without_reply is not None: 647 | addr += "&allow_sending_without_reply=" + str(allow_sending_without_reply) 648 | if caption_entities is not None: 649 | addr += "&caption_entities=" + json.dumps(caption_entities) 650 | 651 | if file_data is None: 652 | return self.request.post(addr) 653 | else: 654 | return self.request.postFile(addr, file_data) 655 | 656 | def sendAnimation(self, chat_id, animation, caption=None, parse_mode="Text", reply_to_message_id=None, 657 | reply_markup=None, allow_sending_without_reply=None, caption_entities=None): 658 | """ 659 | 发送动画 gif/mp4 660 | """ 661 | command = inspect.stack()[0].function 662 | if animation[:7] == "http://" or animation[:7] == "https:/": 663 | file_data = None 664 | addr = command + "?chat_id=" + str(chat_id) + "&animation=" + animation 665 | elif type(animation) == bytes: 666 | file_data = {"animation": animation} 667 | addr = command + "?chat_id=" + str(chat_id) 668 | elif type(animation) == str and '.' not in animation: 669 | file_data = None 670 | addr = command + "?chat_id=" + str(chat_id) + "&animation=" + animation 671 | else: 672 | file_data = {"animation": open(animation, 'rb')} 673 | addr = command + "?chat_id=" + str(chat_id) 674 | 675 | if caption is not None: 676 | addr += "&caption=" + quote(caption) 677 | if parse_mode in ("Markdown", "MarkdownV2", "HTML"): 678 | addr += "&parse_mode" + parse_mode 679 | if reply_to_message_id is not None: 680 | addr += "&reply_to_message_id=" + str(reply_to_message_id) 681 | if reply_markup is not None: 682 | addr += "&reply_markup=" + json.dumps(reply_markup) 683 | if allow_sending_without_reply is not None: 684 | addr += "&allow_sending_without_reply=" + str(allow_sending_without_reply) 685 | if caption_entities is not None: 686 | addr += "&caption_entities=" + json.dumps(caption_entities) 687 | 688 | if file_data is None: 689 | return self.request.post(addr) 690 | else: 691 | self.request.postFile(addr, file_data) 692 | 693 | def sendAudio(self, chat_id, audio, caption=None, parse_mode="Text", title=None, reply_to_message_id=None, 694 | reply_markup=None, allow_sending_without_reply=None, caption_entities=None): 695 | """ 696 | 发送音频 mp3 697 | """ 698 | command = inspect.stack()[0].function 699 | if audio[:7] == "http://" or audio[:7] == "https:/": 700 | file_data = None 701 | addr = command + "?chat_id=" + str(chat_id) + "&audio=" + audio 702 | elif type(audio) == bytes: 703 | file_data = {"audio": audio} 704 | addr = command + "?chat_id=" + str(chat_id) 705 | elif type(audio) == str and '.' not in audio: 706 | file_data = None 707 | addr = command + "?chat_id=" + str(chat_id) + "&audio=" + audio 708 | else: 709 | file_data = {"audio": open(audio, 'rb')} 710 | addr = command + "?chat_id=" + str(chat_id) 711 | 712 | if caption is not None: 713 | addr += "&caption=" + quote(caption) 714 | if parse_mode in ("Markdown", "MarkdownV2", "HTML"): 715 | addr += "&parse_mode" + parse_mode 716 | if title is not None: 717 | addr += "&title=" + title 718 | if reply_to_message_id is not None: 719 | addr += "&reply_to_message_id=" + str(reply_to_message_id) 720 | if reply_markup is not None: 721 | addr += "&reply_markup=" + json.dumps(reply_markup) 722 | if allow_sending_without_reply is not None: 723 | addr += "&allow_sending_without_reply=" + str(allow_sending_without_reply) 724 | if caption_entities is not None: 725 | addr += "&caption_entities=" + json.dumps(caption_entities) 726 | 727 | if file_data is None: 728 | return self.request.post(addr) 729 | else: 730 | return self.request.postFile(addr, file_data) 731 | 732 | def sendPhoto(self, chat_id, photo, caption=None, parse_mode="Text", reply_to_message_id=None, 733 | reply_markup=None, allow_sending_without_reply=None, caption_entities=None): # 发送图片 734 | """ 735 | 发送图片 736 | """ 737 | command = inspect.stack()[0].function 738 | if photo[:7] == "http://" or photo[:7] == "https:/": 739 | file_data = None 740 | addr = command + "?chat_id=" + str(chat_id) + "&photo=" + photo 741 | elif type(photo) == bytes: 742 | file_data = {"photo": photo} 743 | addr = command + "?chat_id=" + str(chat_id) 744 | elif type(photo) == str and '.' not in photo: 745 | file_data = None 746 | addr = command + "?chat_id=" + str(chat_id) + "&photo=" + photo 747 | else: 748 | file_data = {"photo": open(photo, 'rb')} 749 | addr = command + "?chat_id=" + str(chat_id) 750 | 751 | if caption is not None: 752 | addr += "&caption=" + quote(caption) 753 | if parse_mode in ("Markdown", "MarkdownV2", "HTML"): 754 | addr += "&parse_mode=" + parse_mode 755 | if reply_to_message_id is not None: 756 | addr += "&reply_to_message_id=" + str(reply_to_message_id) 757 | if reply_markup is not None: 758 | addr += "&reply_markup=" + json.dumps(reply_markup) 759 | if allow_sending_without_reply is not None: 760 | addr += "&allow_sending_without_reply=" + str(allow_sending_without_reply) 761 | if caption_entities is not None: 762 | addr += "&caption_entities=" + json.dumps(caption_entities) 763 | 764 | if file_data is None: 765 | return self.request.post(addr) 766 | else: 767 | return self.request.postFile(addr, file_data) 768 | 769 | def sendVideo(self, chat_id, video, caption=None, parse_mode="Text", reply_to_message_id=None, 770 | reply_markup=None, allow_sending_without_reply=None, caption_entities=None): 771 | """ 772 | 发送视频 773 | """ 774 | command = inspect.stack()[0].function 775 | if video[:7] == "http://" or video[:7] == "https:/": 776 | file_data = None 777 | addr = command + "?chat_id=" + str(chat_id) + "&video=" + video 778 | elif type(video) == bytes: 779 | file_data = {"video": video} 780 | addr = command + "?chat_id=" + str(chat_id) 781 | elif type(video) == str and '.' not in video: 782 | file_data = None 783 | addr = command + "?chat_id=" + str(chat_id) + "&video=" + video 784 | else: 785 | file_data = {"video": open(video, 'rb')} 786 | addr = command + "?chat_id=" + str(chat_id) 787 | 788 | if caption is not None: 789 | addr += "&caption=" + quote(caption) 790 | if parse_mode in ("Markdown", "MarkdownV2", "HTML"): 791 | addr += "&parse_mode=" + parse_mode 792 | if reply_to_message_id is not None: 793 | addr += "&reply_to_message_id=" + str(reply_to_message_id) 794 | if reply_markup is not None: 795 | addr += "&reply_markup=" + json.dumps(reply_markup) 796 | if allow_sending_without_reply is not None: 797 | addr += "&allow_sending_without_reply=" + str(allow_sending_without_reply) 798 | if caption_entities is not None: 799 | addr += "&caption_entities=" + json.dumps(caption_entities) 800 | 801 | if file_data is None: 802 | return self.request.post(addr) 803 | else: 804 | return self.request.postFile(addr, file_data) 805 | 806 | def sendVideoNote(self, chat_id, video_note, caption=None, parse_mode="Text", reply_to_message_id=None, 807 | reply_markup=None, allow_sending_without_reply=None): 808 | """ 809 | 发送圆形或方形视频? 810 | """ 811 | command = inspect.stack()[0].function 812 | char_id_str = str(chat_id) 813 | if video_note[:7] == "http://" or video_note[:7] == "https:/": 814 | file_data = None 815 | addr = command + "?chat_id=" + char_id_str + "&video_note=" + video_note 816 | elif type(video_note) == bytes: 817 | file_data = {"video_note": video_note} 818 | addr = command + "?chat_id=" + char_id_str 819 | elif type(video_note) == str and '.' not in video_note: 820 | file_data = None 821 | addr = command + "?chat_id=" + char_id_str + "&video_note=" + video_note 822 | else: 823 | file_data = {"video_note": open(video_note, 'rb')} 824 | addr = command + "?chat_id=" + char_id_str 825 | 826 | if caption is not None: 827 | addr += "&caption=" + quote(caption) 828 | if parse_mode in ("Markdown", "MarkdownV2", "HTML"): 829 | addr += "&parse_mode=" + parse_mode 830 | if reply_to_message_id is not None: 831 | addr += "&reply_to_message_id=" + str(reply_to_message_id) 832 | if reply_markup is not None: 833 | addr += "&reply_markup=" + json.dumps(reply_markup) 834 | if allow_sending_without_reply is not None: 835 | addr += "&allow_sending_without_reply=" + str(allow_sending_without_reply) 836 | 837 | if file_data is None: 838 | return self.request.post(addr) 839 | else: 840 | return self.request.postFile(addr, file_data) 841 | 842 | def sendMediaGroup(self, chat_id, medias, disable_notification=None, reply_to_message_id=None, 843 | reply_markup=None, allow_sending_without_reply=None): # 暂未弄懂格式。 844 | """ 845 | 使用此方法可以将一组照片,视频,文档或音频作为相册发送。 846 | 文档和音频文件只能在具有相同类型消息的相册中分组。 847 | (目前只支持http链接和文件id,暂不支持上传文件) 848 | media的格式:(同时请求需要加入header头,指定传送参数为json类型, 849 | 并且将data由字典转为json字符串传送) 850 | medias ={ 851 | 'caption': 'test', 852 | 'media': [ 853 | { 854 | 'type': 'photo', 855 | 'media': 'https://xxxx.com/sample/7kwx_2.jpg' 856 | }, 857 | { 858 | 'type': 'photo', 859 | 'media': 'AgACAgQAAx0ETbyLwwADeF5s6QosSI_IW3rKir3PrMUX' 860 | } 861 | ] 862 | } 863 | InputMediaPhoto: 864 | type 865 | media 866 | caption 867 | parse_mode 868 | 869 | InputMediaVideo: 870 | type 871 | media 872 | thumb 873 | caption 874 | parse_mode 875 | width 876 | height 877 | duration 878 | supports_streaming 879 | """ 880 | command = inspect.stack()[0].function 881 | addr = command + "?chat_id=" + str(chat_id) 882 | if disable_notification is not None: 883 | addr += "&disable_notification=" + str(disable_notification) 884 | if reply_to_message_id is not None: 885 | addr += "&reply_to_message_id=" + str(reply_to_message_id) 886 | if reply_markup is not None: 887 | addr += "&reply_markup=" + json.dumps(reply_markup) 888 | if allow_sending_without_reply is not None: 889 | addr += "&allow_sending_without_reply=" + str(allow_sending_without_reply) 890 | 891 | return self.request.postJson(addr, medias) 892 | 893 | def sendDocument(self, chat_id, document, caption=None, parse_mode="Text", 894 | reply_to_message_id=None, reply_markup=None, disable_content_type_detection=None, 895 | allow_sending_without_reply=None, caption_entities=None): 896 | """ 897 | 发送文件 898 | """ 899 | command = inspect.stack()[0].function 900 | if document[:7] == "http://" or document[:7] == "https:/": 901 | file_data = None 902 | addr = command + "?chat_id=" + str(chat_id) + "&document=" + document 903 | elif type(document) == bytes: 904 | file_data = {"document": document} 905 | addr = command + "?chat_id=" + str(chat_id) 906 | elif type(document) == str and '.' not in document: 907 | file_data = None 908 | addr = command + "?chat_id=" + str(chat_id) + "&document=" + document 909 | else: 910 | file_data = {"document": open(document, 'rb')} 911 | addr = command + "?chat_id=" + str(chat_id) 912 | 913 | if caption is not None: 914 | addr += "&caption=" + quote(caption) 915 | if parse_mode in ("Markdown", "MarkdownV2", "HTML"): 916 | addr += "&parse_mode=" + parse_mode 917 | if reply_to_message_id is not None: 918 | addr += "&reply_to_message_id=" + str(reply_to_message_id) 919 | if reply_markup is not None: 920 | addr += "&reply_markup=" + json.dumps(reply_markup) 921 | if disable_content_type_detection is not None: 922 | addr += "&disable_content_type_detection=" + str(disable_content_type_detection) 923 | if allow_sending_without_reply is not None: 924 | addr += "&allow_sending_without_reply=" + str(allow_sending_without_reply) 925 | if caption_entities is not None: 926 | addr += "&caption_entities=" + json.dumps(caption_entities) 927 | 928 | if file_data is None: 929 | return self.request.post(addr) 930 | else: 931 | return self.request.postFile(addr, file_data) 932 | 933 | def leaveChat(self, chat_id): 934 | """ 935 | 退出群组 936 | """ 937 | command = inspect.stack()[0].function 938 | addr = command + "?chat_id=" + str(chat_id) 939 | return self.request.post(addr) 940 | 941 | def getChat(self, chat_id): 942 | """ 943 | 使用此方法可获取有关聊天的最新信息(一对一对话的用户的当前名称, 944 | 用户的当前用户名,组或频道等)。 945 | 成功返回一个Chat对象。 946 | """ 947 | command = inspect.stack()[0].function 948 | addr = command + "?chat_id=" + str(chat_id) 949 | return self.request.post(addr) 950 | 951 | def getChatAdministrators(self, chat_id): 952 | """ 953 | 获取群组所有管理员信息 954 | """ 955 | command = inspect.stack()[0].function 956 | addr = command + "?chat_id=" + str(chat_id) 957 | return self.request.post(addr) 958 | 959 | def getChatMembersCount(self, chat_id): 960 | """ 961 | 获取群组成员总数 962 | """ 963 | command = inspect.stack()[0].function 964 | addr = command + "?chat_id=" + str(chat_id) 965 | return self.request.post(addr) 966 | 967 | def getUserProfilePhotos(self, user_id, offset=None, limit=None): 968 | """ 969 | 获取用户头像 970 | """ 971 | command = inspect.stack()[0].function 972 | addr = command + "?user_id=" + str(user_id) 973 | 974 | if offset is not None: 975 | addr += "&offset=" + str(offset) 976 | if limit is not None and limit in list(range(1, 101)): 977 | addr += "&limit=" + str(limit) 978 | return self.request.post(addr) 979 | 980 | def getChatMember(self, chat_id, user_id): 981 | """ 982 | 获取群组特定用户信息 983 | """ 984 | command = inspect.stack()[0].function 985 | addr = command + "?chat_id=" + str(chat_id) + "&user_id=" + str(user_id) 986 | return self.request.post(addr) 987 | 988 | def setChatTitle(self, chat_id, title): 989 | """ 990 | 设置群组标题 991 | """ 992 | command = inspect.stack()[0].function 993 | addr = command + "?chat_id=" + str(chat_id) + "&title=" + quote(str(title)) 994 | return self.request.post(addr) 995 | 996 | def setChatDescription(self, chat_id, description): 997 | """ 998 | 设置群组简介(测试好像无效。。) 999 | //FIXME 1000 | """ 1001 | command = inspect.stack()[0].function 1002 | addr = command + "?chat_id=" + str(chat_id) + "&description=" + quote(str(description)) 1003 | return self.request.post(addr) 1004 | 1005 | def setChatPhoto(self, chat_id, photo): 1006 | """ 1007 | 设置群组头像 1008 | """ 1009 | command = inspect.stack()[0].function 1010 | file_data = {"photo": open(photo, 'rb')} 1011 | addr = command + "?chat_id=" + str(chat_id) 1012 | 1013 | return self.request.postFile(addr, file_data) 1014 | 1015 | def deleteChatPhoto(self, chat_id): 1016 | """ 1017 | 删除群组头像 1018 | """ 1019 | command = inspect.stack()[0].function 1020 | addr = command + "?chat_id=" + str(chat_id) 1021 | return self.request.post(addr) 1022 | 1023 | def setChatPermissions(self, chat_id, permissions): 1024 | """ 1025 | 设置群组默认聊天权限 1026 | permissions = { 1027 | 'can_send_messages':False, 1028 | 'can_send_media_messages':False, 1029 | 'can_send_polls':False, 1030 | 'can_send_other_messages':False, 1031 | 'can_add_web_page_previews':False, 1032 | 'can_change_info':False, 1033 | 'can_invite_users':False, 1034 | 'can_pin_messages':False 1035 | } 1036 | """ 1037 | command = inspect.stack()[0].function 1038 | addr = command + "?chat_id=" + str(chat_id) 1039 | permissions = {"permissions": permissions} 1040 | 1041 | return self.request.postJson(addr, permissions) 1042 | 1043 | def restrictChatMember(self, chat_id, user_id, permissions, until_date=None): 1044 | """ 1045 | 限制群组用户权限 1046 | permissions = { 1047 | 'can_send_messages':False, 1048 | 'can_send_media_messages':False, 1049 | 'can_send_polls':False, 1050 | 'can_send_other_messages':False, 1051 | 'can_add_web_page_previews':False, 1052 | 'can_change_info':False, 1053 | 'can_invite_users':False, 1054 | 'can_pin_messages':False 1055 | } 1056 | until_date format: 1057 | timestamp + offset 1058 | """ 1059 | command = inspect.stack()[0].function 1060 | addr = command + "?chat_id=" + \ 1061 | str(chat_id) + "&user_id=" + str(user_id) 1062 | if len(permissions) != 8: 1063 | return False 1064 | if until_date is not None: 1065 | until_date = int(time.time()) + int(until_date) 1066 | addr += "&until_date=" + str(until_date) 1067 | 1068 | return self.request.postJson(addr, permissions) 1069 | 1070 | def promoteChatMember(self, chat_id, user_id, is_anonymous=None, 1071 | can_change_info=None, can_post_messages=None, can_edit_messages=None, 1072 | can_delete_messages=None, can_invite_users=None, can_restrict_members=None, 1073 | can_pin_messages=None, can_promote_members=None): 1074 | """ 1075 | 修改管理员权限(只能修改由机器人任命的管理员的权限, 1076 | 范围为机器人权限的子集) 1077 | { 1078 | 'is_anonymous':None, 1079 | 'can_change_info':False, 1080 | 'can_post_messages':False, 1081 | 'can_edit_messages':False, 1082 | 'can_delete_messages':False, 1083 | 'can_invite_users':False, 1084 | 'can_restrict_members':False, 1085 | 'can_pin_messages':False, 1086 | 'can_promote_members':False 1087 | } 1088 | """ 1089 | command = inspect.stack()[0].function 1090 | 1091 | addr = command + "?chat_id=" + str(chat_id) + "&user_id=" + str(user_id) 1092 | 1093 | if is_anonymous is not None: 1094 | addr += "&is_anonymous=" + str(is_anonymous) 1095 | if can_change_info is not None: 1096 | addr += "&can_change_info=" + str(can_change_info) 1097 | if can_post_messages is not None: 1098 | addr += "&can_post_messages=" + str(can_post_messages) 1099 | if can_edit_messages is not None: 1100 | addr += "&can_edit_messages=" + str(can_edit_messages) 1101 | if can_delete_messages is not None: 1102 | addr += "&can_delete_messages=" + str(can_delete_messages) 1103 | if can_invite_users is not None: 1104 | addr += "&can_invite_users=" + str(can_invite_users) 1105 | if can_restrict_members is not None: 1106 | addr += "&can_restrict_members=" + str(can_restrict_members) 1107 | if can_pin_messages is not None: 1108 | addr += "&can_pin_messages=" + str(can_pin_messages) 1109 | if can_promote_members is not None: 1110 | addr += "&can_promote_members=" + str(can_promote_members) 1111 | 1112 | return self.request.post(addr) 1113 | 1114 | def pinChatMessage(self, chat_id, message_id, disable_notification=None): 1115 | """ 1116 | 置顶消息 1117 | """ 1118 | command = inspect.stack()[0].function 1119 | addr = command + "?chat_id=" + str(chat_id) + "&message_id=" + str(message_id) 1120 | if disable_notification is not None: 1121 | addr += "&disable_notification=" + str(disable_notification) 1122 | 1123 | return self.request.post(addr) 1124 | 1125 | def unpinChatMessage(self, chat_id, message_id=None): 1126 | """ 1127 | 使用此方法可以从聊天中的置顶消息列表中删除消息 1128 | """ 1129 | command = inspect.stack()[0].function 1130 | addr = command + "?chat_id=" + str(chat_id) 1131 | 1132 | if message_id is not None: 1133 | addr += "&message_id=" + str(message_id) 1134 | 1135 | return self.request.post(addr) 1136 | 1137 | def unpinAllChatMessages(self, chat_id): 1138 | """ 1139 | 使用此方法可以清除聊天中的置顶消息列表中的所有置顶消息 1140 | """ 1141 | command = inspect.stack()[0].function 1142 | addr = command + "?chat_id=" + str(chat_id) 1143 | 1144 | return self.request.post(addr) 1145 | 1146 | def sendLocation(self, chat_id, latitude, longitude, 1147 | horizontal_accuracy=None, live_period=None, 1148 | heading=None, disable_notification=None, 1149 | reply_to_message_id=None, reply_markup=None, 1150 | allow_sending_without_reply=None): 1151 | """ 1152 | 发送地图定位,经纬度 1153 | """ 1154 | command = inspect.stack()[0].function 1155 | addr = command + "?chat_id=" + str(chat_id) + "&latitude=" + str( 1156 | float(latitude)) + "&longitude=" + str(float(longitude)) 1157 | if live_period is not None: 1158 | addr += "&live_period=" + str(live_period) 1159 | if horizontal_accuracy is not None: 1160 | addr += "&horizontal_accuracy=" + str(horizontal_accuracy) 1161 | if heading is not None: 1162 | addr += "&heading=" + str(heading) 1163 | if disable_notification is not None: 1164 | addr += "&disable_notification=" + str(disable_notification) 1165 | if reply_to_message_id is not None: 1166 | addr += "&reply_to_message_id=" + str(reply_to_message_id) 1167 | if reply_markup is not None: 1168 | addr += "&reply_markup=" + json.dumps(reply_markup) 1169 | if allow_sending_without_reply is not None: 1170 | addr += "&allow_sending_without_reply=" + str(allow_sending_without_reply) 1171 | 1172 | return self.request.post(addr) 1173 | 1174 | def sendContact(self, chat_id, phone_number, first_name, last_name=None, reply_to_message_id=None, 1175 | reply_markup=None, allow_sending_without_reply=None): 1176 | """ 1177 | 发送联系人信息 1178 | """ 1179 | command = inspect.stack()[0].function 1180 | addr = command + "?chat_id=" + str(chat_id) + "&phone_number=" + str(phone_number) + "&first_name=" + str( 1181 | first_name) 1182 | if last_name is not None: 1183 | addr += "&last_name=" + str(last_name) 1184 | if reply_to_message_id is not None: 1185 | addr += "&reply_to_message_id=" + str(reply_to_message_id) 1186 | if reply_markup is not None: 1187 | addr += "&reply_markup=" + json.dumps(reply_markup) 1188 | if allow_sending_without_reply is not None: 1189 | addr += "&allow_sending_without_reply=" + str(allow_sending_without_reply) 1190 | 1191 | return self.request.post(addr) 1192 | 1193 | def sendPoll(self, chat_id, question, options, is_anonymous=None, 1194 | type_=None, allows_multiple_answers=None, correct_option_id=None, 1195 | explanation=None, explanation_parse_mode=None, explanation_entities=None, 1196 | open_period=None, close_date=None, is_closed=None, disable_notification=None, 1197 | reply_to_message_id=None, allow_sending_without_reply=None, reply_markup=None): 1198 | """ 1199 | 使用此方法发起投票(quiz or regular, defaults to regular) 1200 | options格式: 1201 | options = [ 1202 | "option 1", 1203 | "option 2" 1204 | ] 1205 | """ 1206 | command = inspect.stack()[0].function 1207 | addr = command + "?chat_id=" + str(chat_id) + "&question=" + str(question) 1208 | addr += "&options=" + json.dumps(options) 1209 | 1210 | if is_anonymous is not None: 1211 | addr += "&is_anonymous=" + str(is_anonymous) 1212 | if type_ is not None: 1213 | addr += "&type=" + str(type_) 1214 | 1215 | if type_ == "quiz": 1216 | if allows_multiple_answers is not None: 1217 | addr += "&allows_multiple_answers=" + str(allows_multiple_answers) 1218 | if correct_option_id is not None: 1219 | addr += "&correct_option_id=" + str(correct_option_id) 1220 | if explanation is not None: 1221 | addr += "&explanation=" + str(explanation) 1222 | if explanation_parse_mode is not None: 1223 | addr += "&explanation_parse_mode=" + str(explanation_parse_mode) 1224 | if explanation_entities is not None: 1225 | addr += "&explanation_entities=" + json.dumps(explanation_entities) 1226 | 1227 | if open_period is not None: 1228 | addr += "&open_period=" + str(open_period) 1229 | if close_date is not None: 1230 | addr += "&close_date=" + str(close_date) 1231 | if is_closed is not None: 1232 | addr += "&is_closed=" + str(is_closed) 1233 | if disable_notification is not None: 1234 | addr += "&disable_notification=" + str(disable_notification) 1235 | if reply_to_message_id is not None: 1236 | addr += "&reply_to_message_id=" + str(reply_to_message_id) 1237 | if allow_sending_without_reply is not None: 1238 | addr += "&allow_sending_without_reply=" + str(allow_sending_without_reply) 1239 | if reply_markup is not None: 1240 | addr += "&reply_markup=" + json.dumps(reply_markup) 1241 | 1242 | return self.request.post(addr) 1243 | 1244 | def sendDice(self, chat_id, emoji, disable_notification=None, 1245 | reply_to_message_id=None, allow_sending_without_reply=None, 1246 | reply_markup=None): 1247 | """ 1248 | 使用此方法发送一个动画表情 1249 | emoji参数必须是以下几种: 1250 | 1.dice(骰子) values 1-6 1251 | 2.darts(飞镖) values 1-6 1252 | 3.basketball(篮球) values 1-5 1253 | 4.football(足球) values 1-5 1254 | 5.slot machine(老虎机) values 1-64 1255 | 默认为骰子 1256 | """ 1257 | command = inspect.stack()[0].function 1258 | addr = command + "?chat_id=" + str(chat_id) + "&emoji=" + str(emoji) 1259 | 1260 | if disable_notification is not None: 1261 | addr += "&disable_notification=" + str(disable_notification) 1262 | if reply_to_message_id is not None: 1263 | addr += "&reply_to_message_id=" + str(reply_to_message_id) 1264 | if allow_sending_without_reply is not None: 1265 | addr += "&allow_sending_without_reply=" + str(allow_sending_without_reply) 1266 | if reply_markup is not None: 1267 | addr += "&reply_markup=" + json.dumps(reply_markup) 1268 | 1269 | return self.request.post(addr) 1270 | 1271 | def sendVenue(self, chat_id, latitude, longitude, title, address, 1272 | allow_sending_without_reply=None, 1273 | foursquare_id=None, foursquare_type=None, 1274 | google_place_id=None, google_place_type=None, 1275 | disable_notification=None, reply_to_message_id=None, 1276 | reply_markup=None): 1277 | """ 1278 | 使用此方法发送关于地点的信息。 1279 | (发送地点,显示在地图上) 1280 | """ 1281 | command = inspect.stack()[0].function 1282 | addr = command + "?chat_id=" + str(chat_id) + "&latitude=" + str(float(latitude)) + "&longitude=" + str( 1283 | float(longitude)) + "&title=" + str(title) + "&address=" + str(address) 1284 | if allow_sending_without_reply is not None: 1285 | addr += "&allow_sending_without_reply=" + str(allow_sending_without_reply) 1286 | if foursquare_id is not None: 1287 | addr += "&foursquare_id=" + str(foursquare_id) 1288 | if foursquare_type is not None: 1289 | addr += "&foursquare_type=" + str(foursquare_type) 1290 | if google_place_id is not None: 1291 | addr += "&google_place_id=" + str(google_place_id) 1292 | if google_place_type is not None: 1293 | addr += "&google_place_type=" + str(google_place_type) 1294 | if disable_notification is not None: 1295 | addr += "&disable_notification=" + str(disable_notification) 1296 | if reply_to_message_id is not None: 1297 | addr += "&reply_to_message_id=" + str(reply_to_message_id) 1298 | if reply_markup is not None: 1299 | addr += "&reply_markup=" + json.dumps(reply_markup) 1300 | 1301 | 1302 | return self.request.post(addr) 1303 | 1304 | def sendChatAction(self, chat_id, action): 1305 | """ 1306 | 发送聊天状态,类似: 正在输入... 1307 | typing :for text messages, 1308 | upload_photo :for photos, 1309 | record_video/upload_video :for videos, 1310 | record_audio/upload_audio :for audio files, 1311 | upload_document :for general files, 1312 | find_location :for location data, 1313 | record_video_note/upload_video_note :for video notes. 1314 | """ 1315 | command = inspect.stack()[0].function 1316 | addr = command + "?chat_id=" + str(chat_id) + "&action=" + str(action) 1317 | return self.request.post(addr) 1318 | 1319 | def forwardMessage(self, chat_id, from_chat_id, message_id, disable_notification=None): 1320 | """ 1321 | 转发消息 1322 | """ 1323 | command = inspect.stack()[0].function 1324 | addr = command + "?chat_id=" + str(chat_id) + "&from_chat_id=" + str(from_chat_id) \ 1325 | + "&message_id=" + str(message_id) 1326 | 1327 | if disable_notification is not None: 1328 | addr += "&disable_notification=" + str(disable_notification) 1329 | 1330 | return self.request.post(addr) 1331 | 1332 | def copyMessage(self, chat_id, from_chat_id, message_id, 1333 | caption=None, parse_mode="Text", caption_entities=None, 1334 | disable_notification=None, reply_to_message_id=None, 1335 | allow_sending_without_reply=None, reply_markup=None): 1336 | """ 1337 | 使用此方法可以复制任何类型的消息。 1338 | 该方法类似于forwardMessages方法, 1339 | 但是复制的消息没有指向原始消息的链接。 1340 | """ 1341 | command = inspect.stack()[0].function 1342 | addr = command + "?chat_id=" + str(chat_id) + "&from_chat_id=" + str(from_chat_id) \ 1343 | + "&message_id=" + str(message_id) 1344 | 1345 | if caption is not None: 1346 | addr += "&caption=" + quote(caption) 1347 | if parse_mode in ("Markdown", "MarkdownV2", "HTML"): 1348 | addr += "&parse_mode" + parse_mode 1349 | if disable_notification is not None: 1350 | addr += "&disable_notification=" + str(disable_notification) 1351 | if reply_to_message_id is not None: 1352 | addr += "&reply_to_message_id=" + str(reply_to_message_id) 1353 | if allow_sending_without_reply is not None: 1354 | addr += "&allow_sending_without_reply=" + str(allow_sending_without_reply) 1355 | if reply_markup is not None: 1356 | addr += "&reply_markup=" + json.dumps(reply_markup) 1357 | 1358 | if caption_entities is not None: 1359 | return self.request.postJson(addr, caption_entities) 1360 | else: 1361 | return self.request.post(addr) 1362 | 1363 | 1364 | def kickChatMember(self, chat_id, user_id, until_date=None): 1365 | """ 1366 | 从Group、Supergroup或者Channel中踢人,被踢者在until_date期限内不可再次加入 1367 | until_date format: 1368 | timestamp + offset 1369 | """ 1370 | 1371 | command = inspect.stack()[0].function 1372 | if until_date is not None: 1373 | until_date = int(time.time()) + int(until_date) 1374 | addr = command + "?chat_id=" + str(chat_id) + "&user_id=" + str(user_id) + "&until_date=" + str(until_date) 1375 | if until_date is None: 1376 | addr = command + "?chat_id=" + \ 1377 | str(chat_id) + "&user_id=" + str(user_id) 1378 | 1379 | return self.request.post(addr) 1380 | 1381 | def unbanChatMember(self, chat_id, user_id, only_if_banned=None): 1382 | """ 1383 | 使用此方法可以取消超级组或频道中以前被踢过的用户的权限。 1384 | (解除user被设置的until_date) 1385 | ChatPermissions: 1386 | can_send_messages 1387 | can_send_media_messages 1388 | can_send_polls 1389 | can_send_other_messages 1390 | can_add_web_page_previews 1391 | can_change_info 1392 | can_invite_users 1393 | can_pin_messages 1394 | """ 1395 | 1396 | command = inspect.stack()[0].function 1397 | addr = command + "?chat_id=" + \ 1398 | str(chat_id) + "&user_id=" + str(user_id) 1399 | 1400 | if only_if_banned is not None: 1401 | addr += "&only_if_banned=" + str(only_if_banned) 1402 | 1403 | return self.request.post(addr) 1404 | 1405 | def setChatAdministratorCustomTitle(self, chat_id, user_id, custom_title): 1406 | """ 1407 | 为群组的管理员设置自定义头衔 1408 | """ 1409 | command = inspect.stack()[0].function 1410 | addr = command + "?chat_id=" + str(chat_id) + "&user_id=" + str(user_id) + "&custom_title=" + quote(str(custom_title)) 1411 | 1412 | return self.request.post(addr) 1413 | 1414 | def exportChatInviteLink(self, chat_id): 1415 | """ 1416 | 使用此方法生成新的群组分享链接,旧有分享链接全部失效,成功返回分享链接 1417 | """ 1418 | command = inspect.stack()[0].function 1419 | addr = command + "?chat_id=" + str(chat_id) 1420 | 1421 | return self.request.post(addr) 1422 | 1423 | def setChatStickerSet(self, chat_id, sticker_set_name): 1424 | """ 1425 | 为一个超级群组设置贴纸集 1426 | """ 1427 | command = inspect.stack()[0].function 1428 | addr = command + "?chat_id=" + str(chat_id) + "&sticker_set_name=" + str(sticker_set_name) 1429 | 1430 | return self.request.post(addr) 1431 | 1432 | def addStickerToSet(self, user_id, name, emojis, 1433 | png_sticker=None, tgs_sticker=None, mask_position=None): 1434 | """ 1435 | 使用此方法在机器人创建的集合中添加一个新贴纸。 1436 | 必须使用png标签或tgs标签中的一个字段。 1437 | 动画贴纸只能添加到动画贴纸组中。 1438 | 动画贴纸组最多可以有50个贴纸。 1439 | 静态贴纸组最多可以有120个贴纸。 1440 | """ 1441 | command = inspect.stack()[0].function 1442 | addr = command + "?user_id=" + str(user_id) + "&name=" + str(name) \ 1443 | + "&emoji=" + str(emoji) 1444 | 1445 | if png_sticker is not None and tgs_sticker is not None: 1446 | return False 1447 | elif png_sticker is None and tgs_sticker is None: 1448 | return False 1449 | else: 1450 | if png_sticker is not None: 1451 | if png_sticker[:7] == "http://" or png_sticker[:7] == "https:/": 1452 | file_data = None 1453 | addr = command + "?chat_id=" + str(chat_id) + "&png_sticker=" + png_sticker 1454 | elif type(png_sticker) == bytes: 1455 | file_data = {"png_sticker": png_sticker} 1456 | addr = command + "?chat_id=" + str(chat_id) 1457 | elif type(png_sticker) == str and '.' not in png_sticker: 1458 | file_data = None 1459 | addr = command + "?chat_id=" + str(chat_id) + "&png_sticker=" + png_sticker 1460 | else: 1461 | file_data = {"png_sticker": open(png_sticker, 'rb')} 1462 | addr = command + "?chat_id=" + str(chat_id) 1463 | elif tgs_sticker is not None: 1464 | if tgs_sticker[:7] == "http://" or tgs_sticker[:7] == "https:/": 1465 | file_data = None 1466 | addr = command + "?chat_id=" + str(chat_id) + "&tgs_sticker=" + tgs_sticker 1467 | elif type(png_sticker) == bytes: 1468 | file_data = {"tgs_sticker": tgs_sticker} 1469 | addr = command + "?chat_id=" + str(chat_id) 1470 | elif type(tgs_sticker) == str and '.' not in tgs_sticker: 1471 | file_data = None 1472 | addr = command + "?chat_id=" + str(chat_id) + "&tgs_sticker=" + tgs_sticker 1473 | else: 1474 | file_data = {"tgs_sticker": open(tgs_sticker, 'rb')} 1475 | addr = command + "?chat_id=" + str(chat_id) 1476 | 1477 | if file_data is None: 1478 | return self.request.post(addr) 1479 | else: 1480 | return self.request.postFile(addr, file_data) 1481 | 1482 | def deleteChatStickerSet(self, chat_id): 1483 | """ 1484 | 删除超级群组的贴纸集 1485 | """ 1486 | command = inspect.stack()[0].function 1487 | addr = command + "?chat_id=" + str(chat_id) 1488 | 1489 | return self.request.post(addr) 1490 | 1491 | def editMessageLiveLocation(self, latitude, longitude, 1492 | horizontal_accuracy=None, chat_id=None, message_id=None, 1493 | heading=None, inline_message_id=None, reply_markup=None): 1494 | """ 1495 | 使用此方法编辑实时位置消息 1496 | 在未指定inline_message_id的时候chat_id和message_id为必须存在的参数 1497 | """ 1498 | command = inspect.stack()[0].function 1499 | 1500 | if inline_message_id is None: 1501 | if message_id is None or chat_id is None: 1502 | return False 1503 | 1504 | if inline_message_id is not None: 1505 | addr = command + "?inline_message_id=" + str(inline_message_id) 1506 | else: 1507 | addr = command + "?chat_id=" + str(chat_id) 1508 | addr += "&message_id=" + str(message_id) 1509 | 1510 | addr += "&latitude=" + str(latitude) 1511 | addr += "&longitude=" + str(longitude) 1512 | 1513 | if horizontal_accuracy is not None: 1514 | addr += "&horizontal_accuracy=" + str(horizontal_accuracy) 1515 | if heading is not None: 1516 | addr += "&heading=" + str(heading) 1517 | if reply_markup is not None: 1518 | addr += "&reply_markup=" + json.dumps(reply_markup) 1519 | 1520 | return self.request.post(addr) 1521 | 1522 | def stopMessageLiveLocation(self, chat_id=None, message_id=None, inline_message_id=None, reply_markup=None): 1523 | """ 1524 | 使用此方法可在活动期间到期前停止更新活动位置消息 1525 | 在未指定inline_message_id的时候chat_id和message_id为必须存在的参数 1526 | """ 1527 | command = inspect.stack()[0].function 1528 | 1529 | if inline_message_id is None: 1530 | if message_id is None or chat_id is None: 1531 | return False 1532 | 1533 | if inline_message_id is not None: 1534 | addr = command + "?inline_message_id=" + str(inline_message_id) 1535 | else: 1536 | addr = command + "?chat_id=" + str(chat_id) 1537 | addr += "&message_id=" + str(message_id) 1538 | 1539 | if reply_markup is not None: 1540 | addr += "&reply_markup=" + json.dumps(reply_markup) 1541 | 1542 | return self.request.post(addr) 1543 | 1544 | def setMyCommands(self, commands): 1545 | """ 1546 | 使用此方法更改机器人的命令列表 1547 | commands传入格式示例: 1548 | commands = [ 1549 | {"command": "start", "description": "插件列表"}, 1550 | {"command": "bing", "description": "获取每日Bing壁纸"} 1551 | ] 1552 | """ 1553 | command = inspect.stack()[0].function 1554 | addr = command 1555 | commands = {"commands": commands} 1556 | 1557 | return self.request.postJson(addr, commands) 1558 | 1559 | def getMyCommands(self): 1560 | """ 1561 | 使用此方法获取机器人当前的命令列表 1562 | """ 1563 | command = inspect.stack()[0].function 1564 | addr = command 1565 | 1566 | return self.request.post(addr) 1567 | 1568 | # Updating messages 1569 | def editMessageText(self, text, chat_id=None, message_id=None, inline_message_id=None, 1570 | parse_mode="Text", disable_web_page_preview=None, 1571 | reply_markup=None, entities=None): 1572 | """ 1573 | 编辑一条文本消息.成功时,若消息为Bot发送则返回编辑后的消息,其他返回True 1574 | 在未指定inline_message_id的时候chat_id和message_id为必须存在的参数 1575 | """ 1576 | command = inspect.stack()[0].function 1577 | 1578 | if inline_message_id is None: 1579 | if message_id is None or chat_id is None: 1580 | return False 1581 | 1582 | if inline_message_id is not None: 1583 | addr = command + "?inline_message_id=" + str(inline_message_id) 1584 | else: 1585 | addr = command + "?chat_id=" + str(chat_id) 1586 | addr += "&message_id=" + str(message_id) 1587 | 1588 | addr += "&text=" + quote(str(text)) 1589 | if parse_mode in ("Markdown", "MarkdownV2", "HTML"): 1590 | addr += "&parse_mode=" + str(parse_mode) 1591 | if disable_web_page_preview is not None: 1592 | addr += "&disable_web_page_preview=" + \ 1593 | str(disable_web_page_preview) 1594 | if reply_markup is not None: 1595 | addr += "&reply_markup=" + json.dumps(reply_markup) 1596 | if entities is not None: 1597 | addr += "&entities=" + json.dumps(entities) 1598 | 1599 | return self.request.post(addr) 1600 | 1601 | def editMessageCaption(self, chat_id=None, message_id=None, 1602 | inline_message_id=None, caption=None, parse_mode="Text", 1603 | reply_markup=None, caption_entities=None): 1604 | """ 1605 | 编辑消息的Caption。成功时,若消息为Bot发送则返回编辑后的消息,其他返回True 1606 | 在未指定inline_message_id的时候chat_id和message_id为必须存在的参数 1607 | """ 1608 | command = inspect.stack()[0].function 1609 | if inline_message_id is None: 1610 | if message_id is None or chat_id is None: 1611 | return False 1612 | 1613 | if inline_message_id is not None: 1614 | addr = command + "?inline_message_id=" + str(inline_message_id) 1615 | else: 1616 | addr = command + "?chat_id=" + str(chat_id) 1617 | addr += "&message_id=" + str(message_id) 1618 | 1619 | if caption is not None: 1620 | addr += "&caption=" + quote(str(caption)) 1621 | if parse_mode in ("Markdown", "MarkdownV2", "HTML"): 1622 | addr += "&parse_mode=" + str(parse_mode) 1623 | if reply_markup is not None: 1624 | addr += "&reply_markup=" + str(reply_markup) 1625 | if caption_entities is not None: 1626 | addr += "&caption_entities=" + json.dumps(caption_entities) 1627 | 1628 | return self.request.post(addr) 1629 | 1630 | def editMessageMedia(self, media, chat_id=None, message_id=None, inline_message_id=None, reply_markup=None): 1631 | """ 1632 | 编辑消息媒体 1633 | 在未指定inline_message_id的时候chat_id和message_id为必须存在的参数 1634 | media format: 1635 | media = { 1636 | 'media':{ 1637 | 'type': 'photo', 1638 | 'media': 'http://pic1.win4000.com/pic/d/6a/25a2c0e959.jpg', 1639 | 'caption': '编辑后的Media' 1640 | } 1641 | } 1642 | """ 1643 | command = inspect.stack()[0].function 1644 | if inline_message_id is None: 1645 | if message_id is None or chat_id is None: 1646 | return False 1647 | 1648 | if inline_message_id is not None: 1649 | addr = command + "?inline_message_id=" + str(inline_message_id) 1650 | else: 1651 | addr = command + "?chat_id=" + str(chat_id) 1652 | addr += "&message_id=" + str(message_id) 1653 | 1654 | if reply_markup is not None: 1655 | addr += "&reply_markup=" + json.dumps(reply_markup) 1656 | 1657 | return self.request.postJson(addr, media) 1658 | 1659 | def editMessageReplyMarkup(self, chat_id=None, message_id=None, inline_message_id=None, reply_markup=None): 1660 | """ 1661 | 编辑MessageReplyMarkup 1662 | 在未指定inline_message_id的时候chat_id和message_id为必须存在的参数 1663 | """ 1664 | command = inspect.stack()[0].function 1665 | if inline_message_id is None: 1666 | if message_id is None or chat_id is None: 1667 | return False 1668 | 1669 | if inline_message_id is not None: 1670 | addr = command + "?inline_message_id=" + str(inline_message_id) 1671 | else: 1672 | addr = command + "?chat_id=" + str(chat_id) 1673 | addr += "&message_id=" + str(message_id) 1674 | 1675 | if reply_markup is not None: 1676 | addr += "&reply_markup=" + json.dumps(reply_markup) 1677 | 1678 | return self.request.post(addr) 1679 | 1680 | def stopPoll(self, chat_id, message_id, reply_markup=None): 1681 | """ 1682 | 停止投票?并返回最终结果 1683 | """ 1684 | command = inspect.stack()[0].function 1685 | addr = command + "?chat_id=" + str(chat_id) + "&message_id=" + str(message_id) 1686 | 1687 | if reply_markup is not None: 1688 | addr += "&reply_markup=" + json.dumps(reply_markup) 1689 | 1690 | return self.request.post(addr) 1691 | 1692 | def deleteMessage(self, chat_id, message_id): 1693 | """ 1694 | 删除一条消息,机器人必须具备恰当的权限 1695 | """ 1696 | command = inspect.stack()[0].function 1697 | addr = command + "?chat_id=" + str(chat_id) + "&message_id=" + str(message_id) 1698 | 1699 | return self.request.post(addr) 1700 | 1701 | # Inline mode 1702 | 1703 | def answerInlineQuery(self, inline_query_id, results, cache_time=None, 1704 | is_personal=None, next_offset=None, switch_pm_text=None, switch_pm_parameter=None): 1705 | """ 1706 | 使用此方法发送Inline mode的应答 1707 | """ 1708 | command = inspect.stack()[0].function 1709 | addr = command + "?inline_query_id=" + str(inline_query_id) 1710 | if cache_time is not None: 1711 | addr += "&cache_time=" + str(cache_time) 1712 | if is_personal is not None: 1713 | addr += "&is_personal=" + str(is_personal) 1714 | if next_offset is not None: 1715 | addr += "&next_offset=" + str(next_offset) 1716 | if switch_pm_text is not None: 1717 | addr += "&switch_pm_text=" + str(switch_pm_text) 1718 | if switch_pm_parameter is not None: 1719 | addr += "&switch_pm_parameter=" + str(switch_pm_parameter) 1720 | 1721 | return self.request.postJson(addr, results) 1722 | 1723 | def answerCallbackQuery(self, callback_query_id, text=None, show_alert="false", url=None, cache_time=0): 1724 | """ 1725 | 使用此方法发送CallbackQuery的应答 1726 | InlineKeyboardMarkup格式: 1727 | replyKeyboard = [ 1728 | [ 1729 | { "text": "命令菜单","callback_data":"/start"}, 1730 | { "text": "一排之二","url":"https://google.com"} 1731 | ], 1732 | [ 1733 | { "text": "二排之一","url":"https://google.com"}, 1734 | { "text": "二排之二","url":"https://google.com"}, 1735 | { "text": "二排之三","url":"https://google.com"} 1736 | ] 1737 | ] 1738 | reply_markup = { 1739 | "inline_keyboard": replyKeyboard 1740 | } 1741 | ReplyKeyboardMarkup格式(似乎不能用于群组): 1742 | replyKeyboard = [ 1743 | [ 1744 | { "text": "命令菜单"}, 1745 | { "text": "一排之二"} 1746 | ], 1747 | [ 1748 | { "text": "二排之一"}, 1749 | { "text": "二排之二"}, 1750 | { "text": "二排之三"} 1751 | ] 1752 | ] 1753 | reply_markup = { 1754 | "keyboard": replyKeyboard, 1755 | "resize_keyboard": bool("false"), 1756 | "one_time_keyboard": bool("false"), 1757 | "selective": bool("true") 1758 | } 1759 | ReplyKeyboardRemove格式: 1760 | reply_markup = { 1761 | "remove_keyboard": bool("true"), 1762 | "selective": bool("true") 1763 | } 1764 | """ 1765 | command = inspect.stack()[0].function 1766 | addr = command + "?callback_query_id=" + str(callback_query_id) 1767 | if text is not None: 1768 | addr += "&text=" + quote(str(text)) 1769 | if show_alert == "true": 1770 | addr += "&show_alert=" + str(bool(show_alert)) 1771 | if url is not None: 1772 | addr += "&url=" + str(url) 1773 | if cache_time != 0: 1774 | addr += "&cache_time=" + str(cache_time) 1775 | 1776 | return self.request.post(addr) 1777 | 1778 | # Stickers 1779 | def sendSticker(self, chat_id, sticker, disable_notification=None, 1780 | reply_to_message_id=None, reply_markup=None, 1781 | allow_sending_without_reply=None): 1782 | """ 1783 | 使用此方法发送静态、webp或动画、tgs贴纸 1784 | """ 1785 | command = inspect.stack()[0].function 1786 | 1787 | if sticker[:7] == "http://" or sticker[:7] == "https:/": 1788 | file_data = None 1789 | addr = command + "?chat_id=" + str(chat_id) + "&sticker=" + sticker 1790 | elif type(sticker) == bytes: 1791 | file_data = {"sticker": sticker} 1792 | addr = command + "?chat_id=" + str(chat_id) 1793 | elif type(sticker) == str and '.' not in sticker: 1794 | file_data = None 1795 | addr = command + "?chat_id=" + str(chat_id) + "&sticker=" + sticker 1796 | else: 1797 | file_data = {"sticker": open(sticker, 'rb')} 1798 | addr = command + "?chat_id=" + str(chat_id) 1799 | 1800 | if disable_notification is not None: 1801 | addr += "&disable_notification=" + str(disable_notification) 1802 | if reply_to_message_id is not None: 1803 | addr += "&reply_to_message_id=" + str(reply_to_message_id) 1804 | if reply_markup is not None: 1805 | addr += "&reply_markup=" + json.dumps(reply_markup) 1806 | if allow_sending_without_reply is not None: 1807 | addr += "&allow_sending_without_reply=" + str(allow_sending_without_reply) 1808 | 1809 | if file_data is None: 1810 | return self.request.post(addr) 1811 | else: 1812 | return self.request.postFile(addr, file_data) 1813 | 1814 | def getStickerSet(self, name): 1815 | """ 1816 | 使用此方法获取贴纸集 1817 | """ 1818 | command = inspect.stack()[0].function 1819 | addr = command + "?name=" + str(name) 1820 | 1821 | return self.request.post(addr) 1822 | 1823 | def uploadStickerFile(self, user_id, name, title, emojis, 1824 | png_sticker=None, tgs_sticker=None, contains_masks=None, 1825 | mask_position=None): 1826 | """ 1827 | 使用此方法可以上传带有标签的.PNG文件 1828 | 以供以后在createNewStickerSet和addStickerToSet方法中使用 1829 | (可以多次使用) 1830 | """ 1831 | command = inspect.stack()[0].function 1832 | 1833 | user_id_str = str(user_id) 1834 | if png_sticker[:7] == "http://" or png_sticker[:7] == "https:/": 1835 | file_data = None 1836 | addr = command + "?user_id=" + user_id_str + "&png_sticker=" + png_sticker 1837 | elif type(png_sticker) == bytes: 1838 | file_data = {"png_sticker": png_sticker} 1839 | addr = command + "?user_id=" + user_id_str 1840 | elif type(png_sticker) == str and '.' not in png_sticker: 1841 | file_data = None 1842 | addr = command + "?user_id=" + user_id_str + "&png_sticker=" + png_sticker 1843 | else: 1844 | file_data = {"png_sticker": open(png_sticker, 'rb')} 1845 | addr = command + "?user_id=" + user_id_str 1846 | 1847 | if file_data is None: 1848 | return self.request.post(addr) 1849 | else: 1850 | return self.request.postFile(addr, file_data) 1851 | 1852 | def createNewStickerSet(self, user_id, name, title, emojis, png_sticker=None, tgs_sticker=None, 1853 | contains_masks=None, mask_position=None): 1854 | """ 1855 | 使用此方法可以创建用户拥有的新贴纸集 1856 | 机器人将能够编辑由此创建的贴纸集 1857 | png_sticker或tgs_sticker字段只能且必须存在一个 1858 | """ 1859 | command = inspect.stack()[0].function 1860 | addr = command + "?user_id=" + str(user_id) 1861 | addr += "&name=" + str(name) 1862 | addr += "&title=" + str(title) 1863 | addr += "&emojis=" + str(emojis) 1864 | 1865 | if png_sticker is None and tgs_sticker is None: 1866 | return False 1867 | elif png_sticker is not None and tgs_sticker is not None: 1868 | return False 1869 | else: 1870 | if png_sticker is not None: 1871 | if png_sticker[:7] == "http://" or png_sticker[:7] == "https:/": 1872 | file_data = None 1873 | addr += "&png_sticker=" + png_sticker 1874 | elif type(png_sticker) == bytes: 1875 | file_data = {"png_sticker": png_sticker} 1876 | elif type(png_sticker) == str and '.' not in png_sticker: 1877 | file_data = None 1878 | addr += "&png_sticker=" + png_sticker 1879 | else: 1880 | file_data = {"png_sticker": open(png_sticker, 'rb')} 1881 | elif tgs_sticker is not None: 1882 | if tgs_sticker[:7] == "http://" or tgs_sticker[:7] == "https:/": 1883 | file_data = None 1884 | addr += "&tgs_sticker=" + tgs_sticker 1885 | elif type(tgs_sticker) == bytes: 1886 | file_data = {"tgs_sticker": tgs_sticker} 1887 | elif type(tgs_sticker) == str and '.' not in tgs_sticker: 1888 | file_data = None 1889 | addr += "&tgs_sticker=" + tgs_sticker 1890 | else: 1891 | file_data = {"tgs_sticker": open(tgs_sticker, 'rb')} 1892 | 1893 | if contains_masks is not None: 1894 | addr += "&contains_masks=" + str(contains_masks) 1895 | if mask_position is not None: 1896 | addr += "&mask_position=" + json.dumps(mask_position) 1897 | else: 1898 | return False 1899 | 1900 | if file_data is None: 1901 | return self.request.post(addr) 1902 | else: 1903 | return self.request.postFile(addr, file_data) 1904 | 1905 | def addStickerToSet(self, user_id, name, emojis, png_sticker=None, tgs_sticker=None, 1906 | mask_position=None): 1907 | """ 1908 | 使用此方法可以将新标签添加到由机器人创建的集合中 1909 | png_sticker或tgs_sticker字段只能且必须存在一个。 1910 | 可以将动画贴纸添加到动画贴纸集中,并且只能添加到它们 1911 | 动画贴纸集最多可以包含50个贴纸。 静态贴纸集最多可包含120个贴纸 1912 | """ 1913 | command = inspect.stack()[0].function 1914 | addr = command + "?user_id=" + str(user_id) 1915 | addr += "&name=" + str(name) 1916 | addr += "&emojis=" + str(emojis) 1917 | 1918 | if png_sticker is None and tgs_sticker is None: 1919 | return False 1920 | elif png_sticker is not None and tgs_sticker is not None: 1921 | return False 1922 | else: 1923 | if png_sticker is not None: 1924 | if png_sticker[:7] == "http://" or png_sticker[:7] == "https:/": 1925 | file_data = None 1926 | addr += "&png_sticker=" + png_sticker 1927 | elif type(png_sticker) == bytes: 1928 | file_data = {"png_sticker": png_sticker} 1929 | elif type(png_sticker) == str and '.' not in png_sticker: 1930 | file_data = None 1931 | addr += "&png_sticker=" + png_sticker 1932 | else: 1933 | file_data = {"png_sticker": open(png_sticker, 'rb')} 1934 | elif tgs_sticker is not None: 1935 | if tgs_sticker[:7] == "http://" or tgs_sticker[:7] == "https:/": 1936 | file_data = None 1937 | addr += "&tgs_sticker=" + tgs_sticker 1938 | elif type(tgs_sticker) == bytes: 1939 | file_data = {"tgs_sticker": tgs_sticker} 1940 | elif type(tgs_sticker) == str and '.' not in tgs_sticker: 1941 | file_data = None 1942 | addr += "&tgs_sticker=" + tgs_sticker 1943 | else: 1944 | file_data = {"tgs_sticker": open(tgs_sticker, 'rb')} 1945 | 1946 | if mask_position is not None: 1947 | addr += "&mask_position=" + json.dumps(mask_position) 1948 | 1949 | if file_data is None: 1950 | return self.request.post(addr) 1951 | else: 1952 | return self.request.postFile(addr, file_data) 1953 | 1954 | def setStickerPositionInSet(self, sticker, position): 1955 | """ 1956 | 使用此方法将机器人创建的一组贴纸移动到特定位置 1957 | """ 1958 | command = inspect.stack()[0].function 1959 | addr = command + "?sticker=" + str(sticker) 1960 | addr += "&position=" + str(position) 1961 | 1962 | return self.request.post(addr) 1963 | 1964 | def deleteStickerFromSet(self, sticker): 1965 | """ 1966 | 使用此方法从机器人创建的集合中删除贴纸 1967 | """ 1968 | command = inspect.stack()[0].function 1969 | addr = command + "?sticker=" + str(sticker) 1970 | 1971 | return self.request.post(addr) 1972 | 1973 | def setStickerSetThumb(self, name, user_id, thumb=None): 1974 | """ 1975 | 使用此方法设置贴纸集的缩略图 1976 | 只能为动画贴纸集设置动画缩略图 1977 | """ 1978 | command = inspect.stack()[0].function 1979 | addr = command + "?name=" + str(name) 1980 | addr += "&user_id=" + str(user_id) 1981 | 1982 | if thumb is not None: 1983 | if thumb[:7] == "http://" or thumb[:7] == "https:/": 1984 | file_data = None 1985 | addr += "&thumb=" + thumb 1986 | elif type(thumb) == bytes: 1987 | file_data = {"thumb": thumb} 1988 | elif type(thumb) == str and '.' not in thumb: 1989 | file_data = None 1990 | addr += "&thumb=" + thumb 1991 | else: 1992 | file_data = {"thumb": open(thumb, 'rb')} 1993 | 1994 | if file_data is None: 1995 | return self.request.post(addr) 1996 | else: 1997 | return self.request.postFile(addr, file_data) 1998 | 1999 | # Payments 2000 | def sendInvoice(self, chat_id, title, description, payload, provider_token, start_parameter, 2001 | currency, prices, provider_data=None, photo_url=None, 2002 | photo_size=None, photo_width=None, photo_height=None, 2003 | need_name=None, need_phone_number=None, need_email=None, 2004 | need_shipping_address=None, send_phone_number_to_provider=None, 2005 | send_email_to_provider=None, is_flexible=None, disable_notification=None, 2006 | reply_to_message_id=None, reply_markup=None, 2007 | allow_sending_without_reply=None): 2008 | """ 2009 | 使用此方法发送发票 2010 | """ 2011 | command = inspect.stack()[0].function 2012 | addr = command + "?chat_id=" + str(chat_id) 2013 | addr += "&title=" + str(title) 2014 | addr += "&description=" + str(description) 2015 | addr += "&payload" + str(payload) 2016 | addr += "&provider_token=" + str(provider_token) 2017 | addr += "&start_parameter=" + str(start_parameter) 2018 | addr += "¤cy=" + str(currency) 2019 | addr += "&prices=" + json.dumps(prices) 2020 | 2021 | if provider_data is not None: 2022 | addr += "&provider_data=" + str(provider_data) 2023 | if photo_url is not None: 2024 | addr += "&photo_url=" + str(photo_url) 2025 | if photo_size is not None: 2026 | addr += "&photo_size=" + str(photo_size) 2027 | if photo_width is not None: 2028 | addr += "&photo_width=" + str(photo_width) 2029 | if photo_height is not None: 2030 | addr += "&photo_height=" + str(photo_height) 2031 | if need_name is not None: 2032 | addr += "&need_name=" + str(need_name) 2033 | if need_phone_number is not None: 2034 | addr += "&need_phone_number=" + str(need_phone_number) 2035 | if need_email is not None: 2036 | addr += "&need_email=" + str(need_email) 2037 | if need_shipping_address is not None: 2038 | addr += "&need_shipping_address=" + str(need_shipping_address) 2039 | if send_phone_number_to_provider is not None: 2040 | addr += "&send_phone_number_to_provider=" + \ 2041 | str(send_phone_number_to_provider) 2042 | if send_email_to_provider is not None: 2043 | addr += "&send_email_to_provider=" + str(send_email_to_provider) 2044 | if is_flexible is not None: 2045 | addr += "&is_flexible=" + str(is_flexible) 2046 | if disable_notification is not None: 2047 | addr += "&disable_notification=" + str(disable_notification) 2048 | if reply_to_message_id is not None: 2049 | addr += "&reply_to_message_id=" + str(reply_to_message_id) 2050 | if reply_markup is not None: 2051 | addr += "&reply_markup=" + json.dumps(reply_markup) 2052 | if allow_sending_without_reply is not None: 2053 | addr += "&allow_sending_without_reply=" + str(allow_sending_without_reply) 2054 | 2055 | return self.request.post(addr) 2056 | 2057 | def answerShippingQuery(self, shipping_query_id, ok, shipping_options=None, error_message=None): 2058 | """ 2059 | 使用此方法可以答复运输查询 2060 | """ 2061 | command = inspect.stack()[0].function 2062 | addr = command + "?shipping_query_id=" + str(shipping_query_id) 2063 | addr += "&ok=" + str(ok) 2064 | 2065 | if shipping_options is not None: 2066 | addr += "&shipping_options=" + json.dumps(shipping_options) 2067 | if error_message is not None: 2068 | addr += "&error_message=" + str(error_message) 2069 | 2070 | return self.request.post(addr) 2071 | 2072 | def answerPreCheckoutQuery(self, pre_checkout_query_id, ok, error_message=None): 2073 | """ 2074 | 使用此方法来响应此类预结帐查询 2075 | """ 2076 | command = inspect.stack()[0].function 2077 | addr = command + "?pre_checkout_query_id=" + str(pre_checkout_query_id) 2078 | addr += "&ok=" + str(ok) 2079 | 2080 | if error_message is not None: 2081 | addr += "&error_message=" + str(error_message) 2082 | 2083 | return self.request.post(addr) 2084 | 2085 | # Telegram Passport 2086 | 2087 | def setPassportDataErrors(self, user_id, errors): 2088 | """ 2089 | 通知用户他们提供的某些Telegram Passport元素包含错误 2090 | 在错误纠正之前,用户将无法重新提交其护照 2091 | (错误返回字段的内容必须更改) 2092 | """ 2093 | command = inspect.stack()[0].function 2094 | addr = command + "?user_id=" + str(user_id) 2095 | addr += "&errors=" + json.dumps(errors) 2096 | 2097 | return self.request.post(addr) 2098 | 2099 | # Games 2100 | 2101 | def sendGame(self, chat_id, game_short_name, disable_notification=None, 2102 | reply_to_message_id=None, reply_markup=None, 2103 | allow_sending_without_reply=None): 2104 | """ 2105 | 使用此方法发送游戏 2106 | """ 2107 | command = inspect.stack()[0].function 2108 | addr = command + "?chat_id=" + str(chat_id) 2109 | addr += "&game_short_name=" + str(game_short_name) 2110 | 2111 | if disable_notification is not None: 2112 | addr += "&disable_notification=" + str(disable_notification) 2113 | if reply_to_message_id is not None: 2114 | addr += "&reply_to_message_id=" + str(reply_to_message_id) 2115 | if reply_markup is not None: 2116 | addr += "&reply_markup=" + json.dumps(reply_markup) 2117 | if allow_sending_without_reply is not None: 2118 | addr += "&allow_sending_without_reply=" + str(allow_sending_without_reply) 2119 | 2120 | return self.request.post(addr) 2121 | 2122 | def setGameScore(self, user_id, score, force=None, disable_edit_message=None, 2123 | chat_id=None, message_id=None, inline_message_id=None): 2124 | """ 2125 | 使用此方法设置游戏中指定用户的分数 2126 | 在未指定inline_message_id的时候chat_id和message_id为必须存在的参数 2127 | """ 2128 | command = inspect.stack()[0].function 2129 | 2130 | if inline_message_id is None: 2131 | if message_id is None or chat_id is None: 2132 | return False 2133 | 2134 | if inline_message_id is not None: 2135 | addr = command + "?inline_message_id=" + str(inline_message_id) 2136 | else: 2137 | addr = command + "?chat_id=" + str(chat_id) 2138 | addr += "&message_id=" + str(message_id) 2139 | 2140 | addr += "&user_id=" + str(user_id) 2141 | addr += "&score=" + str(score) 2142 | 2143 | if force is not None: 2144 | addr += "&force=" + str(force) 2145 | if disable_edit_message is not None: 2146 | addr += "&disable_edit_message=" + str(disable_edit_message) 2147 | 2148 | return self.request.post(addr) 2149 | 2150 | def getGameHighScores(self, user_id, chat_id=None, message_id=None, inline_message_id=None): 2151 | """ 2152 | 使用此方法获取高分表的数据 2153 | 将返回指定用户及其在游戏中几个邻居的分数 2154 | 在未指定inline_message_id的时候chat_id和message_id为必须存在的参数 2155 | """ 2156 | command = inspect.stack()[0].function 2157 | 2158 | if inline_message_id is None: 2159 | if message_id is None or chat_id is None: 2160 | return False 2161 | 2162 | if inline_message_id is not None: 2163 | addr = command + "?inline_message_id=" + str(inline_message_id) 2164 | else: 2165 | addr = command + "?chat_id=" + str(chat_id) 2166 | addr += "&message_id=" + str(message_id) 2167 | 2168 | addr += "&user_id=" + str(user_id) 2169 | 2170 | return self.request.post(addr) 2171 | -------------------------------------------------------------------------------- /teelebot/version.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | """ 3 | @description:基于Telegram Bot Api 的机器人框架 4 | @creation date: 2019-11-15 5 | @last modify: 2020-11-27 6 | @author: Pluto (github:plutobell) 7 | @version: 1.14.1 8 | """ 9 | 10 | __version__ = "1.14.1" 11 | __author__ = "Pluto" 12 | __email__ = "hi@ojoll.com" 13 | __blog__ = "https://ojoll.com" 14 | __github__ = "https://github.com/plutobell/teelebot" 15 | __description__ = "teelebot is a robot framework based on Telegram Bot API, with plug-in system, easy to extend." -------------------------------------------------------------------------------- /teelebot/webhook.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | ''' 3 | @creation date: 2020-6-12 4 | @last modify: 2020-11-23 5 | ''' 6 | from http.server import HTTPServer, BaseHTTPRequestHandler 7 | #from socketserver import ThreadingMixIn 8 | import ssl 9 | import sys 10 | import json 11 | 12 | 13 | def __MakeRequestHandler(bot): 14 | class RequestHandler(BaseHTTPRequestHandler): 15 | def __init__(self, *args, **kwargs): 16 | super(RequestHandler, self).__init__(*args, **kwargs) 17 | 18 | def do_POST(self): 19 | if self.command == "POST" and self.path == "/bot" + str(bot._key): 20 | req_data = self.rfile.read(int(self.headers['content-length'])) 21 | res = req_data.decode('utf-8') 22 | 23 | message = json.loads(res) 24 | results = [message] 25 | messages = bot._washUpdates(results) 26 | if messages is not None and messages: 27 | for message in messages: 28 | bot._pluginRun(bot, message) 29 | 30 | data = {'status': 'ok'} 31 | data = json.dumps(data) 32 | self.send_response(200) 33 | self.send_header('Content-type', 'application/json') 34 | self.end_headers() 35 | self.wfile.write(data.encode('utf-8')) 36 | else: 37 | data = {'status': 'false'} 38 | data = json.dumps(data) 39 | self.send_response(400) 40 | self.send_header('Content-type', 'application/json') 41 | self.end_headers() 42 | self.wfile.write(data.encode('utf-8')) 43 | 44 | def log_message(self, format, *args): 45 | pass 46 | 47 | return RequestHandler 48 | 49 | 50 | # class ThreadedHTTPServer(ThreadingMixIn, HTTPServer): 51 | # pass 52 | 53 | 54 | def _runWebhook(bot, host, port): 55 | RequestHandler = __MakeRequestHandler(bot) 56 | if bot._local_address == "0.0.0.0": 57 | try: 58 | context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) 59 | context.load_cert_chain(bot._cert_pub, bot._cert_key) 60 | 61 | server = HTTPServer((host, port), RequestHandler) 62 | server.socket = context.wrap_socket(server.socket, server_side=True) 63 | server.serve_forever() 64 | except KeyboardInterrupt: 65 | server.server_close() 66 | sys.exit("Bot Exit.") 67 | else: 68 | try: 69 | server = HTTPServer((host, port), RequestHandler) 70 | server.serve_forever() 71 | except KeyboardInterrupt: 72 | server.server_close() 73 | sys.exit("Bot Exit.") 74 | --------------------------------------------------------------------------------