understanding commited on
Commit
e6cc028
·
verified ·
1 Parent(s): 4a4c06a

Update bot/handlers.py

Browse files
Files changed (1) hide show
  1. bot/handlers.py +115 -56
bot/handlers.py CHANGED
@@ -1,19 +1,23 @@
1
  # PATH: bot/handlers.py
2
- import asyncio
 
 
3
  from hydrogram import Client, filters
4
  from hydrogram.types import Message, CallbackQuery
5
 
6
- from bot.config import Telegram
7
  from bot.ui import texts
8
- from bot.ui.keyboards import profiles_keyboard
9
- from bot.ui.callbacks import parse_cb
10
 
11
  from bot.core.auth import is_owner_id, require_allowed
12
  from bot.core.progress import SpeedETA, human_bytes, human_eta
13
  from bot.core.tasks import create_task, set_task
14
 
15
  from bot.integrations.cf_worker1 import profile_add, profile_list, profile_set_default
16
- from bot.integrations.cf_worker2 import allow_user, disallow_user, pick_profile, access_token, record_upload, stats_today
 
 
17
 
18
  from bot.telegram.media import download_to_temp
19
  from bot.telegram.parse import extract_media_message, extract_title_description
@@ -22,6 +26,10 @@ from bot.temp.files import cleanup_file
22
 
23
  from bot.youtube.uploader import upload_video
24
 
 
 
 
 
25
  def setup_handlers(app: Client) -> None:
26
 
27
  @app.on_message(filters.command(["start"]) & filters.private)
@@ -43,7 +51,6 @@ def setup_handlers(app: Client) -> None:
43
  if not is_owner_id(m.from_user.id if m.from_user else None):
44
  return await safe_reply(m, texts.OWNER_ONLY)
45
 
46
- # If forward present -> use forwarded user id
47
  target = None
48
  if m.reply_to_message and m.reply_to_message.from_user:
49
  target = m.reply_to_message.from_user.id
@@ -68,12 +75,11 @@ def setup_handlers(app: Client) -> None:
68
  parts = (m.text or "").split()
69
  if len(parts) < 2 or not parts[1].isdigit():
70
  return await safe_reply(m, "Usage: /disallow <user_id>")
71
-
72
  target = int(parts[1])
73
  j = await disallow_user(target)
74
  await safe_reply(m, f"✅ disallowed: `{target}`\n{j}")
75
 
76
- # -------- Owner stats --------
77
  @app.on_message(filters.command(["stats"]) & filters.private)
78
  async def stats_cmd(_: Client, m: Message):
79
  if not is_owner_id(m.from_user.id if m.from_user else None):
@@ -81,27 +87,110 @@ def setup_handlers(app: Client) -> None:
81
  j = await stats_today()
82
  await safe_reply(m, f"📊 Stats:\n`{j}`")
83
 
84
- # -------- Auth flow (Worker1) --------
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
85
  @app.on_message(filters.command(["auth"]) & filters.private)
86
  async def auth_cmd(_: Client, m: Message):
87
  uid = m.from_user.id if m.from_user else 0
88
  ok = await require_allowed(uid)
89
  if not ok:
90
  return await safe_reply(m, texts.NOT_ALLOWED)
 
 
 
 
 
 
 
91
 
92
- # expects: /auth <client_id> <client_secret>
93
- parts = (m.text or "").split(maxsplit=2)
94
- if len(parts) < 3:
95
- return await safe_reply(m, "Usage:\n/auth <CLIENT_ID> <CLIENT_SECRET>\n(baad me JSON method add kar denge)")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
96
 
97
- client_id = parts[1].strip()
98
- client_secret = parts[2].strip()
 
 
 
 
 
99
 
100
- j = await profile_add(uid, client_id, client_secret, label="main", ttl_sec=600)
101
- if not j.get("ok"):
102
- return await safe_reply(m, f"❌ profile_add failed: `{j}`")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
103
 
104
- await safe_reply(m, texts.SENT_AUTH_LINK + j["login_url"])
 
