python-plugins 0.1.5__tar.gz → 0.1.6__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. {python_plugins-0.1.5 → python_plugins-0.1.6}/CHANGES.rst +7 -0
  2. {python_plugins-0.1.5 → python_plugins-0.1.6}/PKG-INFO +1 -1
  3. python_plugins-0.1.6/src/python_plugins/__about__.py +1 -0
  4. python_plugins-0.1.6/src/python_plugins/weixin/format_response.py +148 -0
  5. {python_plugins-0.1.5 → python_plugins-0.1.6}/src/python_plugins/weixin/wechat.py +55 -48
  6. python_plugins-0.1.5/src/python_plugins/__about__.py +0 -1
  7. {python_plugins-0.1.5 → python_plugins-0.1.6}/.github/workflows/release.yml +0 -0
  8. {python_plugins-0.1.5 → python_plugins-0.1.6}/.gitignore +0 -0
  9. {python_plugins-0.1.5 → python_plugins-0.1.6}/.readthedocs.yaml +0 -0
  10. {python_plugins-0.1.5 → python_plugins-0.1.6}/LICENSE.rst +0 -0
  11. {python_plugins-0.1.5 → python_plugins-0.1.6}/README.rst +0 -0
  12. {python_plugins-0.1.5 → python_plugins-0.1.6}/docs/Makefile +0 -0
  13. {python_plugins-0.1.5 → python_plugins-0.1.6}/docs/api.rst +0 -0
  14. {python_plugins-0.1.5 → python_plugins-0.1.6}/docs/changes.rst +0 -0
  15. {python_plugins-0.1.5 → python_plugins-0.1.6}/docs/conf.py +0 -0
  16. {python_plugins-0.1.5 → python_plugins-0.1.6}/docs/examples.rst +0 -0
  17. {python_plugins-0.1.5 → python_plugins-0.1.6}/docs/index.rst +0 -0
  18. {python_plugins-0.1.5 → python_plugins-0.1.6}/docs/make.bat +0 -0
  19. {python_plugins-0.1.5 → python_plugins-0.1.6}/docs/requirements.txt +0 -0
  20. {python_plugins-0.1.5 → python_plugins-0.1.6}/docs/usage.rst +0 -0
  21. {python_plugins-0.1.5 → python_plugins-0.1.6}/examples/README.rst +0 -0
  22. {python_plugins-0.1.5 → python_plugins-0.1.6}/pyproject.toml +0 -0
  23. {python_plugins-0.1.5 → python_plugins-0.1.6}/requirements/build.in +0 -0
  24. {python_plugins-0.1.5 → python_plugins-0.1.6}/requirements/test.in +0 -0
  25. {python_plugins-0.1.5 → python_plugins-0.1.6}/src/python_plugins/__init__.py +0 -0
  26. {python_plugins-0.1.5 → python_plugins-0.1.6}/src/python_plugins/convert/__init__.py +0 -0
  27. {python_plugins-0.1.5 → python_plugins-0.1.6}/src/python_plugins/convert/datetime_str.py +0 -0
  28. {python_plugins-0.1.5 → python_plugins-0.1.6}/src/python_plugins/convert/xml.py +0 -0
  29. {python_plugins-0.1.5 → python_plugins-0.1.6}/src/python_plugins/crypto/__init__.py +0 -0
  30. {python_plugins-0.1.5 → python_plugins-0.1.6}/src/python_plugins/crypto/fernet.py +0 -0
  31. {python_plugins-0.1.5 → python_plugins-0.1.6}/src/python_plugins/crypto/str_to_list.py +0 -0
  32. {python_plugins-0.1.5 → python_plugins-0.1.6}/src/python_plugins/dumps/__init__.py +0 -0
  33. {python_plugins-0.1.5 → python_plugins-0.1.6}/src/python_plugins/dumps/postgresql_dump.py +0 -0
  34. {python_plugins-0.1.5 → python_plugins-0.1.6}/src/python_plugins/email/__init__.py +0 -0
  35. {python_plugins-0.1.5 → python_plugins-0.1.6}/src/python_plugins/email/smtp.py +0 -0
  36. {python_plugins-0.1.5 → python_plugins-0.1.6}/src/python_plugins/hashes/__init__.py +0 -0
  37. {python_plugins-0.1.5 → python_plugins-0.1.6}/src/python_plugins/hashes/hash.py +0 -0
  38. {python_plugins-0.1.5 → python_plugins-0.1.6}/src/python_plugins/jwt/__init__.py +0 -0
  39. {python_plugins-0.1.5 → python_plugins-0.1.6}/src/python_plugins/jwt/jwt.py +0 -0
  40. {python_plugins-0.1.5 → python_plugins-0.1.6}/src/python_plugins/models/__init__.py +0 -0
  41. {python_plugins-0.1.5 → python_plugins-0.1.6}/src/python_plugins/models/mixins/__init__.py +0 -0
  42. {python_plugins-0.1.5 → python_plugins-0.1.6}/src/python_plugins/models/mixins/data_mixin.py +0 -0
  43. {python_plugins-0.1.5 → python_plugins-0.1.6}/src/python_plugins/models/mixins/primary_key_mixin.py +0 -0
  44. {python_plugins-0.1.5 → python_plugins-0.1.6}/src/python_plugins/models/mixins/timestamp_mixin.py +0 -0
  45. {python_plugins-0.1.5 → python_plugins-0.1.6}/src/python_plugins/models/mixins/token_minxin.py +0 -0
  46. {python_plugins-0.1.5 → python_plugins-0.1.6}/src/python_plugins/models/mixins/user_minxin.py +0 -0
  47. {python_plugins-0.1.5 → python_plugins-0.1.6}/src/python_plugins/models/update.py +0 -0
  48. {python_plugins-0.1.5 → python_plugins-0.1.6}/src/python_plugins/process/__init__.py +0 -0
  49. {python_plugins-0.1.5 → python_plugins-0.1.6}/src/python_plugins/process/python_venv_process.py +0 -0
  50. {python_plugins-0.1.5 → python_plugins-0.1.6}/src/python_plugins/process/sub_process.py +0 -0
  51. {python_plugins-0.1.5 → python_plugins-0.1.6}/src/python_plugins/random/__init__.py +0 -0
  52. {python_plugins-0.1.5 → python_plugins-0.1.6}/src/python_plugins/random/random_str.py +0 -0
  53. {python_plugins-0.1.5 → python_plugins-0.1.6}/src/python_plugins/utils/remove_pycache.py +0 -0
  54. {python_plugins-0.1.5 → python_plugins-0.1.6}/src/python_plugins/weixin/biz_data_crypt.py +0 -0
  55. {python_plugins-0.1.5 → python_plugins-0.1.6}/src/python_plugins/weixin/error_code.py +0 -0
  56. {python_plugins-0.1.5 → python_plugins-0.1.6}/src/python_plugins/weixin/wechat_crypt.py +0 -0
  57. {python_plugins-0.1.5 → python_plugins-0.1.6}/src/python_plugins/weixin/weixin_api.py +0 -0
  58. {python_plugins-0.1.5 → python_plugins-0.1.6}/tests/__init__.py +0 -0
  59. {python_plugins-0.1.5 → python_plugins-0.1.6}/tests/conftest.py +0 -0
  60. {python_plugins-0.1.5 → python_plugins-0.1.6}/tests/test_crypto.py +0 -0
  61. {python_plugins-0.1.5 → python_plugins-0.1.6}/tests/test_email.py +0 -0
  62. {python_plugins-0.1.5 → python_plugins-0.1.6}/tests/test_jwt.py +0 -0
  63. {python_plugins-0.1.5 → python_plugins-0.1.6}/tests/test_process.py +0 -0
  64. {python_plugins-0.1.5 → python_plugins-0.1.6}/tests/test_random.py +0 -0
  65. {python_plugins-0.1.5 → python_plugins-0.1.6}/tests/test_sqlalchemy.py +0 -0
  66. {python_plugins-0.1.5 → python_plugins-0.1.6}/tests/test_utils.py +0 -0
  67. {python_plugins-0.1.5 → python_plugins-0.1.6}/tests/test_weixin.py +0 -0
