pten 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
pten-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,15 @@
1
+ Metadata-Version: 1.2
2
+ Name: pten
3
+ Version: 0.1.0
4
+ Summary: A tool to use Wework API quickly and easily
5
+ Home-page: https://github.com/bendell02/pten
6
+ Author: PENGyong
7
+ Author-email: 1203029076@qq.com
8
+ License: UNKNOWN
9
+ Description: UNKNOWN
10
+ Keywords: wework qywx wechat weixin robot app
11
+ Platform: UNKNOWN
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Operating System :: OS Independent
15
+ Requires-Python: >=3.8
pten-0.1.0/README.md ADDED
@@ -0,0 +1,12 @@
1
+ # pten
2
+
3
+ 方便快捷地使用企业微信API.
4
+
5
+ ## 安装
6
+
7
+ ```bash
8
+ pip install pten
9
+ ```
10
+
11
+
12
+
pten-0.1.0/setup.cfg ADDED
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
pten-0.1.0/setup.py ADDED
@@ -0,0 +1,27 @@
1
+ from setuptools import setup, find_packages
2
+
3
+ requires = ["apscheduler", "lunardate", "openai", "pycryptodome"]
4
+ test_requirements = [
5
+ "pytest>=3",
6
+ "pytest-mock>=3",
7
+ ]
8
+
9
+ setup(
10
+ name="pten",
11
+ version="0.1.0",
12
+ description="A tool to use Wework API quickly and easily",
13
+ author="PENGyong",
14
+ author_email="1203029076@qq.com",
15
+ url="https://github.com/bendell02/pten",
16
+ packages=find_packages(where="src"),
17
+ package_dir={"": "src"},
18
+ python_requires=">=3.8",
19
+ install_requires=requires,
20
+ tests_require=test_requirements,
21
+ classifiers=[
22
+ "Programming Language :: Python :: 3",
23
+ "License :: OSI Approved :: MIT License",
24
+ "Operating System :: OS Independent",
25
+ ],
26
+ keywords="wework qywx wechat weixin robot app",
27
+ )
@@ -0,0 +1,45 @@
1
+ import logging
2
+ from logging.handlers import RotatingFileHandler
3
+
4
+ logger = logging.getLogger(__name__)
5
+ logger.setLevel(logging.INFO)
6
+
7
+
8
+ class LogColors:
9
+ DEBUG = "\033[96m"
10
+ INFO = "\033[92m"
11
+ WARNING = "\033[93m"
12
+ ERROR = "\033[91m"
13
+ CRITICAL = "\033[41m\033[97m"
14
+ RESET = "\033[0m"
15
+
16
+
17
+ class ColoredFormatter(logging.Formatter):
18
+ def format(self, record):
19
+ level_color = {
20
+ logging.DEBUG: LogColors.DEBUG,
21
+ logging.INFO: LogColors.INFO,
22
+ logging.WARNING: LogColors.WARNING,
23
+ logging.ERROR: LogColors.ERROR,
24
+ logging.CRITICAL: LogColors.CRITICAL,
25
+ }.get(record.levelno, LogColors.RESET)
26
+
27
+ message = super().format(record)
28
+ return f"{level_color}{message}{LogColors.RESET}"
29
+
30
+
31
+ formatter = ColoredFormatter(
32
+ "%(asctime)s | %(levelname)s | %(filename)s:%(lineno)d:%(funcName)s | %(message)s"
33
+ )
34
+ console_handler = logging.StreamHandler()
35
+ console_handler.setFormatter(formatter)
36
+
37
+ formatter = logging.Formatter(
38
+ "%(asctime)s | %(levelname)s | %(filename)s:%(lineno)d:%(funcName)s | %(message)s"
39
+ )
40
+ maxBytes = 30 * 1024 * 1024 # 30MB
41
+ file_handler = RotatingFileHandler(__name__ + ".log", maxBytes=maxBytes, backupCount=3)
42
+ file_handler.setFormatter(formatter)
43
+
44
+ logger.addHandler(file_handler)
45
+ logger.addHandler(console_handler)
@@ -0,0 +1,205 @@
1
+ """
2
+ pten.keys
3
+ ~~~~~~~~~~~~
4
+
5
+ This module implements the keys class for getting keys from local file.
6
+
7
+ """
8
+
9
+ from . import logger
10
+ import configparser
11
+ from configparser import ConfigParser
12
+ from datetime import datetime
13
+ import json
14
+ from pathlib import Path
15
+
16
+
17
+ class Keys:
18
+ """Keys class for getting keys from local file
19
+
20
+ :param keys_filepath: The path of the keys file. Default is "pten_keys.ini"
21
+ """
22
+
23
+ def __init__(self, keys_filepath="pten_keys.ini", *args, **kwargs):
24
+ self.key_cfg = ConfigParser()
25
+ self.keys_filepath = Path(keys_filepath)
26
+ self.TOKEN_PATH = Path("pten_token.json")
27
+ self.bot_weebhook_key = None
28
+ self.access_token = None
29
+ self.access_token_expire_time = float("-inf")
30
+ self.corp_jsapi_ticket = None
31
+ self.corp_jsapi_ticket_expire_time = float("-inf")
32
+ self.app_jsapi_ticket = None
33
+ self.app_jsapi_ticket_expire_time = float("-inf")
34
+
35
+ logger.info(f"keys_filepath : {self.keys_filepath}")
36
+
37
+ if not self.keys_filepath.is_file():
38
+ logger.error(f"Can not find file {self.keys_filepath}")
39
+
40
+ def _get_local_keys(self, section: str, options=[]):
41
+ """Get keys from local file
42
+ :param section: Section name of the keys
43
+ :param options: The keys you want to get
44
+ :return: generator of the keys
45
+ """
46
+ if self.keys_filepath.is_file():
47
+ self.key_cfg.clear()
48
+ self.key_cfg.read(self.keys_filepath)
49
+ try:
50
+ for option in options:
51
+ yield self.key_cfg.get(section, option)
52
+ except (configparser.NoSectionError, configparser.NoOptionError):
53
+ raise configparser.Error("KeyConfigError")
54
+ else:
55
+ raise FileNotFoundError(f"Can not find file {self.keys_filepath}")
56
+
57
+ def get_key(self, section: str, option: str):
58
+ """Get keys from local file
59
+ :param section: Section name of the keys
60
+ :param option: The key you want to get
61
+ :return: key value
62
+ """
63
+ return next(self._get_local_keys(section, [option]))
64
+
65
+ def get_keys(self, section: str, options=[]):
66
+ """Get keys from local file
67
+ :param section: Section name of the keys
68
+ :param options: The keys you want to get
69
+ :return: dict of the keys
70
+ """
71
+ res = {}
72
+ for k, v in zip(options, self._get_local_keys(section, options)):
73
+ res.update({k: v})
74
+ return res
75
+
76
+ def get_debug_mode(self):
77
+ debug_mode_str = "false"
78
+ try:
79
+ debug_mode_str = self.get_key("globals", "debug_mode")
80
+ except (configparser.Error, FileNotFoundError):
81
+ debug_mode_str = "false"
82
+
83
+ debug_mode = debug_mode_str.lower() in ("true", "yes", "on", "1")
84
+ return debug_mode
85
+
86
+ def get_proxies(self):
87
+ proxies = None
88
+ try:
89
+ proxies = self.get_keys(section="proxies", options=["http", "https"])
90
+ except (configparser.Error, FileNotFoundError):
91
+ pass
92
+
93
+ return proxies
94
+
95
+ def get_bot_weebhook_key(self):
96
+ if self.bot_weebhook_key is None:
97
+ key = next(self._get_local_keys(section="bot", options=["webhook_key"]))
98
+ self.bot_weebhook_key = key
99
+
100
+ return self.bot_weebhook_key
101
+
102
+ def get_app_agentid(self):
103
+ return next(self._get_local_keys(section="app", options=["agentid"]))
104
+
105
+ def get_contact_sync_secret(self):
106
+ try:
107
+ s = self.get_key("wwapi", "contact_sync_secret")
108
+ except StopIteration:
109
+ logger.warning("Can not find contact_sync_secret in keys ini file")
110
+ s = None
111
+ return s
112
+
113
+ @staticmethod
114
+ def load_from_file(file_path: Path, key):
115
+ if not file_path.is_file():
116
+ raise FileNotFoundError(f"Can not find file {file_path}.")
117
+
118
+ dict = json.loads(file_path.read_text())
119
+ if key not in dict:
120
+ raise KeyError(f"Can not find token of {key}.")
121
+
122
+ return dict[key]
123
+
124
+ @staticmethod
125
+ def save_to_file(file_path: Path, key, info):
126
+ token_dict = {}
127
+ if file_path.is_file():
128
+ token_dict = json.loads(file_path.read_text())
129
+
130
+ token_dict.update({key: info})
131
+ file_path.write_text(json.dumps(token_dict))
132
+
133
+ def get_access_token(self, token_key):
134
+ if self.access_token_expire_time > datetime.now().timestamp():
135
+ return self.access_token
136
+
137
+ token_info = Keys.load_from_file(self.TOKEN_PATH, token_key)
138
+ self.access_token_expire_time = token_info.get("expire_time", float("-inf"))
139
+ if self.access_token_expire_time < datetime.now().timestamp():
140
+ logger.warning(f"Token of {token_key} is expired.")
141
+ raise Exception("Token expired")
142
+
143
+ self.access_token = token_info["access_token"]
144
+ return self.access_token
145
+
146
+ def save_access_token(self, token_key, access_token):
147
+ self.access_token = access_token
148
+ self.access_token_expire_time = datetime.now().timestamp() + 7200
149
+ token_info = {
150
+ "access_token": access_token,
151
+ "expire_time": self.access_token_expire_time,
152
+ }
153
+ Keys.save_to_file(self.TOKEN_PATH, token_key, token_info)
154
+
155
+ def get_corp_jsapi_ticket(self, token_key):
156
+ now = datetime.now().timestamp()
157
+ if self.corp_jsapi_ticket_expire_time > now:
158
+ return self.corp_jsapi_ticket
159
+
160
+ ticket_key = token_key + "_corp_jsapi_ticket"
161
+ ticket_info = Keys.load_from_file(self.TOKEN_PATH, ticket_key)
162
+ expire_time = ticket_info.get("expire_time", float("-inf"))
163
+ self.corp_jsapi_ticket_expire_time = expire_time
164
+ if expire_time < now:
165
+ logger.warning(f"Ticket of {ticket_key} is expired.")
166
+ raise Exception("Ticket expired")
167
+
168
+ self.corp_jsapi_ticket = ticket_info["corp_jsapi_ticket"]
169
+ return self.corp_jsapi_ticket
170
+
171
+ def save_corp_jsapi_ticket(self, token_key, corp_jsapi_ticket):
172
+ ticket_key = token_key + "_corp_jsapi_ticket"
173
+ self.corp_jsapi_ticket = corp_jsapi_ticket
174
+ self.corp_jsapi_ticket_expire_time = datetime.now().timestamp() + 7200
175
+ ticket_info = {
176
+ "corp_jsapi_ticket": corp_jsapi_ticket,
177
+ "expire_time": self.corp_jsapi_ticket_expire_time,
178
+ }
179
+ Keys.save_to_file(self.TOKEN_PATH, ticket_key, ticket_info)
180
+
181
+ def get_app_jsapi_ticket(self, token_key):
182
+ now = datetime.now().timestamp()
183
+ if self.app_jsapi_ticket_expire_time > now:
184
+ return self.app_jsapi_ticket
185
+
186
+ ticket_key = token_key + "_app_jsapi_ticket"
187
+ ticket_info = Keys.load_from_file(self.TOKEN_PATH, ticket_key)
188
+ expire_time = ticket_info.get("expire_time", float("-inf"))
189
+ self.app_jsapi_ticket_expire_time = expire_time
190
+ if expire_time < now:
191
+ logger.warning(f"Ticket of {ticket_key} is expired.")
192
+ raise Exception("Ticket expired")
193
+
194
+ self.app_jsapi_ticket = ticket_info["app_jsapi_ticket"]
195
+ return self.app_jsapi_ticket
196
+
197
+ def save_app_jsapi_ticket(self, token_key, app_jsapi_ticket):
198
+ ticket_key = token_key + "_app_jsapi_ticket"
199
+ self.app_jsapi_ticket = app_jsapi_ticket
200
+ self.app_jsapi_ticket_expire_time = datetime.now().timestamp() + 7200
201
+ ticket_info = {
202
+ "app_jsapi_ticket": app_jsapi_ticket,
203
+ "expire_time": self.app_jsapi_ticket_expire_time,
204
+ }
205
+ Keys.save_to_file(self.TOKEN_PATH, ticket_key, ticket_info)
@@ -0,0 +1,216 @@
1
+ """
2
+ pten.notice
3
+ ~~~~~~~~~~~~
4
+
5
+ This module implements the notice functions.
6
+
7
+ """
8
+
9
+ from . import logger
10
+ from .keys import Keys
11
+ from apscheduler.schedulers.blocking import BaseScheduler
12
+ import datetime
13
+ from lunardate import LunarDate
14
+ from openai import OpenAI
15
+ import requests
16
+ import json
17
+
18
+
19
+ class Notice:
20
+ def __init__(self):
21
+ self.report_func = print
22
+ self.scheduler: BaseScheduler = None
23
+
24
+ def set_report_func(self, func):
25
+ self.report_func = func
26
+
27
+ def set_scheduler(self, scheduler: BaseScheduler):
28
+ self.scheduler = scheduler
29
+
30
+ def report_text(self, text):
31
+ self.report_func(text)
32
+
33
+
34
+ class Birthday(Notice):
35
+ def __init__(self):
36
+ super().__init__()
37
+
38
+ @staticmethod
39
+ def get_date_str_from_lunar_date(
40
+ lunar_year, lunar_month, lunar_day, hour=8, minute=3, is_leap_month=False
41
+ ):
42
+ lunar_date = LunarDate(lunar_year, lunar_month, lunar_day, is_leap_month)
43
+ solar_date = lunar_date.toSolarDate()
44
+ return f"{solar_date} {hour:02d}:{minute:02d}:00"
45
+
46
+ @staticmethod
47
+ def is_leap_month(lunar_year, lunar_month):
48
+ return lunar_month == LunarDate.leapMonthForYear(lunar_year)
49
+
50
+ @staticmethod
51
+ def get_now_lunar_date():
52
+ now_solar_date = datetime.datetime.now()
53
+ year = now_solar_date.year
54
+ month = now_solar_date.month
55
+ day = now_solar_date.day
56
+ now_lunar_date = LunarDate.fromSolarDate(year, month, day)
57
+ return now_lunar_date
58
+
59
+ @staticmethod
60
+ def generate_birthday_greeting(who, greeting_words=None):
61
+ msg = f"今天是{who}的生日,让我们来祝福{who}吧"
62
+ if greeting_words:
63
+ msg = f"今天是{who}的生日,{greeting_words}"
64
+
65
+ return msg
66
+
67
+ def _add_lunar_schedule(
68
+ self, msg, lunar_year, lunar_month, lunar_day, hour, minute
69
+ ):
70
+ run_date = self.get_date_str_from_lunar_date(
71
+ lunar_year, lunar_month, lunar_day, hour, minute, False
72
+ )
73
+ args = [msg, lunar_month, lunar_day, hour, minute]
74
+ func = self.report_lunar_birthday
75
+ self.scheduler.add_job(func, "date", run_date=run_date, args=args)
76
+
77
+ # solve leap month
78
+ if not self.is_leap_month(lunar_year, lunar_month):
79
+ return
80
+ run_date = self.get_date_str_from_lunar_date(
81
+ lunar_year, lunar_month, lunar_day, hour, minute, True
82
+ )
83
+ self.scheduler.add_job(func, "date", run_date=run_date, args=args)
84
+
85
+ def _add_solar_schedule(self, msg, year, month, day, hour, minute):
86
+ solar_date = datetime.date(year, month, day)
87
+ run_date = f"{solar_date} {hour:02d}:{minute:02d}:00"
88
+ args = [msg, month, day, hour, minute]
89
+ func = self.report_solar_birthday
90
+ self.scheduler.add_job(func, "date", run_date=run_date, args=args)
91
+
92
+ def report_lunar_birthday(self, msg, lunar_month, lunar_day, hour=8, minute=3):
93
+ self.report_func(msg)
94
+
95
+ if self.scheduler is None:
96
+ return
97
+
98
+ # add next year's schedule job
99
+ now_lunar_date = self.get_now_lunar_date()
100
+ self._add_lunar_schedule(
101
+ msg, now_lunar_date.year + 1, lunar_month, lunar_day, hour, minute
102
+ )
103
+
104
+ def report_solar_birthday(self, msg, month, day, hour=8, minute=3):
105
+ self.report_func(msg)
106
+
107
+ if self.scheduler is None:
108
+ return
109
+
110
+ # add next year's schedule job
111
+ today = datetime.date.today()
112
+ next_year = today.year + 1
113
+ self._add_solar_schedule(msg, next_year, month, day, hour, minute)
114
+
115
+ def add_lunar_schedule(
116
+ self,
117
+ lunar_month,
118
+ lunar_day,
119
+ hour=8,
120
+ minute=3,
121
+ who="someone",
122
+ greeting_words=None,
123
+ ):
124
+ if self.scheduler is None:
125
+ logger.error("Scheduler is not set. Please set_scheduler() first.")
126
+ return
127
+
128
+ msg = self.generate_birthday_greeting(who, greeting_words)
129
+
130
+ now_lunar_date = self.get_now_lunar_date()
131
+ lunar_date = LunarDate(now_lunar_date.year, lunar_month, lunar_day)
132
+ if lunar_date < LunarDate.today():
133
+ lunar_date = LunarDate(now_lunar_date.year + 1, lunar_month, lunar_day)
134
+
135
+ self._add_lunar_schedule(
136
+ msg, lunar_date.year, lunar_month, lunar_day, hour, minute
137
+ )
138
+
139
+ def add_solar_schedule(
140
+ self, month, day, hour=8, minute=3, who="someone", greeting_words=None
141
+ ):
142
+ if self.scheduler is None:
143
+ logger.error("Scheduler is not set. Please set_scheduler() first.")
144
+ return
145
+
146
+ msg = self.generate_birthday_greeting(who, greeting_words)
147
+
148
+ now = datetime.datetime.now()
149
+ solar_date = datetime.date(now.year, month, day)
150
+ if solar_date < now.date():
151
+ solar_date = solar_date.replace(year=now.year + 1)
152
+
153
+ self._add_solar_schedule(msg, solar_date.year, month, day, hour, minute)
154
+
155
+
156
+ class Deepseek(Notice):
157
+ def __init__(self, keys_filepath="pten_keys.ini", **kwargs):
158
+ super().__init__()
159
+ self.keys = Keys(keys_filepath)
160
+ sk_api_key = self.keys.get_key("ai", "deepseek_api_key")
161
+ self.deepseek_client = OpenAI(
162
+ api_key=sk_api_key,
163
+ base_url="https://api.deepseek.com",
164
+ )
165
+
166
+ def get_completion(self, prompt):
167
+ response = self.deepseek_client.chat.completions.create(
168
+ model="deepseek-chat",
169
+ messages=[
170
+ {"role": "system", "content": "You are a helpful assistant"},
171
+ {"role": "user", "content": prompt},
172
+ ],
173
+ stream=False,
174
+ )
175
+
176
+ return response.choices[0].message.content
177
+
178
+
179
+ class Weather(Notice):
180
+ def __init__(self, keys_filepath="pten_keys.ini", **kwargs):
181
+ super().__init__()
182
+ self.keys = Keys(keys_filepath)
183
+ self.api_key = self.keys.get_key("weather", "seniverse_api_key")
184
+ self.cities = {}
185
+
186
+ def add_city(self, city_name, city_code):
187
+ self.cities[city_name] = city_code
188
+
189
+ def get_weather(self, city_code):
190
+ url = f"https://api.seniverse.com/v3/weather/now.json?key={self.api_key}&location={city_code}&language=zh-Hans&unit=c"
191
+
192
+ response = requests.get(url)
193
+ weather_json = json.loads(response.text)
194
+ weather = weather_json["results"][0]["now"]
195
+
196
+ return weather
197
+
198
+ def get_city_weather(self, city_name, city_code):
199
+ weather = self.get_weather(city_code)
200
+
201
+ weather_text = weather.get("text")
202
+ weather_temperature = weather.get("temperature")
203
+
204
+ weather_str = f"{city_name}: {weather_text},\t 温度: {weather_temperature}度"
205
+
206
+ return weather_str
207
+
208
+ def report_weather(self):
209
+ if not self.cities:
210
+ logger.warning("No city added. Please add_city() first.")
211
+ return
212
+ weather_str = "今日天气:"
213
+ for city_name, city_code in self.cities.items():
214
+ weather_str += "\n" + self.get_city_weather(city_name, city_code)
215
+
216
+ self.report_func(weather_str)