105
 
106
  @app.on_message(filters.command(["profiles"]) & filters.private)
107
  async def profiles_cmd(_: Client, m: Message):
@@ -112,7 +201,7 @@ def setup_handlers(app: Client) -> None:
112
 
113
  j = await profile_list(uid)
114
  if not j.get("ok"):
115
- return await safe_reply(m, f"❌ profile_list failed: `{j}`")
116
 
117
  profiles = j.get("profiles") or []
118
  txt = f"Default: `{j.get('default_profile_id')}`\n\n"
@@ -122,26 +211,7 @@ def setup_handlers(app: Client) -> None:
122
  kb = profiles_keyboard(profiles)
123
  await safe_reply(m, txt, reply_markup=kb)
124
 
125
- @app.on_callback_query()
126
- async def cb(_: Client, q: CallbackQuery):
127
- uid = q.from_user.id if q.from_user else 0
128
- if not await require_allowed(uid):
129
- return await q.answer("Not allowed", show_alert=True)
130
-
131
- action, value = parse_cb(q.data or "")
132
- if action == "setdef" and value:
133
- j = await profile_set_default(uid, value)
134
- if not j.get("ok"):
135
- return await q.answer("Failed", show_alert=True)
136
- await q.answer("Default set ✅", show_alert=False)
137
- try:
138
- await q.message.edit_text(f"✅ Default set: `{value}`")
139
- except Exception:
140
- pass
141
- else:
142
- await q.answer("OK")
143
-
144
- # -------- Upload handler (send video/document) --------
145
  @app.on_message(filters.private & (filters.video | filters.document))
146
  async def upload_handler(app_: Client, m: Message):
147
  uid = m.from_user.id if m.from_user else 0
@@ -149,14 +219,13 @@ def setup_handlers(app: Client) -> None:
149
  if not ok:
150
  return await safe_reply(m, texts.NOT_ALLOWED)
151
 
152
- media, kind = extract_media_message(m)
153
  if not media:
154
  return await safe_reply(m, "Send a video/document.")
155
 
156
- # find default profile + channel_id from worker1 list
157
  pl = await profile_list(uid)
158
  if not pl.get("ok"):
159
- return await safe_reply(m, f"❌ profile_list failed: `{pl}`")
160
 
161
  default_id = pl.get("default_profile_id")
162
  profiles = pl.get("profiles") or []
@@ -168,18 +237,16 @@ def setup_handlers(app: Client) -> None:
168
  if not channel_id:
169
  return await safe_reply(m, "❌ channel_id missing. Re-auth profile.")
170
 
171
- # pick profile (rotation same channel only)
172
  pick = await pick_profile(uid, channel_id)
173
  if not pick.get("ok"):
174
- return await safe_reply(m, f"❌ pick_profile failed: `{pick}`")
175
  use_profile_id = pick["profile_id"]
176
 
177
  tok = await access_token(uid, use_profile_id)
178
  if not tok.get("ok"):
179
- return await safe_reply(m, f"❌ access_token failed: `{tok}`")
180
  access_tok = tok["access_token"]
181
 
182
- # status message
183
  status = await safe_reply(m, texts.UPLOAD_START)
184
 
185
  task_id = f"{uid}:{m.id}"
@@ -188,30 +255,23 @@ def setup_handlers(app: Client) -> None:
188
 
189
  file_path = ""
190
  try:
191
- # download
192
  file_path, file_size, file_name = await download_to_temp(app_, m)
193
 
194
- # metadata rules
195
  title, desc = extract_title_description(m, file_name)
196
 
197
- # upload progress updates
198
  se = SpeedETA()
199
  last_edit = 0.0
200
 
201
  async def prog(done: int, total: int):
202
  nonlocal last_edit
203
  snap = se.update(done, total)
204
- now = snap["done"]
205
- if not status:
206
- return
207
- # throttle edit
208
  import time
209
  if time.time() - last_edit < 2.0 and done < total:
210
  return
211
  last_edit = time.time()
