AbhiCalls 2.9.8__tar.gz → 2026.2.2__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.
Files changed (24) hide show
  1. abhicalls-2026.2.2/AbhiCalls/Start.py +29 -0
  2. abhicalls-2026.2.2/AbhiCalls/__init__.py +15 -0
  3. {abhicalls-2.9.8 → abhicalls-2026.2.2}/AbhiCalls/_engine/native.py +17 -1
  4. {abhicalls-2.9.8 → abhicalls-2026.2.2}/AbhiCalls/controller.py +22 -3
  5. {abhicalls-2.9.8 → abhicalls-2026.2.2}/AbhiCalls/player.py +2 -1
  6. abhicalls-2026.2.2/AbhiCalls/plugins/__init__.py +1 -0
  7. abhicalls-2026.2.2/AbhiCalls/plugins/base.py +93 -0
  8. {abhicalls-2.9.8 → abhicalls-2026.2.2}/AbhiCalls.egg-info/PKG-INFO +1 -1
  9. {abhicalls-2.9.8 → abhicalls-2026.2.2}/AbhiCalls.egg-info/SOURCES.txt +4 -1
  10. {abhicalls-2.9.8 → abhicalls-2026.2.2}/PKG-INFO +1 -1
  11. {abhicalls-2.9.8 → abhicalls-2026.2.2}/pyproject.toml +1 -1
  12. {abhicalls-2.9.8 → abhicalls-2026.2.2}/setup.py +1 -1
  13. abhicalls-2.9.8/AbhiCalls/__init__.py +0 -5
  14. {abhicalls-2.9.8 → abhicalls-2026.2.2}/AbhiCalls/_engine/__init__.py +0 -0
  15. {abhicalls-2.9.8 → abhicalls-2026.2.2}/AbhiCalls/models.py +0 -0
  16. {abhicalls-2.9.8 → abhicalls-2026.2.2}/AbhiCalls/queue.py +0 -0
  17. {abhicalls-2.9.8 → abhicalls-2026.2.2}/AbhiCalls/runtime.py +0 -0
  18. {abhicalls-2.9.8 → abhicalls-2026.2.2}/AbhiCalls/vc.py +0 -0
  19. {abhicalls-2.9.8 → abhicalls-2026.2.2}/AbhiCalls/yt.py +0 -0
  20. {abhicalls-2.9.8 → abhicalls-2026.2.2}/AbhiCalls.egg-info/dependency_links.txt +0 -0
  21. {abhicalls-2.9.8 → abhicalls-2026.2.2}/AbhiCalls.egg-info/requires.txt +0 -0
  22. {abhicalls-2.9.8 → abhicalls-2026.2.2}/AbhiCalls.egg-info/top_level.txt +0 -0
  23. {abhicalls-2.9.8 → abhicalls-2026.2.2}/README.md +0 -0
  24. {abhicalls-2.9.8 → abhicalls-2026.2.2}/setup.cfg +0 -0
@@ -0,0 +1,29 @@
1
+ import requests
2
+ from colorama import init, Fore, Style
3
+ from AbhiCalls import __version__, __author__
4
+
5
+ init(autoreset=True)
6
+
7
+ def check_latest_version(pkg_name="AbhiCalls"):
8
+ try:
9
+ response = requests.get(f"https://pypi.org/pypi/{pkg_name}/json", timeout=3)
10
+ if response.status_code == 200:
11
+ return response.json()["info"]["version"]
12
+ except Exception:
13
+ return None
14
+
15
+ def print_startup_message():
16
+ print(Fore.GREEN + "✅ AbhiCalls started...\n")
17
+
18
+ print(Fore.CYAN + f"Version : {__version__}")
19
+ print(Fore.CYAN + f"Author : {__author__}")
20
+ print(Fore.CYAN + "License : Abhishek Special")
21
+ print(Fore.CYAN + "GitHub : https://github.com/YouTubeMusicAPI/AbhiCalls")
22
+ print(Fore.CYAN + "Telegram : https://t.me/WhyAbhishek")
23
+
24
+ latest = check_latest_version("AbhiCalls")
25
+ if latest and latest != __version__:
26
+ print(Fore.YELLOW + "\nUpdate Available!")
27
+ print(Fore.YELLOW + f"New AbhiCalls v{latest} is now available!")
28
+ else:
29
+ print(Fore.GREEN + "\nYou are using the latest version!")
@@ -0,0 +1,15 @@
1
+ from AbhiCalls.vc import VoiceEngine
2
+ from AbhiCalls.runtime import idle
3
+ from .player import Player
4
+ from .plugins import Plugin
5
+
6
+ __all__ = ["VoiceEngine", "idle"]
7
+
8
+ __version__ = "2026.2.2"
9
+ __author__ = "ABHISHEK THAKUR"
10
+
11
+ try:
12
+ from .Start import print_startup_message
13
+ print_startup_message()
14
+ except Exception:
15
+ pass
@@ -1,16 +1,32 @@
1
1
  from pytgcalls import PyTgCalls, filters