@@ -1,3 +1,10 @@
1
+ v0.1.6
2
+ ------
3
+
4
+ Released 2024-10-01
5
+
6
+ - weixin.wechat support news
7
+
1
8
  v0.1.5
2
9
  ------
3
10
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: python-plugins
3
- Version: 0.1.5
3
+ Version: 0.1.6
4
4
  Summary: A collection of Python functions and classes.
5
5
  Project-URL: Documentation, https://python-plugins.readthedocs.io
6
6
  Project-URL: Source, https://github.com/ojso/python-plugins
@@ -0,0 +1 @@
1
+ __version__ = "0.1.6"
@@ -0,0 +1,148 @@
1
+ import time
2
+
3
+ # see https://developers.weixin.qq.com/doc/offiaccount/Message_Management/Passive_user_reply_message.html
4
+
5
+ # 文本消息
6
+ # Content 回复的消息内容(换行:在content中能够换行\n,支持超链接<a href="url">xxx</a>),
7
+ XML_TEXT_TEMPLATE = """<xml>
8
+ <ToUserName><![CDATA[{toUser}]]></ToUserName>
9
+ <FromUserName><![CDATA[{fromUser}]]></FromUserName>
10
+ <CreateTime>{createtime}</CreateTime>
11
+ <MsgType><![CDATA[text]]></MsgType>
12
+ <Content><![CDATA[{content}]]></Content>
13
+ </xml>"""
14
+
15
+
16
+ # 图片消息
17
+ # MediaId 是 通过素材管理中的接口上传多媒体文件,得到的id
18
+ XML_IMAGE_TEMPLATE = """<xml>
19
+ <ToUserName><![CDATA [{toUser}]]></ToUserName>
20
+ <FromUserName><![CDATA[{fromUser}]]></FromUserName>
21
+ <CreateTime>{createtime}</CreateTime>
22
+ <MsgType><![CDATA[image]]></MsgType>
23
+ <Image>
24
+ <MediaId><![CDATA[{media_id}]]></MediaId>
25
+ </Image>
26
+ </xml>"""
27
+
28
+
29
+ # 语音消息
30
+ # MediaId 是 通过素材管理中的接口上传多媒体文件,得到的id
31
+
32
+ XML_VOICE_TEMPLATE = """<xml>
33
+ <ToUserName><![CDATA [{toUser}]]></ToUserName>
34
+ <FromUserName><![CDATA[{fromUser}]]></FromUserName>
35
+ <CreateTime>{createtime}</CreateTime>
36
+ <MsgType><![CDATA[voice]]></MsgType>
37
+ <Voice>
38
+ <MediaId><![CDATA[{media_id}]]></MediaId>
39
+ </Voice>
40
+ </xml>"""
41
+
42
+ # 视频消息
43
+ # MediaId 是 通过素材管理中的接口上传多媒体文件,得到的id
44
+ # Title 否 视频消息的标题
45
+ # Description 否 视频消息的描述
46
+
47
+ XML_VIDEO_TEMPLATE = """<xml>
48
+ <ToUserName><![CDATA [{toUser}]]></ToUserName>
49
+ <FromUserName><![CDATA[{fromUser}]]></FromUserName>
50
+ <CreateTime>{createtime}</CreateTime>
51
+ <MsgType><![CDATA[video]]></MsgType>
52
+ <Video>
53
+ <MediaId><![CDATA[{media_id}]]></MediaId>
54
+ <Title><![CDATA[{title}]]></Title>
55
+ <Description><![CDATA[{description}]]></Description>
56
+ </Video>
57
+ </xml>"""
58
+
59
+ # 音乐消息
60
+
61
+ # Title 否 音乐标题
62
+ # Description 否 音乐描述
63
+ # MusicURL 否 音乐链接
64
+ # HQMusicUrl 否 高质量音乐链接,WIFI环境优先使用该链接播放音乐
65
+ # ThumbMediaId 是 缩略图的媒体id,通过素材管理中的接口上传多媒体文件,得到的id
66
+
67
+ XML_MUSIC_TEMPLATE = """<xml>
68
+ <ToUserName><![CDATA [{toUser}]]></ToUserName>
69
+ <FromUserName><![CDATA[{fromUser}]]></FromUserName>
70
+ <CreateTime>{createtime}</CreateTime>
71
+ <MsgType><![CDATA[music]]></MsgType>
72
+ <Music>
73
+ <Title><![CDATA[{title}]]></Title>
74
+ <Description><![CDATA[{description}]]></Description>
75
+ <MusicUrl><![CDATA[{music_url}]]></MusicUrl>
76
+ <HQMusicUrl><![CDATA[{hq_music_url}]]></HQMusicUrl>
77
+ <ThumbMediaId><![CDATA[{media_id}]]></ThumbMediaId>
78
+ </Music>
79
+ </xml>"""
80
+
81
+ # 图文消息
82
+
83
+ # ArticleCount 是 图文消息个数;当用户发送文本、图片、语音、视频、图文、地理位置这六种消息时,开发者只能回复1条图文消息;其余场景最多可回复8条图文消息
84
+ # Articles 是 图文消息信息,注意,如果图文数超过限制,则将只发限制内的条数
85
+ # Title 是 图文消息标题
86
+ # Description 是 图文消息描述
87
+ # PicUrl 是 图片链接,支持JPG、PNG格式,较好的效果为大图360*200,小图200*200
88
+ # Url 是 点击图文消息跳转链接
89
+
90
+ XML_NEWS_TEMPLATE_OLD = """<xml>
91
+ <ToUserName><![CDATA [{toUser}]]></ToUserName>
92
+ <FromUserName><![CDATA[{fromUser}]]></FromUserName>
93
+ <CreateTime>{createtime}</CreateTime>
94
+ <MsgType><![CDATA[news]]></MsgType>
95
+ <ArticleCount>{article_count}</ArticleCount>
96
+ <Articles>
97
+ <item>
98
+ <Title><![CDATA[{title}]]></Title>
99
+ <Description><![CDATA[{description}]]></Description>
100
+ <PicUrl><![CDATA[{pic_url}]]></PicUrl>
101
+ <Url><![CDATA[{url}]]></Url>
102
+ </item>
103
+ </Articles>
104
+ </xml>"""
105
+
106
+ XML_ARTICLE_ITEM = """<item>
107
+ <Title><![CDATA[{title}]]></Title>
108
+ <Description><![CDATA[{description}]]></Description>
109
+ <PicUrl><![CDATA[{pic_url}]]></PicUrl>
110
+ <Url><![CDATA[{url}]]></Url>
111
+ </item>"""
112
+
113
+ XML_NEWS_TEMPLATE = """<xml>
114
+ <ToUserName><![CDATA [{toUser}]]></ToUserName>
115
+ <FromUserName><![CDATA[{fromUser}]]></FromUserName>
116
+ <CreateTime>{createtime}</CreateTime>
117
+ <MsgType><![CDATA[news]]></MsgType>
118
+ <ArticleCount>{article_count}</ArticleCount>
119
+ <Articles>
120
+ {xml_articles_items}
121
+ </Articles>
122
+ </xml>"""
123
+
124
+
125
+ def get_wechat_xml_response(data):
126
+ if "createtime" not in data:
127
+ data["createtime"] = int(time.time())
128
+
129
+ match data["type"]:
130
+ case "text":
131
+ xml = XML_TEXT_TEMPLATE.format(**data)
132
+ case "image":
133
+ xml = XML_IMAGE_TEMPLATE.format(**data)
134
+ case "voice":
135
+ xml = XML_VOICE_TEMPLATE.format(**data)
136
+ case "video":
137
+ xml = XML_VIDEO_TEMPLATE.format(**data)
138
+ case "music":
139
+ xml = XML_MUSIC_TEMPLATE.format(**data)
140
+ case "news":
141
+ xml_articles_items = ""
142
+ for article in data["articles"]:
143
+ xml_articles_items += XML_ARTICLE_ITEM.format(**article)
144
+ data["article_count"] = len(data["articles"])
145
+ data["xml_articles_items"] = xml_articles_items
146
+ xml = XML_NEWS_TEMPLATE.format(**data)
147
+
148
+ return xml
@@ -1,15 +1,7 @@
1
1
  import hashlib
