devhubsesion 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.
- devhubsesion-0.1.0/NOTICE +4 -0
- devhubsesion-0.1.0/PKG-INFO +58 -0
- devhubsesion-0.1.0/README.md +43 -0
- devhubsesion-0.1.0/pyproject.toml +20 -0
- devhubsesion-0.1.0/setup.cfg +4 -0
- devhubsesion-0.1.0/src/devhubsesion/__init__.py +5 -0
- devhubsesion-0.1.0/src/devhubsesion/account.py +24 -0
- devhubsesion-0.1.0/src/devhubsesion/backup.py +71 -0
- devhubsesion-0.1.0/src/devhubsesion/chat.py +59 -0
- devhubsesion-0.1.0/src/devhubsesion/client.py +139 -0
- devhubsesion-0.1.0/src/devhubsesion/contacts.py +69 -0
- devhubsesion-0.1.0/src/devhubsesion/exceptions.py +10 -0
- devhubsesion-0.1.0/src/devhubsesion/monitor.py +33 -0
- devhubsesion-0.1.0/src/devhubsesion/utils.py +25 -0
- devhubsesion-0.1.0/src/devhubsesion.egg-info/PKG-INFO +58 -0
- devhubsesion-0.1.0/src/devhubsesion.egg-info/SOURCES.txt +17 -0
- devhubsesion-0.1.0/src/devhubsesion.egg-info/dependency_links.txt +1 -0
- devhubsesion-0.1.0/src/devhubsesion.egg-info/requires.txt +2 -0
- devhubsesion-0.1.0/src/devhubsesion.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: devhubsesion
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Telegram account manager with session strings
|
|
5
|
+
Author: Astrea
|
|
6
|
+
License: Apache-2.0
|
|
7
|
+
Project-URL: Homepage, https://github.com/TheDevHubpro/devhubsesion
|
|
8
|
+
Project-URL: Repository, https://github.com/TheDevHubpro/devhubsesion
|
|
9
|
+
Requires-Python: >=3.9
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
License-File: NOTICE
|
|
12
|
+
Requires-Dist: telethon>=1.34
|
|
13
|
+
Requires-Dist: nest-asyncio>=1.6
|
|
14
|
+
Dynamic: license-file
|
|
15
|
+
|
|
16
|
+
# devhubsesion
|
|
17
|
+
|
|
18
|
+
Telegram account manager with session strings. No Telethon code needed.
|
|
19
|
+
|
|
20
|
+
## install
|
|
21
|
+
|
|
22
|
+
pip install devhubsesion
|
|
23
|
+
|
|
24
|
+
## usage
|
|
25
|
+
|
|
26
|
+
from devhubsesion import DevHubSession
|
|
27
|
+
|
|
28
|
+
with DevHubSession(session_string="...", api_id=123, api_hash="abc") as tg:
|
|
29
|
+
for c in tg.list_private_chats():
|
|
30
|
+
print(c["name"], c["id"])
|
|
31
|
+
|
|
32
|
+
or from .env:
|
|
33
|
+
|
|
34
|
+
from devhubsesion import DevHubSession
|
|
35
|
+
|
|
36
|
+
with DevHubSession.from_env() as tg:
|
|
37
|
+
print(tg.list_private_chats())
|
|
38
|
+
|
|
39
|
+
## methods
|
|
40
|
+
|
|
41
|
+
- list_private_chats
|
|
42
|
+
- list_groups_channels
|
|
43
|
+
- dump_history(user, limit, save)
|
|
44
|
+
- last_message(user)
|
|
45
|
+
- backup_voices(user=None)
|
|
46
|
+
- backup_photos(user=None)
|
|
47
|
+
- backup_full()
|
|
48
|
+
- watch(user=None, callback=None)
|
|
49
|
+
- list_contacts()
|
|
50
|
+
- extract_numbers(limit_per_chat, save)
|
|
51
|
+
- list_active_sessions()
|
|
52
|
+
- change_bio(text)
|
|
53
|
+
- change_name(first, last)
|
|
54
|
+
- send_message(user, text)
|
|
55
|
+
|
|
56
|
+
## license
|
|
57
|
+
|
|
58
|
+
Apache-2.0
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# devhubsesion
|
|
2
|
+
|
|
3
|
+
Telegram account manager with session strings. No Telethon code needed.
|
|
4
|
+
|
|
5
|
+
## install
|
|
6
|
+
|
|
7
|
+
pip install devhubsesion
|
|
8
|
+
|
|
9
|
+
## usage
|
|
10
|
+
|
|
11
|
+
from devhubsesion import DevHubSession
|
|
12
|
+
|
|
13
|
+
with DevHubSession(session_string="...", api_id=123, api_hash="abc") as tg:
|
|
14
|
+
for c in tg.list_private_chats():
|
|
15
|
+
print(c["name"], c["id"])
|
|
16
|
+
|
|
17
|
+
or from .env:
|
|
18
|
+
|
|
19
|
+
from devhubsesion import DevHubSession
|
|
20
|
+
|
|
21
|
+
with DevHubSession.from_env() as tg:
|
|
22
|
+
print(tg.list_private_chats())
|
|
23
|
+
|
|
24
|
+
## methods
|
|
25
|
+
|
|
26
|
+
- list_private_chats
|
|
27
|
+
- list_groups_channels
|
|
28
|
+
- dump_history(user, limit, save)
|
|
29
|
+
- last_message(user)
|
|
30
|
+
- backup_voices(user=None)
|
|
31
|
+
- backup_photos(user=None)
|
|
32
|
+
- backup_full()
|
|
33
|
+
- watch(user=None, callback=None)
|
|
34
|
+
- list_contacts()
|
|
35
|
+
- extract_numbers(limit_per_chat, save)
|
|
36
|
+
- list_active_sessions()
|
|
37
|
+
- change_bio(text)
|
|
38
|
+
- change_name(first, last)
|
|
39
|
+
- send_message(user, text)
|
|
40
|
+
|
|
41
|
+
## license
|
|
42
|
+
|
|
43
|
+
Apache-2.0
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "devhubsesion"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Telegram account manager with session strings"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = {text = "Apache-2.0"}
|
|
11
|
+
authors = [{name = "Astrea"}]
|
|
12
|
+
requires-python = ">=3.9"
|
|
13
|
+
dependencies = ["telethon>=1.34", "nest-asyncio>=1.6"]
|
|
14
|
+
|
|
15
|
+
[project.urls]
|
|
16
|
+
Homepage = "https://github.com/TheDevHubpro/devhubsesion"
|
|
17
|
+
Repository = "https://github.com/TheDevHubpro/devhubsesion"
|
|
18
|
+
|
|
19
|
+
[tool.setuptools.packages.find]
|
|
20
|
+
where = ["src"]
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
from telethon.tl.functions.account import GetAuthorizationsRequest
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
async def list_active_sessions(client):
|
|
5
|
+
r = await client(GetAuthorizationsRequest())
|
|
6
|
+
out = []
|
|
7
|
+
for a in r.authorizations:
|
|
8
|
+
out.append({
|
|
9
|
+
"device": a.device_model,
|
|
10
|
+
"ip": a.ip,
|
|
11
|
+
"country": a.country,
|
|
12
|
+
"current": bool(getattr(a, "current", False)),
|
|
13
|
+
})
|
|
14
|
+
return out
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
async def change_bio(client, new_bio):
|
|
18
|
+
await client.edit_about(about=new_bio)
|
|
19
|
+
return {"ok": True, "bio": new_bio}
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
async def change_name(client, first, last=""):
|
|
23
|
+
await client.edit_profile(first_name=first, last_name=last)
|
|
24
|
+
return {"ok": True, "first": first, "last": last}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from .utils import clean, mkdir, now
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
async def _targets(client, user):
|
|
6
|
+
if user is not None:
|
|
7
|
+
return [await client.get_entity(user)]
|
|
8
|
+
me = await client.get_me()
|
|
9
|
+
out = []
|
|
10
|
+
async for d in client.iter_dialogs():
|
|
11
|
+
if d.is_user and d.entity.id != me.id:
|
|
12
|
+
out.append(d.entity)
|
|
13
|
+
return out
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
async def backup_voices(client, user, save_path):
|
|
17
|
+
root = mkdir(os.path.join(save_path, f"voices_{now()}"))
|
|
18
|
+
total = 0
|
|
19
|
+
for e in await _targets(client, user):
|
|
20
|
+
n = clean(getattr(e, "first_name", None) or "chat")
|
|
21
|
+
folder = mkdir(os.path.join(root, f"{n}_{e.id}"))
|
|
22
|
+
c = 0
|
|
23
|
+
try:
|
|
24
|
+
async for m in client.iter_messages(e, limit=500):
|
|
25
|
+
if m.voice:
|
|
26
|
+
c += 1
|
|
27
|
+
await client.download_media(m, os.path.join(folder, f"{c}.ogg"))
|
|
28
|
+
except Exception:
|
|
29
|
+
continue
|
|
30
|
+
total += c
|
|
31
|
+
return {"folder": root, "total": total}
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
async def backup_photos(client, user, save_path):
|
|
35
|
+
root = mkdir(os.path.join(save_path, f"photos_{now()}"))
|
|
36
|
+
total = 0
|
|
37
|
+
for e in await _targets(client, user):
|
|
38
|
+
n = clean(getattr(e, "first_name", None) or "chat")
|
|
39
|
+
folder = mkdir(os.path.join(root, f"{n}_{e.id}"))
|
|
40
|
+
c = 0
|
|
41
|
+
try:
|
|
42
|
+
async for m in client.iter_messages(e, limit=500):
|
|
43
|
+
if m.photo:
|
|
44
|
+
c += 1
|
|
45
|
+
await client.download_media(m, os.path.join(folder, f"{c}.jpg"))
|
|
46
|
+
except Exception:
|
|
47
|
+
continue
|
|
48
|
+
total += c
|
|
49
|
+
return {"folder": root, "total": total}
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
async def backup_full(client, save_path):
|
|
53
|
+
root = mkdir(os.path.join(save_path, f"full_{now()}"))
|
|
54
|
+
me = await client.get_me()
|
|
55
|
+
total = 0
|
|
56
|
+
async for d in client.iter_dialogs():
|
|
57
|
+
if not (d.is_user and d.entity.id != me.id):
|
|
58
|
+
continue
|
|
59
|
+
n = clean(getattr(d.entity, "first_name", None) or "chat")
|
|
60
|
+
folder = mkdir(os.path.join(root, f"{n}_{d.entity.id}"))
|
|
61
|
+
c = 0
|
|
62
|
+
with open(os.path.join(folder, "messages.txt"), "w", encoding="utf-8") as f:
|
|
63
|
+
try:
|
|
64
|
+
async for m in client.iter_messages(d.entity, limit=500):
|
|
65
|
+
c += 1
|
|
66
|
+
t = m.date.isoformat() if m.date else "-"
|
|
67
|
+
f.write(f"[{t}] {'me' if m.out else n}: {m.text or ''}\n")
|
|
68
|
+
except Exception:
|
|
69
|
+
continue
|
|
70
|
+
total += c
|
|
71
|
+
return {"folder": root, "total": total}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from .utils import clean, now
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
async def list_private_chats(client):
|
|
6
|
+
me = await client.get_me()
|
|
7
|
+
out = []
|
|
8
|
+
async for d in client.iter_dialogs():
|
|
9
|
+
if d.is_user and d.entity.id != me.id:
|
|
10
|
+
out.append({
|
|
11
|
+
"id": d.entity.id,
|
|
12
|
+
"name": d.name or "",
|
|
13
|
+
"username": getattr(d.entity, "username", None),
|
|
14
|
+
})
|
|
15
|
+
return out
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
async def list_groups_channels(client):
|
|
19
|
+
g, c = [], []
|
|
20
|
+
async for d in client.iter_dialogs():
|
|
21
|
+
item = {"id": d.id, "name": d.name}
|
|
22
|
+
if d.is_group:
|
|
23
|
+
g.append(item)
|
|
24
|
+
elif d.is_channel:
|
|
25
|
+
c.append(item)
|
|
26
|
+
return {"groups": g, "channels": c}
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
async def dump_history(client, user, limit=500, save=True, out_dir="."):
|
|
30
|
+
e = await client.get_entity(user)
|
|
31
|
+
rows = []
|
|
32
|
+
async for m in client.iter_messages(e, limit=limit):
|
|
33
|
+
rows.append({
|
|
34
|
+
"date": m.date.isoformat() if m.date else None,
|
|
35
|
+
"out": bool(m.out),
|
|
36
|
+
"text": m.text or "[media]",
|
|
37
|
+
})
|
|
38
|
+
rows.reverse()
|
|
39
|
+
r = {"chat_id": e.id, "count": len(rows), "messages": rows}
|
|
40
|
+
if save:
|
|
41
|
+
n = clean(getattr(e, "first_name", None) or getattr(e, "title", None) or "chat")
|
|
42
|
+
f = os.path.join(out_dir, f"{n}_{e.id}_{now()}.txt")
|
|
43
|
+
with open(f, "w", encoding="utf-8") as fh:
|
|
44
|
+
for x in rows:
|
|
45
|
+
fh.write(f"[{x['date']}] {'me' if x['out'] else 'them'}: {x['text']}\n")
|
|
46
|
+
r["file"] = f
|
|
47
|
+
return r
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
async def last_message(client, user):
|
|
51
|
+
e = await client.get_entity(user)
|
|
52
|
+
async for m in client.iter_messages(e, limit=1):
|
|
53
|
+
return {
|
|
54
|
+
"chat_id": e.id,
|
|
55
|
+
"date": m.date.isoformat() if m.date else None,
|
|
56
|
+
"out": bool(m.out),
|
|
57
|
+
"text": m.text or "[media]",
|
|
58
|
+
}
|
|
59
|
+
return None
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import os
|
|
3
|
+
|
|
4
|
+
try:
|
|
5
|
+
import nest_asyncio
|
|
6
|
+
nest_asyncio.apply()
|
|
7
|
+
except ImportError:
|
|
8
|
+
pass
|
|
9
|
+
|
|
10
|
+
from telethon import TelegramClient
|
|
11
|
+
from telethon.sessions import StringSession
|
|
12
|
+
|
|
13
|
+
from .exceptions import NotConnectedError, SessionError
|
|
14
|
+
from .utils import mkdir
|
|
15
|
+
from . import chat, backup, monitor, contacts, account
|
|
16
|
+
|
|
17
|
+
SAVE = "./devhub_backup"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class DevHubSession:
|
|
21
|
+
def __init__(self, session_string, api_id, api_hash, save_path=SAVE):
|
|
22
|
+
if not session_string or not isinstance(session_string, str):
|
|
23
|
+
raise SessionError("session_string is invalid")
|
|
24
|
+
if not api_id or not api_hash:
|
|
25
|
+
raise SessionError("api_id and api_hash are required")
|
|
26
|
+
|
|
27
|
+
self._s = session_string.strip()
|
|
28
|
+
self._i = int(api_id)
|
|
29
|
+
self._h = str(api_hash)
|
|
30
|
+
self.save_path = save_path
|
|
31
|
+
self.client = None
|
|
32
|
+
self.me = None
|
|
33
|
+
self._loop = asyncio.new_event_loop()
|
|
34
|
+
asyncio.set_event_loop(self._loop)
|
|
35
|
+
|
|
36
|
+
@classmethod
|
|
37
|
+
def from_env(cls, env_file=".env", save_path=SAVE):
|
|
38
|
+
if env_file and os.path.exists(env_file):
|
|
39
|
+
with open(env_file, "r", encoding="utf-8") as f:
|
|
40
|
+
for line in f:
|
|
41
|
+
line = line.strip()
|
|
42
|
+
if not line or line.startswith("#") or "=" not in line:
|
|
43
|
+
continue
|
|
44
|
+
k, v = line.split("=", 1)
|
|
45
|
+
os.environ.setdefault(k.strip(), v.strip())
|
|
46
|
+
|
|
47
|
+
s = os.environ.get("TG_SESSION")
|
|
48
|
+
i = os.environ.get("TG_API_ID")
|
|
49
|
+
h = os.environ.get("TG_API_HASH")
|
|
50
|
+
if not all([s, i, h]):
|
|
51
|
+
raise SessionError("TG_SESSION / TG_API_ID / TG_API_HASH missing")
|
|
52
|
+
return cls(session_string=s, api_id=int(i), api_hash=h, save_path=save_path)
|
|
53
|
+
|
|
54
|
+
async def connect_async(self):
|
|
55
|
+
if self.client and self.client.is_connected():
|
|
56
|
+
return self
|
|
57
|
+
self.client = TelegramClient(StringSession(self._s), self._i, self._h)
|
|
58
|
+
await self.client.start()
|
|
59
|
+
self.me = await self.client.get_me()
|
|
60
|
+
mkdir(self.save_path)
|
|
61
|
+
return self
|
|
62
|
+
|
|
63
|
+
def connect(self):
|
|
64
|
+
return self._loop.run_until_complete(self.connect_async())
|
|
65
|
+
|
|
66
|
+
async def disconnect_async(self):
|
|
67
|
+
if self.client:
|
|
68
|
+
await self.client.disconnect()
|
|
69
|
+
self.client = None
|
|
70
|
+
|
|
71
|
+
def disconnect(self):
|
|
72
|
+
return self._loop.run_until_complete(self.disconnect_async())
|
|
73
|
+
|
|
74
|
+
def __enter__(self):
|
|
75
|
+
return self.connect()
|
|
76
|
+
|
|
77
|
+
def __exit__(self, *a):
|
|
78
|
+
self.disconnect()
|
|
79
|
+
|
|
80
|
+
async def __aenter__(self):
|
|
81
|
+
return await self.connect_async()
|
|
82
|
+
|
|
83
|
+
async def __aexit__(self, *a):
|
|
84
|
+
await self.disconnect_async()
|
|
85
|
+
|
|
86
|
+
def _c(self):
|
|
87
|
+
if not self.client or not self.client.is_connected():
|
|
88
|
+
raise NotConnectedError("call connect() first")
|
|
89
|
+
return self.client
|
|
90
|
+
|
|
91
|
+
def _run(self, coro):
|
|
92
|
+
return self._loop.run_until_complete(coro)
|
|
93
|
+
|
|
94
|
+
def list_private_chats(self):
|
|
95
|
+
return self._run(chat.list_private_chats(self._c()))
|
|
96
|
+
|
|
97
|
+
def list_groups_channels(self):
|
|
98
|
+
return self._run(chat.list_groups_channels(self._c()))
|
|
99
|
+
|
|
100
|
+
def dump_history(self, user, limit=500, save=True):
|
|
101
|
+
return self._run(chat.dump_history(self._c(), user, limit, save, self.save_path))
|
|
102
|
+
|
|
103
|
+
def last_message(self, user):
|
|
104
|
+
return self._run(chat.last_message(self._c(), user))
|
|
105
|
+
|
|
106
|
+
def backup_voices(self, user=None):
|
|
107
|
+
return self._run(backup.backup_voices(self._c(), user, self.save_path))
|
|
108
|
+
|
|
109
|
+
def backup_photos(self, user=None):
|
|
110
|
+
return self._run(backup.backup_photos(self._c(), user, self.save_path))
|
|
111
|
+
|
|
112
|
+
def backup_full(self):
|
|
113
|
+
return self._run(backup.backup_full(self._c(), self.save_path))
|
|
114
|
+
|
|
115
|
+
def watch(self, user=None, callback=None):
|
|
116
|
+
return self._run(monitor.watch(self._c(), user, callback))
|
|
117
|
+
|
|
118
|
+
def list_contacts(self):
|
|
119
|
+
return self._run(contacts.list_contacts(self._c()))
|
|
120
|
+
|
|
121
|
+
def extract_numbers(self, limit_per_chat=200, save=False):
|
|
122
|
+
return self._run(contacts.extract_numbers(self._c(), limit_per_chat, save, self.save_path))
|
|
123
|
+
|
|
124
|
+
def list_active_sessions(self):
|
|
125
|
+
return self._run(account.list_active_sessions(self._c()))
|
|
126
|
+
|
|
127
|
+
def change_bio(self, new_bio):
|
|
128
|
+
return self._run(account.change_bio(self._c(), new_bio))
|
|
129
|
+
|
|
130
|
+
def change_name(self, first, last=""):
|
|
131
|
+
return self._run(account.change_name(self._c(), first, last))
|
|
132
|
+
|
|
133
|
+
def send_message(self, user, text):
|
|
134
|
+
return self._run(self._send(user, text))
|
|
135
|
+
|
|
136
|
+
async def _send(self, user, text):
|
|
137
|
+
e = await self._c().get_entity(user)
|
|
138
|
+
await self._c().send_message(e, text)
|
|
139
|
+
return {"to": e.id, "ok": True}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import os
|
|
3
|
+
import re
|
|
4
|
+
|
|
5
|
+
from telethon.tl.functions.contacts import GetContactsRequest
|
|
6
|
+
from .utils import phones, mkdir, now
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
async def list_contacts(client):
|
|
10
|
+
r = await client(GetContactsRequest(hash=0))
|
|
11
|
+
out = []
|
|
12
|
+
for u in r.users:
|
|
13
|
+
if u.contact:
|
|
14
|
+
out.append({
|
|
15
|
+
"id": u.id,
|
|
16
|
+
"name": f"{u.first_name or ''} {u.last_name or ''}".strip() or "",
|
|
17
|
+
"username": u.username,
|
|
18
|
+
"phone": u.phone,
|
|
19
|
+
})
|
|
20
|
+
return out
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
async def extract_numbers(client, limit_per_chat=200, save=False, save_path="."):
|
|
24
|
+
me = await client.get_me()
|
|
25
|
+
res = []
|
|
26
|
+
total = 0
|
|
27
|
+
async for d in client.iter_dialogs():
|
|
28
|
+
if not (d.is_user and d.entity.id != me.id):
|
|
29
|
+
continue
|
|
30
|
+
n = f"{d.entity.first_name or ''} {d.entity.last_name or ''}".strip() or ""
|
|
31
|
+
found = []
|
|
32
|
+
try:
|
|
33
|
+
async for m in client.iter_messages(d.entity, limit=limit_per_chat):
|
|
34
|
+
if not m.text:
|
|
35
|
+
continue
|
|
36
|
+
for pat in phones:
|
|
37
|
+
for num in re.findall(pat, m.text):
|
|
38
|
+
if num not in found:
|
|
39
|
+
found.append(num)
|
|
40
|
+
except Exception:
|
|
41
|
+
continue
|
|
42
|
+
if found:
|
|
43
|
+
total += len(found)
|
|
44
|
+
res.append({
|
|
45
|
+
"name": n,
|
|
46
|
+
"id": d.entity.id,
|
|
47
|
+
"username": d.entity.username,
|
|
48
|
+
"numbers": found,
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
out = {"chats": res, "total_numbers": total}
|
|
52
|
+
if save:
|
|
53
|
+
root = mkdir(os.path.join(save_path, f"numbers_{now()}"))
|
|
54
|
+
base = os.path.join(root, "numbers")
|
|
55
|
+
with open(base + ".json", "w", encoding="utf-8") as f:
|
|
56
|
+
json.dump(res, f, ensure_ascii=False, indent=2)
|
|
57
|
+
with open(base + ".txt", "w", encoding="utf-8") as f:
|
|
58
|
+
for r in res:
|
|
59
|
+
f.write(f"{r['name']} ({r['id']})\n")
|
|
60
|
+
for x in r["numbers"]:
|
|
61
|
+
f.write(f" {x}\n")
|
|
62
|
+
with open(base + ".csv", "w", encoding="utf-8") as f:
|
|
63
|
+
f.write("name,id,username,numbers\n")
|
|
64
|
+
for r in res:
|
|
65
|
+
u = f"@{r['username']}" if r["username"] else ""
|
|
66
|
+
nums = " | ".join(r["numbers"])
|
|
67
|
+
f.write(f'"{r["name"]}",{r["id"]},"{u}","{nums}"\n')
|
|
68
|
+
out["folder"] = root
|
|
69
|
+
return out
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
from datetime import datetime
|
|
2
|
+
from telethon import events
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
async def watch(client, user, callback=None):
|
|
6
|
+
tid = None
|
|
7
|
+
if user is not None:
|
|
8
|
+
tid = (await client.get_entity(user)).id
|
|
9
|
+
|
|
10
|
+
@client.on(events.NewMessage)
|
|
11
|
+
async def h(e):
|
|
12
|
+
if not e.is_private:
|
|
13
|
+
return
|
|
14
|
+
if tid is not None and e.chat_id != tid:
|
|
15
|
+
return
|
|
16
|
+
s = await e.get_sender()
|
|
17
|
+
p = {
|
|
18
|
+
"time": datetime.now().strftime("%H:%M:%S"),
|
|
19
|
+
"chat_id": e.chat_id,
|
|
20
|
+
"sender": getattr(s, "first_name", None) or str(e.chat_id),
|
|
21
|
+
"out": bool(e.message.out),
|
|
22
|
+
"text": e.message.text or "[media]",
|
|
23
|
+
}
|
|
24
|
+
if callback:
|
|
25
|
+
try:
|
|
26
|
+
callback(p)
|
|
27
|
+
except Exception:
|
|
28
|
+
pass
|
|
29
|
+
|
|
30
|
+
try:
|
|
31
|
+
await client.run_until_disconnected()
|
|
32
|
+
finally:
|
|
33
|
+
client.remove_event_handler(h)
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import re
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
|
|
5
|
+
_bad = re.compile(r'[<>:"/\\|?*\[\](){} \'\t\n\r]')
|
|
6
|
+
|
|
7
|
+
phones = [
|
|
8
|
+
r'0[0-9]{10,11}',
|
|
9
|
+
r'\+98[0-9]{10,11}',
|
|
10
|
+
r'0098[0-9]{10,11}',
|
|
11
|
+
r'[0-9]{4,5}[-][0-9]{4,6}',
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def clean(name):
|
|
16
|
+
return _bad.sub('_', name or "no_name")
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def mkdir(path):
|
|
20
|
+
os.makedirs(path, exist_ok=True)
|
|
21
|
+
return path
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def now():
|
|
25
|
+
return datetime.now().strftime('%Y%m%d_%H%M%S')
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: devhubsesion
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Telegram account manager with session strings
|
|
5
|
+
Author: Astrea
|
|
6
|
+
License: Apache-2.0
|
|
7
|
+
Project-URL: Homepage, https://github.com/TheDevHubpro/devhubsesion
|
|
8
|
+
Project-URL: Repository, https://github.com/TheDevHubpro/devhubsesion
|
|
9
|
+
Requires-Python: >=3.9
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
License-File: NOTICE
|
|
12
|
+
Requires-Dist: telethon>=1.34
|
|
13
|
+
Requires-Dist: nest-asyncio>=1.6
|
|
14
|
+
Dynamic: license-file
|
|
15
|
+
|
|
16
|
+
# devhubsesion
|
|
17
|
+
|
|
18
|
+
Telegram account manager with session strings. No Telethon code needed.
|
|
19
|
+
|
|
20
|
+
## install
|
|
21
|
+
|
|
22
|
+
pip install devhubsesion
|
|
23
|
+
|
|
24
|
+
## usage
|
|
25
|
+
|
|
26
|
+
from devhubsesion import DevHubSession
|
|
27
|
+
|
|
28
|
+
with DevHubSession(session_string="...", api_id=123, api_hash="abc") as tg:
|
|
29
|
+
for c in tg.list_private_chats():
|
|
30
|
+
print(c["name"], c["id"])
|
|
31
|
+
|
|
32
|
+
or from .env:
|
|
33
|
+
|
|
34
|
+
from devhubsesion import DevHubSession
|
|
35
|
+
|
|
36
|
+
with DevHubSession.from_env() as tg:
|
|
37
|
+
print(tg.list_private_chats())
|
|
38
|
+
|
|
39
|
+
## methods
|
|
40
|
+
|
|
41
|
+
- list_private_chats
|
|
42
|
+
- list_groups_channels
|
|
43
|
+
- dump_history(user, limit, save)
|
|
44
|
+
- last_message(user)
|
|
45
|
+
- backup_voices(user=None)
|
|
46
|
+
- backup_photos(user=None)
|
|
47
|
+
- backup_full()
|
|
48
|
+
- watch(user=None, callback=None)
|
|
49
|
+
- list_contacts()
|
|
50
|
+
- extract_numbers(limit_per_chat, save)
|
|
51
|
+
- list_active_sessions()
|
|
52
|
+
- change_bio(text)
|
|
53
|
+
- change_name(first, last)
|
|
54
|
+
- send_message(user, text)
|
|
55
|
+
|
|
56
|
+
## license
|
|
57
|
+
|
|
58
|
+
Apache-2.0
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
NOTICE
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
src/devhubsesion/__init__.py
|
|
5
|
+
src/devhubsesion/account.py
|
|
6
|
+
src/devhubsesion/backup.py
|
|
7
|
+
src/devhubsesion/chat.py
|
|
8
|
+
src/devhubsesion/client.py
|
|
9
|
+
src/devhubsesion/contacts.py
|
|
10
|
+
src/devhubsesion/exceptions.py
|
|
11
|
+
src/devhubsesion/monitor.py
|
|
12
|
+
src/devhubsesion/utils.py
|
|
13
|
+
src/devhubsesion.egg-info/PKG-INFO
|
|
14
|
+
src/devhubsesion.egg-info/SOURCES.txt
|
|
15
|
+
src/devhubsesion.egg-info/dependency_links.txt
|
|
16
|
+
src/devhubsesion.egg-info/requires.txt
|
|
17
|
+
src/devhubsesion.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
devhubsesion
|