yee88 0.7.1__py3-none-any.whl → 0.9.0__py3-none-any.whl
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.
- yee88/cron/manager.py +12 -18
- yee88/cron/models.py +3 -0
- yee88/cron/scheduler.py +105 -12
- yee88/cron/watch.py +54 -0
- yee88/telegram/loop.py +81 -9
- {yee88-0.7.1.dist-info → yee88-0.9.0.dist-info}/METADATA +1 -1
- {yee88-0.7.1.dist-info → yee88-0.9.0.dist-info}/RECORD +10 -9
- {yee88-0.7.1.dist-info → yee88-0.9.0.dist-info}/WHEEL +0 -0
- {yee88-0.7.1.dist-info → yee88-0.9.0.dist-info}/entry_points.txt +0 -0
- {yee88-0.7.1.dist-info → yee88-0.9.0.dist-info}/licenses/LICENSE +0 -0
yee88/cron/manager.py
CHANGED
|
@@ -19,37 +19,32 @@ class CronManager:
|
|
|
19
19
|
def _validate_project(self, project: str) -> None:
|
|
20
20
|
if not project:
|
|
21
21
|
return
|
|
22
|
-
|
|
22
|
+
|
|
23
23
|
path = Path(project).expanduser().resolve()
|
|
24
24
|
if path.exists() and path.is_dir():
|
|
25
25
|
return
|
|
26
|
-
|
|
26
|
+
|
|
27
27
|
from ..settings import load_settings_if_exists
|
|
28
28
|
from ..engines import list_backend_ids
|
|
29
|
-
|
|
29
|
+
|
|
30
30
|
result = load_settings_if_exists()
|
|
31
31
|
if result is None:
|
|
32
32
|
return
|
|
33
|
-
|
|
33
|
+
|
|
34
34
|
settings, config_path = result
|
|
35
35
|
engine_ids = list_backend_ids()
|
|
36
36
|
projects_config = settings.to_projects_config(
|
|
37
|
-
config_path=config_path,
|
|
38
|
-
engine_ids=engine_ids
|
|
37
|
+
config_path=config_path, engine_ids=engine_ids
|
|
39
38
|
)
|
|
40
|
-
|
|
39
|
+
|
|
41
40
|
if project.lower() in projects_config.projects:
|
|
42
41
|
return
|
|
43
|
-
|
|
42
|
+
|
|
44
43
|
available = list(projects_config.projects.keys())
|
|
45
44
|
if available:
|
|
46
|
-
raise ValueError(
|
|
47
|
-
f"未知项目: {project}。可用项目: {', '.join(available)}"
|
|
48
|
-
)
|
|
45
|
+
raise ValueError(f"未知项目: {project}。可用项目: {', '.join(available)}")
|
|
49
46
|
else:
|
|
50
|
-
raise ValueError(
|
|
51
|
-
f"未知项目: {project}。请先使用 'yee88 init' 注册项目"
|
|
52
|
-
)
|
|
47
|
+
raise ValueError(f"未知项目: {project}。请先使用 'yee88 init' 注册项目")
|
|
53
48
|
|
|
54
49
|
def load(self):
|
|
55
50
|
if not self.file.exists():
|
|
@@ -59,10 +54,7 @@ class CronManager:
|
|
|
59
54
|
with open(self.file, "rb") as f:
|
|
60
55
|
data = tomllib.load(f)
|
|
61
56
|
|
|
62
|
-
self.jobs = [
|
|
63
|
-
CronJob(**job)
|
|
64
|
-
for job in data.get("jobs", [])
|
|
65
|
-
]
|
|
57
|
+
self.jobs = [CronJob(**job) for job in data.get("jobs", [])]
|
|
66
58
|
|
|
67
59
|
def save(self):
|
|
68
60
|
data = {
|
|
@@ -76,6 +68,8 @@ class CronManager:
|
|
|
76
68
|
"last_run": job.last_run,
|
|
77
69
|
"next_run": job.next_run,
|
|
78
70
|
"one_time": job.one_time,
|
|
71
|
+
"engine": job.engine or "",
|
|
72
|
+
"model": job.model or "",
|
|
79
73
|
}
|
|
80
74
|
for job in self.jobs
|
|
81
75
|
]
|
yee88/cron/models.py
CHANGED
yee88/cron/scheduler.py
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import asyncio
|
|
2
|
-
from
|
|
2
|
+
from datetime import datetime
|
|
3
|
+
from typing import Callable, Awaitable, Set, Dict
|
|
3
4
|
from anyio.abc import TaskGroup
|
|
5
|
+
from anyio import Lock, move_on_after
|
|
4
6
|
from .manager import CronManager
|
|
5
7
|
from .models import CronJob
|
|
6
8
|
from ..logging import get_logger
|
|
@@ -19,6 +21,52 @@ class CronScheduler:
|
|
|
19
21
|
self.callback = callback
|
|
20
22
|
self.task_group = task_group
|
|
21
23
|
self.running = False
|
|
24
|
+
self._running_jobs: Set[str] = set()
|
|
25
|
+
self._job_locks: Dict[str, Lock] = {}
|
|
26
|
+
|
|
27
|
+
def _calculate_next_check(self) -> float:
|
|
28
|
+
now = datetime.now(self.manager.timezone)
|
|
29
|
+
min_sleep = 1.0
|
|
30
|
+
max_sleep = 60.0
|
|
31
|
+
|
|
32
|
+
earliest_next_run = None
|
|
33
|
+
|
|
34
|
+
for job in self.manager.jobs:
|
|
35
|
+
if not job.enabled:
|
|
36
|
+
continue
|
|
37
|
+
|
|
38
|
+
try:
|
|
39
|
+
if job.one_time:
|
|
40
|
+
exec_time = datetime.fromisoformat(job.schedule)
|
|
41
|
+
if exec_time.tzinfo is None:
|
|
42
|
+
exec_time = exec_time.replace(tzinfo=self.manager.timezone)
|
|
43
|
+
if exec_time > now:
|
|
44
|
+
if earliest_next_run is None or exec_time < earliest_next_run:
|
|
45
|
+
earliest_next_run = exec_time
|
|
46
|
+
else:
|
|
47
|
+
if job.next_run:
|
|
48
|
+
next_run = datetime.fromisoformat(job.next_run)
|
|
49
|
+
if next_run.tzinfo is None:
|
|
50
|
+
next_run = next_run.replace(tzinfo=self.manager.timezone)
|
|
51
|
+
else:
|
|
52
|
+
from croniter import croniter
|
|
53
|
+
|
|
54
|
+
itr = croniter(job.schedule, now)
|
|
55
|
+
next_run = itr.get_next(datetime)
|
|
56
|
+
if next_run.tzinfo is None:
|
|
57
|
+
next_run = next_run.replace(tzinfo=self.manager.timezone)
|
|
58
|
+
|
|
59
|
+
if next_run > now:
|
|
60
|
+
if earliest_next_run is None or next_run < earliest_next_run:
|
|
61
|
+
earliest_next_run = next_run
|
|
62
|
+
except Exception:
|
|
63
|
+
continue
|
|
64
|
+
|
|
65
|
+
if earliest_next_run is None:
|
|
66
|
+
return max_sleep
|
|
67
|
+
|
|
68
|
+
seconds_until = (earliest_next_run - now).total_seconds()
|
|
69
|
+
return max(min_sleep, min(seconds_until, max_sleep))
|
|
22
70
|
|
|
23
71
|
async def start(self):
|
|
24
72
|
self.running = True
|
|
@@ -28,29 +76,74 @@ class CronScheduler:
|
|
|
28
76
|
cycle = 0
|
|
29
77
|
while self.running:
|
|
30
78
|
cycle += 1
|
|
31
|
-
self.
|
|
32
|
-
|
|
79
|
+
sleep_seconds = self._calculate_next_check()
|
|
80
|
+
|
|
81
|
+
if sleep_seconds > 5:
|
|
82
|
+
logger.info(
|
|
83
|
+
"cron.scheduler.check_cycle",
|
|
84
|
+
cycle=cycle,
|
|
85
|
+
job_count=len(self.manager.jobs),
|
|
86
|
+
next_check_in=sleep_seconds,
|
|
87
|
+
)
|
|
88
|
+
|
|
33
89
|
due_jobs = self.manager.get_due_jobs()
|
|
34
90
|
|
|
35
91
|
if due_jobs:
|
|
36
|
-
logger.info(
|
|
37
|
-
|
|
38
|
-
|
|
92
|
+
logger.info(
|
|
93
|
+
"cron.scheduler.due_jobs_found",
|
|
94
|
+
cycle=cycle,
|
|
95
|
+
count=len(due_jobs),
|
|
96
|
+
job_ids=[j.id for j in due_jobs],
|
|
97
|
+
)
|
|
98
|
+
elif sleep_seconds <= 5:
|
|
99
|
+
logger.debug("cron.scheduler.no_due_jobs", cycle=cycle)
|
|
39
100
|
|
|
40
101
|
for job in due_jobs:
|
|
41
|
-
logger.info(
|
|
102
|
+
logger.info(
|
|
103
|
+
"cron.scheduler.dispatching_job",
|
|
104
|
+
job_id=job.id,
|
|
105
|
+
message=job.message[:50],
|
|
106
|
+
)
|
|
42
107
|
self.task_group.start_soon(self._run_job_safe, job)
|
|
43
108
|
|
|
44
|
-
logger.info("cron.scheduler.sleeping", cycle=cycle, seconds=
|
|
45
|
-
await asyncio.sleep(
|
|
109
|
+
logger.info("cron.scheduler.sleeping", cycle=cycle, seconds=sleep_seconds)
|
|
110
|
+
await asyncio.sleep(sleep_seconds)
|
|
111
|
+
|
|
112
|
+
def _acquire_job_lock(self, job_id: str) -> bool:
|
|
113
|
+
if job_id in self._running_jobs:
|
|
114
|
+
return False
|
|
115
|
+
|
|
116
|
+
if job_id not in self._job_locks:
|
|
117
|
+
self._job_locks[job_id] = Lock()
|
|
118
|
+
|
|
119
|
+
job_lock = self._job_locks[job_id]
|
|
120
|
+
|
|
121
|
+
if job_lock.locked():
|
|
122
|
+
return False
|
|
123
|
+
|
|
124
|
+
self._running_jobs.add(job_id)
|
|
125
|
+
return True
|
|
126
|
+
|
|
127
|
+
def _release_job_lock(self, job_id: str) -> None:
|
|
128
|
+
self._running_jobs.discard(job_id)
|
|
46
129
|
|
|
47
130
|
async def _run_job_safe(self, job: CronJob) -> None:
|
|
48
|
-
|
|
131
|
+
if not self._acquire_job_lock(job.id):
|
|
132
|
+
logger.warning("cron.job.already_running", job_id=job.id)
|
|
133
|
+
return
|
|
134
|
+
|
|
49
135
|
try:
|
|
50
|
-
|
|
51
|
-
|
|
136
|
+
logger.info("cron.job.executing", job_id=job.id)
|
|
137
|
+
with move_on_after(180):
|
|
138
|
+
await self.callback(job)
|
|
139
|
+
logger.info("cron.job.completed", job_id=job.id)
|
|
140
|
+
return
|
|
141
|
+
|
|
142
|
+
logger.warning("cron.job.timeout", job_id=job.id, timeout_s=180)
|
|
52
143
|
except Exception as exc:
|
|
53
144
|
logger.error("cron.job.failed", job_id=job.id, error=str(exc))
|
|
145
|
+
finally:
|
|
146
|
+
self._release_job_lock(job.id)
|
|
54
147
|
|
|
55
148
|
def stop(self):
|
|
56
149
|
self.running = False
|
yee88/cron/watch.py
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
from typing import Callable, Awaitable, List
|
|
3
|
+
|
|
4
|
+
import anyio
|
|
5
|
+
from watchfiles import watch
|
|
6
|
+
|
|
7
|
+
from ..logging import get_logger
|
|
8
|
+
from .manager import CronManager
|
|
9
|
+
|
|
10
|
+
logger = get_logger()
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
async def watch_cron_config(
|
|
14
|
+
cron_file: Path,
|
|
15
|
+
manager: CronManager,
|
|
16
|
+
on_reload: Callable[[List[str]], Awaitable[None]] | None = None,
|
|
17
|
+
) -> None:
|
|
18
|
+
if not cron_file.exists():
|
|
19
|
+
logger.warning("cron.watch.file_not_found", path=str(cron_file))
|
|
20
|
+
return
|
|
21
|
+
|
|
22
|
+
logger.info("cron.watch.started", path=str(cron_file))
|
|
23
|
+
|
|
24
|
+
try:
|
|
25
|
+
for changes in watch(str(cron_file)):
|
|
26
|
+
for change_type, path in changes:
|
|
27
|
+
if Path(path).name == "cron.toml":
|
|
28
|
+
logger.info(
|
|
29
|
+
"cron.watch.file_changed",
|
|
30
|
+
path=path,
|
|
31
|
+
change_type=change_type.name,
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
changed_jobs = manager.reload_jobs()
|
|
35
|
+
|
|
36
|
+
if changed_jobs:
|
|
37
|
+
logger.info(
|
|
38
|
+
"cron.watch.reloaded",
|
|
39
|
+
changed_count=len(changed_jobs),
|
|
40
|
+
changed_jobs=changed_jobs,
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
if on_reload:
|
|
44
|
+
await on_reload(changed_jobs)
|
|
45
|
+
else:
|
|
46
|
+
logger.debug("cron.watch.no_changes_detected")
|
|
47
|
+
|
|
48
|
+
break
|
|
49
|
+
except anyio.get_cancelled_exc_class():
|
|
50
|
+
logger.info("cron.watch.cancelled")
|
|
51
|
+
raise
|
|
52
|
+
except Exception as exc:
|
|
53
|
+
logger.error("cron.watch.error", error=str(exc))
|
|
54
|
+
raise
|
yee88/telegram/loop.py
CHANGED
|
@@ -1092,15 +1092,57 @@ async def run_main_loop(
|
|
|
1092
1092
|
|
|
1093
1093
|
async def _execute_cron_job(job: CronJob) -> None:
|
|
1094
1094
|
try:
|
|
1095
|
-
|
|
1095
|
+
from ..model import EngineId
|
|
1096
|
+
from ..markdown import MarkdownParts
|
|
1097
|
+
from ..transport import RenderedMessage, SendOptions
|
|
1098
|
+
from .render import prepare_telegram
|
|
1099
|
+
|
|
1100
|
+
context = (
|
|
1101
|
+
RunContext(project=job.project) if job.project else None
|
|
1102
|
+
)
|
|
1103
|
+
engine_override: EngineId | None = (
|
|
1104
|
+
job.engine if job.engine else None
|
|
1105
|
+
)
|
|
1106
|
+
|
|
1107
|
+
header_text = f"⏰ 定时任务开始: {job.id}"
|
|
1108
|
+
if job.project:
|
|
1109
|
+
header_text += f"\n📁 项目: {job.project}"
|
|
1110
|
+
|
|
1111
|
+
rendered_text, entities = prepare_telegram(
|
|
1112
|
+
MarkdownParts(header=header_text)
|
|
1113
|
+
)
|
|
1114
|
+
|
|
1115
|
+
initial_ref = await cfg.exec_cfg.transport.send(
|
|
1116
|
+
channel_id=cfg.chat_id,
|
|
1117
|
+
message=RenderedMessage(
|
|
1118
|
+
text=rendered_text, extra={"entities": entities}
|
|
1119
|
+
),
|
|
1120
|
+
options=SendOptions(
|
|
1121
|
+
notify=True,
|
|
1122
|
+
),
|
|
1123
|
+
)
|
|
1124
|
+
|
|
1125
|
+
if initial_ref is None:
|
|
1126
|
+
logger.error(
|
|
1127
|
+
"cron.initial_message_failed",
|
|
1128
|
+
job_id=job.id,
|
|
1129
|
+
error="Failed to send initial message to Telegram",
|
|
1130
|
+
)
|
|
1131
|
+
return
|
|
1132
|
+
|
|
1096
1133
|
await run_job(
|
|
1097
1134
|
chat_id=cfg.chat_id,
|
|
1098
|
-
user_msg_id=
|
|
1135
|
+
user_msg_id=int(initial_ref.message_id),
|
|
1099
1136
|
text=job.message,
|
|
1100
1137
|
resume_token=None,
|
|
1101
1138
|
context=context,
|
|
1102
|
-
thread_id=
|
|
1139
|
+
thread_id=int(initial_ref.thread_id)
|
|
1140
|
+
if initial_ref.thread_id
|
|
1141
|
+
else None,
|
|
1103
1142
|
force_hide_resume_line=True,
|
|
1143
|
+
force_new_session=True,
|
|
1144
|
+
run_options_model=job.model,
|
|
1145
|
+
engine_override=engine_override,
|
|
1104
1146
|
)
|
|
1105
1147
|
except Exception as exc:
|
|
1106
1148
|
logger.error(
|
|
@@ -1116,6 +1158,18 @@ async def run_main_loop(
|
|
|
1116
1158
|
|
|
1117
1159
|
tg.start_soon(run_cron_scheduler)
|
|
1118
1160
|
|
|
1161
|
+
from ..cron.watch import watch_cron_config
|
|
1162
|
+
|
|
1163
|
+
cron_file = config_path.parent / "cron.toml"
|
|
1164
|
+
|
|
1165
|
+
async def run_cron_watch() -> None:
|
|
1166
|
+
await watch_cron_config(
|
|
1167
|
+
cron_file=cron_file,
|
|
1168
|
+
manager=cron_manager,
|
|
1169
|
+
)
|
|
1170
|
+
|
|
1171
|
+
tg.start_soon(run_cron_watch)
|
|
1172
|
+
|
|
1119
1173
|
async def run_signal_watcher() -> None:
|
|
1120
1174
|
if not hasattr(signal, "SIGHUP"):
|
|
1121
1175
|
return
|
|
@@ -1168,6 +1222,8 @@ async def run_main_loop(
|
|
|
1168
1222
|
engine_override: EngineId | None = None,
|
|
1169
1223
|
progress_ref: MessageRef | None = None,
|
|
1170
1224
|
force_hide_resume_line: bool = False,
|
|
1225
|
+
force_new_session: bool = False,
|
|
1226
|
+
run_options_model: str | None = None,
|
|
1171
1227
|
) -> None:
|
|
1172
1228
|
topic_key = (
|
|
1173
1229
|
(chat_id, thread_id)
|
|
@@ -1178,15 +1234,22 @@ async def run_main_loop(
|
|
|
1178
1234
|
)
|
|
1179
1235
|
else None
|
|
1180
1236
|
)
|
|
1237
|
+
if force_new_session:
|
|
1238
|
+
topic_key = None
|
|
1239
|
+
chat_session_key = None
|
|
1181
1240
|
stateful_mode = topic_key is not None or chat_session_key is not None
|
|
1182
|
-
show_resume_line =
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1241
|
+
show_resume_line = (
|
|
1242
|
+
False
|
|
1243
|
+
if force_hide_resume_line
|
|
1244
|
+
else should_show_resume_line(
|
|
1245
|
+
show_resume_line=cfg.show_resume_line,
|
|
1246
|
+
stateful_mode=stateful_mode,
|
|
1247
|
+
context=context,
|
|
1248
|
+
)
|
|
1186
1249
|
)
|
|
1187
1250
|
engine_for_overrides = (
|
|
1188
1251
|
resume_token.engine
|
|
1189
|
-
if resume_token is not None
|
|
1252
|
+
if resume_token is not None and not force_new_session
|
|
1190
1253
|
else engine_override
|
|
1191
1254
|
if engine_override is not None
|
|
1192
1255
|
else cfg.runtime.resolve_engine(
|
|
@@ -1203,6 +1266,15 @@ async def run_main_loop(
|
|
|
1203
1266
|
topic_store=state.topic_store,
|
|
1204
1267
|
system_prompt=cfg.runtime.resolve_system_prompt(context),
|
|
1205
1268
|
)
|
|
1269
|
+
if run_options_model:
|
|
1270
|
+
if run_options is None:
|
|
1271
|
+
run_options = EngineRunOptions(model=run_options_model)
|
|
1272
|
+
else:
|
|
1273
|
+
run_options = EngineRunOptions(
|
|
1274
|
+
model=run_options_model,
|
|
1275
|
+
reasoning=run_options.reasoning,
|
|
1276
|
+
system=run_options.system,
|
|
1277
|
+
)
|
|
1206
1278
|
await run_engine(
|
|
1207
1279
|
exec_cfg=cfg.exec_cfg,
|
|
1208
1280
|
runtime=cfg.runtime,
|
|
@@ -1210,7 +1282,7 @@ async def run_main_loop(
|
|
|
1210
1282
|
chat_id=chat_id,
|
|
1211
1283
|
user_msg_id=user_msg_id,
|
|
1212
1284
|
text=text,
|
|
1213
|
-
resume_token=resume_token,
|
|
1285
|
+
resume_token=None if force_new_session else resume_token,
|
|
1214
1286
|
context=context,
|
|
1215
1287
|
reply_ref=reply_ref,
|
|
1216
1288
|
on_thread_known=wrap_on_thread_known(
|
|
@@ -40,9 +40,10 @@ yee88/cli/reload.py,sha256=ays9R2kJ0IQEPGfxb3BY7R2mxbJBmp325dKrqqv1nw8,3552
|
|
|
40
40
|
yee88/cli/run.py,sha256=qKMJCEwoiNN4Qqt59Gmm1bDQHTboGf8JO73gMeACAck,14197
|
|
41
41
|
yee88/cli/topic.py,sha256=6j_o0wpHMfkyeyj9wcjU5T5PVXw_cOM0qM5fsMDRiIw,11019
|
|
42
42
|
yee88/cron/__init__.py,sha256=WZN2RcqJD4i6ZPZjI6b_sKI1VxhNGzhDu2Pju5u9J0o,152
|
|
43
|
-
yee88/cron/manager.py,sha256=
|
|
44
|
-
yee88/cron/models.py,sha256=
|
|
45
|
-
yee88/cron/scheduler.py,sha256=
|
|
43
|
+
yee88/cron/manager.py,sha256=VRnhue8AzIWoRyqSs_xe4xiMT01Dp24v_yOVeFXk-T0,5440
|
|
44
|
+
yee88/cron/models.py,sha256=Ue7Q9ZhA34t0y4HttZrJX9aU_v8G0dT4fsotz1iGpXg,317
|
|
45
|
+
yee88/cron/scheduler.py,sha256=bpjLRy0aVUG6lMzkR8Ty_1S5I6PybmOxhBDbk_pTIzI,5071
|
|
46
|
+
yee88/cron/watch.py,sha256=pMIYiQuXcm9gW0G4ns7GRakkBtB_yGM5wwi8cvxJmDM,1637
|
|
46
47
|
yee88/runners/__init__.py,sha256=McKaMqLXT9dJlgiEwKf6biD0Ns66Fk7SrxwtcP0ZgzI,30
|
|
47
48
|
yee88/runners/claude.py,sha256=mj-L_V-cuCUWL9BW90I645EgDsjRHNdmpLQxN7skUL0,15518
|
|
48
49
|
yee88/runners/codex.py,sha256=fUipu9NsRhhHCIt8s5WkZcT03si1CIEbGrXGmcSlFUo,21427
|
|
@@ -70,7 +71,7 @@ yee88/telegram/context.py,sha256=Hb8-k-YbAjO0EmZ35hA8yyMvl1kozgYHUL1L-lbCglA,468
|
|
|
70
71
|
yee88/telegram/engine_defaults.py,sha256=n6ROkTmP_s-H5AhPz_OdT62oZf0QtZJyFEDjp5gfub4,2594
|
|
71
72
|
yee88/telegram/engine_overrides.py,sha256=kv2j102VP-Bqzbutd5ApBkjW3LmVwvCYixsFewVXVeY,3122
|
|
72
73
|
yee88/telegram/files.py,sha256=Cvmw6r_ocSb3jLzJLGVbzr46m8cRU159majJ1-A5lvg,5053
|
|
73
|
-
yee88/telegram/loop.py,sha256=
|
|
74
|
+
yee88/telegram/loop.py,sha256=emRad9j60oQ_T0V8Ku2FsHr6hunT23uzCJ69va4dSAI,72880
|
|
74
75
|
yee88/telegram/onboarding.py,sha256=QWYaJT_s2bujDxzKjsZuLytyxs_XFDRuiBrsZGRjoOw,35633
|
|
75
76
|
yee88/telegram/outbox.py,sha256=OcoRyQ7zmQCXR8ZXEMK2f_7-UMRVRAbBgmJGS1u_lcU,5939
|
|
76
77
|
yee88/telegram/parsing.py,sha256=5PvIPns1NnKryt3XNxPCp4BpWX1gI8kjKi4VxcQ0W-Q,7429
|
|
@@ -104,8 +105,8 @@ yee88/utils/json_state.py,sha256=cnSvGbB9zj90GdYSyigId1M0nEx54T3A3CqqhkAm9kQ,524
|
|
|
104
105
|
yee88/utils/paths.py,sha256=_Tp-LyFLeyGD0P0agRudLuT1NR_XTIpryxk3OYDJAGQ,1318
|
|
105
106
|
yee88/utils/streams.py,sha256=TQezA-A5VCNksLOtwsJplfr8vm1xPTXoGxvik8G2NPI,1121
|
|
106
107
|
yee88/utils/subprocess.py,sha256=2if6IxTZVSB1kDa8SXw3igj3E-zhKB8P4z5MVe-odzY,2169
|
|
107
|
-
yee88-0.
|
|
108
|
-
yee88-0.
|
|
109
|
-
yee88-0.
|
|
110
|
-
yee88-0.
|
|
111
|
-
yee88-0.
|
|
108
|
+
yee88-0.9.0.dist-info/METADATA,sha256=jglWFP-VyXSgi3a0tuGE_A0SPfIpkms5rg3NjY9QQB8,4340
|
|
109
|
+
yee88-0.9.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
110
|
+
yee88-0.9.0.dist-info/entry_points.txt,sha256=P4MVZ_sZfrHaARVMImNJjoGamP8VDukARWMKfDh20V8,282
|
|
111
|
+
yee88-0.9.0.dist-info/licenses/LICENSE,sha256=poyQ59wnbmL3Ox3TiiephfHvUpLvJl0DwLFFgqBDdHY,1063
|
|
112
|
+
yee88-0.9.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|