2
- import time
3
2
  from python_plugins.convert import xml2dict
4
3
  from .wechat_crypt import MessageCrypt
5
-
6
- XML_TEXT_TEMPLATE = """<xml>
7
- <ToUserName><![CDATA[{touser}]]></ToUserName>
8
- <FromUserName><![CDATA[{fromuser}]]></FromUserName>
9
- <CreateTime>{createtime}</CreateTime>
10
- <MsgType><![CDATA[text]]></MsgType>
11
- <Content><![CDATA[{content}]]></Content>
12
- </xml>"""
4
+ from .format_response import get_wechat_xml_response
13
5
 
14
6
 
15
7
  class Wechat:
@@ -32,7 +24,7 @@ class Wechat:
32
24
  nonce = args["nonce"]
33
25
  echostr = args["echostr"]
34
26
  token = self.app["token"]
35
- tmpstr = "".join(sorted([token, timestamp, nonce])).encode("utf8")
27
+ tmpstr = "".join(sorted([token, timestamp, nonce])).encode()
36
28
  if hashlib.sha1(tmpstr).hexdigest() == signature:
37
29
  return echostr
38
30
  else:
@@ -48,41 +40,37 @@ class Wechat:
48
40
  xml_dict = xml2dict(content)
49
41
 
50
42
  if not encrypt_type:
