dragxd commited on
Commit
7a6d557
·
1 Parent(s): ef2a5e4

Convert bot to userbot: Remove BOT_TOKEN dependency, use SESSION string for user account

Browse files
Files changed (3) hide show
  1. .sample.env +2 -3
  2. bot.py +58 -64
  3. core/userbot.py +195 -0
.sample.env CHANGED
@@ -1,6 +1,6 @@
1
- # Mandatory Environment Variable
2
 
3
- BOT_TOKEN=
4
  MAIN_CHANNEL=-1001111
5
  LOG_CHANNEL=-1001111
6
  CLOUD_CHANNEL=-1001111
@@ -14,7 +14,6 @@ API_ID=
14
  API_HASH=
15
  FORCESUB_CHANNEL_LINK=
16
  FORCESUB_CHANNEL=
17
- SESSION=
18
  SEND_SCHEDULE=
19
  THUMBNAIL=
20
  CRF=
 
1
+ # Mandatory Environment Variable for UserBot
2
 
3
+ SESSION=
4
  MAIN_CHANNEL=-1001111
5
  LOG_CHANNEL=-1001111
6
  CLOUD_CHANNEL=-1001111
 
14
  API_HASH=
15
  FORCESUB_CHANNEL_LINK=
16
  FORCESUB_CHANNEL=
 
17
  SEND_SCHEDULE=
18
  THUMBNAIL=
19
  CRF=
bot.py CHANGED
@@ -21,7 +21,7 @@ from traceback import format_exc
21
 
22
  from telethon import Button, events
23
 
24
- from core.bot import Bot
25
  from core.executors import Executors
26
  from database import DataBase
27
  from functions.info import AnimeInfo
@@ -35,76 +35,70 @@ from libs.subsplease import SubsPlease
35
  tools = Tools()
36
  tools.init_dir()
37
 
38
- # Check if running in API mode (HF Spaces)
39
- if os.getenv('HF_SPACES_MODE', 'false').lower() == 'true':
40
- from hf_api import app
41
- print("Starting in HF Spaces API mode...")
42
- app.run(host='0.0.0.0', port=7860)
43
- else:
44
- # Normal bot mode
45
- bot = Bot()
46
- dB = DataBase()
47
- subsplease = SubsPlease(dB)
48
- torrent = Torrent()
49
- schedule = ScheduleTasks(bot)
50
- admin = AdminUtils(dB, bot)
51
-
52
-
53
- @bot.on(
54
- events.NewMessage(
55
- incoming=True, pattern="^/start ?(.*)", func=lambda e: e.is_private
56
- )
57
  )
58
- async def _start(event):
59
- xnx = await event.reply("`Please Wait...`")
60
- msg_id = event.pattern_match.group(1)
61
- await dB.add_broadcast_user(event.sender_id)
62
- if Var.FORCESUB_CHANNEL and Var.FORCESUB_CHANNEL_LINK:
63
- is_user_joined = await bot.is_joined(Var.FORCESUB_CHANNEL, event.sender_id)
64
- if is_user_joined:
65
- pass
66
- else:
67
- return await xnx.edit(
68
- f"**Please Join The Following Channel To Use This Bot 🫡**",
69
- buttons=[
70
- [Button.url("🚀 JOIN CHANNEL", url=Var.FORCESUB_CHANNEL_LINK)],
71
- [
72
- Button.url(
73
- "♻️ REFRESH",
74
- url=f"https://t.me/{((await bot.get_me()).username)}?start={msg_id}",
75
- )
76
- ],
77
- ],
78
- )
79
- if msg_id:
80
- if msg_id.isdigit():
81
- msg = await bot.get_messages(Var.BACKUP_CHANNEL, ids=int(msg_id))
82
- await event.reply(msg)
83
- else:
84
- items = await dB.get_store_items(msg_id)
85
- if items:
86
- for id in items:
87
- msg = await bot.get_messages(Var.CLOUD_CHANNEL, ids=id)
88
- await event.reply(file=[i for i in msg])
89
  else:
