diff --git a/app.json b/app.json index aec0ddaaa..723caf340 100644 --- a/app.json +++ b/app.json @@ -38,14 +38,16 @@ "description": "Mongodb url from https://cloud.mongodb.com/" }, "G_DRIVE_CLIENT_ID": { - "description": "Googel Drive API Keys from https://console.developers.google.com/" + "description": "Googel Drive API Keys from https://console.developers.google.com/", + "required": false }, "G_DRIVE_CLIENT_SECRET": { - "description": "Googel Drive API Keys from https://console.developers.google.com/" + "description": "Googel Drive API Keys from https://console.developers.google.com/", + "required": false }, "G_DRIVE_IS_TD": { - "description": "Set True if it is TeamDrive", - "value": "True" + "description": "Set true if it is TeamDrive", + "required": false }, "LOG_CHANNEL_ID": { "description": "Telegram Log Channel ID" @@ -127,24 +129,15 @@ "required": false } }, - "addons": [ - ], + "addons": [], "buildpacks": [ - { - "url": "https://github.com/jonathanong/heroku-buildpack-ffmpeg-latest.git" - }, { - "url": "https://github.com/HasibulKabir/heroku-buildpack-rarlab.git" - }, { - "url": "https://github.com/opendoor-labs/heroku-buildpack-p7zip" - }, { - "url": "https://github.com/rking32/heroku-buildpack-google-chrome" - }, { - "url": "https://github.com/rking32/heroku-buildpack-chromedriver" - }, { - "url": "https://github.com/heroku/heroku-buildpack-apt.git" - }, { - "url": "heroku/python" - } + {"url": "https://github.com/jonathanong/heroku-buildpack-ffmpeg-latest.git"}, + {"url": "https://github.com/HasibulKabir/heroku-buildpack-rarlab.git"}, + {"url": "https://github.com/opendoor-labs/heroku-buildpack-p7zip"}, + {"url": "https://github.com/rking32/heroku-buildpack-google-chrome"}, + {"url": "https://github.com/rking32/heroku-buildpack-chromedriver"}, + {"url": "https://github.com/heroku/heroku-buildpack-apt.git"}, + {"url": "heroku/python"} ], "formation": { "worker": { diff --git a/config.env.sample b/config.env.sample index 929ebc049..937553758 100644 --- a/config.env.sample +++ b/config.env.sample @@ -13,6 +13,9 @@ _____REMOVE_____THIS_____LINE_____=true +# ----------- REQUIRED ----------- # + + # Get them from https://my.telegram.org/ API_ID="" API_HASH="" @@ -26,24 +29,15 @@ HU_STRING_SESSION="" DATABASE_URL="" -# Googel Drive API Keys from https://console.developers.google.com/ -G_DRIVE_CLIENT_ID="" -G_DRIVE_CLIENT_SECRET="" - - -# Set true if it is TeamDrive -G_DRIVE_IS_TD=false - - # Telegram Log Channel ID LOG_CHANNEL_ID="" -# Set true if your like to use unofficial plugins -LOAD_UNOFFICIAL_PLUGINS=false +# ----------- OPTIONAL ----------- # -# ----------- OPTIONAL ----------- # +# Set true if your like to use unofficial plugins +LOAD_UNOFFICIAL_PLUGINS=false # Get this from https://t.me/botfather if you like to use userge as a bot @@ -58,6 +52,15 @@ OWNER_ID="" WORKERS=4 +# Googel Drive API Keys from https://console.developers.google.com/ +G_DRIVE_CLIENT_ID="" +G_DRIVE_CLIENT_SECRET="" + + +# Set true if it is TeamDrive +G_DRIVE_IS_TD=false + + # Index link for gdrive G_DRIVE_INDEX_LINK="" diff --git a/init/logbot/core/api.sh b/init/logbot/core/api.sh index 9e2ae47d8..3c6ee4a60 100644 --- a/init/logbot/core/api.sh +++ b/init/logbot/core/api.sh @@ -29,7 +29,11 @@ _getResponse() { let _mid+=1 fi else - quit "invalid request ! caused by (core.Api.$FUNCNAME)\n$rawUpdate" + local errcode=$(echo $rawUpdate | jq .error_code) + local desc=$(echo $rawUpdate | jq .description) + quit "invalid request ! (caused by core.api.$FUNCNAME) +\terror_code : [$errcode] +\tdescription : $desc" fi fi } @@ -51,7 +55,7 @@ getLastMessage() { if test ${#_allMessages[@]} -gt 0; then ${_allMessages[-1]}.$1 "$2" elif [[ -n $BOT_TOKEN && -n $LOG_CHANNEL_ID ]]; then - log "first sendMessage ! caused by (core.Api.$FUNCNAME)\n"$2"" + log "first sendMessage ! (caused by \"core.api.$FUNCNAME\")\n"$2"" else log "$2" fi diff --git a/init/logbot/methods/methods.sh b/init/logbot/methods/methods.sh index 5654600b2..164974130 100644 --- a/init/logbot/methods/methods.sh +++ b/init/logbot/methods/methods.sh @@ -64,7 +64,7 @@ _polling() { test -z $cmd && break;; esac done - log "Ended LogBot Polling !" + log "LogBot Polling Ended with SIGTERM !" exit 0 } @@ -72,6 +72,6 @@ declare -i _to=1 _pollsleep() { let _to+=1 - log "sleeping ($_to) caused by (LogBot.polling)" + log "sleeping (${_to}s) (caused by \"LogBot.polling\")" sleep $_to } diff --git a/init/utils.sh b/init/utils.sh index d6733c085..f63b3adcf 100644 --- a/init/utils.sh +++ b/init/utils.sh @@ -12,11 +12,12 @@ declare -r pVer=$(sed -E 's/\w+ ([2-3])\.([0-9]+)\.([0-9]+)/\1.\2.\3/g' < <(pyth log() { local text="$*" - test ${#text} -gt 0 && test ${text::1} != '~' && echo -e "[LOGS] >>> ${text#\~}" + test ${#text} -gt 0 && test ${text::1} != '~' \ + && echo -e "[$(date +'%d-%b-%y %H:%M:%S') - INIT] - ${text#\~}" } quit() { - local err="\t:: ERROR :: $1 -> Exiting ..." + local err="\t:: ERROR :: $1\nExiting With SIGTERM ..." if (( getMessageCount )); then replyLastMessage "$err" else diff --git a/userge/core/client.py b/userge/core/client.py index 7eb3351b6..dbd561f13 100644 --- a/userge/core/client.py +++ b/userge/core/client.py @@ -44,6 +44,15 @@ async def _complete_init_tasks() -> None: class _AbstractUserge(Methods, RawClient): + @property + def is_bot(self) -> bool: + """ returns client is bot or not """ + if self._bot is not None: + return hasattr(self, 'ubot') + if Config.BOT_TOKEN: + return True + return False + @property def uptime(self) -> str: """ returns userge uptime """ diff --git a/userge/core/methods/decorators/on_cmd.py b/userge/core/methods/decorators/on_cmd.py index 90f7fd869..6b70f4639 100644 --- a/userge/core/methods/decorators/on_cmd.py +++ b/userge/core/methods/decorators/on_cmd.py @@ -28,6 +28,7 @@ def on_cmd(self, name: str = '', trigger: str = Config.CMD_TRIGGER, filter_me: bool = True, + only_admins: bool = False, allow_private: bool = True, allow_bots: bool = True, allow_groups: bool = True, @@ -71,6 +72,9 @@ def on_cmd(self, filter_me (``bool``, *optional*): If ``False``, anyone can access, defaults to True. + only_admins (``bool``, *optional*): + If ``True``, client should be an admin, defaults to False. + allow_private (``bool``, *optional*): If ``False``, prohibit private chats, defaults to True. @@ -110,6 +114,8 @@ def on_cmd(self, pattern += r"(?:\s([\S\s]+))?$" cmd = types.raw.Command(self, cname, about, group, allow_via_bot) scope: List[str] = [] + if only_admins: + scope.append('admin') if allow_private: scope.append('private') if allow_bots: diff --git a/userge/core/methods/decorators/raw_decorator.py b/userge/core/methods/decorators/raw_decorator.py index a2e536731..ba496dc72 100644 --- a/userge/core/methods/decorators/raw_decorator.py +++ b/userge/core/methods/decorators/raw_decorator.py @@ -13,6 +13,7 @@ import time import asyncio from traceback import format_exc +from functools import partial from typing import List, Union, Any, Callable, Optional from pyrogram import ( @@ -28,9 +29,29 @@ _LOG_STR = "<<>>" _PYROFUNC = Callable[['types.bound.Message'], Any] -_B_CMN_CHT: List[int] = [] _START_TO = time.time() + _B_ID = 0 +_B_CMN_CHT: List[int] = [] +_B_AD_CHT: List[int] = [] +_B_NM_CHT: List[int] = [] + +_U_ID = 0 +_U_AD_CHT: List[int] = [] +_U_NM_CHT: List[int] = [] + + +async def _raise_func(r_c: Union['_client.Userge', '_client._UsergeBot'], + chat_id: int, message_id: int, text: str) -> None: + try: + _sent = await r_c.send_message( + chat_id=chat_id, + text=f"< **ERROR** : {text} ! >", + reply_to_message_id=message_id) + await asyncio.sleep(5) + await _sent.delete() + except ChatAdminRequired: + pass async def _get_bot_chats(r_c: Union['_client.Userge', '_client._UsergeBot'], @@ -54,6 +75,32 @@ async def _get_bot_chats(r_c: Union['_client.Userge', '_client._UsergeBot'], return _B_CMN_CHT +async def _is_admin(r_c: Union['_client.Userge', '_client._UsergeBot'], + r_m: RawMessage) -> bool: + global _U_ID, _B_ID # pylint: disable=global-statement + if r_m.chat.type in ("private", "bot"): + return False + if isinstance(r_c, _client.Userge): + if not _U_ID: + _U_ID = (await r_c.get_me()).id + if r_m.chat.id not in _U_AD_CHT + _U_NM_CHT: + user = await r_m.chat.get_member(_U_ID) + if user.status in ("creator", "administrator"): + _U_AD_CHT.append(r_m.chat.id) + else: + _U_NM_CHT.append(r_m.chat.id) + return r_m.chat.id in _U_AD_CHT + if not _B_ID: + _B_ID = (await r_c.get_me()).id + if r_m.chat.id not in _B_AD_CHT + _B_NM_CHT: + bot = await r_m.chat.get_member(_B_ID) + if bot.status in ("creator", "administrator"): + _B_AD_CHT.append(r_m.chat.id) + else: + _B_NM_CHT.append(r_m.chat.id) + return r_m.chat.id in _B_AD_CHT + + class RawDecorator(RawClient): """ userge raw decoretor """ _PYRORETTYPE = Callable[[_PYROFUNC], _PYROFUNC] @@ -88,17 +135,12 @@ async def template(r_c: Union['_client.Userge', '_client._UsergeBot'], if r_m.chat.id in await _get_bot_chats(r_c, r_m): if isinstance(r_c, _client.Userge): return - if isinstance(flt, types.raw.Command) and r_m.chat and (r_m.chat.type not in scope): - try: - _sent = await r_c.send_message( - r_m.chat.id, - "**ERROR** : `Sorry!, this command not supported " - f"in this chat type [{r_m.chat.type}] !`", - reply_to_message_id=r_m.message_id) - await asyncio.sleep(5) - await _sent.delete() - except ChatAdminRequired: - pass + _raise = partial(_raise_func, r_c, r_m.chat.id, r_m.message_id) + if isinstance(flt, types.raw.Command) and r_m.chat and r_m.chat.type not in scope: + await _raise(f"`invalid chat type [{r_m.chat.type}]`") + elif (isinstance(flt, types.raw.Command) and r_m.chat and r_m.from_user + and 'admin' in scope and not await _is_admin(r_c, r_m)): + await _raise("`chat admin required`") else: try: await func(types.bound.Message(r_c, r_m, **kwargs)) @@ -106,19 +148,11 @@ async def template(r_c: Union['_client.Userge', '_client._UsergeBot'], raise except Exception as f_e: # pylint: disable=broad-except _LOG.exception(_LOG_STR, f_e) - await self._channel.log("#ERROR #TRACEBACK\n\n" - f"**Module** : `{func.__module__}`\n" - f"**Function** : `{func.__name__}`\n" - f"**Traceback** : ```{format_exc().strip()}```") - try: - _sent = await r_c.send_message( - r_m.chat.id, - f"**ERROR** : `{f_e}`\n__see logs for more info !__", - reply_to_message_id=r_m.message_id) - await asyncio.sleep(5) - await _sent.delete() - except ChatAdminRequired: - pass + await self._channel.log(f"**PLUGIN** : `{func.__module__}`\n" + f"**FUNCTION** : `{func.__name__}`\n" + f"\n```{format_exc().strip()}```", + "TRACEBACK") + await _raise(f"`{f_e}`\n__see logs for more info__") _LOG.debug(_LOG_STR, f"Loading => [ async def {func.__name__}(message) ] " f"from {func.__module__} `{log}`") flt.update(func, MessageHandler(template, filters)) diff --git a/userge/core/methods/messages/edit_message_text.py b/userge/core/methods/messages/edit_message_text.py index 9f3391a58..45cb6579c 100644 --- a/userge/core/methods/messages/edit_message_text.py +++ b/userge/core/methods/messages/edit_message_text.py @@ -88,9 +88,10 @@ async def edit_message_text(self, # pylint: disable=arguments-differ disable_web_page_preview=disable_web_page_preview, reply_markup=reply_markup) if log: + args = [msg] if isinstance(log, str): - self._channel.update(log) - await self._channel.fwd_msg(msg) + args.append(log) + await self._channel.fwd_msg(*args) del_in = del_in or Config.MSG_DELETE_TIMEOUT if del_in > 0: await asyncio.sleep(del_in) diff --git a/userge/core/methods/messages/send_as_file.py b/userge/core/methods/messages/send_as_file.py index c89adae0c..7b69d67ce 100644 --- a/userge/core/methods/messages/send_as_file.py +++ b/userge/core/methods/messages/send_as_file.py @@ -78,7 +78,8 @@ async def send_as_file(self, reply_to_message_id=reply_to_message_id) os.remove(filename) if log: + args = [msg] if isinstance(log, str): - self._channel.update(log) - await self._channel.fwd_msg(msg) + args.append(log) + await self._channel.fwd_msg(*args) return types.bound.Message(self, msg) diff --git a/userge/core/methods/messages/send_message.py b/userge/core/methods/messages/send_message.py index 9a57e11b9..ffbb2b818 100644 --- a/userge/core/methods/messages/send_message.py +++ b/userge/core/methods/messages/send_message.py @@ -99,9 +99,10 @@ async def send_message(self, # pylint: disable=arguments-differ schedule_date=schedule_date, reply_markup=reply_markup) if log: + args = [msg] if isinstance(log, str): - self._channel.update(log) - await self._channel.fwd_msg(msg) + args.append(log) + await self._channel.fwd_msg(*args) del_in = del_in or Config.MSG_DELETE_TIMEOUT if del_in > 0: await asyncio.sleep(del_in) diff --git a/userge/core/types/bound/message.py b/userge/core/types/bound/message.py index 63e7992b5..3a4a43c11 100644 --- a/userge/core/types/bound/message.py +++ b/userge/core/types/bound/message.py @@ -21,7 +21,6 @@ from userge import logging from ... import client as _client # pylint: disable=unused-import -from ..new import ChannelLogger _CANCEL_LIST: List[int] = [] _ERROR_MSG_DELETE_TIMEOUT = 5 @@ -52,7 +51,6 @@ def __init__(self, self.reply_to_message: Optional[RawMessage] if self.reply_to_message: self.reply_to_message = self.__class__(self._client, self.reply_to_message) - self._channel = ChannelLogger(client, "CORE") self._filtered = False self._process_canceled = False self._filtered_input_str: str = '' diff --git a/userge/core/types/new/channel_logger.py b/userge/core/types/new/channel_logger.py index 9bb73cf3b..b2b29a507 100644 --- a/userge/core/types/new/channel_logger.py +++ b/userge/core/types/new/channel_logger.py @@ -134,23 +134,30 @@ def update(self, name: str) -> None: """ self._string = _gen_string(name) - async def log(self, text: str) -> int: + async def log(self, text: str, name: str = '') -> int: """\nsend text message to log channel. Parameters: text (``str``): Text of the message to be sent. + name (``str``, *optional*): + New Name for logger. + Returns: message_id on success or None """ + string = self._string + if name: + string = _gen_string(name) _LOG.debug(_LOG_STR, f"logging text : {text} to channel : {Config.LOG_CHANNEL_ID}") msg = await self._client.send_message(chat_id=Config.LOG_CHANNEL_ID, - text=self._string.format(text.strip())) + text=string.format(text.strip())) return msg.message_id async def fwd_msg(self, message: '_message.Message', + name: str = '', as_copy: bool = True, remove_caption: bool = False) -> None: """\nforward message to log channel. @@ -159,6 +166,9 @@ async def fwd_msg(self, message (`pyrogram.Message`): pass pyrogram.Message object which want to forward. + name (``str``, *optional*): + New Name for logger. + as_copy (`bool`, *optional*): Pass True to forward messages without the forward header (i.e.: send a copy of the message content so @@ -178,7 +188,7 @@ async def fwd_msg(self, _LOG_STR, f"forwarding msg : {message} to channel : {Config.LOG_CHANNEL_ID}") if isinstance(message, RawMessage): if message.media: - asyncio.get_event_loop().create_task(self.log("**Forwarding Message...**")) + asyncio.get_event_loop().create_task(self.log("**Forwarding Message...**", name)) await self._client.forward_messages(chat_id=Config.LOG_CHANNEL_ID, from_chat_id=message.chat.id, message_ids=message.message_id, @@ -186,7 +196,7 @@ async def fwd_msg(self, remove_caption=remove_caption) else: await self.log( - message.text.html if hasattr(message.text, 'html') else message.text) + message.text.html if hasattr(message.text, 'html') else message.text, name) async def store(self, message: Optional['_message.Message'], diff --git a/userge/core/types/raw/filter.py b/userge/core/types/raw/filter.py index 0dd089f7a..479249943 100644 --- a/userge/core/types/raw/filter.py +++ b/userge/core/types/raw/filter.py @@ -157,7 +157,7 @@ async def disable(self) -> str: async def load(self) -> str: """ load the filter """ - if self._loaded: + if self._loaded or (self._client.is_bot and not self._allow_via_bot): return '' self._client.add_handler(self._handler, self._group) # pylint: disable=protected-access diff --git a/userge/plugins/admin/gban.py b/userge/plugins/admin/gban.py index 55943ab88..668603419 100644 --- a/userge/plugins/admin/gban.py +++ b/userge/plugins/admin/gban.py @@ -86,7 +86,7 @@ async def gban_user(message: Message): f"\n\n**First Name:** [{firstname}](tg://user?id={user_id})\n" f"**User ID:** `{user_id}`\n**Reason:** `{reason}`")) # TODO: can we add something like "GBanned by {any_sudo_user_fname}" - if hasattr(message.client, 'bot'): + if not message.client.is_bot: for chat in await message.client.get_common_chats(user_id): try: await chat.kick_member(user_id) @@ -137,7 +137,7 @@ async def ungban_user(message: Message): r"\\**#UnGbanned_User**//" f"\n\n**First Name:** [{firstname}](tg://user?id={user_id})\n" f"**User ID:** `{user_id}`")) - if hasattr(message.client, 'bot'): + if not message.client.is_bot: for chat in await message.client.get_common_chats(user_id): try: await chat.unban_member(user_id)