212
  txt = (
213
  f"{texts.UPLOAD_TO_YT}\n"
214
- f"{human_bytes(now)}/{human_bytes(total)}\n"
215
  f"speed: {human_bytes(snap['speed_bps'])}/s | eta: {human_eta(snap['eta_sec'])}"
216
  )
217
  await safe_edit(status, txt)
@@ -219,7 +279,6 @@ def setup_handlers(app: Client) -> None:
219
  set_task(task_id, "uploading", "")
220
  yt_url = await upload_video(access_tok, file_path, title, desc, privacy="private", progress_cb=prog)
221
 
222
- # record upload counter
223
  await record_upload(uid, use_profile_id)
224
 
225
  set_task(task_id, "done", "", yt_url=yt_url)
 
1
  # PATH: bot/handlers.py
2
+ import asyncio, json, re
3
+ from typing import Dict
4
+
5
  from hydrogram import Client, filters
6
  from hydrogram.types import Message, CallbackQuery
7
 
8
+ from bot.config import Telegram, Workers
9
  from bot.ui import texts
10
+ from bot.ui.keyboards import profiles_keyboard, auth_menu_keyboard
11
+ from bot.ui.callbacks import parse_cb, AUTH_JSON, AUTH_CI, CANCEL
12
 
13
  from bot.core.auth import is_owner_id, require_allowed
14
  from bot.core.progress import SpeedETA, human_bytes, human_eta
15
  from bot.core.tasks import create_task, set_task
16
 
17
  from bot.integrations.cf_worker1 import profile_add, profile_list, profile_set_default
18
+ from bot.integrations.cf_worker2 import (
19
+ allow_user, disallow_user, pick_profile, access_token, record_upload, stats_today
20
+ )
21
 
22
  from bot.telegram.media import download_to_temp
23
  from bot.telegram.parse import extract_media_message, extract_title_description
 
26
 
27
  from bot.youtube.uploader import upload_video
28
 
29
+ # ---------- in-memory auth state (simple FSM) ----------
30
+ # values: "json" | "ci"
31
+ _AWAIT_AUTH: Dict[int, str] = {}
32
+
33
  def setup_handlers(app: Client) -> None:
34
 
35
  @app.on_message(filters.command(["start"]) & filters.private)
 
51
  if not is_owner_id(m.from_user.id if m.from_user else None):
52
  return await safe_reply(m, texts.OWNER_ONLY)
53
 
 
54
  target = None
55
  if m.reply_to_message and m.reply_to_message.from_user:
56
  target = m.reply_to_message.from_user.id
 
75
  parts = (m.text or "").split()
76
  if len(parts) < 2 or not parts[1].isdigit():
77
  return await safe_reply(m, "Usage: /disallow <user_id>")
 
78
  target = int(parts[1])
79
  j = await disallow_user(target)
80
  await safe_reply(m, f"✅ disallowed: `{target}`\n{j}")
81
 
82
+ # -------- Owner stats + diag --------
83
  @app.on_message(filters.command(["stats"]) & filters.private)
84
  async def stats_cmd(_: Client, m: Message):
85
  if not is_owner_id(m.from_user.id if m.from_user else None):
 
87
  j = await stats_today()
88
  await safe_reply(m, f"📊 Stats:\n`{j}`")
89
 
90
+ @app.on_message(filters.command(["diag"]) & filters.private)
91
+ async def diag_cmd(_: Client, m: Message):
92
+ if not is_owner_id(m.from_user.id if m.from_user else None):
93
+ return await safe_reply(m, texts.OWNER_ONLY)
94
+ # quick DNS/connect check
95
+ from bot.integrations.http import get_client
96
+ c = get_client()
97
+ res1 = res2 = None
98
+ try:
99
+ res1 = await c.get((Workers.WORKER1_URL or "") + "/")
100
+ r1 = f"{res1.status_code}"
101
+ except Exception as e:
102
+ r1 = f"ERR:{type(e).__name__}:{e}"
103
+ try:
104
+ res2 = await c.get((Workers.WORKER2_URL or "") + "/")
105
+ r2 = f"{res2.status_code}"
106
+ except Exception as e:
107
+ r2 = f"ERR:{type(e).__name__}:{e}"
108
+ await safe_reply(m, f"🔎 DIAG\nW1={Workers.WORKER1_URL} -> {r1}\nW2={Workers.WORKER2_URL} -> {r2}")
109
+
110
+ # -------- AUTH UI --------
111
  @app.on_message(filters.command(["auth"]) & filters.private)
