From 90021a816703b3bb802b6800c50b45bb19b4d222 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=AE=E4=BF=A1?= Date: Sun, 31 Oct 2021 13:38:35 +0000 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E5=BE=AE=E4=BF=A1=E4=BA=92?= =?UTF-8?q?=E5=8A=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/ISSUE_TEMPLATE.md | 4 +- DOCKER.md | 72 ++++- SourcePackages/config/default_template.conf | 1 + SourcePackages/pandalearning.py | 28 +- SourcePackages/pdlearn/globalvar.py | 11 +- SourcePackages/pdlearn/score.py | 112 ++++---- SourcePackages/pdlearn/threads.py | 4 +- SourcePackages/pdlearn/version_info.json | 5 +- SourcePackages/pdlearn/wechat.py | 46 +++- SourcePackages/wechatListener.py | 250 ++++++++++++++++++ start.sh | 3 + ...13\350\275\275\346\226\271\345\274\217.md" | 8 +- 12 files changed, 457 insertions(+), 87 deletions(-) create mode 100644 SourcePackages/wechatListener.py diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index 6bc3f5e7..384a4be4 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -9,7 +9,7 @@ --> ### 清单 -- [ ] 我已经仔细阅读过 README.md (https://github.com/TechXueXi/TechXueXi/blob/master/README.md) +- [ ] 我已经仔细阅读过 README.md ( https://github.com/TechXueXi/TechXueXi/blob/developing/README.md ) - [ ] 我已经查看/搜索过所有已有 issue,无论是open还是close的 - [ ] 我已经通过搜索引擎搜索 www.google.com www.baidu.com - [ ] 我已经到提供的在线聊天室询问过 (聊天室说明:https://github.com/TechXueXi/TechXueXi/issues/14) @@ -60,7 +60,7 @@ 许多IT人员本终日埋头写代码,对我们这类软件确实有需求, 与其各人重复修改编写浪费生产力不如团队合作维护,因此我们希望长期维护此生态。 -有意愿加入本组织者,请https://techxuexi.github.io/ +有意愿加入本组织者,请 https://techxuexi.js.org/ 我们不接受任何捐赠。远离非法牟利。 --> diff --git a/DOCKER.md b/DOCKER.md index d5f9f768..49b426f3 100644 --- a/DOCKER.md +++ b/DOCKER.md @@ -54,9 +54,9 @@ docker pull techxuexi/techxuexi-arm64v8:{tag} ### 版本说明 -分为开发版:tag含有dev, +分为开发版:tag 含有 dev, -稳定版: tag为 latest +稳定版: tag 为 latest # Docker 命令运行 @@ -270,7 +270,6 @@ services: 5. 在`/xuexi/user/settings.conf`中添加微信配置 ``` addition { - Pushmode="2" wechat{ appid = "第2步中获取的appid" appsecret = "第2步中获取的appsecret" @@ -278,7 +277,55 @@ services: } } ``` - _后续 **可能** 会增加`账号绑定`功能,允许多用户关注并绑定学 xi 账号,进行消息分发_ + +### 微信进阶设置 + +以下操作适用于有公网 IP,且需要跟微信互动的用户 + +1. 修改上面第五步中的配置文件追加`token`属性。token 用于验证请求,随意填写 8~16 位字符即可,也可以到[这里](https://www.toolzl.com/tools/createString.html)生成,_不要勾选字符_ + ``` + addition { + wechat{ + appid = "第2步中获取的appid" + appsecret = "第2步中获取的appsecret" + openid = "第4步中获取的微信号" + token = "随机字符串" + } + } + ``` +2. 在【接口配置信息】中填入`URL`和第 1 步的`token`。![image](https://user-images.githubusercontent.com/91232777/139572541-42184b52-350c-4c49-b646-d763f16b715f.png) + _图中 url 仅为参考,请以文档为准_ + URL:`http://你的域名或IP地址:端口号/wechat` + 【端口号】是你从 docker 中映射出来的本地端口号,记得在路由器或防火墙中开启端口转发。访问链大概为:`微信--端口A-->路由--端口B-->docker--端口8088-->程序`。 + 这里要填写的就是`端口A`。 +3. 点【提交】,如果提示`token验证失败`,要么端口不通,要么 docker 没启动监听,就不用往下看了。 + +#### 微信的使用 + +恭喜你完成所有配置,下面开始介绍功能的使用 + +配置成功之后,给公众号发送`/help`即可获得答复,如果没有,检查第一步中的 openid 是不是你的微信号。**只有主账号才能使用指令** + +首先,给公众号发送 `/init` 初始化订阅号菜单,操作成功后等菜单出现,或者重新关注微信号,即可看到菜单, +![image](https://user-images.githubusercontent.com/91232777/139573137-141675e4-8939-4ce4-8cff-4432e7ff6fd8.png) + +点击`我的-账号编码`获取微信号编码。 + +发送`/add` 登录学 xi 账号,登录完成后获得 `数字ID_昵称`登录成功的消息。 + +发送`/bind 微信编码 数字ID` 吧微信号和学 xi 账号绑定。如`/bind gw_djahdhfs 155555555`,不要有多余的空格。可以重复绑定,最后绑定的账号有效。 + +点`开始学xi`即可开始今天的学习。 + +`我的-今日积分` 获取今天学习积分。 + +其他账号绑定 + +1. 让`用户A`先关注你的公众号。然后点击`账号编码`,并将编码给你。 +2. 给你的公众号发送`/add`指令,让`用户A`登录。你可以获得 ta 的数字 ID +3. 给你的公众号发送`/bind 用户A的账号编码 用户A的数字ID`绑定成功后,`用户A`就可以自己学习和查分了。 + +`\unbind 微信编码` 解绑指定用户。 ## Server 酱 @@ -325,11 +372,12 @@ services: 其他没有固定下来的用法,请加群了解。 -# Web网页控制台 +# Web 网页控制台 参考 telegram 需要打开端口映射 -docker指令 +docker 指令 + ```sh @@ -343,9 +391,11 @@ docker run \ -d --name=techxuexi --shm-size="2g" \ -p 9980:80 \ techxuexi/techxuexi-amd64:latest - + ``` -docker-config.yaml配置 + +docker-config.yaml 配置 + ```yaml version: '3.5' services: @@ -356,7 +406,7 @@ services: ports: - 9980:80/tcp volumes: - - ./user:/xuexi/user + - ./user:/xuexi/user environment: - Scheme=https://techxuexi.js.org/jump/techxuexi-20211023.html? - ZhuanXiang=True @@ -364,12 +414,10 @@ services: - CRONTIME=30 9 * * * build: context: . - shm_size: '2gb' + shm_size: '2gb' shm_size: '2gb' - ``` - **[交流群地址及说明](https://github.com/TechXueXi/TechXueXi/issues/14)** ## Docker 如果发给你一个学习强国链接,不是让你下载,是让你登录,复制链接到学习#国 app,发给某个人,比如自己,再点击链接 diff --git a/SourcePackages/config/default_template.conf b/SourcePackages/config/default_template.conf index a5f1ddfb..8d306d98 100644 --- a/SourcePackages/config/default_template.conf +++ b/SourcePackages/config/default_template.conf @@ -99,6 +99,7 @@ addition { appid = "" appsecret = "" openid = "" + token = "" #互动操作需要,6~16位任意半角字符 } } diff --git a/SourcePackages/pandalearning.py b/SourcePackages/pandalearning.py index 17f33eff..3766e569 100644 --- a/SourcePackages/pandalearning.py +++ b/SourcePackages/pandalearning.py @@ -67,7 +67,7 @@ def start_learn(uid, name): else: msg = name+" 登录信息失效,请重新扫码" print(msg) - gl.pushprint(msg) + gl.pushprint(msg, chat_id=uid) driver_login = Mydriver() cookies = driver_login.login() driver_login.quit() @@ -85,7 +85,7 @@ def start_learn(uid, name): video_index = 1 # user.get_video_index(uid) total, scores = show_score(cookies) - gl.pushprint(output) + gl.pushprint(output, chat_id=uid) if TechXueXi_mode in ["1", "3"]: article_thread = threads.MyThread( @@ -114,8 +114,8 @@ def start_learn(uid, name): seconds_used = int(time.time() - start_time) gl.pushprint(name+" 总计用时 " + str(math.floor(seconds_used / 60)) + - " 分 " + str(seconds_used % 60) + " 秒") - show_scorePush(cookies) + " 分 " + str(seconds_used % 60) + " 秒", chat_id=uid) + show_scorePush(cookies, chat_id=uid) try: user.shutdown(stime) except Exception as e: @@ -132,7 +132,7 @@ def start(nick_name=None): user_list.append(["", "新用户"]) for i in range(len(user_list)): try: - if nick_name == None or nick_name == user_list[i][1]: + if nick_name == None or nick_name == user_list[i][1] or nick_name == user_list[i][0]: _learn = threads.MyThread( user_list[i][0]+"开始学xi", start_learn, user_list[i][0], user_list[i][1], lock=Single) _learn.start() @@ -140,6 +140,16 @@ def start(nick_name=None): gl.pushprint("学习页面崩溃,学习终止") +def get_my_score(uid): + get_argv() + user.refresh_all_cookies() + cookies = user.get_cookie(uid) + if not cookies: + return False + show_scorePush(cookies, chat_id=uid) + return True + + def get_user_list(): get_argv() dic = user.refresh_all_cookies(display_score=True) @@ -160,20 +170,20 @@ def get_all_user_name(): return names -def add_user(): +def add_user(chat_id=None): get_argv() - gl.pushprint("请扫码登录:") + gl.pushprint("请扫码登录:", chat_id=chat_id) driver_login = Mydriver() cookies = driver_login.login() driver_login.quit() if not cookies: - gl.pushprint("登录超时。") + gl.pushprint("登录超时。", chat_id=chat_id) return user.save_cookies(cookies) uid = user.get_userId(cookies) user_fullname = user.get_fullname(uid) user.update_last_user(uid) - gl.pushprint(user_fullname+"登录成功") + gl.pushprint(user_fullname+"登录成功", chat_id=chat_id) if __name__ == '__main__': diff --git a/SourcePackages/pdlearn/globalvar.py b/SourcePackages/pdlearn/globalvar.py index 703a9a70..31329f8a 100644 --- a/SourcePackages/pdlearn/globalvar.py +++ b/SourcePackages/pdlearn/globalvar.py @@ -38,7 +38,7 @@ def init_global(): nohead = True else: nohead = cfg_get("addition.Nohead", False) - + if os.getenv('islooplogin') == "True": islooplogin = True @@ -52,7 +52,7 @@ def init_global(): if os.getenv("Scheme") != None: scheme = os.getenv("Scheme") - #elif pushmode in ["5"]: # telegram 默认开启我们提供的 + # elif pushmode in ["5"]: # telegram 默认开启我们提供的 # scheme = 'https://techxuexi.js.org/jump/techxuexi-20211023.html?' if os.getenv('AccessToken'): @@ -82,7 +82,8 @@ def init_global(): wechat = WechatHandler() is_init = True -def pushprint(text): + +def pushprint(text, chat_id=None): """ 推送或者显示 """ @@ -97,7 +98,9 @@ def pushprint(text): push = DingDingHandler(accesstoken, secret) push.ddtextsend(text) elif pushmode == "2": - wechat.send_text(text) + if chat_id: + chat_id = wechat.get_opendid_by_uid(chat_id) + wechat.send_text(text, uid=chat_id) elif pushmode == "3": push = FangtangHandler(accesstoken) push.fttext(text) diff --git a/SourcePackages/pdlearn/score.py b/SourcePackages/pdlearn/score.py index 46e68fda..f4c1fc59 100644 --- a/SourcePackages/pdlearn/score.py +++ b/SourcePackages/pdlearn/score.py @@ -4,7 +4,9 @@ from requests.cookies import RequestsCookieJar import json from pdlearn import color +from pdlearn import file from pdlearn.const import const +import threading # 总积分 @@ -38,7 +40,7 @@ def show_score(cookies): return total, scores -def show_scorePush(cookies): +def show_scorePush(cookies, chat_id=None): userId, total, scores, userName = get_score(cookies) globalvar.pushprint(userName+" 当前学 xi 总积分:" + str(total) + "\t" + "今日得分:" + str(scores["today"]) + "\n阅读文章:" + handle_score_color(scores["article_num"], const.article_num_all, False) + "," + @@ -48,56 +50,68 @@ def show_scorePush(cookies): "\n每日登陆:" + handle_score_color(scores["login"], const.login_all, False) + "," + "每日答题:" + handle_score_color(scores["daily"], const.daily_all, False) + "," + "每周答题:" + handle_score_color(scores["weekly"], const.weekly_all, False) + "," + - "专项答题:" + handle_score_color(scores["zhuanxiang"], const.zhuanxiang_all, False)) + "专项答题:" + handle_score_color(scores["zhuanxiang"], const.zhuanxiang_all, False), chat_id) return total, scores def get_score(cookies): - try: - requests.adapters.DEFAULT_RETRIES = 5 - jar = RequestsCookieJar() - for cookie in cookies: - jar.set(cookie['name'], cookie['value']) - total_json = requests.get("https://pc-api.xuexi.cn/open/api/score/get", cookies=jar, - headers={'Cache-Control': 'no-cache'}).content.decode("utf8") - total = int(json.loads(total_json)["data"]["score"]) - #userId = json.loads(total_json)["data"]["userId"] - user_info = requests.get("https://pc-api.xuexi.cn/open/api/user/info", cookies=jar, - headers={'Cache-Control': 'no-cache'}).content.decode("utf8") - userId = json.loads(user_info)["data"]["uid"] - userName = json.loads(user_info)["data"]["nick"] - # score_json = requests.get("https://pc-api.xuexi.cn/open/api/score/today/queryrate", cookies=jar, - # headers={'Cache-Control': 'no-cache'}).content.decode("utf8") - # today_json = requests.get("https://pc-api.xuexi.cn/open/api/score/today/query", cookies=jar, - # headers={'Cache-Control': 'no-cache'}).content.decode("utf8") - today = 0 - # today = int(json.loads(today_json)["data"]["score"]) - score_json = requests.get("https://pc-proxy-api.xuexi.cn/api/score/days/listScoreProgress?sence=score&deviceType=2", cookies=jar, - headers={'Cache-Control': 'no-cache'}).content.decode("utf8") - dayScoreDtos = json.loads(score_json)["data"] - today = dayScoreDtos["totalScore"] - rule_list = [1, 2, 9, 1002, 1003, 6, 5, 4] - score_list = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0] # 长度为十 - for i in dayScoreDtos["taskProgress"]: - for j in range(len(rule_list)): - if str(rule_list[j]) in i["taskCode"]: - score_list[j] = int( - int(i["currentScore"])/len(i["taskCode"])) - # 阅读文章,视听学 xi ,登录,文章时长,视听学 xi 时长,每日答题,每周答题,专项答题 - scores = {} - scores["article_num"] = score_list[0] # 0阅读文章 - scores["video_num"] = score_list[1] # 1视听学 xi - scores["login"] = score_list[2] # 7登录 - scores["article_time"] = score_list[3] # 6文章时长 - scores["video_time"] = score_list[4] # 5视听学 xi 时长 - scores["daily"] = score_list[5] # 2每日答题 - scores["weekly"] = score_list[6] # 3每周答题 - scores["zhuanxiang"] = score_list[7] # 4专项答题 + chat_id = None + th_name = threading.current_thread().name + if "开始学xi" in th_name: + chat_id = th_name[:th_name.index("开始学xi")] + requests.adapters.DEFAULT_RETRIES = 5 + jar = RequestsCookieJar() + for cookie in cookies: + jar.set(cookie['name'], cookie['value']) + total_json = requests.get("https://pc-api.xuexi.cn/open/api/score/get", cookies=jar, + headers={'Cache-Control': 'no-cache'}).content.decode("utf8") + if not json.loads(total_json)["data"]: + globalvar.pushprint("cookie过期,请重新登录", chat_id) + if chat_id: + remove_cookie(chat_id) + raise - scores["today"] = today # 8今日得分 - return userId, total, scores, userName - except Exception as e: - print("=" * 60) - print("get_score 获取失败:"+str(e)) - print("=" * 60) - return 0, 0, 0, "" + total = int(json.loads(total_json)["data"]["score"]) + #userId = json.loads(total_json)["data"]["userId"] + user_info = requests.get("https://pc-api.xuexi.cn/open/api/user/info", cookies=jar, + headers={'Cache-Control': 'no-cache'}).content.decode("utf8") + userId = json.loads(user_info)["data"]["uid"] + userName = json.loads(user_info)["data"]["nick"] + # score_json = requests.get("https://pc-api.xuexi.cn/open/api/score/today/queryrate", cookies=jar, + # headers={'Cache-Control': 'no-cache'}).content.decode("utf8") + # today_json = requests.get("https://pc-api.xuexi.cn/open/api/score/today/query", cookies=jar, + # headers={'Cache-Control': 'no-cache'}).content.decode("utf8") + today = 0 + # today = int(json.loads(today_json)["data"]["score"]) + score_json = requests.get("https://pc-proxy-api.xuexi.cn/api/score/days/listScoreProgress?sence=score&deviceType=2", cookies=jar, + headers={'Cache-Control': 'no-cache'}).content.decode("utf8") + dayScoreDtos = json.loads(score_json)["data"] + today = dayScoreDtos["totalScore"] + rule_list = [1, 2, 9, 1002, 1003, 6, 5, 4] + score_list = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0] # 长度为十 + for i in dayScoreDtos["taskProgress"]: + for j in range(len(rule_list)): + if str(rule_list[j]) in i["taskCode"]: + score_list[j] = int( + int(i["currentScore"])/len(i["taskCode"])) + # 阅读文章,视听学 xi ,登录,文章时长,视听学 xi 时长,每日答题,每周答题,专项答题 + scores = {} + scores["article_num"] = score_list[0] # 0阅读文章 + scores["video_num"] = score_list[1] # 1视听学 xi + scores["login"] = score_list[2] # 7登录 + scores["article_time"] = score_list[3] # 6文章时长 + scores["video_time"] = score_list[4] # 5视听学 xi 时长 + scores["daily"] = score_list[5] # 2每日答题 + scores["weekly"] = score_list[6] # 3每周答题 + scores["zhuanxiang"] = score_list[7] # 4专项答题 + + scores["today"] = today # 8今日得分 + return userId, total, scores, userName + + +def remove_cookie(uid): + template_json_str = '''{}''' + cookies_json_obj = file.get_json_data( + "user/cookies.json", template_json_str) + cookies_json_obj.pop(str(uid)) + file.save_json_data("user/cookies.json", cookies_json_obj) diff --git a/SourcePackages/pdlearn/threads.py b/SourcePackages/pdlearn/threads.py index 8a029e47..4634ae43 100644 --- a/SourcePackages/pdlearn/threads.py +++ b/SourcePackages/pdlearn/threads.py @@ -1,7 +1,6 @@ # import sys # import signal # from pdlearn import color -from pdlearn import globalvar from threading import Thread from threading import Lock @@ -23,9 +22,10 @@ # func() # print(color.green("工作线程清理完毕. Bye.")) # sys.exit() - + # signal.signal(signal.SIGINT,signal_handler) + class MyThread(Thread): def __init__(self, name, func, *args, lock=False): Thread.__init__(self) diff --git a/SourcePackages/pdlearn/version_info.json b/SourcePackages/pdlearn/version_info.json index bd4deae9..50133255 100644 --- a/SourcePackages/pdlearn/version_info.json +++ b/SourcePackages/pdlearn/version_info.json @@ -1,6 +1,9 @@ { - "techxuexi_version": "v20211030", + "techxuexi_version": "v20211031", "techxuexi_update_log": [{ + "version": "v20211031", + "info": "docker: 1.增加微信互动功能; 2.微信token改为文件存储,方便主线程和守护线程共享; 3.修改bot消息发送,可以发送给指定用户; 4.增加文档说明; 5.增加默认配置文件的配置项; 6.微信暂时使用nohup启动,方便查阅日志,稳定后改为supervisord; 7.修复文档,get_score异常处理; 8.access_token过期后强制刷新; " + },{ "version": "v20211030", "info": "优化参数配置 修复了无窗模式默认开启的问题,优化v指令" },{ diff --git a/SourcePackages/pdlearn/wechat.py b/SourcePackages/pdlearn/wechat.py index f93c1aeb..b18e9082 100644 --- a/SourcePackages/pdlearn/wechat.py +++ b/SourcePackages/pdlearn/wechat.py @@ -1,6 +1,7 @@ import requests import json from pdlearn.config import cfg_get +from pdlearn import file import time @@ -10,9 +11,19 @@ def __init__(self): self.token = self.get_access_token() self.openid = cfg_get("addition.wechat.openid", "") - def get_access_token(self): - if self.token and self.token[1] > time.time(): - return self.token + def get_access_token(self, refresh=False): + if not refresh: + # 检查变量 + if self.token and self.token[1] > time.time(): + return self.token + # 检查文件 + template_json_str = '''[]''' + token_json_obj = file.get_json_data( + "user/wechat_token.json", template_json_str) + if token_json_obj and token_json_obj[1] > time.time(): + self.token = token_json_obj + return self.token + # 获取新token appid = cfg_get("addition.wechat.appid", "") appsecret = cfg_get("addition.wechat.appsecret", "") url_token = 'https://api.weixin.qq.com/cgi-bin/token?' @@ -24,18 +35,39 @@ def get_access_token(self): token = res.get('access_token') expires = int(res.get('expires_in'))-10+time.time() self.token = [token, expires] + file.save_json_data("user/wechat_token.json", self.token) return self.token - def send_text(self, text): + def send_text(self, text, uid=""): + if not uid: + uid = self.openid token = self.get_access_token() url_msg = 'https://api.weixin.qq.com/cgi-bin/message/custom/send?' body = { - "touser": self.openid, + "touser": uid, "msgtype": "text", "text": { "content": text } } - requests.post(url=url_msg, params={ + res = requests.post(url=url_msg, params={ 'access_token': token - }, data=json.dumps(body, ensure_ascii=False).encode('utf-8')) + }, data=json.dumps(body, ensure_ascii=False).encode('utf-8')).json() + print(res) + if res["errcode"] == 40001: + self.get_access_token(True) + self.send_text(text, uid) + + def get_opendid_by_uid(self, uid): + """ + 账号换绑定的openid,没有则返回主账号 + """ + json_str = '''[]''' + json_obj = file.get_json_data( + "user/wechat_bind.json", json_str) + wx_list = list( + filter(lambda w: w["accountId"] == uid or w["openId"] == uid, json_obj)) + if wx_list: + return wx_list[0]["openId"] + else: + return self.openid diff --git a/SourcePackages/wechatListener.py b/SourcePackages/wechatListener.py new file mode 100644 index 00000000..0df42d8e --- /dev/null +++ b/SourcePackages/wechatListener.py @@ -0,0 +1,250 @@ +from hashlib import sha1 +import os +from flask import Flask, request +import requests +import json + +from selenium.webdriver.support import ui +from pdlearn.config import cfg_get +from pdlearn.wechat import WechatHandler +from pdlearn.threads import MyThread +from pdlearn import file +import pandalearning as pdl + +app = Flask(__name__) +appid = cfg_get("addition.wechat.appid", "") +appsecret = cfg_get("addition.wechat.appsecret", "") +token = cfg_get("addition.wechat.token", "") +openid = cfg_get("addition.wechat.openid", "") +wechat = WechatHandler() + + +class MessageInfo: + to_user_name = "" + from_user_name = "" + create_time = "" + msg_type = "" + content = "" + msg_id = "" + event = "" + event_key = "" + + def __init__(self, root): + for child in root: + if child.tag == 'ToUserName': + self.to_user_name = child.text + elif child.tag == 'FromUserName': + self.from_user_name = child.text + elif child.tag == 'CreateTime': + self.create_time = child.text + elif child.tag == 'MsgType': + self.msg_type = child.text + elif child.tag == 'Content': + self.content = child.text + elif child.tag == 'MsgId': + self.msg_id = child.text + elif child.tag == 'Event': + self.event = child.text + elif child.tag == 'EventKey': + self.event_key = child.text + + +def get_update(timestamp, nonce): + arguments = '' + for k in sorted([token, timestamp, nonce]): + arguments = arguments + str(k) + m = sha1() + m.update(arguments.encode('utf8')) + return m.hexdigest() + + +def check_signature(): + signature = request.args.get('signature', '') + timestamp = request.args.get('timestamp', '') + nonce = request.args.get('nonce', '') + check = get_update(timestamp, nonce) + return True if check == signature else False + + +def parse_xml(data): + try: + import xml.etree.cElementTree as ET + except ImportError: + import xml.etree.ElementTree as ET + root = ET.fromstring(data) + return MessageInfo(root) + + +def wechat_init(): + """ + 初始化订阅号菜单 + """ + url = "https://api.weixin.qq.com/cgi-bin/menu/create?" + body = { + "button": [ + { + "type": "click", + "name": "开始学xi", + "key": "MENU_LEARN" + }, + { + "name": "我的", + "sub_button": [ + { + "type": "click", + "name": "今日积分", + "key": "MENU_SCORE" + }, + { + "type": "click", + "name": "账号编码", + "key": "MENU_OPENID" + }, + ] + } + ] + } + + res = requests.post(url=url, params={ + 'access_token': wechat.get_access_token() + }, data=json.dumps(body, ensure_ascii=False).encode('utf-8')).json() + if res.get("errcode") == 0: + wechat.send_text("菜单初始化成功,请重新关注订阅号") + else: + wechat.send_text(res.get("errmsg")) + + +def get_uid(oid): + json_str = '''[]''' + json_obj = file.get_json_data( + "user/wechat_bind.json", json_str) + wx_list = list(filter(lambda w: w["openId"] == oid, json_obj)) + if wx_list: + return wx_list[0]["accountId"] + else: + return"" + + +def wechat_get_openid(msg: MessageInfo): + """ + 获取用户的openId + """ + wechat.send_text(msg.from_user_name, msg.from_user_name) + + +def wechat_learn(msg: MessageInfo): + """ + 开始学习 + """ + uid = get_uid(msg.from_user_name) + if not uid: + wechat.send_text("您未绑定账号,请联系管理员绑定", uid=msg.from_user_name) + else: + pdl.start(uid) + + +def wechat_get_score(msg: MessageInfo): + """ + 获取今日分数 + """ + uid = get_uid(msg.from_user_name) + if not uid: + wechat.send_text("您未绑定账号,请联系管理员绑定", uid=msg.from_user_name) + else: + score = pdl.get_my_score(uid) + if not score: + wechat.send_text("登录过期,请重新登录后再试", msg.from_user_name) + pdl.add_user(msg.from_user_name) + + +def wechat_help(): + """ + 获取帮助菜单 + """ + wechat.send_text( + "/help 显示帮助消息\n/init 初始化订阅号菜单,仅需要执行一次\n/add 添加新账号\n/bind 绑定账号,如:/bind 账号编码 学xi编号\n/unbind 解除绑定 如:/unbind 账号编码") + + +def wechat_add(): + """ + 添加新账号 + """ + pdl.add_user() + + +def wechat_bind(msg: MessageInfo): + """ + 绑定微信号 + """ + args = msg.content.split(" ") + if len(args) == 3: + json_str = '''[]''' + json_obj = file.get_json_data( + "user/wechat_bind.json", json_str) + wx_list = list(filter(lambda w: w["openId"] == args[1], json_obj)) + if wx_list: + index = json_obj.index(wx_list[0]) + json_obj[index]["accountId"] = args[2] + else: + json_obj.append({"openId": args[1], "accountId": args[2]}) + file.save_json_data("user/wechat_bind.json", json_obj) + wechat.send_text("绑定成功") + else: + wechat.send_text("参数格式错误") + + +def wechat_unbind(msg: MessageInfo): + """ + 解绑微信号 + """ + args = msg.content.split(" ") + if len(args) == 2: + json_str = '''[]''' + json_obj = file.get_json_data( + "user/wechat_bind.json", json_str) + wx_list = list(filter(lambda w: w["openId"] == args[1], json_obj)) + if wx_list: + index = json_obj.index(wx_list[0]) + json_obj.pop(index) + file.save_json_data("user/wechat_bind.json", json_obj) + wechat.send_text("解绑成功") + else: + wechat.send_text("账号编码错误或该编码未绑定账号") + else: + wechat.send_text("参数格式错误") + + +@ app.route('/wechat', methods=['GET', 'POST']) +def weixinInterface(): + if check_signature: + if request.method == 'GET': + echostr = request.args.get('echostr', '') + return echostr + elif request.method == 'POST': + data = request.data + msg = parse_xml(data) + if msg.msg_type == "event" and msg.event == "CLICK": + if msg.event_key == "MENU_OPENID": + MyThread("get_user_openid", wechat_get_openid, msg).start() + if msg.event_key == "MENU_LEARN": + MyThread("wechat_learn", wechat_learn, msg).start() + if msg.event_key == "MENU_SCORE": + MyThread("wechat_get_score", wechat_get_score, msg).start() + if msg.from_user_name == openid: + if msg.content.startswith("/init"): + MyThread("wechat_init", wechat_init).start() + if msg.content.startswith("/help"): + MyThread("wechat_help", wechat_help).start() + if msg.content.startswith("/bind"): + MyThread("wechat_bind", wechat_bind, msg).start() + if msg.content.startswith("/unbind"): + MyThread("wechat_unbind", wechat_unbind, msg).start() + if msg.content.startswith("/add"): + MyThread("wechat_add", wechat_add).start() + return "success" + else: + return 'signature error' + + +if __name__ == '__main__': + app.run(host='0.0.0.0', port=8088) diff --git a/start.sh b/start.sh index 0e6b9d6c..e0fce9b2 100644 --- a/start.sh +++ b/start.sh @@ -9,6 +9,9 @@ if [ "${Pushmode}" = "5" ]; then supervisord -c /etc/supervisord.conf # nohup /usr/local/bin/python /xuexi/telegramListener.py >> /xuexi/user/tg_listener.log 2>&1 & fi +if [ "${Pushmode}" = "2" ]; then + nohup /usr/local/bin/python /xuexi/wechatListener.py >> /xuexi/user/wechat_listener.log 2>&1 & +fi ./run.sh 2>&1 & echo -e "$CRONTIME $USER /xuexi/run.sh >> /var/log/cron.log 2>&1\n#empty line" > /etc/cron.d/mycron crontab /etc/cron.d/mycron diff --git "a/\344\275\277\347\224\250\346\226\271\346\263\225-\346\233\264\346\226\260\346\226\271\346\263\225-\344\270\213\350\275\275\346\226\271\345\274\217.md" "b/\344\275\277\347\224\250\346\226\271\346\263\225-\346\233\264\346\226\260\346\226\271\346\263\225-\344\270\213\350\275\275\346\226\271\345\274\217.md" index 35098cce..de52a6ba 100644 --- "a/\344\275\277\347\224\250\346\226\271\346\263\225-\346\233\264\346\226\260\346\226\271\346\263\225-\344\270\213\350\275\275\346\226\271\345\274\217.md" +++ "b/\344\275\277\347\224\250\346\226\271\346\263\225-\346\233\264\346\226\260\346\226\271\346\263\225-\344\270\213\350\275\275\346\226\271\345\274\217.md" @@ -54,6 +54,12 @@ TypeError: 'int' object is not subscriptable **20211014开始,xuexi 官方修改了一些接口,如果继续使用老版本,您可能会被封号。请使用 新版 源码,docker,或者浏览器脚本** +**我们发在这里的完整包都是测试了的。但是源码更新太快,开发者们主要是自己使用,主要精力都放在对源码和 docker 运行上。所以经常出现旧的打包不兼容最新代码的情况。** + +比如 v20211025-v20211030 的代码出现了问题。有兴趣的同志可以一起解决( https://github.com/TechXueXi/TechXueXi/blob/developing/CONTRIBUTING.md )。不再提供 windows exe 打包版也是因为它不兼容现在的代码。 + +计算机基础薄弱的同志建议用这个浏览器脚本。原理都是 xxqg 网页。 https://github.com/TechXueXi/techxuexi-js + ---- 压缩包有两种。一个是完整包,现在没有最新完整包,一个只有源码。 @@ -72,7 +78,7 @@ TypeError: 'int' object is not subscriptable 也可以在 telegram群下载。 - +**如果您看不懂 windows系统git_pull_一键更新.bat 文件的代码,请不要使用它。**