2
- from pytgcalls.types import MediaStream, AudioQuality, StreamEnded
2
+ from pytgcalls.types import MediaStream, AudioQuality, StreamEnded, ChatUpdate
3
+
3
4
 
4
5
  class _NativeEngine:
5
6
  def __init__(self, app):
6
7
  self._core = PyTgCalls(app)
7
8
  self.on_end = None
9
+ self.on_vc_closed = None
8
10
 
11
+ # 🔹 STREAM END
9
12
  @self._core.on_update(filters.stream_end())
10
13
  async def _ended(_, update: StreamEnded):
11
14
  if self.on_end:
12
15
  await self.on_end(update.chat_id)
13
16
 
17
+ # 🔹 VC CLOSED
18
+ @self._core.on_update(
19
+ filters.chat_update(ChatUpdate.Status.CLOSED_VOICE_CHAT)
20
+ )
21
+ async def _vc_closed(_, update):
22
+ chat_id = update.chat_id
23
+
24
+ if self.on_vc_closed:
25
+ try:
26
+ await self.on_vc_closed(chat_id)
27
+ except Exception as e:
28
+ print("[NativeEngine] on_vc_closed error:", e)
29
+
14
30
  async def start(self):
15
31
  await self._core.start()
16
32
 
@@ -7,7 +7,11 @@ class VoiceController:
7
7
  def __init__(self, engine):
8
8
  self.engine = engine
9
9
  self.player = Player(engine)
10
+
11
+ # engine hooks
10
12
  self.engine.on_end = self._on_end
13
+ self.engine.on_vc_closed = self._on_vc_closed
14
+
11
15
  self.plugins = []
12
16
 
13
17
  def load_plugin(self, plugin):
@@ -52,7 +56,7 @@ class VoiceController:
52
56
  first_pos = pos
53
57
  last_song = song
54
58
 
55
- # 🔥 First song actually started
59
+ # first song actually started
56
60
  if first_pos == 1 and last_song:
57
61
  await self._hook("on_song_start", chat_id, last_song)
58
62
 
@@ -97,12 +101,10 @@ class VoiceController:
97
101
  async def _on_end(self, chat_id):
98
102
  q = self.player.queues.get(chat_id)
99
103
 
100
- # old song end hook
101
104
  old_song = q.current() if q else None
102
105
  if old_song:
103
106
  await self._hook("on_song_end", chat_id, old_song)
104
107
 
105
- # move to next song
106
108
  await self.player.skip(chat_id)
107
109
 
108
110
  q = self.player.queues.get(chat_id)
@@ -110,3 +112,20 @@ class VoiceController:
110
112
 
111
113
  if next_song:
112
114
  await self._hook("on_song_start", chat_id, next_song)
115
+
116
+ # =========================
117
+ # VC CLOSED (CLEANUP)
118
+ # =========================
119
+ async def _on_vc_closed(self, chat_id):
120
+ q = self.player.queues.get(chat_id)
121
+ if q:
122
+ q.clear()
123
+
124
+ try:
125
+ await self.player.stop(chat_id)
126
+ except:
127
+ pass
128
+
129
+ await self._hook("on_vc_closed", chat_id)
130
+ print(f"[VoiceController] VC closed → cleaned {chat_id}")
131
+
@@ -80,4 +80,5 @@ class Player:
80
80
  return None
81
81
  elapsed = int(time.time() - self.start_time.get(chat_id, 0))
82
82
  return max(q.current().duration_sec - elapsed, 0)