112
  async def auth_cmd(_: Client, m: Message):
113
  uid = m.from_user.id if m.from_user else 0
114
  ok = await require_allowed(uid)
115
  if not ok:
116
  return await safe_reply(m, texts.NOT_ALLOWED)
117
+ _AWAIT_AUTH.pop(uid, None)
118
+ await safe_reply(m, texts.AUTH_MENU, reply_markup=auth_menu_keyboard())
119
+
120
+ @app.on_message(filters.command(["cancel"]) & filters.private)
121
+ async def cancel_cmd(_: Client, m: Message):
122
+ _AWAIT_AUTH.pop(m.from_user.id, None)
123
+ await safe_reply(m, texts.CANCELLED)
124
 
125
+ @app.on_callback_query()
126
+ async def cb(_: Client, q: CallbackQuery):
127
+ uid = q.from_user.id if q.from_user else 0
128
+ action, value = parse_cb(q.data or "")
129
+ if action in (AUTH_JSON, AUTH_CI, CANCEL):
130
+ if action == CANCEL:
131
+ _AWAIT_AUTH.pop(uid, None)
132
+ return await q.answer("Cancelled")
133
+ if action == AUTH_JSON:
134
+ _AWAIT_AUTH[uid] = "json"
135
+ await q.message.edit_text(texts.ASK_JSON)
136
+ return await q.answer("Paste JSON")
137
+ if action == AUTH_CI:
138
+ _AWAIT_AUTH[uid] = "ci"
139
+ await q.message.edit_text(texts.ASK_ID_SECRET)
140
+ return await q.answer("Send ID & Secret")
141
+ return
142
+
143
+ # handle set default profile callbacks too
144
+ if action == "setdef" and value:
145
+ # owner/allowed only
146
+ from bot.core.auth import require_allowed
147
+ if not await require_allowed(uid):
148
+ return await q.answer("Not allowed", show_alert=True)
149
+ j = await profile_set_default(uid, value)
150
+ if not j.get("ok"):
151
+ return await q.answer("Failed", show_alert=True)
152
+ await q.answer("Default set ✅", show_alert=False)
153
+ try:
154
+ await q.message.edit_text(f"✅ Default set: `{value}`")
155
+ except Exception:
156
+ pass
157
+ return
158
+ await q.answer("OK")
159
 
160
+ # message sink for auth states
161
+ @app.on_message(filters.private & filters.text & ~filters.command(["start","ping","me","auth","cancel","profiles","stats","allow","disallow","diag"]))
162
+ async def auth_sink(_: Client, m: Message):
163
+ uid = m.from_user.id if m.from_user else 0
164
+ mode = _AWAIT_AUTH.get(uid)
165
+ if not mode:
166
+ return # not in auth flow
167
 
168
+ try:
169
+ if mode == "json":
170
+ # parse JSON
171
+ data = json.loads(m.text)
172
+ # accept both shapes
173
+ root = data.get("installed") or data.get("web") or {}
174
+ client_id = root.get("client_id") or data.get("client_id")
175
+ client_secret = root.get("client_secret") or data.get("client_secret")
176
+ if not client_id or not client_secret:
177
+ return await safe_reply(m, texts.PARSE_FAIL)
178
+ else:
179
+ # expect "id\nsecret" or "id | secret"
180
+ txt = m.text.strip()
181
+ parts = [p.strip() for p in re.split(r"[|\n]+", txt) if p.strip()]
182
+ if len(parts) < 2:
183
+ return await safe_reply(m, texts.PARSE_FAIL)
184
+ client_id, client_secret = parts[0], parts[1]
185
+
186
+ j = await profile_add(uid, client_id, client_secret, label="main", ttl_sec=600)
187
+ if not j.get("ok"):
188
+ return await safe_reply(m, texts.PROFILE_ADD_FAIL.format(j))
189
+ _AWAIT_AUTH.pop(uid, None)
190
+ await safe_reply(m, texts.SENT_AUTH_LINK + j["login_url"])
191
 
