mirror of https://github.com/coddrago/Heroku
commit
0337c97a1f
|
@ -133,6 +133,8 @@ dmypy.json
|
|||
*.pyc
|
||||
hikka/api_token.py
|
||||
hikka/loaded_modules
|
||||
loaded_modules
|
||||
hikka/debug_modules
|
||||
hikka*.session*
|
||||
database-*.json
|
||||
*.swp
|
||||
|
@ -154,4 +156,11 @@ config.json
|
|||
*.jpg
|
||||
*.jpeg
|
||||
*.webp
|
||||
*.webm
|
||||
*.webm
|
||||
*.tgs
|
||||
*.mp4
|
||||
*.mp3
|
||||
*.ogg
|
||||
*.m4a
|
||||
*.mp3
|
||||
*.avi
|
|
@ -159,4 +159,7 @@ config.json
|
|||
*.tgs
|
||||
*.mp4
|
||||
*.mp3
|
||||
*.ogg
|
||||
*.ogg
|
||||
*.m4a
|
||||
*.mp3
|
||||
*.avi
|
40
Dockerfile
40
Dockerfile
|
@ -1,9 +1,37 @@
|
|||
FROM python:3.8
|
||||
# Template
|
||||
FROM python:3.8-slim-buster as main
|
||||
|
||||
# Add files
|
||||
ADD . /
|
||||
ENV OKTETO=true
|
||||
RUN pip install -r requirements.txt
|
||||
RUN pip install -r optional_requirements.txt
|
||||
RUN apt update && apt install ffmpeg libavcodec-dev libavutil-dev libavformat-dev libswscale-dev libavdevice-dev -y
|
||||
|
||||
# Tell Hikka, that it's running docker
|
||||
# Currently it's used only in .info badge
|
||||
ENV DOCKER=true
|
||||
|
||||
# Suppress weird gitpython error
|
||||
ENV GIT_PYTHON_REFRESH=quiet
|
||||
|
||||
# Do not user pip cache dir
|
||||
ENV PIP_NO_CACHE_DIR=1
|
||||
|
||||
# Install mandatory pip requirements
|
||||
RUN pip install \
|
||||
--no-warn-script-location \
|
||||
--no-cache-dir \
|
||||
-r requirements.txt
|
||||
|
||||
# Install mandatory apt packages
|
||||
RUN apt update && apt install \
|
||||
libcairo2 git -y --no-install-recommends
|
||||
|
||||
# Clean the cache
|
||||
RUN rm -rf /var/lib/apt/lists /var/cache/apt/archives /tmp/*
|
||||
|
||||
# Expose IP address
|
||||
EXPOSE 8080
|
||||
|
||||
# Create data dir
|
||||
RUN mkdir /data
|
||||
CMD ["python3", "-m", "hikka"]
|
||||
|
||||
# Run Hikka
|
||||
CMD ["python3", "-m", "hikka"]
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
# Template
|
||||
FROM python:3.8-slim-buster as main
|
||||
|
||||
# Add files
|
||||
ADD . /
|
||||
|
||||
# Tell Hikka, that it's running on Okteto
|
||||
ENV OKTETO=true
|
||||
|
||||
# Suppress weird gitpython error
|
||||
ENV GIT_PYTHON_REFRESH=quiet
|
||||
|
||||
# Do not user pip cache dir
|
||||
ENV PIP_NO_CACHE_DIR=1
|
||||
|
||||
# Install mandatory pip requirements
|
||||
RUN pip install \
|
||||
--no-warn-script-location \
|
||||
--no-cache-dir \
|
||||
-r requirements.txt
|
||||
|
||||
# Install non-mandatory pip requirements
|
||||
# As we are running Okteto, we don't care about resources consuming
|
||||
RUN pip install \
|
||||
--no-warn-script-location \
|
||||
--no-cache-dir \
|
||||
-r optional_requirements.txt
|
||||
|
||||
# Install mandatory apt packages
|
||||
RUN apt update && apt install \
|
||||
libcairo2 git ffmpeg libavcodec-dev \
|
||||
libavutil-dev libavformat-dev libswscale-dev \
|
||||
libavdevice-dev -y --no-install-recommends
|
||||
|
||||
# Clean the cache
|
||||
RUN rm -rf /var/lib/apt/lists /var/cache/apt/archives /tmp/*
|
||||
|
||||
# Expose IP address
|
||||
EXPOSE 8080
|
||||
|
||||
# Create data dir
|
||||
RUN mkdir /data
|
||||
|
||||
# Run Hikka
|
||||
CMD ["python3", "-m", "hikka"]
|
|
@ -0,0 +1,9 @@
|
|||
version: "3"
|
||||
services:
|
||||
worker:
|
||||
build: .
|
||||
volumes:
|
||||
- worker:/data
|
||||
|
||||
volumes:
|
||||
worker:
|
|
@ -36,6 +36,7 @@ if (
|
|||
getpass.getuser() == "root"
|
||||
and "--root" not in " ".join(sys.argv)
|
||||
and "OKTETO" not in os.environ
|
||||
and "DOCKER" not in os.environ
|
||||
):
|
||||
print("🚫" * 30)
|
||||
print("NEVER EVER RUN USERBOT FROM ROOT")
|
||||
|
|
|
@ -22,7 +22,7 @@ from . import utils
|
|||
|
||||
DATA_DIR = (
|
||||
os.path.normpath(os.path.join(utils.get_base_dir(), ".."))
|
||||
if "OKTETO" not in os.environ
|
||||
if "OKTETO" not in os.environ and "DOCKER" not in os.environ
|
||||
else "/data"
|
||||
)
|
||||
|
||||
|
|
|
@ -82,10 +82,11 @@ class StringLoader(SourceLoader):
|
|||
self.origin = origin
|
||||
|
||||
def get_code(self, fullname: str) -> str:
|
||||
if not (source := self.get_source(fullname)):
|
||||
return None
|
||||
|
||||
return compile(source, self.origin, "exec", dont_inherit=True)
|
||||
return (
|
||||
compile(source, self.origin, "exec", dont_inherit=True)
|
||||
if (source := self.get_source(fullname))
|
||||
else None
|
||||
)
|
||||
|
||||
def get_filename(self, *args, **kwargs) -> str:
|
||||
return self.origin
|
||||
|
@ -208,7 +209,7 @@ en_keys = "`qwertyuiop[]asdfghjkl;'zxcvbnm,./~@#$%^&QWERTYUIOP{}ASDFGHJKL:\"|ZXC
|
|||
|
||||
DATA_DIR = (
|
||||
os.path.normpath(os.path.join(utils.get_base_dir(), ".."))
|
||||
if "OKTETO" not in os.environ
|
||||
if "OKTETO" not in os.environ and "DOCKER" not in os.environ
|
||||
else "/data"
|
||||
)
|
||||
|
||||
|
|
|
@ -41,6 +41,8 @@ import sqlite3
|
|||
import sys
|
||||
from math import ceil
|
||||
from typing import Union
|
||||
import uvloop
|
||||
import subprocess
|
||||
|
||||
from telethon import TelegramClient, events
|
||||
from telethon.errors.rpcerrorlist import (
|
||||
|
@ -68,18 +70,18 @@ except ImportError:
|
|||
else:
|
||||
web_available = True
|
||||
|
||||
is_okteto = "OKTETO" in os.environ
|
||||
|
||||
omit_log = False
|
||||
|
||||
DATA_DIR = (
|
||||
os.path.normpath(os.path.join(utils.get_base_dir(), ".."))
|
||||
if not is_okteto
|
||||
if "OKTETO" not in os.environ and "DOCKER" not in os.environ
|
||||
else "/data"
|
||||
)
|
||||
|
||||
CONFIG_PATH = os.path.join(DATA_DIR, "config.json")
|
||||
|
||||
uvloop.install()
|
||||
|
||||
|
||||
def run_config(
|
||||
db: database.Database,
|
||||
|
@ -139,9 +141,11 @@ def gen_port() -> int:
|
|||
"""
|
||||
Generates random free port in case of VDS, and
|
||||
8080 in case of Okteto
|
||||
In case of Docker, also return 8080, as it's already
|
||||
exposed by default
|
||||
:returns: Integer value of generated port
|
||||
"""
|
||||
if "OKTETO" in os.environ:
|
||||
if "OKTETO" in os.environ or "DOCKER" in os.environ:
|
||||
return 8080
|
||||
|
||||
# But for own server we generate new free port, and assign to it
|
||||
|
@ -386,35 +390,36 @@ class Hikka:
|
|||
importlib.invalidate_caches()
|
||||
self._get_api_token()
|
||||
|
||||
async def fetch_clients_from_web(self):
|
||||
"""Imports clients from web module"""
|
||||
for client in self.web.clients:
|
||||
session = SQLiteSession(
|
||||
os.path.join(
|
||||
self.arguments.data_root or DATA_DIR,
|
||||
f"hikka-+{'x' * (len(client.phone) - 5)}{client.phone[-4:]}-{(await client.get_me()).id}",
|
||||
)
|
||||
async def save_client_session(self, client: TelegramClient):
|
||||
session = SQLiteSession(
|
||||
os.path.join(
|
||||
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(
|
||||
client.session.dc_id,
|
||||
client.session.server_address,
|
||||
client.session.port,
|
||||
)
|
||||
session.auth_key = client.session.auth_key
|
||||
session.save()
|
||||
client.session = session
|
||||
# Set db attribute to this client in order to save
|
||||
# custom bot nickname from web
|
||||
client.hikka_db = database.Database(client)
|
||||
await client.hikka_db.init()
|
||||
|
||||
self.clients = list(set(self.clients + self.web.clients))
|
||||
session.set_dc(
|
||||
client.session.dc_id,
|
||||
client.session.server_address,
|
||||
client.session.port,
|
||||
)
|
||||
session.auth_key = client.session.auth_key
|
||||
session.save()
|
||||
client.session = session
|
||||
# Set db attribute to this client in order to save
|
||||
# custom bot nickname from web
|
||||
client.hikka_db = database.Database(client)
|
||||
await client.hikka_db.init()
|
||||
|
||||
def _web_banner(self):
|
||||
"""Shows web banner"""
|
||||
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):
|
||||
"""Waits for web auth confirmation in Telegram"""
|
||||
|
@ -449,9 +454,12 @@ class Hikka:
|
|||
|
||||
return True
|
||||
|
||||
def _init_clients(self):
|
||||
"""Reads session from disk and inits them"""
|
||||
for phone_id, phone in self.phones.items():
|
||||
def _init_clients(self) -> bool:
|
||||
"""
|
||||
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(
|
||||
self.arguments.data_root or DATA_DIR,
|
||||
f'hikka{f"-{phone_id}" if phone_id else ""}',
|
||||
|
@ -473,7 +481,7 @@ class Hikka:
|
|||
|
||||
install_entity_caching(client)
|
||||
|
||||
self.clients.append(client)
|
||||
self.clients += [client]
|
||||
except sqlite3.OperationalError:
|
||||
print(
|
||||
"Check that this is the only instance running. "
|
||||
|
@ -482,20 +490,22 @@ class Hikka:
|
|||
continue
|
||||
except (TypeError, AuthKeyDuplicatedError):
|
||||
os.remove(os.path.join(DATA_DIR, f"{session}.session"))
|
||||
self.main()
|
||||
del self.phones[phone_id]
|
||||
except (ValueError, ApiIdInvalidError):
|
||||
# Bad API hash/ID
|
||||
run_config({}, self.arguments.data_root)
|
||||
return
|
||||
return False
|
||||
except PhoneNumberInvalidError:
|
||||
print(
|
||||
"Phone number is incorrect. Use international format (+XX...) "
|
||||
"and don't put spaces in it."
|
||||
)
|
||||
continue
|
||||
del self.phones[phone_id]
|
||||
except InteractiveAuthRequired:
|
||||
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):
|
||||
"""Initializes main event loop and starts handler for each client"""
|
||||
|
@ -654,11 +664,13 @@ class Hikka:
|
|||
save_config_key("port", self.arguments.port)
|
||||
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
|
||||
|
||||
self._init_clients()
|
||||
|
||||
self.loop.set_exception_handler(
|
||||
lambda _, x: logging.error(
|
||||
f"Exception on event loop! {x['message']}",
|
||||
|
|
|
@ -154,10 +154,11 @@ class HikkaSecurityMod(loader.Module):
|
|||
is_inline: bool,
|
||||
):
|
||||
cmd = (
|
||||
self.allmodules.commands[command]
|
||||
if not is_inline
|
||||
else self.allmodules.inline_handlers[command]
|
||||
self.allmodules.inline_handlers[command]
|
||||
if is_inline
|
||||
else self.allmodules.commands[command]
|
||||
)
|
||||
|
||||
mask = self._db.get(security.__name__, "masks", {}).get(
|
||||
f"{cmd.__module__}.{cmd.__name__}",
|
||||
getattr(cmd, "security", security.DEFAULT_PERMISSIONS),
|
||||
|
@ -188,7 +189,9 @@ class HikkaSecurityMod(loader.Module):
|
|||
|
||||
await call.edit(
|
||||
self.strings("permissions").format(
|
||||
self.get_prefix() if not is_inline else f"@{self.inline.bot_username} ",
|
||||
f"@{self.inline.bot_username} "
|
||||
if is_inline
|
||||
else self.get_prefix(),
|
||||
command,
|
||||
),
|
||||
reply_markup=self._build_markup(cmd, is_inline),
|
||||
|
@ -231,7 +234,7 @@ class HikkaSecurityMod(loader.Module):
|
|||
return utils.chunks(
|
||||
[
|
||||
{
|
||||
"text": f"{('🚫' if not level else '✅')} {self.strings[group]}",
|
||||
"text": f"{'✅' if level else '🚫'} {self.strings[group]}",
|
||||
"callback": self.inline__switch_perm,
|
||||
"args": (
|
||||
command.__name__.rsplit("cmd", maxsplit=1)[0],
|
||||
|
@ -243,12 +246,20 @@ class HikkaSecurityMod(loader.Module):
|
|||
for group, level in perms.items()
|
||||
],
|
||||
2,
|
||||
) + [[{"text": self.strings("close_menu"), "callback": self.inline_close}]]
|
||||
) + [
|
||||
[
|
||||
{
|
||||
"text": self.strings("close_menu"),
|
||||
"callback": self.inline_close,
|
||||
}
|
||||
]
|
||||
]
|
||||
|
||||
|
||||
return utils.chunks(
|
||||
[
|
||||
{
|
||||
"text": f"{('🚫' if not level else '✅')} {self.strings[group]}",
|
||||
"text": f"{'✅' if level else '🚫'} {self.strings[group]}",
|
||||
"callback": self.inline__switch_perm,
|
||||
"args": (
|
||||
command.__name__.rsplit("_inline_handler", maxsplit=1)[0],
|
||||
|
@ -267,7 +278,7 @@ class HikkaSecurityMod(loader.Module):
|
|||
return utils.chunks(
|
||||
[
|
||||
{
|
||||
"text": f"{('🚫' if not level else '✅')} {self.strings[group]}",
|
||||
"text": f"{'✅' if level else '🚫'} {self.strings[group]}",
|
||||
"callback": self.inline__switch_perm_bm,
|
||||
"args": (group, not level, is_inline),
|
||||
}
|
||||
|
@ -286,6 +297,12 @@ class HikkaSecurityMod(loader.Module):
|
|||
def _perms_map(perms: int, is_inline: bool) -> dict:
|
||||
return (
|
||||
{
|
||||
"sudo": bool(perms & SUDO),
|
||||
"support": bool(perms & SUPPORT),
|
||||
"everyone": bool(perms & EVERYONE),
|
||||
}
|
||||
if is_inline
|
||||
else {
|
||||
"sudo": bool(perms & SUDO),
|
||||
"support": bool(perms & SUPPORT),
|
||||
"group_owner": bool(perms & GROUP_OWNER),
|
||||
|
@ -300,12 +317,6 @@ class HikkaSecurityMod(loader.Module):
|
|||
"pm": bool(perms & PM),
|
||||
"everyone": bool(perms & EVERYONE),
|
||||
}
|
||||
if not is_inline
|
||||
else {
|
||||
"sudo": bool(perms & SUDO),
|
||||
"support": bool(perms & SUPPORT),
|
||||
"everyone": bool(perms & EVERYONE),
|
||||
}
|
||||
)
|
||||
|
||||
def _get_current_perms(
|
||||
|
@ -477,7 +488,7 @@ class HikkaSecurityMod(loader.Module):
|
|||
list(set(self._db.get(main.__name__, "nonickusers", []) + [user.id])),
|
||||
)
|
||||
|
||||
call.edit(
|
||||
await call.edit(
|
||||
self.strings("user_nn").format(
|
||||
user.id,
|
||||
utils.escape_html(get_display_name(user)),
|
||||
|
@ -494,9 +505,10 @@ class HikkaSecurityMod(loader.Module):
|
|||
self._db.set(
|
||||
security.__name__,
|
||||
group,
|
||||
list(set(self._db.get(security.__name__, group, [])) - set([user.id])),
|
||||
list(set(self._db.get(security.__name__, group, [])) - {user.id}),
|
||||
)
|
||||
|
||||
|
||||
m = self.strings(f"{group}_removed").format(
|
||||
user.id,
|
||||
utils.escape_html(get_display_name(user)),
|
||||
|
|
|
@ -39,7 +39,7 @@ import sys
|
|||
import uuid
|
||||
from collections import ChainMap
|
||||
from importlib.machinery import ModuleSpec
|
||||
from typing import List, Optional, Union
|
||||
from typing import Optional, Union
|
||||
from urllib.parse import urlparse
|
||||
|
||||
import requests
|
||||
|
@ -48,7 +48,7 @@ from telethon.tl.types import Message
|
|||
|
||||
from .. import loader, main, utils
|
||||
from ..compat import geek
|
||||
from ..inline.types import InlineCall, InlineMessage
|
||||
from ..inline.types import InlineCall
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
@ -102,7 +102,7 @@ def get_git_api(url):
|
|||
|
||||
branch = m.group(2)
|
||||
path_ = m.group(3)
|
||||
api_url = "https://api.github.com/repos{}/contents".format(m.group(1))
|
||||
api_url = f"https://api.github.com/repos{m.group(1)}/contents"
|
||||
|
||||
if path_ is not None and len(path_) > 0:
|
||||
api_url += path_
|
||||
|
|
|
@ -15,7 +15,10 @@ import time
|
|||
|
||||
from telethon.errors.rpcerrorlist import YouBlockedUserError
|
||||
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 .. import loader, main, utils
|
||||
|
@ -54,8 +57,12 @@ class OktetoMod(loader.Module):
|
|||
if messages:
|
||||
logger.info("Deleting previously scheduled Okteto pinger messages")
|
||||
|
||||
for message in messages:
|
||||
await message.delete()
|
||||
await client(
|
||||
DeleteScheduledMessagesRequest(
|
||||
self._bot,
|
||||
[message.id for message in messages],
|
||||
)
|
||||
)
|
||||
|
||||
raise loader.SelfUnload
|
||||
|
||||
|
|
|
@ -45,7 +45,6 @@ import git
|
|||
import grapheme
|
||||
import requests
|
||||
import telethon
|
||||
from aiogram.types import CallbackQuery
|
||||
from telethon.hints import Entity
|
||||
from telethon.tl.custom.message import Message
|
||||
from telethon.tl.functions.account import UpdateNotifySettingsRequest
|
||||
|
@ -83,7 +82,7 @@ from telethon.tl.types import (
|
|||
User,
|
||||
)
|
||||
|
||||
from .inline.types import InlineCall
|
||||
from .inline.types import InlineCall, InlineMessage
|
||||
|
||||
FormattingEntity = Union[
|
||||
MessageEntityUnknown,
|
||||
|
@ -170,7 +169,7 @@ def get_entity_id(entity: Entity) -> int:
|
|||
|
||||
def escape_html(text: str, /) -> str:
|
||||
"""Pass all untrusted/potentially corrupt input here"""
|
||||
return str(text).replace("&", "&").replace("<", "<").replace(">", ">")
|
||||
return text.replace("&", "&").replace("<", "<").replace(">", ">")
|
||||
|
||||
|
||||
def escape_quotes(text: str, /) -> str:
|
||||
|
@ -279,16 +278,16 @@ def relocate_entities(
|
|||
|
||||
|
||||
async def answer(
|
||||
message: Union[Message, CallbackQuery, InlineCall],
|
||||
message: Union[Message, InlineCall, InlineMessage],
|
||||
response: str,
|
||||
**kwargs,
|
||||
) -> Union[CallbackQuery, Message]:
|
||||
) -> Union[InlineCall, InlineMessage, Message]:
|
||||
"""Use this to give the response to a command"""
|
||||
# Compatibility with FTG\GeekTG
|
||||
if isinstance(message, list) and message:
|
||||
message = message[0]
|
||||
|
||||
if isinstance(message, (CallbackQuery, InlineCall)):
|
||||
if isinstance(message, (InlineMessage, InlineCall)):
|
||||
await message.edit(response)
|
||||
return message
|
||||
|
||||
|
@ -578,16 +577,23 @@ def chunks(_list: Union[list, tuple, set], n: int, /) -> list:
|
|||
|
||||
def get_named_platform() -> str:
|
||||
"""Returns formatted platform name"""
|
||||
if os.path.isfile("/proc/device-tree/model"):
|
||||
with open("/proc/device-tree/model") as f:
|
||||
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())
|
||||
try:
|
||||
if os.path.isfile("/proc/device-tree/model"):
|
||||
with open("/proc/device-tree/model") as f:
|
||||
model = f.read()
|
||||
return f"🍇 {model}" if model.startswith("Raspberry") else f"❓ {model}"
|
||||
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_docker = "DOCKER" in os.environ
|
||||
is_lavhost = "LAVHOST" in os.environ
|
||||
|
||||
if is_docker:
|
||||
return "🐳 Docker"
|
||||
|
||||
if is_termux:
|
||||
return "🕶 Termux"
|
||||
|
||||
|
@ -607,7 +613,7 @@ def uptime() -> int:
|
|||
|
||||
def formatted_uptime() -> str:
|
||||
"""Returnes formmated uptime"""
|
||||
return "{}".format(str(timedelta(seconds=uptime())))
|
||||
return f"{str(timedelta(seconds=uptime()))}"
|
||||
|
||||
|
||||
def ascii_face() -> str:
|
||||
|
@ -616,18 +622,13 @@ def ascii_face() -> str:
|
|||
random.choice(
|
||||
[
|
||||
"ヽ(๑◠ܫ◠๑)ノ",
|
||||
"☜(⌒▽⌒)☞",
|
||||
"/|\\ ^._.^ /|\\",
|
||||
"(◕ᴥ◕ʋ)",
|
||||
"ᕙ(`▽´)ᕗ",
|
||||
"(☞゚∀゚)☞",
|
||||
"(✿◠‿◠)",
|
||||
"(▰˘◡˘▰)",
|
||||
"(˵ ͡° ͜ʖ ͡°˵)",
|
||||
"ʕっ•ᴥ•ʔっ",
|
||||
"( ͡° ᴥ ͡°)",
|
||||
"ʕ♥ᴥ♥ʔ",
|
||||
"\\m/,(> . <)_\\m/",
|
||||
"(๑•́ ヮ •̀๑)",
|
||||
"٩(^‿^)۶",
|
||||
"(っˆڡˆς)",
|
||||
|
@ -635,22 +636,49 @@ def ascii_face() -> str:
|
|||
"⊙ω⊙",
|
||||
"٩(^ᴗ^)۶",
|
||||
"(´・ω・)っ由",
|
||||
"※\\(^o^)/※",
|
||||
"٩(*❛⊰❛)~❤",
|
||||
"( ͡~ ͜ʖ ͡°)",
|
||||
"✧♡(◕‿◕✿)",
|
||||
"โ๏௰๏ใ ื",
|
||||
"∩。• ᵕ •。∩ ♡",
|
||||
"(♡´౪`♡)",
|
||||
"(◍>◡<◍)⋈。✧♡",
|
||||
"♥(ˆ⌣ˆԅ)",
|
||||
"╰(✿´⌣`✿)╯♡",
|
||||
"ʕ•ᴥ•ʔ",
|
||||
"ᶘ ◕ᴥ◕ᶅ",
|
||||
"▼・ᴥ・▼",
|
||||
"【≽ܫ≼】",
|
||||
"ฅ^•ﻌ•^ฅ",
|
||||
"(΄◞ิ౪◟ิ‵)",
|
||||
"٩(^ᴗ^)۶",
|
||||
"ᕴーᴥーᕵ",
|
||||
"ʕ→ᴥ←ʔ",
|
||||
"ʕᵕᴥᵕʔ",
|
||||
"ʕᵒᴥᵒʔ",
|
||||
"ᵔᴥᵔ",
|
||||
"(✿╹◡╹)",
|
||||
"(๑→ܫ←)",
|
||||
"ʕ·ᴥ· ʔ",
|
||||
"(ノ≧ڡ≦)",
|
||||
"(≖ᴗ≖✿)",
|
||||
"(〜^∇^ )〜",
|
||||
"( ノ・ェ・ )ノ",
|
||||
"~( ˘▾˘~)",
|
||||
"(〜^∇^)〜",
|
||||
"ヽ(^ᴗ^ヽ)",
|
||||
"(´・ω・`)",
|
||||
"₍ᐢ•ﻌ•ᐢ₎*・゚。",
|
||||
"(。・・)_且",
|
||||
"(=`ω´=)",
|
||||
"(*•‿•*)",
|
||||
"(*゚∀゚*)",
|
||||
"(☉⋆‿⋆☉)",
|
||||
"ɷ◡ɷ",
|
||||
"ʘ‿ʘ",
|
||||
"(。-ω-)ノ",
|
||||
"( ・ω・)ノ",
|
||||
"(=゚ω゚)ノ",
|
||||
"(・ε・`*) …",
|
||||
"ʕっ•ᴥ•ʔっ",
|
||||
"(*˘︶˘*)",
|
||||
]
|
||||
)
|
||||
)
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
"""Represents current userbot version"""
|
||||
__version__ = (1, 1, 21)
|
||||
__version__ = (1, 1, 22)
|
||||
|
|
|
@ -50,7 +50,7 @@ from telethon.tl.functions.contacts import UnblockRequest
|
|||
|
||||
DATA_DIR = (
|
||||
os.path.normpath(os.path.join(utils.get_base_dir(), ".."))
|
||||
if "OKTETO" not in os.environ
|
||||
if "OKTETO" not in os.environ and "DOCKER" not in os.environ
|
||||
else "/data"
|
||||
)
|
||||
|
||||
|
@ -67,7 +67,7 @@ def restart(*argv):
|
|||
|
||||
class Web:
|
||||
sign_in_clients = {}
|
||||
clients = []
|
||||
_pending_clients = []
|
||||
_sessions = []
|
||||
_ratelimit = {}
|
||||
|
||||
|
@ -108,10 +108,11 @@ class Web:
|
|||
return self.clients_set.wait()
|
||||
|
||||
def _check_session(self, request) -> bool:
|
||||
if not main.hikka.clients:
|
||||
return True
|
||||
|
||||
return request.cookies.get("session", None) in self._sessions
|
||||
return (
|
||||
request.cookies.get("session", None) in self._sessions
|
||||
if main.hikka.clients
|
||||
else True
|
||||
)
|
||||
|
||||
async def _check_bot(
|
||||
self,
|
||||
|
@ -151,7 +152,7 @@ class Web:
|
|||
return web.Response(status=401)
|
||||
|
||||
text = await request.text()
|
||||
client = self.clients[0]
|
||||
client = self._pending_clients[0]
|
||||
db = database.Database(client)
|
||||
await db.init()
|
||||
|
||||
|
@ -287,18 +288,30 @@ class Web:
|
|||
del self.sign_in_clients[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()
|
||||
|
||||
async def finish_login(self, request):
|
||||
if not self._check_session(request):
|
||||
return web.Response(status=401)
|
||||
|
||||
if not self.clients:
|
||||
if not self._pending_clients:
|
||||
return web.Response(status=400)
|
||||
|
||||
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()
|
||||
|
||||
|
|
|
@ -2,16 +2,18 @@ name: hikka
|
|||
services:
|
||||
worker:
|
||||
public: true
|
||||
build: .
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Okteto
|
||||
replicas: 1
|
||||
ports:
|
||||
- 8080
|
||||
resources:
|
||||
cpu: 900m
|
||||
memory: 2Gi
|
||||
cpu: "1"
|
||||
memory: 3Gi
|
||||
volumes:
|
||||
- worker:/data
|
||||
|
||||
volumes:
|
||||
worker:
|
||||
size: 1Gi
|
||||
size: 3Gi
|
||||
|
|
|
@ -9,5 +9,6 @@ requests==2.27.1
|
|||
aiogram==2.19
|
||||
websockets==10.2
|
||||
grapheme==0.6.0
|
||||
uvloop==0.16.0
|
||||
|
||||
# Python 3.8+
|
||||
|
|
Loading…
Reference in New Issue