83
-
83
+
84
+
@@ -0,0 +1 @@
1
+ from .base import Plugin
@@ -0,0 +1,93 @@
1
+ import asyncio
2
+
3
+
4
+ QUEUE_DELETE_AFTER = 30 # seconds
5
+ VC_END_DELETE_AFTER = 10 # seconds
6
+
7
+
8
+ class Plugin:
9
+ name = "base"
10
+
11
+ def __init__(self, app):
12
+ self.app = app
13
+ self.now_playing_msg = {} # chat_id -> message
14
+
15
+ async def on_song_start(self, chat_id, song):
16
+ caption = (
17
+ "▶️ Nᴏᴡ Pʟᴀʏɪɴɢ\n\n"
18
+ f"🎵 Tɪᴛʟᴇ : [{song.title[:19]}]({song.url})\n"
19
+ f"⏱ Dᴜʀᴀᴛɪᴏɴ : {song.duration}\n"
20
+ f"🙋 Rᴇǫᴜᴇsᴛᴇᴅ Bʏ : {song.requested_by}"
21
+ )
22
+
23
+ # delete old now-playing msg if exists
24
+ old = self.now_playing_msg.get(chat_id)
25
+ if old:
26
+ try:
27
+ await old.delete()
28
+ except:
29
+ pass
30
+
31
+ if song.thumb:
32
+ msg = await self.app.send_photo(
33
+ chat_id,
34
+ photo=song.thumb,
35
+ caption=caption
36
+ )
37
+ else:
38
+ msg = await self.app.send_message(chat_id, caption)
39
+
40
+ # store current now playing msg
41
+ self.now_playing_msg[chat_id] = msg
42
+
43
+ async def on_song_end(self, chat_id, song):
44
+ # delete current song msg when song ends
45
+ msg = self.now_playing_msg.pop(chat_id, None)
46
+ if msg:
47
+ try:
48
+ await msg.delete()
49
+ except:
50
+ pass
51
+
52
+ async def on_queue_add(self, chat_id, song, position):
53
+ if position == 1:
54
+ return
55
+
56
+ caption = (
57
+ "➕ Aᴅᴅᴇᴅ Tᴏ Qᴜᴇᴜᴇ\n\n"
58
+ f"🎵 Tɪᴛʟᴇ : [{song.title[:19]}]({song.url})\n"
59
+ f"⏱ Dᴜʀᴀᴛɪᴏɴ : {song.duration}\n"
60
+ f"🙋 Rᴇǫᴜᴇsᴛᴇᴅ Bʏ : {song.requested_by}\n"
61
+ f"📍 Pᴏsɪᴛɪᴏɴ : {position}"
62
+ )
63
+
64
+ if song.thumb:
65
+ msg = await self.app.send_photo(
66
+ chat_id,
67
+ photo=song.thumb,
68
+ caption=caption
69
+ )
70
+ else:
71
+ msg = await self.app.send_message(chat_id, caption)
72
+
73
+ # auto delete queue msg after 30 sec
74
+ asyncio.create_task(self._auto_delete(msg, QUEUE_DELETE_AFTER))
75
+
76
+ async def on_vc_closed(self, chat_id):
77
+ try:
78
+ msg = await self.app.send_message(
79
+ chat_id,
80
+ "🔴 Vᴏɪᴄᴇ Cʜᴀᴛ Eɴᴅᴇᴅ\n\n"
81
+ "🧹 Qᴜᴇᴜᴇ Cʟᴇᴀʀᴇᴅ & Pʟᴀʏᴇʀ Sᴛᴏᴘᴘᴇᴅ."
82
+ )
83
+ asyncio.create_task(self._auto_delete(msg, VC_END_DELETE_AFTER))
84
+ except:
85
+ pass
86
+
87
+ async def _auto_delete(self, msg, delay):
88
+ try:
89
+ await asyncio.sleep(delay)
90
+ await msg.delete()
91
+ except:
92
+ pass
93
+
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: AbhiCalls
3
- Version: 2.9.8
3
+ Version: 2026.2.2
4
4
  Summary: Telegram Voice Chat Music Engine with Queue, Loop, ETA and Player controls
5
5
  Home-page: https://github.com/YouTubeMusicAPI/TgCall
6
6
  Author: Abhishek Thakur
@@ -1,6 +1,7 @@
1
1
  README.md
2
2
  pyproject.toml
3
3
  setup.py
4
+ AbhiCalls/Start.py
4
5
  AbhiCalls/__init__.py
5
6
  AbhiCalls/controller.py
6
7
  AbhiCalls/models.py
@@ -15,4 +16,6 @@ AbhiCalls.egg-info/dependency_links.txt
15
16
  AbhiCalls.egg-info/requires.txt
16
17
  AbhiCalls.egg-info/top_level.txt
17
18
  AbhiCalls/_engine/__init__.py
18
- AbhiCalls/_engine/native.py
19
+ AbhiCalls/_engine/native.py
20
+ AbhiCalls/plugins/__init__.py
21
+ AbhiCalls/plugins/base.py
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: AbhiCalls
3
- Version: 2.9.8
3
+ Version: 2026.2.2
4
4
  Summary: Telegram Voice Chat Music Engine with Queue, Loop, ETA and Player controls
5
5
  Home-page: https://github.com/YouTubeMusicAPI/TgCall
6
6
  Author: Abhishek Thakur
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "AbhiCalls"
7
- version = "2.9.8"
7
+ version = "2026.2.2"
8
8
  description = "Telegram Voice Chat Music Engine with Queue, Loop, ETA and Player controls"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"
@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
2
2
 
3
3
  setup(
4
4
  name="AbhiCalls",
5
- version="2.9.8",
5
+ version="2026.2.2",
6
6
  description="Telegram Voice Chat Music Engine with Queue, Loop, ETA support",
7
7
  long_description=open("README.md", encoding="utf-8").read(),
8
8
  long_description_content_type="text/markdown",
@@ -1,5 +0,0 @@
1
- from AbhiCalls.vc import VoiceEngine
2
- from AbhiCalls.runtime import idle
3
- from .player import Player
4
-
5
- __all__ = ["VoiceEngine", "idle"]
File without changes
File without changes
File without changes
File without changes