192
+ except Exception as e:
193
+ await safe_reply(m, f"{texts.PARSE_FAIL}\n`{type(e).__name__}: {e}`")
194
 
195
  @app.on_message(filters.command(["profiles"]) & filters.private)
196
  async def profiles_cmd(_: Client, m: Message):
 
201
 
202
  j = await profile_list(uid)
203
  if not j.get("ok"):
204
+ return await safe_reply(m, texts.PROFILE_LIST_FAIL.format(j))
205
 
206
  profiles = j.get("profiles") or []
207
  txt = f"Default: `{j.get('default_profile_id')}`\n\n"
 
211
  kb = profiles_keyboard(profiles)
212
  await safe_reply(m, txt, reply_markup=kb)
213
 
214
+ # -------- Upload handler --------
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
215
  @app.on_message(filters.private & (filters.video | filters.document))
216
  async def upload_handler(app_: Client, m: Message):
217
  uid = m.from_user.id if m.from_user else 0
 
219
  if not ok:
220
  return await safe_reply(m, texts.NOT_ALLOWED)
221
 
222
+ media = m.video or m.document
223
  if not media:
224
  return await safe_reply(m, "Send a video/document.")
225
 
 
226
  pl = await profile_list(uid)
227
  if not pl.get("ok"):
228
+ return await safe_reply(m, texts.PROFILE_LIST_FAIL.format(pl))
229
 
230
  default_id = pl.get("default_profile_id")
231
  profiles = pl.get("profiles") or []
 
237
  if not channel_id:
238
  return await safe_reply(m, "❌ channel_id missing. Re-auth profile.")
239
 
 
240
  pick = await pick_profile(uid, channel_id)
241
  if not pick.get("ok"):
242
+ return await safe_reply(m, texts.PICK_FAIL.format(pick))
243
  use_profile_id = pick["profile_id"]
244
 
245
  tok = await access_token(uid, use_profile_id)
246
  if not tok.get("ok"):
247
+ return await safe_reply(m, texts.TOKEN_FAIL.format(tok))
248
  access_tok = tok["access_token"]
249
 
 
250
  status = await safe_reply(m, texts.UPLOAD_START)
251
 
252
  task_id = f"{uid}:{m.id}"
 
255
 
256
  file_path = ""
257
  try:
 
258
  file_path, file_size, file_name = await download_to_temp(app_, m)
259
 
 
260
  title, desc = extract_title_description(m, file_name)
261
 
 
262
  se = SpeedETA()
263
  last_edit = 0.0
264
 
265
  async def prog(done: int, total: int):
266
  nonlocal last_edit
267
  snap = se.update(done, total)
 
 
 
 
268
  import time
269
  if time.time() - last_edit < 2.0 and done < total:
270
  return
271
  last_edit = time.time()
272
  txt = (
273
  f"{texts.UPLOAD_TO_YT}\n"
274
+ f"{human_bytes(done)}/{human_bytes(total)}\n"
275
  f"speed: {human_bytes(snap['speed_bps'])}/s | eta: {human_eta(snap['eta_sec'])}"
276
  )
277
  await safe_edit(status, txt)
 
279
  set_task(task_id, "uploading", "")
280
  yt_url = await upload_video(access_tok, file_path, title, desc, privacy="private", progress_cb=prog)
281
 
 
282
  await record_upload(uid, use_profile_id)
283
 
284
  set_task(task_id, "done", "", yt_url=yt_url)