51
- # 未加密
52
- result = self.dispatch(xml_dict)
53
- xml_reponse = self.responseText(result)
43
+ self.input = xml_dict
44
+ self.dispatch()
45
+ self.get_xml_response()
46
+ xml_reponse = self.xml_response
54
47
  else:
55
- # 解密
48
+ # decrypt
56
49
  mc = MessageCrypt(self.app["appid"], self.app["token"], self.app["aeskey"])
57
50
  xml_decrypted = mc.decrypt_msg(
58
51
  timestamp, nonce, xml_dict["Encrypt"], msg_signature
59
52
  )
60
- decrypted_dict = xml2dict(xml_decrypted)
61
- result = self.dispatch(decrypted_dict)
62
- unencrypted_xml = self.responseText(result)
63
- # 加密
64
- xml_reponse = mc.encrypt_msg(unencrypted_xml, timestamp, nonce)
53
+ self.input = xml2dict(xml_decrypted)
54
+ self.dispatch()
55
+ self.get_xml_response()
56
+ # encrypt
57
+ xml_reponse = mc.encrypt_msg(self.xml_response, timestamp, nonce)
65
58
 
66
- return xml_reponse
59
+ # 返回前记录下日志,如果实现记录日志的话
60
+ self.log_data()
67
61
 