90
- if event.sender_id == Var.OWNER:
91
- return await xnx.edit(
92
- "** < ADMIN PANEL > **",
93
- buttons=admin.admin_panel(),
94
- )
95
- await event.reply(
96
- f"**Enjoy Ongoing Anime's Best Encode 24/7 🫡**",
97
  buttons=[
 
98
  [
99
- Button.url("👨‍💻 DEV", url="t.me/kaif_00z"),
100
  Button.url(
101
- "💖 OPEN SOURCE",
102
- url="https://github.com/kaif-00z/AutoAnimeBot/",
103
- ),
104
- ]
105
  ],
106
  )
107
- await xnx.delete()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
108
 
109
 
110
  @bot.on(
 
21
 
22
  from telethon import Button, events
23
 
24
+ from core.userbot import UserBot
25
  from core.executors import Executors
26
  from database import DataBase
27
  from functions.info import AnimeInfo
 
35
  tools = Tools()
36
  tools.init_dir()
37
 
38
+ # Initialize UserBot
39
+ bot = UserBot()
40
+ dB = DataBase()
41
+ subsplease = SubsPlease(dB)
42
+ torrent = Torrent()
43
+ schedule = ScheduleTasks(bot)
44
+ admin = AdminUtils(dB, bot)
45
+
46
+
47
+ @bot.on(
48
+ events.NewMessage(
49
+ incoming=True, pattern="^/start ?(.*)", func=lambda e: e.is_private
 
 
 
 
 
 
 
50
  )
51
+ )
52
+ async def _start(event):
53
+ xnx = await event.reply("`Please Wait...`")
54
+ msg_id = event.pattern_match.group(1)
55
+ await dB.add_broadcast_user(event.sender_id)
56
+ if Var.FORCESUB_CHANNEL and Var.FORCESUB_CHANNEL_LINK:
57
+ is_user_joined = await bot.is_user_in_channel(Var.FORCESUB_CHANNEL, event.sender_id)
58
+ if is_user_joined:
59
+ pass
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
60
  else:
61
+ return await xnx.edit(
62
+ f"**Please Join The Following Channel To Use This UserBot 🫡**",
 
 
 
 
 
63
  buttons=[
64
+ [Button.url("🚀 JOIN CHANNEL", url=Var.FORCESUB_CHANNEL_LINK)],
65
  [
 
66
  Button.url(
67
+ "♻️ REFRESH",
68
+ url=f"https://t.me/{((await bot.get_me()).username)}?start={msg_id}",
69
+ )
70
+ ],
71
  ],
72
  )
73
+ if msg_id:
74
+ if msg_id.isdigit():
75
+ msg = await bot.get_messages(Var.BACKUP_CHANNEL, ids=int(msg_id))
76
+ await event.reply(msg)
77
+ else:
78
+ items = await dB.get_store_items(msg_id)
79
+ if items:
80
+ for id in items:
81
+ msg = await bot.get_messages(Var.CLOUD_CHANNEL, ids=id)
82
+ await event.reply(file=[i for i in msg])
83
+ else:
84
+ if event.sender_id == Var.OWNER:
85
+ return await xnx.edit(
86
+ "** < ADMIN PANEL > **",
87
+ buttons=admin.admin_panel(),
88
+ )
89
+ await event.reply(
90
+ f"**Enjoy Ongoing Anime's Best Encode 24/7 🫡**",
91
+ buttons=[
92
+ [
93
+ Button.url("👨‍💻 DEV", url="t.me/kaif_00z"),
94
+ Button.url(
95
+ "💖 OPEN SOURCE",
96
+ url="https://github.com/kaif-00z/AutoAnimeBot/",
97
+ ),
98
+ ]
99
+ ],
100
+ )
101
+ await xnx.delete()
102
 
103
 
104
  @bot.on(
core/userbot.py ADDED
@@ -0,0 +1,195 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # This file is part of the AutoAnime distribution.
2
+ # Copyright (c) 2025 Kaif_00z
3
+ #
4
+ # This program is free software: you can redistribute it and/or modify
5
+ # it under the terms of the GNU General Public License as published by
6
+ # the Free Software Foundation, version 3.
7
+ #
8
+ # This program is distributed in the hope that it will be useful, but
9
+ # WITHOUT ANY WARRANTY; without even the implied warranty of
10
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11
+ # General Public License as published by the Free Software Foundation, version 3.
12
+ #
13
+ # License can be found in <
14
+ # https://github.com/kaif-00z/AutoAnimeBot/blob/main/LICENSE > .
15
+
16
+ # if you are using this following code then don't forgot to give proper
17
+ # credit to t.me/kAiF_00z (github.com/kaif-00z)
18
+ # little bit inspired from pyUltroid.BaseClient
19
+
20
+ import asyncio
21
+ import sys
22
+ from logging import Logger
23
+ from traceback import format_exc
24
+
25
+ from pyrogram import Client, utils
26
+ from telethon import TelegramClient
27
+ from telethon.errors import (
28
+ AccessTokenExpiredError,
29
+ AccessTokenInvalidError,
30
+ ApiIdInvalidError,
31
+ AuthKeyDuplicatedError,
32
+ ConnectionError,
33
+ )
34
+ from telethon.sessions import StringSession
35
+ from telethon.utils import get_display_name
36
+
37
+ from libs.logger import LOGS, TelethonLogger
38
+ from functions.config import Var
39
+
40
+
41
+ class UserBot(TelegramClient):
42
+ def __init__(
43
+ self,
44
+ session_string=None,
45
+ api_id=None,
46
+ api_hash=None,
47
+ logger: Logger = LOGS,
48
+ log_attempt=True,
49
+ exit_on_error=True,
50
+ *args,
51
+ **kwargs,
52
+ ):
53
+ self._handle_error = exit_on_error
54
+ self._log_at = log_attempt
55
+ self.logger = logger
56
+
57
+ # Validate required parameters for userbot
58
+ session_string = session_string or Var.SESSION
59
+ if not session_string:
60
+ self.logger.critical("SESSION string is required for userbot!")
61
+ sys.exit(1)
62
+
63
+ api_id = api_id or Var.API_ID
64
+ api_hash = api_hash or Var.API_HASH
65
+ if not api_id or not api_hash:
66
+ self.logger.critical("API_ID and API_HASH are required for userbot!")
67
+ sys.exit(1)
68
+
69
+ kwargs["api_id"] = api_id
70
+ kwargs["api_hash"] = api_hash
71
+ kwargs["base_logger"] = TelethonLogger
72
+ kwargs["connection_retries"] = 3
73
+ kwargs["timeout"] = 30
74
+ utils.MIN_CHANNEL_ID = -1002881690459
75
+
76
+ # Initialize as user account (not bot)
77
+ super().__init__(StringSession(session_string), **kwargs)
78
+
79
+ # Initialize Pyrogram client for user account
80
+ self.pyro_client = Client(
81
+ name="pekka_user",
82
+ api_id=api_id,
83
+ api_hash=api_hash,
84
+ session_string=session_string,
85
+ in_memory=True,
86
+ )
87
+
88
+ self.run_in_loop(self.start_client())
89
+
90
+ def __repr__(self):
91
+ return "<AutoAnimeUserBot.Client :\n user: {}\n>".format(self._bot)
92
+
93
+ async def start_client(self, retry_count=0, **kwargs):
94
+ """function to start userbot client"""
95
+ if self._log_at:
96
+ self.logger.info("Trying to login as user...")
97
+
98
+ # Maximum retry limit to prevent infinite loops
99
+ max_retries = 3
100
+ if retry_count >= max_retries:
101
+ self.logger.critical(f"Failed to connect after {max_retries} attempts. Exiting.")
102
+ sys.exit(1)
103
+
104
+ try:
105
+ await self.start()
106
+ await self.pyro_client.start()
107
+
108
+ # Get user info
109
+ me = await self.get_me()
110
+ self.logger.info(f"Logged in as: {get_display_name(me)} (@{me.username})")
111
+
112
+ except ApiIdInvalidError:
113
+ self.logger.critical("API ID and API_HASH combination does not match!")
114
+ sys.exit(1)
115
+ except (AuthKeyDuplicatedError, EOFError):
116
+ if self._handle_error:
117
+ self.logger.critical("String session expired. Create new!")
118
+ sys.exit(1)
119
+ self.logger.critical("String session expired.")
120
+ except ConnectionError as e:
121
+ self.logger.error(f"Connection failed: {e}")
122
+ if retry_count < max_retries - 1:
123
+ self.logger.info(f"Retrying connection in 10 seconds... (Attempt {retry_count + 1}/{max_retries})")
124
+ await asyncio.sleep(10)
125
+ await self.start_client(retry_count=retry_count + 1)
126
+ else:
127
+ self.logger.warning("Max retries reached. HF Spaces detected - running in offline mode...")
128
+ self.logger.info("UserBot running in offline mode (HF Spaces compatibility)")
129
+ return
130
+ except (AccessTokenExpiredError, AccessTokenInvalidError):
131
+ self.logger.critical("Session expired or invalid. Create new session!")
132
+ sys.exit(1)
133
+ except Exception as e:
134
+ self.logger.critical(f"Unexpected error: {e}")
135
+ if self._handle_error:
136
+ sys.exit(1)
137
+
138
+ def run_in_loop(self, function):
139
+ """Run function in event loop"""
140
+ try:
141
+ return self.loop.run_until_complete(function)
142
+ except KeyboardInterrupt:
143
+ self.logger.info("UserBot stopped by user")
144
+ except Exception as e:
145
+ self.logger.error(f"Error in event loop: {e}")
146
+ if self._handle_error:
147
+ sys.exit(1)
148
+
149
+ async def send_message_to_channel(self, channel_id, message, **kwargs):
150
+ """Send message to channel as user"""
151
+ try:
152
+ return await self.send_message(channel_id, message, **kwargs)
153
+ except Exception as e:
154
+ self.logger.error(f"Error sending message to channel: {e}")
155
+ return None
156
+
157
+ async def send_file_to_channel(self, channel_id, file, **kwargs):
158
+ """Send file to channel as user"""
159
+ try:
160
+ return await self.send_file(channel_id, file, **kwargs)
161
+ except Exception as e:
162
+ self.logger.error(f"Error sending file to channel: {e}")
163
+ return None
164
+
165
+ async def get_channel_messages(self, channel_id, limit=10):
166
+ """Get messages from channel"""
167
+ try:
168
+ messages = []
169
+ async for message in self.iter_messages(channel_id, limit=limit):
170
+ messages.append(message)
171
+ return messages
172
+ except Exception as e:
173
+ self.logger.error(f"Error getting channel messages: {e}")
174
+ return []
175
+
176
+ async def is_user_in_channel(self, channel_id, user_id):
177
+ """Check if user is in channel"""
178
+ try:
179
+ participant = await self.get_participants(channel_id, filter=lambda p: p.id == user_id)
180
+ return len(participant) > 0
181
+ except Exception as e:
182
+ self.logger.error(f"Error checking user in channel: {e}")
183
+ return False
184
+
185
+ def run(self):
186
+ """Start the userbot"""
187
+ try:
188
+ self.logger.info("Starting AutoAnime UserBot...")
189
+ self.run_until_disconnected()
190
+ except KeyboardInterrupt:
191
+ self.logger.info("UserBot stopped by user")
192
+ except Exception as e:
193
+ self.logger.error(f"Error running userbot: {e}")
194
+ if self._handle_error:
195
+ sys.exit(1)