Merge branch 'master' into mautrix-appservice-0.3.0

This commit is contained in:
Tulir Asokan
2018-07-15 00:15:30 +03:00
8 changed files with 122 additions and 38 deletions

8
.codeclimate.yml Normal file
View File

@@ -0,0 +1,8 @@
engines:
sonar-python:
enabled: true
checks:
python:S107:
enabled: false
exclude_patterns:
- "alembic/"

1
.gitignore vendored
View File

@@ -7,4 +7,5 @@ __pycache__
config.yaml
registration.yaml
logs/
*.db

View File

@@ -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]

View File

@@ -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)

View File

@@ -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

View File

@@ -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.")

View File

@@ -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
}

View File

@@ -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