68
- def responseText(self, content):
69
- data = {
70
- "touser": self.fromUser,
71
- "fromuser": self.toUser,
72
- "createtime": int(time.time()),
73
- "content": content,
74
- }
75
- return XML_TEXT_TEMPLATE.format(**data)
62
+ return xml_reponse
76
63
 
77
- def dispatch(self, data):
78
- self.toUser = data["ToUserName"]
79
- self.fromUser = data["FromUserName"]
80
- msgType = data["MsgType"]
64
+ def dispatch(self):
65
+ self.default_answer()
66
+ self.toUser = self.input["ToUserName"]
67
+ self.fromUser = self.input["FromUserName"]
68
+ msgType = self.input["MsgType"]
81
69
 
82
70
  if msgType == "text":
83
- keyword = data["Content"]
71
+ keyword = self.input["Content"]
84
72
  elif msgType == "event":
85
- event = data["Event"]
73
+ event = self.input["Event"]
86
74
  if event == "subscribe":
87
75
  # self.onSubscribe()
88
76
  keyword = "subscribe"
@@ -90,7 +78,7 @@ class Wechat:
90
78
  # self.onUnsubscribe()
91
79
  keyword = "unsubscribe"
92
80
  elif event == "CLICK":
93
- eventKey = data["EventKey"]
81
+ eventKey = self.input["EventKey"]
94
82
  keyword = eventKey
95
83
  else:
96
84
  keyword = "<event:{event}>"
@@ -103,8 +91,8 @@ class Wechat:
103
91
  elif msgType == "shortvideo":
104
92
  keyword = f"<{msgType}>"
105
93
  elif msgType == "location":
106
- location_x = data["location_x"]
107
- location_y = data["location_y"]
94
+ location_x = self.input["location_x"]
95
+ location_y = self.input["location_y"]
108
96
  keyword = f"<{msgType}({location_x},{location_y})>"
109
97
  elif msgType == "link":
110
98
  keyword = f"<{msgType}>"
@@ -112,17 +100,36 @@ class Wechat:
112
100
  keyword = f"<{msgType}>"
113
101
 
114
102
  self.keyword = keyword
115
- answer = self.answer()
116
103
 
117
- # 返回前记录下日志,如果实现记录日志的话
118
- self.log_data()
104
+ self.get_answer()
105
+ return
119
106
 
120
- return self.responseText(answer)
107
+ def default_answer(self):
108
+ self.answer = {"type": "text", "content": "I'm sorry, I don't understand."}
109
+ return
121
110
 
122
- def answer(self):
123
- q = self.keyword
124
- if q == "subscribe":
125
- r = f"您好,欢迎关注[{self.app['name']}]!"
126
- else:
127
- r = q
128
- return r
111
+ def get_answer(self):
112
+ match self.keyword:
113
+ case "subscribe":
114
+ r = f"Hello, welcome to {self.app['name']}!"
115
+ self.answer = {"type": "text", "content": r}
116
+ case _:
117
+ self.answer = {"type": "text", "content": "a:" + self.keyword}
118
+ return
119
+
120
+ def get_xml_response(self):
121
+ self.data_response = {
122
+ "type" : self.answer["type"],
123
+ "toUser": self.fromUser,
124
+ "fromUser": self.toUser,
125
+ }
126
+
127
+ match self.answer["type"]:
128
+ case "text":
129
+ self.data_response["content"] = self.answer["content"]
130
+ case "news":
131
+ self.data_response["articles"] = self.answer["articles"]
132
+
133
+ self.xml_response = get_wechat_xml_response(self.data_response)
134
+
135
+ return
@@ -1 +0,0 @@
1
- __version__ = "0.1.5"