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.
@@ -0,0 +1,4 @@
1
+ devhubsesion
2
+ Copyright 2026 Astrea
3
+
4
+ Uses Telethon (https://github.com/LonamiWebs/Telethon), MIT License.
@@ -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,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,5 @@
1
+ from .client import DevHubSession
2
+ from .exceptions import DevHubError, NotConnectedError, SessionError
3
+
4
+ __all__ = ["DevHubSession", "DevHubError", "NotConnectedError", "SessionError"]
5
+ __version__ = "0.1.0"
@@ -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,10 @@
1
+ class DevHubError(Exception):
2
+ pass
3
+
4
+
5
+ class NotConnectedError(DevHubError):
6
+ pass
7
+
8
+
9
+ class SessionError(DevHubError):
10
+ pass
@@ -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,2 @@
1
+ telethon>=1.34
2
+ nest-asyncio>=1.6
@@ -0,0 +1 @@
1
+ devhubsesion