mirror of
https://github.com/mautrix/telegram.git
synced 2026-05-17 07:25:46 +03:00
Merge branch 'master' into mautrix-appservice-0.3.0
This commit is contained in:
8
.codeclimate.yml
Normal file
8
.codeclimate.yml
Normal file
@@ -0,0 +1,8 @@
|
||||
engines:
|
||||
sonar-python:
|
||||
enabled: true
|
||||
checks:
|
||||
python:S107:
|
||||
enabled: false
|
||||
exclude_patterns:
|
||||
- "alembic/"
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -7,4 +7,5 @@ __pycache__
|
||||
|
||||
config.yaml
|
||||
registration.yaml
|
||||
logs/
|
||||
*.db
|
||||
|
||||
@@ -11,12 +11,11 @@ homeserver:
|
||||
# Application service host/registration related details
|
||||
# Changing these values requires regeneration of the registration.
|
||||
appservice:
|
||||
# The protocol the homeserver should use when connecting to this appservice.
|
||||
# Usually "http" or "https".
|
||||
protocol: http
|
||||
# The address that the homeserver can use to connect to this appservice.
|
||||
address: http://localhost:8080
|
||||
|
||||
# The hostname and port where the homeserver can find this appservice.
|
||||
hostname: localhost
|
||||
# The hostname and port where this appservice should listen.
|
||||
hostname: 0.0.0.0
|
||||
port: 8080
|
||||
|
||||
# The full URI to the database.
|
||||
@@ -34,9 +33,6 @@ appservice:
|
||||
# implicitly.
|
||||
external: https://example.com/public
|
||||
|
||||
# Whether or not to enable debug messages in the console.
|
||||
debug: true
|
||||
|
||||
# The unique ID of this appservice.
|
||||
id: telegram
|
||||
# Username of the appservice bot.
|
||||
@@ -194,3 +190,44 @@ telegram:
|
||||
api_hash: tjyd5yge35lbodk1xwzw2jstp90k55qz
|
||||
# (Optional) Create your own bot at https://t.me/BotFather
|
||||
bot_token: disabled
|
||||
# Telethon proxy configuration.
|
||||
# You must install PySocks from pip for proxies to work.
|
||||
proxy:
|
||||
# Allowed types: disabled, socks4, socks5, http
|
||||
type: disabled
|
||||
# Proxy IP address and port.
|
||||
address: 127.0.0.1
|
||||
port: 1080
|
||||
# Whether or not to perform DNS resolving remotely.
|
||||
rdns: true
|
||||
# Proxy authentication (optional).
|
||||
username: ""
|
||||
password: ""
|
||||
|
||||
# Python logging configuration.
|
||||
#
|
||||
# See section 16.7.2 of the Python documentation for more info:
|
||||
# https://docs.python.org/3.6/library/logging.config.html#configuration-dictionary-schema
|
||||
logging:
|
||||
version: 1
|
||||
formatters:
|
||||
precise:
|
||||
format: "[%(asctime)s] [%(levelname)s@%(name)s] %(message)s"
|
||||
handlers:
|
||||
file:
|
||||
class: logging.handlers.RotatingFileHandler
|
||||
formatter: precise
|
||||
filename: ./mautrix-telegram.log
|
||||
maxBytes: 10485760
|
||||
backupCount: 10
|
||||
console:
|
||||
class: logging.StreamHandler
|
||||
formatter: precise
|
||||
loggers:
|
||||
mau:
|
||||
level: DEBUG
|
||||
telethon:
|
||||
level: DEBUG
|
||||
root:
|
||||
level: DEBUG
|
||||
handlers: [file, console]
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
import argparse
|
||||
import sys
|
||||
import logging
|
||||
import logging.config
|
||||
import asyncio
|
||||
|
||||
import sqlalchemy as sql
|
||||
@@ -29,6 +30,7 @@ from .base import Base
|
||||
from .config import Config
|
||||
from .matrix import MatrixHandler
|
||||
|
||||
from . import __version__
|
||||
from .db import init as init_db
|
||||
from .abstract_user import init as init_abstract_user
|
||||
from .user import init as init_user, User
|
||||
@@ -40,12 +42,6 @@ from .public import PublicBridgeWebsite
|
||||
from .context import Context
|
||||
from .sqlstatestore import SQLStateStore
|
||||
|
||||
log = logging.getLogger("mau")
|
||||
time_formatter = logging.Formatter("[%(asctime)s] [%(levelname)s@%(name)s] %(message)s")
|
||||
handler = logging.StreamHandler()
|
||||
handler.setFormatter(time_formatter)
|
||||
log.addHandler(handler)
|
||||
|
||||
parser = argparse.ArgumentParser(
|
||||
description="A Matrix-Telegram puppeting bridge.",
|
||||
prog="python -m mautrix-telegram")
|
||||
@@ -70,14 +66,11 @@ if args.generate_registration:
|
||||
print(f"Registration generated and saved to {config.registration_path}")
|
||||
sys.exit(0)
|
||||
|
||||
if config["appservice.debug"]:
|
||||
telethon_log = logging.getLogger("telethon")
|
||||
telethon_log.addHandler(handler)
|
||||
telethon_log.setLevel(logging.DEBUG)
|
||||
log.setLevel(logging.DEBUG)
|
||||
log.debug("Debug messages enabled.")
|
||||
logging.config.dictConfig(config["logging"])
|
||||
log = logging.getLogger("mau.init")
|
||||
log.debug(f"Initializing mautrix-telegram {__version__}")
|
||||
|
||||
db_engine = sql.create_engine(config.get("appservice.database", "sqlite:///mautrix-telegram.db"))
|
||||
db_engine = sql.create_engine(config["appservice.database"] or "sqlite:///mautrix-telegram.db")
|
||||
db_factory = orm.sessionmaker(bind=db_engine)
|
||||
db_session = orm.scoping.scoped_session(db_factory)
|
||||
Base.metadata.bind = db_engine
|
||||
@@ -114,9 +107,13 @@ with appserv.run(config["appservice.hostname"], config["appservice.port"]) as st
|
||||
startup_actions.append(context.bot.start())
|
||||
|
||||
try:
|
||||
log.debug("Initialization complete, running startup actions")
|
||||
loop.run_until_complete(asyncio.gather(*startup_actions, loop=loop))
|
||||
log.debug("Startup actions complete, now running forever")
|
||||
loop.run_forever()
|
||||
except KeyboardInterrupt:
|
||||
for user in User.by_tgid.values():
|
||||
user.stop()
|
||||
log.debug("Keyboard interrupt received, stopping clients")
|
||||
loop.run_until_complete(
|
||||
asyncio.gather(*[user.stop() for user in User.by_tgid.values()], loop=loop))
|
||||
log.debug("Clients stopped, shutting down")
|
||||
sys.exit(0)
|
||||
|
||||
@@ -50,6 +50,23 @@ class AbstractUser:
|
||||
def connected(self):
|
||||
return self.client and self.client.is_connected()
|
||||
|
||||
@property
|
||||
def _proxy_settings(self):
|
||||
type = config["telegram.proxy.type"].lower()
|
||||
if type == "disabled":
|
||||
return None
|
||||
elif type == "socks4":
|
||||
type = 1
|
||||
elif type == "socks5":
|
||||
type = 2
|
||||
elif type == "http":
|
||||
type = 3
|
||||
|
||||
return (type,
|
||||
config["telegram.proxy.address"], config["telegram.proxy.port"],
|
||||
config["telegram.proxy.rdns"],
|
||||
config["telegram.proxy.username"], config["telegram.proxy.password"])
|
||||
|
||||
def _init_client(self):
|
||||
self.log.debug(f"Initializing client for {self.name}")
|
||||
device = f"{platform.system()} {platform.release()}"
|
||||
@@ -62,7 +79,8 @@ class AbstractUser:
|
||||
app_version=__version__,
|
||||
system_version=sysversion,
|
||||
device_model=device,
|
||||
timeout=120)
|
||||
timeout=120,
|
||||
proxy=self._proxy_settings)
|
||||
self.client.add_event_handler(self._update_catch)
|
||||
|
||||
async def update(self, update):
|
||||
@@ -95,7 +113,9 @@ class AbstractUser:
|
||||
return self.client and await self.client.is_user_authorized()
|
||||
|
||||
async def has_full_access(self, allow_bot=False):
|
||||
return self.puppet_whitelisted and (not self.is_bot or allow_bot) and await self.is_logged_in()
|
||||
return (self.puppet_whitelisted
|
||||
and (not self.is_bot or allow_bot)
|
||||
and await self.is_logged_in())
|
||||
|
||||
async def start(self, delete_unless_authenticated=False):
|
||||
if not self.client:
|
||||
@@ -118,8 +138,8 @@ class AbstractUser:
|
||||
await self.start(delete_unless_authenticated=not even_if_no_session)
|
||||
return self
|
||||
|
||||
def stop(self):
|
||||
self.client.disconnect()
|
||||
async def stop(self):
|
||||
await self.client.disconnect()
|
||||
self.client = None
|
||||
|
||||
# region Telegram update handling
|
||||
|
||||
@@ -174,7 +174,7 @@ async def enter_phone_or_token(evt: CommandEvent):
|
||||
# phone numbers don't contain colons but telegram bot auth tokens do
|
||||
if evt.args[0].find(":") > 0:
|
||||
try:
|
||||
await sign_in(bot_token=evt.args[0])
|
||||
await sign_in(evt, bot_token=evt.args[0])
|
||||
except Exception:
|
||||
evt.log.exception("Error sending auth token")
|
||||
return await evt.reply("Unhandled exception while sending auth token. "
|
||||
@@ -194,7 +194,7 @@ async def enter_code(evt: CommandEvent):
|
||||
return await evt.reply("This bridge instance does not allow in-Matrix login. "
|
||||
"Please use `$cmdprefix+sp login` to get login instructions")
|
||||
try:
|
||||
await sign_in(code=evt.args[0])
|
||||
await sign_in(evt, code=evt.args[0])
|
||||
except Exception:
|
||||
evt.log.exception("Error sending phone code")
|
||||
return await evt.reply("Unhandled exception while sending code. "
|
||||
@@ -209,12 +209,17 @@ async def enter_password(evt: CommandEvent):
|
||||
return await evt.reply("This bridge instance does not allow in-Matrix login. "
|
||||
"Please use `$cmdprefix+sp login` to get login instructions")
|
||||
try:
|
||||
await sign_in(password=" ".join(evt.args))
|
||||
await sign_in(evt, password=" ".join(evt.args))
|
||||
except AccessTokenInvalidError:
|
||||
return await evt.reply("That bot token is not valid.")
|
||||
except AccessTokenExpiredError:
|
||||
return await evt.reply("That bot token has expired.")
|
||||
except Exception:
|
||||
evt.log.exception("Error sending password")
|
||||
return await evt.reply("Unhandled exception while sending password. "
|
||||
"Check console for more details.")
|
||||
|
||||
|
||||
async def sign_in(evt: CommandEvent, **sign_in_info):
|
||||
try:
|
||||
await evt.sender.ensure_started(even_if_no_session=True)
|
||||
@@ -236,6 +241,7 @@ async def sign_in(evt: CommandEvent, **sign_in_info):
|
||||
return await evt.reply("Your account has two-factor authentication. "
|
||||
"Please send your password here.")
|
||||
|
||||
|
||||
@command_handler(needs_auth=True,
|
||||
help_section=SECTION_AUTH,
|
||||
help_text="Log out from Telegram.")
|
||||
|
||||
@@ -144,7 +144,12 @@ class Config(DictWithRecursion):
|
||||
copy("homeserver.verify_ssl")
|
||||
copy("homeserver.domain")
|
||||
|
||||
copy("appservice.protocol")
|
||||
if "appservice.protocol" in self and "appservice.address" not in self:
|
||||
protocol, hostname, port = (self["appservice.protocol"], self["appservice.hostname"],
|
||||
self["appservice.port"])
|
||||
base["appservice.address"] = f"{protocol}://{hostname}:{port}"
|
||||
else:
|
||||
copy("appservice.address")
|
||||
copy("appservice.hostname")
|
||||
copy("appservice.port")
|
||||
|
||||
@@ -154,8 +159,6 @@ class Config(DictWithRecursion):
|
||||
copy("appservice.public.prefix")
|
||||
copy("appservice.public.external")
|
||||
|
||||
copy("appservice.debug")
|
||||
|
||||
copy("appservice.id")
|
||||
copy("appservice.bot_username")
|
||||
copy("appservice.bot_displayname")
|
||||
@@ -217,6 +220,20 @@ class Config(DictWithRecursion):
|
||||
copy("telegram.api_id")
|
||||
copy("telegram.api_hash")
|
||||
copy("telegram.bot_token")
|
||||
copy("telegram.proxy.type")
|
||||
copy("telegram.proxy.address")
|
||||
copy("telegram.proxy.port")
|
||||
copy("telegram.proxy.rdns")
|
||||
copy("telegram.proxy.username")
|
||||
copy("telegram.proxy.password")
|
||||
|
||||
if "appservice.debug" in self and "logging" not in self:
|
||||
level = "DEBUG" if self["appservice.debug"] else "INFO"
|
||||
base["logging.root.level"] = level
|
||||
base["logging.loggers.mau.level"] = level
|
||||
base["logging.loggers.telethon.level"] = level
|
||||
else:
|
||||
copy("logging")
|
||||
|
||||
self._data = base._data
|
||||
self.save()
|
||||
@@ -251,10 +268,8 @@ class Config(DictWithRecursion):
|
||||
self.set("appservice.as_token", self._new_token())
|
||||
self.set("appservice.hs_token", self._new_token())
|
||||
|
||||
url = (f"{self['appservice.protocol']}://"
|
||||
f"{self['appservice.hostname']}:{self['appservice.port']}")
|
||||
self._registration = {
|
||||
"id": self.get("appservice.id", "telegram"),
|
||||
"id": self["appservice.id"] or "telegram",
|
||||
"as_token": self["appservice.as_token"],
|
||||
"hs_token": self["appservice.hs_token"],
|
||||
"namespaces": {
|
||||
@@ -267,7 +282,7 @@ class Config(DictWithRecursion):
|
||||
"regex": f"#{alias_format}:{homeserver}"
|
||||
}]
|
||||
},
|
||||
"url": url,
|
||||
"url": self["appservice.address"],
|
||||
"sender_localpart": self["appservice.bot_username"],
|
||||
"rate_limited": False
|
||||
}
|
||||
|
||||
@@ -145,7 +145,7 @@ class User(AbstractUser):
|
||||
asyncio.ensure_future(self.post_login(), loop=self.loop)
|
||||
elif delete_unless_authenticated:
|
||||
self.log.debug(f"Unauthenticated user {self.name} start()ed, deleting session...")
|
||||
self.client.disconnect()
|
||||
await self.client.disconnect()
|
||||
self.client.session.delete()
|
||||
return self
|
||||
|
||||
|
||||
Reference in New Issue
Block a user