mirror of https://github.com/coddrago/Heroku
1.1.22
- Fix bugs related to web, more specifically: Session save timing, adding more than 1 account and proper restart - Rework Dockerfiles so they work properly - Add `uvloop` so the asyncio runs faster - Add `Docker` badge to info - Improve Okteto performance by adjusting settings in `okteto-stack.yml` - New ascii_faces in utils - Typehints update - Fix Okteto pinger messages removalpull/1/head
parent
5aad62610c
commit
a6d01c6cdb
|
@ -25,7 +25,7 @@ RUN apt update && apt install \
|
||||||
libcairo2 git -y --no-install-recommends
|
libcairo2 git -y --no-install-recommends
|
||||||
|
|
||||||
# Clean the cache
|
# Clean the cache
|
||||||
RUN rm -rf /var/lib/apt/lists /var/cache/apt/archives /tmp
|
RUN rm -rf /var/lib/apt/lists /var/cache/apt/archives /tmp/*
|
||||||
|
|
||||||
# Expose IP address
|
# Expose IP address
|
||||||
EXPOSE 8080
|
EXPOSE 8080
|
||||||
|
|
2
Okteto
2
Okteto
|
@ -33,7 +33,7 @@ RUN apt update && apt install \
|
||||||
libavdevice-dev -y --no-install-recommends
|
libavdevice-dev -y --no-install-recommends
|
||||||
|
|
||||||
# Clean the cache
|
# Clean the cache
|
||||||
RUN rm -rf /var/lib/apt/lists /var/cache/apt/archives /tmp
|
RUN rm -rf /var/lib/apt/lists /var/cache/apt/archives /tmp/*
|
||||||
|
|
||||||
# Expose IP address
|
# Expose IP address
|
||||||
EXPOSE 8080
|
EXPOSE 8080
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
version: "3"
|
||||||
|
services:
|
||||||
|
worker:
|
||||||
|
build: .
|
||||||
|
volumes:
|
||||||
|
- worker:/data
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
worker:
|
|
@ -41,6 +41,8 @@ import sqlite3
|
||||||
import sys
|
import sys
|
||||||
from math import ceil
|
from math import ceil
|
||||||
from typing import Union
|
from typing import Union
|
||||||
|
import uvloop
|
||||||
|
import subprocess
|
||||||
|
|
||||||
from telethon import TelegramClient, events
|
from telethon import TelegramClient, events
|
||||||
from telethon.errors.rpcerrorlist import (
|
from telethon.errors.rpcerrorlist import (
|
||||||
|
@ -78,6 +80,8 @@ DATA_DIR = (
|
||||||
|
|
||||||
CONFIG_PATH = os.path.join(DATA_DIR, "config.json")
|
CONFIG_PATH = os.path.join(DATA_DIR, "config.json")
|
||||||
|
|
||||||
|
uvloop.install()
|
||||||
|
|
||||||
|
|
||||||
def run_config(
|
def run_config(
|
||||||
db: database.Database,
|
db: database.Database,
|
||||||
|
@ -386,35 +390,36 @@ class Hikka:
|
||||||
importlib.invalidate_caches()
|
importlib.invalidate_caches()
|
||||||
self._get_api_token()
|
self._get_api_token()
|
||||||
|
|
||||||
async def fetch_clients_from_web(self):
|
async def save_client_session(self, client: TelegramClient):
|
||||||
"""Imports clients from web module"""
|
session = SQLiteSession(
|
||||||
for client in self.web.clients:
|
os.path.join(
|
||||||
session = SQLiteSession(
|
self.arguments.data_root or DATA_DIR,
|
||||||
os.path.join(
|
f"hikka-+{'x' * (len(client.phone) - 5)}{client.phone[-4:]}-{(await client.get_me()).id}",
|
||||||
self.arguments.data_root or DATA_DIR,
|
|
||||||
f"hikka-+{'x' * (len(client.phone) - 5)}{client.phone[-4:]}-{(await client.get_me()).id}",
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
)
|
||||||
|
|
||||||
session.set_dc(
|
session.set_dc(
|
||||||
client.session.dc_id,
|
client.session.dc_id,
|
||||||
client.session.server_address,
|
client.session.server_address,
|
||||||
client.session.port,
|
client.session.port,
|
||||||
)
|
)
|
||||||
session.auth_key = client.session.auth_key
|
session.auth_key = client.session.auth_key
|
||||||
session.save()
|
session.save()
|
||||||
client.session = session
|
client.session = session
|
||||||
# Set db attribute to this client in order to save
|
# Set db attribute to this client in order to save
|
||||||
# custom bot nickname from web
|
# custom bot nickname from web
|
||||||
client.hikka_db = database.Database(client)
|
client.hikka_db = database.Database(client)
|
||||||
await client.hikka_db.init()
|
await client.hikka_db.init()
|
||||||
|
|
||||||
self.clients = list(set(self.clients + self.web.clients))
|
|
||||||
|
|
||||||
def _web_banner(self):
|
def _web_banner(self):
|
||||||
"""Shows web banner"""
|
"""Shows web banner"""
|
||||||
print("✅ Web mode ready for configuration")
|
print("✅ Web mode ready for configuration")
|
||||||
print(f"🌐 Please visit http://127.0.0.1:{self.web.port}")
|
ip = (
|
||||||
|
"127.0.0.1"
|
||||||
|
if "DOCKER" not in os.environ
|
||||||
|
else subprocess.run(["hostname", "-i"], stdout=subprocess.PIPE).stdout
|
||||||
|
)
|
||||||
|
print(f"🌐 Please visit http://{ip}:{self.web.port}")
|
||||||
|
|
||||||
async def wait_for_web_auth(self, token: str):
|
async def wait_for_web_auth(self, token: str):
|
||||||
"""Waits for web auth confirmation in Telegram"""
|
"""Waits for web auth confirmation in Telegram"""
|
||||||
|
@ -449,9 +454,12 @@ class Hikka:
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def _init_clients(self):
|
def _init_clients(self) -> bool:
|
||||||
"""Reads session from disk and inits them"""
|
"""
|
||||||
for phone_id, phone in self.phones.items():
|
Reads session from disk and inits them
|
||||||
|
:returns: `True` if at least one client started successfully
|
||||||
|
"""
|
||||||
|
for phone_id, phone in self.phones.copy().items():
|
||||||
session = os.path.join(
|
session = os.path.join(
|
||||||
self.arguments.data_root or DATA_DIR,
|
self.arguments.data_root or DATA_DIR,
|
||||||
f'hikka{f"-{phone_id}" if phone_id else ""}',
|
f'hikka{f"-{phone_id}" if phone_id else ""}',
|
||||||
|
@ -473,7 +481,7 @@ class Hikka:
|
||||||
|
|
||||||
install_entity_caching(client)
|
install_entity_caching(client)
|
||||||
|
|
||||||
self.clients.append(client)
|
self.clients += [client]
|
||||||
except sqlite3.OperationalError:
|
except sqlite3.OperationalError:
|
||||||
print(
|
print(
|
||||||
"Check that this is the only instance running. "
|
"Check that this is the only instance running. "
|
||||||
|
@ -482,20 +490,22 @@ class Hikka:
|
||||||
continue
|
continue
|
||||||
except (TypeError, AuthKeyDuplicatedError):
|
except (TypeError, AuthKeyDuplicatedError):
|
||||||
os.remove(os.path.join(DATA_DIR, f"{session}.session"))
|
os.remove(os.path.join(DATA_DIR, f"{session}.session"))
|
||||||
self.main()
|
del self.phones[phone_id]
|
||||||
except (ValueError, ApiIdInvalidError):
|
except (ValueError, ApiIdInvalidError):
|
||||||
# Bad API hash/ID
|
# Bad API hash/ID
|
||||||
run_config({}, self.arguments.data_root)
|
run_config({}, self.arguments.data_root)
|
||||||
return
|
return False
|
||||||
except PhoneNumberInvalidError:
|
except PhoneNumberInvalidError:
|
||||||
print(
|
print(
|
||||||
"Phone number is incorrect. Use international format (+XX...) "
|
"Phone number is incorrect. Use international format (+XX...) "
|
||||||
"and don't put spaces in it."
|
"and don't put spaces in it."
|
||||||
)
|
)
|
||||||
continue
|
del self.phones[phone_id]
|
||||||
except InteractiveAuthRequired:
|
except InteractiveAuthRequired:
|
||||||
print(f"Session {session} was terminated and re-auth is required")
|
print(f"Session {session} was terminated and re-auth is required")
|
||||||
continue
|
del self.phones[phone_id]
|
||||||
|
|
||||||
|
return bool(self.phones)
|
||||||
|
|
||||||
def _init_loop(self):
|
def _init_loop(self):
|
||||||
"""Initializes main event loop and starts handler for each client"""
|
"""Initializes main event loop and starts handler for each client"""
|
||||||
|
@ -654,11 +664,13 @@ class Hikka:
|
||||||
save_config_key("port", self.arguments.port)
|
save_config_key("port", self.arguments.port)
|
||||||
self._get_token()
|
self._get_token()
|
||||||
|
|
||||||
if not self.clients and not self.phones and not self._initial_setup():
|
if (
|
||||||
|
not self.clients # Search for already inited clients
|
||||||
|
and not self.phones # Search for already added phones / sessions
|
||||||
|
or not self._init_clients() # Attempt to read sessions from env
|
||||||
|
) and not self._initial_setup(): # Otherwise attempt to run setup
|
||||||
return
|
return
|
||||||
|
|
||||||
self._init_clients()
|
|
||||||
|
|
||||||
self.loop.set_exception_handler(
|
self.loop.set_exception_handler(
|
||||||
lambda _, x: logging.error(
|
lambda _, x: logging.error(
|
||||||
f"Exception on event loop! {x['message']}",
|
f"Exception on event loop! {x['message']}",
|
||||||
|
|
|
@ -477,7 +477,7 @@ class HikkaSecurityMod(loader.Module):
|
||||||
list(set(self._db.get(main.__name__, "nonickusers", []) + [user.id])),
|
list(set(self._db.get(main.__name__, "nonickusers", []) + [user.id])),
|
||||||
)
|
)
|
||||||
|
|
||||||
call.edit(
|
await call.edit(
|
||||||
self.strings("user_nn").format(
|
self.strings("user_nn").format(
|
||||||
user.id,
|
user.id,
|
||||||
utils.escape_html(get_display_name(user)),
|
utils.escape_html(get_display_name(user)),
|
||||||
|
|
|
@ -15,7 +15,10 @@ import time
|
||||||
|
|
||||||
from telethon.errors.rpcerrorlist import YouBlockedUserError
|
from telethon.errors.rpcerrorlist import YouBlockedUserError
|
||||||
from telethon.tl.functions.contacts import UnblockRequest
|
from telethon.tl.functions.contacts import UnblockRequest
|
||||||
from telethon.tl.functions.messages import GetScheduledHistoryRequest
|
from telethon.tl.functions.messages import (
|
||||||
|
GetScheduledHistoryRequest,
|
||||||
|
DeleteScheduledMessagesRequest,
|
||||||
|
)
|
||||||
from telethon.tl.types import Message
|
from telethon.tl.types import Message
|
||||||
|
|
||||||
from .. import loader, main, utils
|
from .. import loader, main, utils
|
||||||
|
@ -54,8 +57,12 @@ class OktetoMod(loader.Module):
|
||||||
if messages:
|
if messages:
|
||||||
logger.info("Deleting previously scheduled Okteto pinger messages")
|
logger.info("Deleting previously scheduled Okteto pinger messages")
|
||||||
|
|
||||||
for message in messages:
|
await client(
|
||||||
await message.delete()
|
DeleteScheduledMessagesRequest(
|
||||||
|
self._bot,
|
||||||
|
[message.id for message in messages],
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
raise loader.SelfUnload
|
raise loader.SelfUnload
|
||||||
|
|
||||||
|
|
|
@ -45,7 +45,6 @@ import git
|
||||||
import grapheme
|
import grapheme
|
||||||
import requests
|
import requests
|
||||||
import telethon
|
import telethon
|
||||||
from aiogram.types import CallbackQuery
|
|
||||||
from telethon.hints import Entity
|
from telethon.hints import Entity
|
||||||
from telethon.tl.custom.message import Message
|
from telethon.tl.custom.message import Message
|
||||||
from telethon.tl.functions.account import UpdateNotifySettingsRequest
|
from telethon.tl.functions.account import UpdateNotifySettingsRequest
|
||||||
|
@ -83,7 +82,7 @@ from telethon.tl.types import (
|
||||||
User,
|
User,
|
||||||
)
|
)
|
||||||
|
|
||||||
from .inline.types import InlineCall
|
from .inline.types import InlineCall, InlineMessage
|
||||||
|
|
||||||
FormattingEntity = Union[
|
FormattingEntity = Union[
|
||||||
MessageEntityUnknown,
|
MessageEntityUnknown,
|
||||||
|
@ -279,16 +278,16 @@ def relocate_entities(
|
||||||
|
|
||||||
|
|
||||||
async def answer(
|
async def answer(
|
||||||
message: Union[Message, CallbackQuery, InlineCall],
|
message: Union[Message, InlineCall, InlineMessage],
|
||||||
response: str,
|
response: str,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
) -> Union[CallbackQuery, Message]:
|
) -> Union[InlineCall, InlineMessage, Message]:
|
||||||
"""Use this to give the response to a command"""
|
"""Use this to give the response to a command"""
|
||||||
# Compatibility with FTG\GeekTG
|
# Compatibility with FTG\GeekTG
|
||||||
if isinstance(message, list) and message:
|
if isinstance(message, list) and message:
|
||||||
message = message[0]
|
message = message[0]
|
||||||
|
|
||||||
if isinstance(message, (CallbackQuery, InlineCall)):
|
if isinstance(message, (InlineMessage, InlineCall)):
|
||||||
await message.edit(response)
|
await message.edit(response)
|
||||||
return message
|
return message
|
||||||
|
|
||||||
|
@ -578,13 +577,16 @@ def chunks(_list: Union[list, tuple, set], n: int, /) -> list:
|
||||||
|
|
||||||
def get_named_platform() -> str:
|
def get_named_platform() -> str:
|
||||||
"""Returns formatted platform name"""
|
"""Returns formatted platform name"""
|
||||||
if os.path.isfile("/proc/device-tree/model"):
|
try:
|
||||||
with open("/proc/device-tree/model") as f:
|
if os.path.isfile("/proc/device-tree/model"):
|
||||||
model = f.read()
|
with open("/proc/device-tree/model") as f:
|
||||||
return f"🍇 {model}" if model.startswith("Raspberry") else f"❓ {model}"
|
model = f.read()
|
||||||
|
return f"🍇 {model}" if model.startswith("Raspberry") else f"❓ {model}"
|
||||||
is_termux = bool(os.popen('echo $PREFIX | grep -o "com.termux"').read())
|
except Exception:
|
||||||
|
# In case of weird fs, aka Termux
|
||||||
|
pass
|
||||||
|
|
||||||
|
is_termux = "com.termux" in os.environ.get("PREFIX", "")
|
||||||
is_okteto = "OKTETO" in os.environ
|
is_okteto = "OKTETO" in os.environ
|
||||||
is_docker = "DOCKER" in os.environ
|
is_docker = "DOCKER" in os.environ
|
||||||
is_lavhost = "LAVHOST" in os.environ
|
is_lavhost = "LAVHOST" in os.environ
|
||||||
|
@ -620,18 +622,13 @@ def ascii_face() -> str:
|
||||||
random.choice(
|
random.choice(
|
||||||
[
|
[
|
||||||
"ヽ(๑◠ܫ◠๑)ノ",
|
"ヽ(๑◠ܫ◠๑)ノ",
|
||||||
"☜(⌒▽⌒)☞",
|
|
||||||
"/|\\ ^._.^ /|\\",
|
|
||||||
"(◕ᴥ◕ʋ)",
|
"(◕ᴥ◕ʋ)",
|
||||||
"ᕙ(`▽´)ᕗ",
|
"ᕙ(`▽´)ᕗ",
|
||||||
"(☞゚∀゚)☞",
|
|
||||||
"(✿◠‿◠)",
|
"(✿◠‿◠)",
|
||||||
"(▰˘◡˘▰)",
|
"(▰˘◡˘▰)",
|
||||||
"(˵ ͡° ͜ʖ ͡°˵)",
|
"(˵ ͡° ͜ʖ ͡°˵)",
|
||||||
"ʕっ•ᴥ•ʔっ",
|
"ʕっ•ᴥ•ʔっ",
|
||||||
"( ͡° ᴥ ͡°)",
|
"( ͡° ᴥ ͡°)",
|
||||||
"ʕ♥ᴥ♥ʔ",
|
|
||||||
"\\m/,(> . <)_\\m/",
|
|
||||||
"(๑•́ ヮ •̀๑)",
|
"(๑•́ ヮ •̀๑)",
|
||||||
"٩(^‿^)۶",
|
"٩(^‿^)۶",
|
||||||
"(っˆڡˆς)",
|
"(っˆڡˆς)",
|
||||||
|
@ -639,22 +636,49 @@ def ascii_face() -> str:
|
||||||
"⊙ω⊙",
|
"⊙ω⊙",
|
||||||
"٩(^ᴗ^)۶",
|
"٩(^ᴗ^)۶",
|
||||||
"(´・ω・)っ由",
|
"(´・ω・)っ由",
|
||||||
"※\\(^o^)/※",
|
|
||||||
"٩(*❛⊰❛)~❤",
|
|
||||||
"( ͡~ ͜ʖ ͡°)",
|
"( ͡~ ͜ʖ ͡°)",
|
||||||
"✧♡(◕‿◕✿)",
|
"✧♡(◕‿◕✿)",
|
||||||
"โ๏௰๏ใ ื",
|
"โ๏௰๏ใ ื",
|
||||||
"∩。• ᵕ •。∩ ♡",
|
"∩。• ᵕ •。∩ ♡",
|
||||||
"(♡´౪`♡)",
|
"(♡´౪`♡)",
|
||||||
"(◍>◡<◍)⋈。✧♡",
|
"(◍>◡<◍)⋈。✧♡",
|
||||||
"♥(ˆ⌣ˆԅ)",
|
|
||||||
"╰(✿´⌣`✿)╯♡",
|
"╰(✿´⌣`✿)╯♡",
|
||||||
"ʕ•ᴥ•ʔ",
|
"ʕ•ᴥ•ʔ",
|
||||||
"ᶘ ◕ᴥ◕ᶅ",
|
"ᶘ ◕ᴥ◕ᶅ",
|
||||||
"▼・ᴥ・▼",
|
"▼・ᴥ・▼",
|
||||||
"【≽ܫ≼】",
|
|
||||||
"ฅ^•ﻌ•^ฅ",
|
"ฅ^•ﻌ•^ฅ",
|
||||||
"(΄◞ิ౪◟ิ‵)",
|
"(΄◞ิ౪◟ิ‵)",
|
||||||
|
"٩(^ᴗ^)۶",
|
||||||
|
"ᕴーᴥーᕵ",
|
||||||
|
"ʕ→ᴥ←ʔ",
|
||||||
|
"ʕᵕᴥᵕʔ",
|
||||||
|
"ʕᵒᴥᵒʔ",
|
||||||
|
"ᵔᴥᵔ",
|
||||||
|
"(✿╹◡╹)",
|
||||||
|
"(๑→ܫ←)",
|
||||||
|
"ʕ·ᴥ· ʔ",
|
||||||
|
"(ノ≧ڡ≦)",
|
||||||
|
"(≖ᴗ≖✿)",
|
||||||
|
"(〜^∇^ )〜",
|
||||||
|
"( ノ・ェ・ )ノ",
|
||||||
|
"~( ˘▾˘~)",
|
||||||
|
"(〜^∇^)〜",
|
||||||
|
"ヽ(^ᴗ^ヽ)",
|
||||||
|
"(´・ω・`)",
|
||||||
|
"₍ᐢ•ﻌ•ᐢ₎*・゚。",
|
||||||
|
"(。・・)_且",
|
||||||
|
"(=`ω´=)",
|
||||||
|
"(*•‿•*)",
|
||||||
|
"(*゚∀゚*)",
|
||||||
|
"(☉⋆‿⋆☉)",
|
||||||
|
"ɷ◡ɷ",
|
||||||
|
"ʘ‿ʘ",
|
||||||
|
"(。-ω-)ノ",
|
||||||
|
"( ・ω・)ノ",
|
||||||
|
"(=゚ω゚)ノ",
|
||||||
|
"(・ε・`*) …",
|
||||||
|
"ʕっ•ᴥ•ʔっ",
|
||||||
|
"(*˘︶˘*)",
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,2 +1,2 @@
|
||||||
"""Represents current userbot version"""
|
"""Represents current userbot version"""
|
||||||
__version__ = (1, 1, 21)
|
__version__ = (1, 1, 22)
|
||||||
|
|
|
@ -67,7 +67,7 @@ def restart(*argv):
|
||||||
|
|
||||||
class Web:
|
class Web:
|
||||||
sign_in_clients = {}
|
sign_in_clients = {}
|
||||||
clients = []
|
_pending_clients = []
|
||||||
_sessions = []
|
_sessions = []
|
||||||
_ratelimit = {}
|
_ratelimit = {}
|
||||||
|
|
||||||
|
@ -151,7 +151,7 @@ class Web:
|
||||||
return web.Response(status=401)
|
return web.Response(status=401)
|
||||||
|
|
||||||
text = await request.text()
|
text = await request.text()
|
||||||
client = self.clients[0]
|
client = self._pending_clients[0]
|
||||||
db = database.Database(client)
|
db = database.Database(client)
|
||||||
await db.init()
|
await db.init()
|
||||||
|
|
||||||
|
@ -287,18 +287,30 @@ class Web:
|
||||||
del self.sign_in_clients[phone]
|
del self.sign_in_clients[phone]
|
||||||
|
|
||||||
client.phone = f"+{user.phone}"
|
client.phone = f"+{user.phone}"
|
||||||
self.clients.append(client)
|
|
||||||
|
# At this step we don't want `main.hikka` to "know" about our client
|
||||||
|
# so it doesn't create bot immediately. That's why we only save its session
|
||||||
|
# in case user closes web early. It will be handled on restart
|
||||||
|
# If user finishes login further, client will be passed to main
|
||||||
|
await main.hikka.save_client_session(client)
|
||||||
|
|
||||||
|
# But now it's pending
|
||||||
|
self._pending_clients += [client]
|
||||||
|
|
||||||
return web.Response()
|
return web.Response()
|
||||||
|
|
||||||
async def finish_login(self, request):
|
async def finish_login(self, request):
|
||||||
if not self._check_session(request):
|
if not self._check_session(request):
|
||||||
return web.Response(status=401)
|
return web.Response(status=401)
|
||||||
|
|
||||||
if not self.clients:
|
if not self._pending_clients:
|
||||||
return web.Response(status=400)
|
return web.Response(status=400)
|
||||||
|
|
||||||
first_session = not bool(main.hikka.clients)
|
first_session = not bool(main.hikka.clients)
|
||||||
await main.hikka.fetch_clients_from_web()
|
|
||||||
|
# Client is ready to pass in to dispatcher
|
||||||
|
main.hikka.clients = list(set(main.hikka.clients + self._pending_clients))
|
||||||
|
self._pending_clients = []
|
||||||
|
|
||||||
self.clients_set.set()
|
self.clients_set.set()
|
||||||
|
|
||||||
|
|
|
@ -9,5 +9,6 @@ requests==2.27.1
|
||||||
aiogram==2.19
|
aiogram==2.19
|
||||||
websockets==10.2
|
websockets==10.2
|
||||||
grapheme==0.6.0
|
grapheme==0.6.0
|
||||||
|
uvloop==0.16.0
|
||||||
|
|
||||||
# Python 3.8+
|
# Python 3.8+
|
||||||
|
|
Loading…
Reference in New Issue