pygpt-net 2.6.1__py3-none-any.whl → 2.6.2__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.
- pygpt_net/CHANGELOG.txt +4 -0
- pygpt_net/__init__.py +3 -3
- pygpt_net/app.py +15 -1
- pygpt_net/controller/chat/response.py +5 -3
- pygpt_net/controller/chat/stream.py +40 -2
- pygpt_net/controller/plugins/plugins.py +25 -0
- pygpt_net/controller/presets/editor.py +33 -88
- pygpt_net/controller/presets/experts.py +20 -1
- pygpt_net/controller/presets/presets.py +2 -2
- pygpt_net/controller/ui/mode.py +17 -66
- pygpt_net/core/agents/runner.py +15 -7
- pygpt_net/core/experts/experts.py +3 -3
- pygpt_net/data/config/config.json +3 -3
- pygpt_net/data/config/models.json +3 -3
- pygpt_net/data/locale/locale.de.ini +2 -0
- pygpt_net/data/locale/locale.en.ini +2 -0
- pygpt_net/data/locale/locale.es.ini +2 -0
- pygpt_net/data/locale/locale.fr.ini +2 -0
- pygpt_net/data/locale/locale.it.ini +2 -0
- pygpt_net/data/locale/locale.pl.ini +3 -1
- pygpt_net/data/locale/locale.uk.ini +2 -0
- pygpt_net/data/locale/locale.zh.ini +2 -0
- pygpt_net/plugin/base/plugin.py +35 -3
- pygpt_net/plugin/bitbucket/__init__.py +12 -0
- pygpt_net/plugin/bitbucket/config.py +267 -0
- pygpt_net/plugin/bitbucket/plugin.py +125 -0
- pygpt_net/plugin/bitbucket/worker.py +569 -0
- pygpt_net/plugin/facebook/__init__.py +12 -0
- pygpt_net/plugin/facebook/config.py +359 -0
- pygpt_net/plugin/facebook/plugin.py +114 -0
- pygpt_net/plugin/facebook/worker.py +698 -0
- pygpt_net/plugin/github/__init__.py +12 -0
- pygpt_net/plugin/github/config.py +441 -0
- pygpt_net/plugin/github/plugin.py +124 -0
- pygpt_net/plugin/github/worker.py +674 -0
- pygpt_net/plugin/google/__init__.py +12 -0
- pygpt_net/plugin/google/config.py +367 -0
- pygpt_net/plugin/google/plugin.py +126 -0
- pygpt_net/plugin/google/worker.py +826 -0
- pygpt_net/plugin/slack/__init__.py +12 -0
- pygpt_net/plugin/slack/config.py +349 -0
- pygpt_net/plugin/slack/plugin.py +116 -0
- pygpt_net/plugin/slack/worker.py +639 -0
- pygpt_net/plugin/telegram/__init__.py +12 -0
- pygpt_net/plugin/telegram/config.py +308 -0
- pygpt_net/plugin/telegram/plugin.py +118 -0
- pygpt_net/plugin/telegram/worker.py +563 -0
- pygpt_net/plugin/twitter/__init__.py +12 -0
- pygpt_net/plugin/twitter/config.py +491 -0
- pygpt_net/plugin/twitter/plugin.py +126 -0
- pygpt_net/plugin/twitter/worker.py +837 -0
- pygpt_net/provider/agents/llama_index/legacy/openai_assistant.py +35 -3
- pygpt_net/ui/base/config_dialog.py +4 -0
- pygpt_net/ui/dialog/preset.py +34 -77
- pygpt_net/ui/layout/toolbox/presets.py +2 -2
- pygpt_net/ui/main.py +3 -1
- {pygpt_net-2.6.1.dist-info → pygpt_net-2.6.2.dist-info}/METADATA +145 -2
- {pygpt_net-2.6.1.dist-info → pygpt_net-2.6.2.dist-info}/RECORD +61 -33
- {pygpt_net-2.6.1.dist-info → pygpt_net-2.6.2.dist-info}/LICENSE +0 -0
- {pygpt_net-2.6.1.dist-info → pygpt_net-2.6.2.dist-info}/WHEEL +0 -0
- {pygpt_net-2.6.1.dist-info → pygpt_net-2.6.2.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,563 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
# ================================================== #
|
|
4
|
+
# This file is a part of PYGPT package #
|
|
5
|
+
# Website: https://pygpt.net #
|
|
6
|
+
# GitHub: https://github.com/szczyglis-dev/py-gpt #
|
|
7
|
+
# MIT License #
|
|
8
|
+
# Created By : Marcin Szczygliński #
|
|
9
|
+
# Updated Date: 2025.08.15 00:00:00 #
|
|
10
|
+
# ================================================== #
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import json
|
|
15
|
+
import mimetypes
|
|
16
|
+
import os
|
|
17
|
+
import threading
|
|
18
|
+
import requests
|
|
19
|
+
|
|
20
|
+
from PySide6.QtCore import Slot
|
|
21
|
+
|
|
22
|
+
from pygpt_net.plugin.base.worker import BaseWorker, BaseSignals
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class WorkerSignals(BaseSignals):
|
|
26
|
+
pass
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class Worker(BaseWorker):
|
|
30
|
+
"""
|
|
31
|
+
Telegram plugin worker: Bot API (simple HTTP) and User mode (Telethon).
|
|
32
|
+
Auto-login for user mode (sends code when possible), bot mode needs only bot_token.
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
def __init__(self, *args, **kwargs):
|
|
36
|
+
super(Worker, self).__init__()
|
|
37
|
+
self.signals = WorkerSignals()
|
|
38
|
+
self.args = args
|
|
39
|
+
self.kwargs = kwargs
|
|
40
|
+
self.plugin = None
|
|
41
|
+
self.cmds = None
|
|
42
|
+
self.ctx = None
|
|
43
|
+
self.msg = None
|
|
44
|
+
self._tl_client = None
|
|
45
|
+
self._tl_lock = threading.Lock()
|
|
46
|
+
|
|
47
|
+
# ---------------------- Core runner ----------------------
|
|
48
|
+
|
|
49
|
+
@Slot()
|
|
50
|
+
def run(self):
|
|
51
|
+
try:
|
|
52
|
+
responses = []
|
|
53
|
+
for item in self.cmds:
|
|
54
|
+
if self.is_stopped():
|
|
55
|
+
break
|
|
56
|
+
try:
|
|
57
|
+
response = None
|
|
58
|
+
if item["cmd"] in self.plugin.allowed_cmds and self.plugin.has_cmd(item["cmd"]):
|
|
59
|
+
|
|
60
|
+
# -------- Auth (user mode) --------
|
|
61
|
+
if item["cmd"] == "tg_login_begin":
|
|
62
|
+
response = self.cmd_tg_login_begin(item)
|
|
63
|
+
elif item["cmd"] == "tg_login_complete":
|
|
64
|
+
response = self.cmd_tg_login_complete(item)
|
|
65
|
+
elif item["cmd"] == "tg_logout":
|
|
66
|
+
response = self.cmd_tg_logout(item)
|
|
67
|
+
|
|
68
|
+
# -------- Info --------
|
|
69
|
+
elif item["cmd"] == "tg_mode":
|
|
70
|
+
response = self.cmd_tg_mode(item)
|
|
71
|
+
elif item["cmd"] == "tg_me":
|
|
72
|
+
response = self.cmd_tg_me(item)
|
|
73
|
+
|
|
74
|
+
# -------- Messaging --------
|
|
75
|
+
elif item["cmd"] == "tg_send_message":
|
|
76
|
+
response = self.cmd_tg_send_message(item)
|
|
77
|
+
elif item["cmd"] == "tg_send_photo":
|
|
78
|
+
response = self.cmd_tg_send_photo(item)
|
|
79
|
+
elif item["cmd"] == "tg_send_document":
|
|
80
|
+
response = self.cmd_tg_send_document(item)
|
|
81
|
+
|
|
82
|
+
# -------- Chats --------
|
|
83
|
+
elif item["cmd"] == "tg_get_chat":
|
|
84
|
+
response = self.cmd_tg_get_chat(item)
|
|
85
|
+
|
|
86
|
+
# -------- Updates / Files (bot) --------
|
|
87
|
+
elif item["cmd"] == "tg_get_updates":
|
|
88
|
+
response = self.cmd_tg_get_updates(item)
|
|
89
|
+
elif item["cmd"] == "tg_download_file":
|
|
90
|
+
response = self.cmd_tg_download_file(item)
|
|
91
|
+
|
|
92
|
+
# -------- Contacts / Dialogs / History (user) --------
|
|
93
|
+
elif item["cmd"] == "tg_contacts_list":
|
|
94
|
+
response = self.cmd_tg_contacts_list(item)
|
|
95
|
+
elif item["cmd"] == "tg_dialogs_list":
|
|
96
|
+
response = self.cmd_tg_dialogs_list(item)
|
|
97
|
+
elif item["cmd"] == "tg_messages_get":
|
|
98
|
+
response = self.cmd_tg_messages_get(item)
|
|
99
|
+
|
|
100
|
+
if response:
|
|
101
|
+
responses.append(response)
|
|
102
|
+
|
|
103
|
+
except Exception as e:
|
|
104
|
+
responses.append(self.make_response(item, self.throw_error(e)))
|
|
105
|
+
|
|
106
|
+
if responses:
|
|
107
|
+
self.reply_more(responses)
|
|
108
|
+
if self.msg is not None:
|
|
109
|
+
self.status(self.msg)
|
|
110
|
+
except Exception as e:
|
|
111
|
+
self.error(e)
|
|
112
|
+
finally:
|
|
113
|
+
self.cleanup()
|
|
114
|
+
|
|
115
|
+
# ---------------------- Common helpers ----------------------
|
|
116
|
+
|
|
117
|
+
def _mode(self) -> str:
|
|
118
|
+
m = (self.plugin.get_option_value("mode") or "bot").strip().lower()
|
|
119
|
+
return "user" if m == "user" else "bot"
|
|
120
|
+
|
|
121
|
+
def _timeout(self) -> int:
|
|
122
|
+
try:
|
|
123
|
+
return int(self.plugin.get_option_value("http_timeout") or 30)
|
|
124
|
+
except Exception:
|
|
125
|
+
return 30
|
|
126
|
+
|
|
127
|
+
def _api_base(self) -> str:
|
|
128
|
+
return (self.plugin.get_option_value("api_base") or "https://api.telegram.org").rstrip("/")
|
|
129
|
+
|
|
130
|
+
def _guess_mime(self, path: str) -> str:
|
|
131
|
+
mt, _ = mimetypes.guess_type(path)
|
|
132
|
+
return mt or "application/octet-stream"
|
|
133
|
+
|
|
134
|
+
def prepare_path(self, path: str) -> str:
|
|
135
|
+
if path in [".", "./"]:
|
|
136
|
+
return self.plugin.window.core.config.get_user_dir("data")
|
|
137
|
+
if self.is_absolute_path(path):
|
|
138
|
+
return path
|
|
139
|
+
return os.path.join(self.plugin.window.core.config.get_user_dir("data"), path)
|
|
140
|
+
|
|
141
|
+
def is_absolute_path(self, path: str) -> bool:
|
|
142
|
+
return os.path.isabs(path)
|
|
143
|
+
|
|
144
|
+
# ---------------------- Bot API helpers ----------------------
|
|
145
|
+
|
|
146
|
+
def _bot_token(self) -> str:
|
|
147
|
+
tok = (self.plugin.get_option_value("bot_token") or "").strip()
|
|
148
|
+
if not tok:
|
|
149
|
+
raise RuntimeError("Telegram bot_token is missing (set it in options or switch mode to 'user').")
|
|
150
|
+
return tok
|
|
151
|
+
|
|
152
|
+
def _bot_url(self, method: str) -> str:
|
|
153
|
+
return f"{self._api_base()}/bot{self._bot_token()}/{method}"
|
|
154
|
+
|
|
155
|
+
def _bot_file_url(self, file_path: str) -> str:
|
|
156
|
+
return f"{self._api_base()}/file/bot{self._bot_token()}/{file_path}"
|
|
157
|
+
|
|
158
|
+
def _bot_handle(self, r: requests.Response) -> dict:
|
|
159
|
+
try:
|
|
160
|
+
data = r.json() if r.content else {}
|
|
161
|
+
except Exception:
|
|
162
|
+
data = {"raw": r.text}
|
|
163
|
+
if r.status_code != 200 or not data.get("ok"):
|
|
164
|
+
desc = (data.get("description") or r.text)
|
|
165
|
+
raise RuntimeError(f"Telegram Bot API error {r.status_code}: {desc}")
|
|
166
|
+
return data["result"]
|
|
167
|
+
|
|
168
|
+
def _bot_get(self, method: str, params: dict = None) -> dict:
|
|
169
|
+
r = requests.get(self._bot_url(method), params=params or {}, timeout=self._timeout())
|
|
170
|
+
return self._bot_handle(r)
|
|
171
|
+
|
|
172
|
+
def _bot_post(self, method: str, data: dict = None, files: dict | None = None) -> dict:
|
|
173
|
+
r = requests.post(self._bot_url(method), data=data or {}, files=files, timeout=self._timeout())
|
|
174
|
+
return self._bot_handle(r)
|
|
175
|
+
|
|
176
|
+
# ---------------------- Telethon (user mode) ----------------------
|
|
177
|
+
|
|
178
|
+
def _ensure_telethon(self):
|
|
179
|
+
try:
|
|
180
|
+
import telethon # noqa
|
|
181
|
+
except Exception:
|
|
182
|
+
raise RuntimeError("Telethon not installed. Install: pip install telethon")
|
|
183
|
+
|
|
184
|
+
def _tl_get_client(self, need_auth: bool = False):
|
|
185
|
+
"""
|
|
186
|
+
Returns connected Telethon client (creates on demand).
|
|
187
|
+
"""
|
|
188
|
+
self._ensure_telethon()
|
|
189
|
+
from telethon.sessions import StringSession
|
|
190
|
+
from telethon.sync import TelegramClient
|
|
191
|
+
|
|
192
|
+
api_id = self.plugin.get_option_value("api_id")
|
|
193
|
+
api_hash = self.plugin.get_option_value("api_hash")
|
|
194
|
+
if not (api_id and api_hash):
|
|
195
|
+
raise RuntimeError("api_id/api_hash required for user mode (set options).")
|
|
196
|
+
|
|
197
|
+
session_str = (self.plugin.get_option_value("user_session") or "").strip()
|
|
198
|
+
sess = StringSession(session_str or None)
|
|
199
|
+
|
|
200
|
+
with self._tl_lock:
|
|
201
|
+
if self._tl_client is None:
|
|
202
|
+
self._tl_client = TelegramClient(sess, int(api_id), api_hash)
|
|
203
|
+
self._tl_client.connect()
|
|
204
|
+
else:
|
|
205
|
+
if not self._tl_client.is_connected():
|
|
206
|
+
self._tl_client.connect()
|
|
207
|
+
|
|
208
|
+
authed = bool(self._tl_client.is_user_authorized())
|
|
209
|
+
if not authed and need_auth:
|
|
210
|
+
# Optional auto-begin: send code when phone is available
|
|
211
|
+
phone = (self.plugin.get_option_value("phone_number") or "").strip()
|
|
212
|
+
if phone and bool(self.plugin.get_option_value("auto_login_begin") or True):
|
|
213
|
+
try:
|
|
214
|
+
self._tl_client.send_code_request(phone)
|
|
215
|
+
self.msg = f"Telegram (user): code sent to {phone}. Now run tg_login_complete with 'code'."
|
|
216
|
+
except Exception as e:
|
|
217
|
+
raise RuntimeError(f"Failed to send login code: {e}")
|
|
218
|
+
raise RuntimeError("Not authorized in user mode. Run tg_login_begin then tg_login_complete.")
|
|
219
|
+
return self._tl_client
|
|
220
|
+
|
|
221
|
+
def _tl_save_session(self):
|
|
222
|
+
# Save StringSession to options
|
|
223
|
+
from telethon.sessions import StringSession
|
|
224
|
+
s = self._tl_client.session.save()
|
|
225
|
+
self.plugin.set_option_value("user_session", s)
|
|
226
|
+
|
|
227
|
+
def _tl_resolve(self, client, chat: str | int):
|
|
228
|
+
# Resolves @username, phone, or numeric id/entity
|
|
229
|
+
if isinstance(chat, int):
|
|
230
|
+
return chat
|
|
231
|
+
chat = (chat or "").strip()
|
|
232
|
+
if not chat:
|
|
233
|
+
raise RuntimeError("Param 'chat' required.")
|
|
234
|
+
return client.get_entity(chat)
|
|
235
|
+
|
|
236
|
+
# ---------------------- Commands: Auth (user) ----------------------
|
|
237
|
+
|
|
238
|
+
def cmd_tg_login_begin(self, item: dict) -> dict:
|
|
239
|
+
self._ensure_telethon()
|
|
240
|
+
p = item.get("params", {}) or {}
|
|
241
|
+
phone = (p.get("phone") or self.plugin.get_option_value("phone_number") or "").strip()
|
|
242
|
+
if not phone:
|
|
243
|
+
return self.make_response(item, "Param 'phone' required or set 'phone_number' in options.")
|
|
244
|
+
client = self._tl_get_client(need_auth=False)
|
|
245
|
+
try:
|
|
246
|
+
client.send_code_request(phone)
|
|
247
|
+
return self.make_response(item, f"Code sent to {phone}. Next call tg_login_complete with 'code'.")
|
|
248
|
+
except Exception as e:
|
|
249
|
+
return self.make_response(item, self.throw_error(e))
|
|
250
|
+
|
|
251
|
+
def cmd_tg_login_complete(self, item: dict) -> dict:
|
|
252
|
+
self._ensure_telethon()
|
|
253
|
+
from telethon.errors import SessionPasswordNeededError
|
|
254
|
+
p = item.get("params", {}) or {}
|
|
255
|
+
code = (p.get("code") or "").strip()
|
|
256
|
+
phone = (p.get("phone") or self.plugin.get_option_value("phone_number") or "").strip()
|
|
257
|
+
password = p.get("password") # optional 2FA
|
|
258
|
+
|
|
259
|
+
if not (phone and code):
|
|
260
|
+
return self.make_response(item, "Params 'phone' and 'code' required (phone can be saved in options).")
|
|
261
|
+
|
|
262
|
+
client = self._tl_get_client(need_auth=False)
|
|
263
|
+
try:
|
|
264
|
+
client.sign_in(phone=phone, code=code)
|
|
265
|
+
except SessionPasswordNeededError:
|
|
266
|
+
if not password and not (self.plugin.get_option_value("password_2fa") or ""):
|
|
267
|
+
return self.make_response(item, "2FA password required. Provide 'password' or set 'password_2fa' in options.")
|
|
268
|
+
client.sign_in(password=password or self.plugin.get_option_value("password_2fa"))
|
|
269
|
+
|
|
270
|
+
# Save session for reuse
|
|
271
|
+
self._tl_save_session()
|
|
272
|
+
|
|
273
|
+
# Cache identity
|
|
274
|
+
me = client.get_me()
|
|
275
|
+
data = {
|
|
276
|
+
"id": me.id,
|
|
277
|
+
"username": getattr(me, "username", None),
|
|
278
|
+
"first_name": getattr(me, "first_name", None),
|
|
279
|
+
"phone": getattr(me, "phone", None),
|
|
280
|
+
"is_bot": bool(getattr(me, "bot", False)),
|
|
281
|
+
}
|
|
282
|
+
return self.make_response(item, {"authorized": True, "me": data})
|
|
283
|
+
|
|
284
|
+
def cmd_tg_logout(self, item: dict) -> dict:
|
|
285
|
+
self._ensure_telethon()
|
|
286
|
+
client = self._tl_get_client(need_auth=False)
|
|
287
|
+
try:
|
|
288
|
+
client.log_out()
|
|
289
|
+
except Exception:
|
|
290
|
+
pass
|
|
291
|
+
try:
|
|
292
|
+
client.disconnect()
|
|
293
|
+
except Exception:
|
|
294
|
+
pass
|
|
295
|
+
self._tl_client = None
|
|
296
|
+
self.plugin.set_option_value("user_session", "")
|
|
297
|
+
return self.make_response(item, {"authorized": False})
|
|
298
|
+
|
|
299
|
+
# ---------------------- Commands: Info ----------------------
|
|
300
|
+
|
|
301
|
+
def cmd_tg_mode(self, item: dict) -> dict:
|
|
302
|
+
return self.make_response(item, {"mode": self._mode()})
|
|
303
|
+
|
|
304
|
+
def cmd_tg_me(self, item: dict) -> dict:
|
|
305
|
+
if self._mode() == "bot":
|
|
306
|
+
res = self._bot_get("getMe")
|
|
307
|
+
return self.make_response(item, res)
|
|
308
|
+
else:
|
|
309
|
+
client = self._tl_get_client(need_auth=True)
|
|
310
|
+
me = client.get_me()
|
|
311
|
+
data = {
|
|
312
|
+
"id": me.id,
|
|
313
|
+
"username": getattr(me, "username", None),
|
|
314
|
+
"first_name": getattr(me, "first_name", None),
|
|
315
|
+
"last_name": getattr(me, "last_name", None),
|
|
316
|
+
"phone": getattr(me, "phone", None),
|
|
317
|
+
"is_bot": bool(getattr(me, "bot", False)),
|
|
318
|
+
}
|
|
319
|
+
return self.make_response(item, data)
|
|
320
|
+
|
|
321
|
+
# ---------------------- Commands: Messaging ----------------------
|
|
322
|
+
|
|
323
|
+
def _default_send_flags(self, p: dict) -> dict:
|
|
324
|
+
flags = {}
|
|
325
|
+
if p.get("parse_mode") or self.plugin.get_option_value("default_parse_mode"):
|
|
326
|
+
flags["parse_mode"] = p.get("parse_mode") or self.plugin.get_option_value("default_parse_mode")
|
|
327
|
+
if p.get("disable_web_page_preview") is not None:
|
|
328
|
+
flags["disable_web_page_preview"] = bool(p.get("disable_web_page_preview"))
|
|
329
|
+
else:
|
|
330
|
+
if self.plugin.get_option_value("default_disable_preview"):
|
|
331
|
+
flags["disable_web_page_preview"] = True
|
|
332
|
+
if p.get("disable_notification") is not None:
|
|
333
|
+
flags["disable_notification"] = bool(p.get("disable_notification"))
|
|
334
|
+
else:
|
|
335
|
+
if self.plugin.get_option_value("default_disable_notification"):
|
|
336
|
+
flags["disable_notification"] = True
|
|
337
|
+
if p.get("protect_content") is not None:
|
|
338
|
+
flags["protect_content"] = bool(p.get("protect_content"))
|
|
339
|
+
else:
|
|
340
|
+
if self.plugin.get_option_value("default_protect_content"):
|
|
341
|
+
flags["protect_content"] = True
|
|
342
|
+
if p.get("reply_to_message_id"):
|
|
343
|
+
flags["reply_to_message_id"] = int(p.get("reply_to_message_id"))
|
|
344
|
+
return flags
|
|
345
|
+
|
|
346
|
+
def cmd_tg_send_message(self, item: dict) -> dict:
|
|
347
|
+
p = item.get("params", {}) or {}
|
|
348
|
+
chat = p.get("chat")
|
|
349
|
+
text = p.get("text", "")
|
|
350
|
+
if not (chat and text):
|
|
351
|
+
return self.make_response(item, "Params 'chat' and 'text' required.")
|
|
352
|
+
|
|
353
|
+
if self._mode() == "bot":
|
|
354
|
+
data = {"chat_id": chat, "text": text}
|
|
355
|
+
data.update(self._default_send_flags(p))
|
|
356
|
+
res = self._bot_post("sendMessage", data=data)
|
|
357
|
+
return self.make_response(item, res)
|
|
358
|
+
else:
|
|
359
|
+
client = self._tl_get_client(need_auth=True)
|
|
360
|
+
entity = self._tl_resolve(client, chat)
|
|
361
|
+
parse_mode = (p.get("parse_mode") or self.plugin.get_option_value("default_parse_mode") or None)
|
|
362
|
+
# Telethon accepts "html" or "markdown"
|
|
363
|
+
if parse_mode and parse_mode.upper() == "MARKDOWNV2":
|
|
364
|
+
parse_mode = "markdown"
|
|
365
|
+
msg = client.send_message(entity, text, parse_mode=(parse_mode.lower() if parse_mode else None), link_preview=not bool(p.get("disable_web_page_preview") or self.plugin.get_option_value("default_disable_preview")))
|
|
366
|
+
return self.make_response(item, {"id": msg.id, "date": msg.date.isoformat()})
|
|
367
|
+
|
|
368
|
+
def cmd_tg_send_photo(self, item: dict) -> dict:
|
|
369
|
+
p = item.get("params", {}) or {}
|
|
370
|
+
chat = p.get("chat")
|
|
371
|
+
photo = p.get("photo") or p.get("path")
|
|
372
|
+
caption = p.get("caption", "")
|
|
373
|
+
if not (chat and photo):
|
|
374
|
+
return self.make_response(item, "Params 'chat' and 'photo' (or 'path') required.")
|
|
375
|
+
|
|
376
|
+
if self._mode() == "bot":
|
|
377
|
+
data = {"chat_id": chat, "caption": caption}
|
|
378
|
+
data.update(self._default_send_flags(p))
|
|
379
|
+
files = None
|
|
380
|
+
# Accept local path, URL or file_id
|
|
381
|
+
if os.path.exists(self.prepare_path(photo)):
|
|
382
|
+
local = self.prepare_path(photo)
|
|
383
|
+
files = {"photo": (os.path.basename(local), open(local, "rb"), self._guess_mime(local))}
|
|
384
|
+
else:
|
|
385
|
+
data["photo"] = photo
|
|
386
|
+
res = self._bot_post("sendPhoto", data=data, files=files)
|
|
387
|
+
return self.make_response(item, res)
|
|
388
|
+
else:
|
|
389
|
+
client = self._tl_get_client(need_auth=True)
|
|
390
|
+
entity = self._tl_resolve(client, chat)
|
|
391
|
+
local = self.prepare_path(photo) if not photo.lower().startswith("http") else photo
|
|
392
|
+
msg = client.send_file(entity, local, caption=caption)
|
|
393
|
+
return self.make_response(item, {"id": msg.id, "date": msg.date.isoformat()})
|
|
394
|
+
|
|
395
|
+
def cmd_tg_send_document(self, item: dict) -> dict:
|
|
396
|
+
p = item.get("params", {}) or {}
|
|
397
|
+
chat = p.get("chat")
|
|
398
|
+
doc = p.get("document") or p.get("path")
|
|
399
|
+
caption = p.get("caption", "")
|
|
400
|
+
if not (chat and doc):
|
|
401
|
+
return self.make_response(item, "Params 'chat' and 'document' (or 'path') required.")
|
|
402
|
+
|
|
403
|
+
if self._mode() == "bot":
|
|
404
|
+
data = {"chat_id": chat, "caption": caption}
|
|
405
|
+
data.update(self._default_send_flags(p))
|
|
406
|
+
files = None
|
|
407
|
+
if os.path.exists(self.prepare_path(doc)):
|
|
408
|
+
local = self.prepare_path(doc)
|
|
409
|
+
files = {"document": (os.path.basename(local), open(local, "rb"), self._guess_mime(local))}
|
|
410
|
+
else:
|
|
411
|
+
data["document"] = doc
|
|
412
|
+
res = self._bot_post("sendDocument", data=data, files=files)
|
|
413
|
+
return self.make_response(item, res)
|
|
414
|
+
else:
|
|
415
|
+
client = self._tl_get_client(need_auth=True)
|
|
416
|
+
entity = self._tl_resolve(client, chat)
|
|
417
|
+
local = self.prepare_path(doc) if not str(doc).lower().startswith("http") else doc
|
|
418
|
+
msg = client.send_file(entity, local, caption=caption, force_document=True)
|
|
419
|
+
return self.make_response(item, {"id": msg.id, "date": msg.date.isoformat()})
|
|
420
|
+
|
|
421
|
+
# ---------------------- Commands: Chats ----------------------
|
|
422
|
+
|
|
423
|
+
def cmd_tg_get_chat(self, item: dict) -> dict:
|
|
424
|
+
p = item.get("params", {}) or {}
|
|
425
|
+
chat = p.get("chat")
|
|
426
|
+
if not chat:
|
|
427
|
+
return self.make_response(item, "Param 'chat' required (id or @username).")
|
|
428
|
+
|
|
429
|
+
if self._mode() == "bot":
|
|
430
|
+
res = self._bot_get("getChat", params={"chat_id": chat})
|
|
431
|
+
return self.make_response(item, res)
|
|
432
|
+
else:
|
|
433
|
+
client = self._tl_get_client(need_auth=True)
|
|
434
|
+
ent = self._tl_resolve(client, chat)
|
|
435
|
+
data = {
|
|
436
|
+
"id": getattr(ent, "id", None),
|
|
437
|
+
"title": getattr(ent, "title", None) or f"{getattr(ent, 'first_name', '')} {getattr(ent, 'last_name', '')}".strip(),
|
|
438
|
+
"username": getattr(ent, "username", None),
|
|
439
|
+
"is_channel": getattr(ent, "__class__", type("x",(object,),{})).__name__.lower().find("channel") >= 0,
|
|
440
|
+
}
|
|
441
|
+
return self.make_response(item, data)
|
|
442
|
+
|
|
443
|
+
# ---------------------- Commands: Updates / Files (bot) ----------------------
|
|
444
|
+
|
|
445
|
+
def cmd_tg_get_updates(self, item: dict) -> dict:
|
|
446
|
+
if self._mode() != "bot":
|
|
447
|
+
return self.make_response(item, "tg_get_updates is available in bot mode only.")
|
|
448
|
+
p = item.get("params", {}) or {}
|
|
449
|
+
offset = p.get("offset")
|
|
450
|
+
if offset is None:
|
|
451
|
+
# continue from last stored
|
|
452
|
+
last = self.plugin.get_option_value("last_update_id")
|
|
453
|
+
if last:
|
|
454
|
+
try:
|
|
455
|
+
offset = int(last) + 1
|
|
456
|
+
except Exception:
|
|
457
|
+
offset = None
|
|
458
|
+
params = {
|
|
459
|
+
"timeout": int(p.get("timeout") or 0),
|
|
460
|
+
}
|
|
461
|
+
if offset is not None:
|
|
462
|
+
params["offset"] = int(offset)
|
|
463
|
+
if p.get("allowed_updates"):
|
|
464
|
+
params["allowed_updates"] = json.dumps(p.get("allowed_updates"))
|
|
465
|
+
res = self._bot_get("getUpdates", params=params)
|
|
466
|
+
if res:
|
|
467
|
+
max_id = max(u.get("update_id", 0) for u in res)
|
|
468
|
+
self.plugin.set_option_value("last_update_id", str(max_id))
|
|
469
|
+
return self.make_response(item, res or [])
|
|
470
|
+
|
|
471
|
+
def cmd_tg_download_file(self, item: dict) -> dict:
|
|
472
|
+
if self._mode() != "bot":
|
|
473
|
+
return self.make_response(item, "tg_download_file is available in bot mode only.")
|
|
474
|
+
p = item.get("params", {}) or {}
|
|
475
|
+
file_id = p.get("file_id")
|
|
476
|
+
save_as = p.get("save_as") # relative to data dir if not absolute
|
|
477
|
+
if not file_id:
|
|
478
|
+
return self.make_response(item, "Param 'file_id' required.")
|
|
479
|
+
info = self._bot_post("getFile", data={"file_id": file_id})
|
|
480
|
+
file_path = info.get("file_path")
|
|
481
|
+
if not file_path:
|
|
482
|
+
return self.make_response(item, "No file_path returned by getFile.")
|
|
483
|
+
url = self._bot_file_url(file_path)
|
|
484
|
+
r = requests.get(url, timeout=self._timeout())
|
|
485
|
+
if r.status_code != 200:
|
|
486
|
+
return self.make_response(item, f"Download error: HTTP {r.status_code}")
|
|
487
|
+
local = self.prepare_path(save_as or os.path.basename(file_path))
|
|
488
|
+
os.makedirs(os.path.dirname(local), exist_ok=True)
|
|
489
|
+
with open(local, "wb") as fh:
|
|
490
|
+
fh.write(r.content)
|
|
491
|
+
return self.make_response(item, {"saved_path": local, "size": len(r.content)})
|
|
492
|
+
|
|
493
|
+
# ---------------------- Commands: Contacts / Dialogs / Messages (user) ----------------------
|
|
494
|
+
|
|
495
|
+
def cmd_tg_contacts_list(self, item: dict) -> dict:
|
|
496
|
+
if self._mode() != "user":
|
|
497
|
+
return self.make_response(item, "tg_contacts_list is available in user mode only.")
|
|
498
|
+
client = self._tl_get_client(need_auth=True)
|
|
499
|
+
contacts = client.get_contacts()
|
|
500
|
+
out = []
|
|
501
|
+
for u in contacts:
|
|
502
|
+
out.append({
|
|
503
|
+
"id": u.id,
|
|
504
|
+
"first_name": getattr(u, "first_name", None),
|
|
505
|
+
"last_name": getattr(u, "last_name", None),
|
|
506
|
+
"username": getattr(u, "username", None),
|
|
507
|
+
"phone": getattr(u, "phone", None),
|
|
508
|
+
})
|
|
509
|
+
return self.make_response(item, {"count": len(out), "contacts": out})
|
|
510
|
+
|
|
511
|
+
def cmd_tg_dialogs_list(self, item: dict) -> dict:
|
|
512
|
+
if self._mode() != "user":
|
|
513
|
+
return self.make_response(item, "tg_dialogs_list is available in user mode only.")
|
|
514
|
+
p = item.get("params", {}) or {}
|
|
515
|
+
limit = int(p.get("limit") or 20)
|
|
516
|
+
client = self._tl_get_client(need_auth=True)
|
|
517
|
+
dialogs = client.get_dialogs(limit=limit)
|
|
518
|
+
out = []
|
|
519
|
+
for d in dialogs:
|
|
520
|
+
ent = d.entity
|
|
521
|
+
title = getattr(ent, "title", None) or f"{getattr(ent, 'first_name', '')} {getattr(ent, 'last_name', '')}".strip()
|
|
522
|
+
out.append({
|
|
523
|
+
"id": getattr(ent, "id", None),
|
|
524
|
+
"title": title,
|
|
525
|
+
"username": getattr(ent, "username", None),
|
|
526
|
+
"unread_count": getattr(d, "unread_count", 0),
|
|
527
|
+
"is_channel": "Channel" in ent.__class__.__name__,
|
|
528
|
+
})
|
|
529
|
+
return self.make_response(item, {"count": len(out), "dialogs": out})
|
|
530
|
+
|
|
531
|
+
def cmd_tg_messages_get(self, item: dict) -> dict:
|
|
532
|
+
if self._mode() != "user":
|
|
533
|
+
return self.make_response(item, "tg_messages_get is available in user mode only.")
|
|
534
|
+
p = item.get("params", {}) or {}
|
|
535
|
+
chat = p.get("chat")
|
|
536
|
+
limit = int(p.get("limit") or 30)
|
|
537
|
+
if not chat:
|
|
538
|
+
return self.make_response(item, "Param 'chat' required.")
|
|
539
|
+
client = self._tl_get_client(need_auth=True)
|
|
540
|
+
ent = self._tl_resolve(client, chat)
|
|
541
|
+
|
|
542
|
+
kwargs = {}
|
|
543
|
+
if p.get("offset_id"):
|
|
544
|
+
kwargs["offset_id"] = int(p["offset_id"])
|
|
545
|
+
if p.get("min_id"):
|
|
546
|
+
kwargs["min_id"] = int(p["min_id"])
|
|
547
|
+
if p.get("max_id"):
|
|
548
|
+
kwargs["max_id"] = int(p["max_id"])
|
|
549
|
+
if p.get("search"):
|
|
550
|
+
kwargs["search"] = p.get("search")
|
|
551
|
+
|
|
552
|
+
msgs = client.get_messages(ent, limit=limit, **kwargs)
|
|
553
|
+
out = []
|
|
554
|
+
for m in msgs:
|
|
555
|
+
out.append({
|
|
556
|
+
"id": m.id,
|
|
557
|
+
"date": m.date.isoformat() if m.date else None,
|
|
558
|
+
"text": m.message or "",
|
|
559
|
+
"sender_id": getattr(m, "sender_id", None),
|
|
560
|
+
"reply_to": getattr(m, "reply_to_msg_id", None),
|
|
561
|
+
"media": bool(m.media),
|
|
562
|
+
})
|
|
563
|
+
return self.make_response(item, {"count": len(out), "messages": out})
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
# ================================================== #
|
|
4
|
+
# This file is a part of PYGPT package #
|
|
5
|
+
# Website: https://pygpt.net #
|
|
6
|
+
# GitHub: https://github.com/szczyglis-dev/py-gpt #
|
|
7
|
+
# MIT License #
|
|
8
|
+
# Created By : Marcin Szczygliński #
|
|
9
|
+
# Updated Date: 2025.06.30 02:00:00 #
|
|
10
|
+
# ================================================== #
|
|
11
|
+
|
|
12
|
+
from .plugin import *
|