Compare commits

..

20 Commits

Author SHA1 Message Date
Tulir Asokan b4cf8cd451 Bump version to 0.7.1rc1 2020-01-11 20:08:47 +02:00
Tulir Asokan 80ff9d0f66 Precalculate list of channel IDs to get info for to fix #393 2020-01-11 20:07:21 +02:00
Tulir Asokan b0e60e60e4 Fix parameter name error in has_power_level call 2020-01-11 19:58:08 +02:00
Tulir Asokan c4b9a76931 Merge pull request #406 from Ma27/fix-tests
Fix several broken tests that were missing some required positional arguments
2019-12-28 14:56:21 +02:00
Maximilian Bosch fe52f0ad10 Fix several broken tests that were missing some required positional arguments 2019-12-28 13:00:39 +01:00
Tulir Asokan a9abf9a1af Bump version to 0.7.0 2019-12-28 01:29:38 +02:00
Tulir Asokan 815f9605f9 Bump version to 0.7.0rc4 2019-12-25 16:31:00 +02:00
Tulir Asokan 9a9d6fc0bb Fix handling m.emotes 2019-12-25 16:29:22 +02:00
Tulir Asokan 2f691bf1b8 Bump version to 0.7.0rc3 2019-12-25 16:01:39 +02:00
Tulir Asokan 50984dab14 Trust displaynames from non-contacts when syncing puppets 2019-12-25 15:49:35 +02:00
Tulir Asokan 6f6ce4bcc7 Try deleting sources in docker image 2019-12-23 19:44:44 +02:00
Tulir Asokan 119729393c Restore git for version info in CI builds 2019-12-23 19:30:55 +02:00
Tulir Asokan 9f3869e878 Try to fix version info in CI builds again 2019-12-23 19:15:36 +02:00
Tulir Asokan 9fb2a73ec5 Update mautrix-python to handle invites separately from leaves. Fixes #402 2019-12-21 21:02:41 +02:00
Tulir Asokan 64b3699b3c Only print stack traces for admins. Fixes #392 2019-12-21 20:46:49 +02:00
Tulir Asokan 76ad31a3bc Update to Alpine 3.11 and fix version info in CI builds 2019-12-21 20:45:02 +02:00
Tulir Asokan 71cdee5a4d Fix crash when login shared secret is not enabled 2019-12-15 19:04:43 +02:00
Tulir Asokan 2ae4b23528 Add option to log in to custom puppet with shared secret 2019-12-15 18:50:07 +02:00
Tulir Asokan 39927ac6c0 Try to fix cleaning up rooms
Not tested at all
2019-12-11 10:03:05 +02:00
Tulir Asokan 3e6e59db29 Add postgres password field to example helm chart values 2019-12-06 15:57:53 +02:00
20 changed files with 91 additions and 42 deletions
+4 -2
View File
@@ -18,7 +18,7 @@ RUN apk add --no-cache libpng libpng-dev zlib zlib-dev \
&& git checkout 543c1d23ac9322f4f03c7fb6612ea7d026d44ac0 \
&& make
FROM docker.io/alpine:3.10
FROM docker.io/alpine:3.11
ENV UID=1337 \
GID=1337 \
@@ -52,7 +52,7 @@ RUN apk add --no-cache --virtual .build-deps \
py3-markupsafe \
#moviepy
py3-decorator \
#py3-tqdm \
py3-tqdm \
py3-requests \
#imageio
py3-numpy \
@@ -68,6 +68,8 @@ RUN apk add --no-cache --virtual .build-deps \
# lottieconverter
zlib libpng \
&& pip3 install .[speedups,hq_thumbnails,metrics] \
# pip installs the sources to /usr/lib/python3.8/site-packages, so we don't need them here
&& rm -rf /opt/mautrix-telegram/mautrix_telegram \
&& apk del .build-deps
VOLUME /data
+6
View File
@@ -154,6 +154,12 @@ bridge:
# Whether or not to use /sync to get presence, read receipts and typing notifications when using
# your own Matrix account as the Matrix puppet for your Telegram account.
sync_with_custom_puppets: true
# Shared secret for https://github.com/devture/matrix-synapse-shared-secret-auth
#
# If set, custom puppets will be enabled automatically for local users
# instead of users having to find an access token and run `login-matrix`
# manually.
login_shared_secret: null
# Set to false to disable link previews in messages sent to Telegram.
telegram_link_preview: true
# Use inline images instead of a separate message for the caption.
+1
View File
@@ -35,6 +35,7 @@ affinity: {}
postgresql:
enabled: true
postgresqlDatabase: mxtg
postgresqlPassword: SET TO RANDOM STRING
persistence:
size: 2Gi
resources:
+1 -1
View File
@@ -1,2 +1,2 @@
__version__ = "0.7.0rc2"
__version__ = "0.7.1rc1"
__author__ = "Tulir Asokan <tulir@maunium.net>"
+2 -2
View File
@@ -108,9 +108,9 @@ class Bot(AbstractUser):
if isinstance(chat, ChatForbidden) or chat.left or chat.deactivated:
self.remove_chat(TelegramID(chat.id))
channel_ids = (InputChannel(chat_id, 0)
channel_ids = [InputChannel(chat_id, 0)
for chat_id, chat_type in self.chats.items()
if chat_type == "channel")
if chat_type == "channel"]
for channel_id in channel_ids:
try:
await self.client(GetChannelsRequest([channel_id]))
+1 -1
View File
@@ -169,7 +169,7 @@ async def execute_room_cleanup(evt, rooms_to_clean: List[Union[po.Portal, RoomID
await room.cleanup_and_delete()
cleaned += 1
else:
await po.Portal.cleanup_room(evt.az.intent, room, message="Room deleted")
await po.Portal.cleanup_room(evt.az.intent, room, "Room deleted")
cleaned += 1
evt.sender.command_status = None
await evt.reply(f"{cleaned} rooms cleaned up successfully.")
+4
View File
@@ -51,6 +51,10 @@ class CommandEvent(BaseCommandEvent):
self.config = processor.config
self.public_website = processor.public_website
@property
def print_error_traceback(self) -> bool:
return self.sender.is_admin
async def get_help_key(self) -> HelpCacheKey:
return HelpCacheKey(self.is_management, self.is_portal, self.sender.puppet_whitelisted,
self.sender.matrix_puppet_whitelisted, self.sender.is_admin,
+3 -5
View File
@@ -113,12 +113,10 @@ async def cleanup_old_portal_while_bridging(evt: CommandEvent, portal: "po.Porta
"Continuing without touching previous Matrix room...")
return True, None
elif evt.args[0] == "delete-and-continue":
return True, portal.cleanup_room(portal.main_intent, portal.mxid,
message="Portal deleted (moving to another room)")
return True, portal.cleanup_portal("Portal deleted (moving to another room)")
elif evt.args[0] == "unbridge-and-continue":
return True, portal.cleanup_room(portal.main_intent, portal.mxid,
message="Room unbridged (portal moving to another room)",
puppets_only=True)
return True, portal.cleanup_portal("Room unbridged (portal moving to another room)",
puppets_only=True)
else:
await evt.reply(
"The chat you were trying to bridge already has a Matrix portal room.\n\n"
+6 -3
View File
@@ -20,7 +20,8 @@ from telethon.errors import ( # isort: skip
AccessTokenExpiredError, AccessTokenInvalidError, FirstNameInvalidError, FloodWaitError,
PasswordHashInvalidError, PhoneCodeExpiredError, PhoneCodeInvalidError,
PhoneNumberAppSignupForbiddenError, PhoneNumberBannedError, PhoneNumberFloodError,
PhoneNumberOccupiedError, PhoneNumberUnoccupiedError, SessionPasswordNeededError)
PhoneNumberOccupiedError, PhoneNumberUnoccupiedError, SessionPasswordNeededError,
PhoneNumberInvalidError)
from mautrix.types import EventID
@@ -84,7 +85,7 @@ async def enter_code_register(evt: CommandEvent) -> EventID:
await evt.sender.ensure_started(even_if_no_session=True)
first_name, last_name = evt.sender.command_status["full_name"]
user = await evt.sender.client.sign_up(evt.args[0], first_name, last_name)
asyncio.ensure_future(evt.sender.post_login(user), loop=evt.loop)
asyncio.ensure_future(evt.sender.post_login(user, first_login=True), loop=evt.loop)
evt.sender.command_status = None
return await evt.reply(f"Successfully registered to Telegram.")
except PhoneNumberOccupiedError:
@@ -166,6 +167,8 @@ async def _request_code(evt: CommandEvent, phone_number: str, next_status: Dict[
except PhoneNumberUnoccupiedError:
return await evt.reply("That phone number has not been registered. "
"Please register with `$cmdprefix+sp register <phone>`.")
except PhoneNumberInvalidError:
return await evt.reply("That phone number is not valid.")
except Exception:
evt.log.exception("Error requesting phone code")
return await evt.reply("Unhandled exception while requesting code. "
@@ -244,7 +247,7 @@ async def _sign_in(evt: CommandEvent, **sign_in_info) -> EventID:
await evt.reply(f"[{existing_user.displayname}]"
f"(https://matrix.to/#/{existing_user.mxid})"
" was logged out from the account.")
asyncio.ensure_future(evt.sender.post_login(user), loop=evt.loop)
asyncio.ensure_future(evt.sender.post_login(user, first_login=True), loop=evt.loop)
evt.sender.command_status = None
name = f"@{user.username}" if user.username else f"+{user.phone}"
return await evt.reply(f"Successfully logged in as {name}")
+1
View File
@@ -109,6 +109,7 @@ class Config(BaseBridgeConfig):
copy("bridge.plaintext_highlights")
copy("bridge.public_portals")
copy("bridge.sync_with_custom_puppets")
copy("bridge.login_shared_secret")
copy("bridge.telegram_link_preview")
copy("bridge.inline_images")
copy("bridge.image_as_file_size")
+5 -5
View File
@@ -1,4 +1,5 @@
import subprocess
import shutil
import os
from . import __version__
@@ -14,8 +15,7 @@ cmd_env = {
def run(cmd):
return subprocess.check_output(cmd, stderr=subprocess.DEVNULL, env=cmd_env)
if os.path.exists(".git"):
if os.path.exists(".git") and shutil.which("git"):
try:
git_revision = run(["git", "rev-parse", "HEAD"]).strip().decode("ascii")
git_revision_url = f"https://github.com/tulir/mautrix-telegram/commit/{git_revision}"
@@ -26,15 +26,15 @@ if os.path.exists(".git"):
try:
git_tag = run(["git", "describe", "--exact-match", "--tags"]).strip().decode("ascii")
git_tag_url = f"https://github.com/tulir/mautrix-telegram/releases/tag/{git_tag}"
except (subprocess.SubprocessError, OSError):
git_tag = None
git_tag_url = None
else:
git_revision = "unknown"
git_revision_url = None
git_tag = None
git_tag_url = None
git_tag_url = (f"https://github.com/tulir/mautrix-telegram/releases/tag/{git_tag}"
if git_tag else None)
if git_tag and __version__ == git_tag[1:].replace("-", ""):
version = __version__
+5
View File
@@ -215,6 +215,11 @@ class MatrixHandler(BaseMatrixHandler):
event_id: EventID) -> None:
await self.handle_kick_ban(False, room_id, user_id, kicked_by, reason, event_id)
async def handle_unban(self, room_id: RoomID, user_id: UserID, unbanned_by: UserID,
reason: str, event_id: EventID) -> None:
# TODO handle unbans properly instead of handling it as a kick
await self.handle_kick_ban(False, room_id, user_id, unbanned_by, reason, event_id)
async def handle_ban(self, room_id: RoomID, user_id: UserID, banned_by: UserID, reason: str,
event_id: EventID) -> None:
await self.handle_kick_ban(True, room_id, user_id, banned_by, reason, event_id)
+14 -10
View File
@@ -222,7 +222,7 @@ class BasePortal(ABC):
return False
evt_type = EventType.find(f"net.maunium.telegram.{event}")
evt_type.t_class = EventType.Class.STATE
return self.main_intent.state_store.has_power_level(self.mxid, user.mxid, event=evt_type)
return self.main_intent.state_store.has_power_level(self.mxid, user.mxid, evt_type)
def get_input_entity(self, user: 'AbstractUser'
) -> Awaitable[Union[TypeInputPeer, TypeInputChannel]]:
@@ -273,17 +273,13 @@ class BasePortal(ABC):
authenticated.append(user)
return authenticated
async def cleanup_room(self, intent: IntentAPI, room_id: RoomID,
message: str = "Portal deleted", puppets_only: bool = False) -> None:
@staticmethod
async def cleanup_room(intent: IntentAPI, room_id: RoomID, message: str,
puppets_only: bool = False) -> None:
try:
members = await intent.get_room_members(room_id)
except MatrixRequestError:
members = []
if self.username:
try:
await intent.remove_room_alias(self.alias_localpart)
except (MatrixRequestError, IntentError):
self.log.warning("Failed to remove alias when cleaning up room", exc_info=True)
for user in members:
puppet = p.Puppet.get_by_mxid(UserID(user), create=False)
if user != intent.mxid and (not puppets_only or puppet):
@@ -299,12 +295,20 @@ class BasePortal(ABC):
except (MatrixRequestError, IntentError):
self.log.warning("Failed to leave room when cleaning up room", exc_info=True)
async def cleanup_portal(self, message: str, puppets_only: bool = False) -> None:
if self.username:
try:
await self.main_intent.remove_room_alias(self.alias_localpart)
except (MatrixRequestError, IntentError):
self.log.warning("Failed to remove alias when cleaning up room", exc_info=True)
await self.cleanup_room(self.main_intent, self.mxid, message, puppets_only)
async def unbridge(self) -> None:
await self.cleanup_room(self.main_intent, self.mxid, "Room unbridged", puppets_only=True)
await self.cleanup_portal("Room unbridged", puppets_only=True)
self.delete()
async def cleanup_and_delete(self) -> None:
await self.cleanup_room(self.main_intent, self.mxid)
await self.cleanup_portal("Portal deleted")
self.delete()
# endregion
+1 -1
View File
@@ -367,7 +367,7 @@ class PortalMatrix(BasePortal, MautrixBasePortal, ABC):
if not bridge_notices and not excepted:
return
if content.msgtype in (MessageType.TEXT, MessageType.NOTICE):
if content.msgtype in (MessageType.TEXT, MessageType.EMOTE, MessageType.NOTICE):
await self._pre_process_matrix_message(sender, not logged_in, content)
await self._handle_matrix_text(sender_id, event_id, space, client, content, reply_to)
elif content.msgtype == MessageType.LOCATION:
+6
View File
@@ -258,6 +258,8 @@ class Puppet(CustomPuppetMixin):
return False
allow_source = (source.is_relaybot
or self.displayname_source == source.tgid
# User is not a contact, so there's no custom name
or not info.contact
# No displayname source, so just trust anything
or self.displayname_source is None)
if not allow_source:
@@ -424,4 +426,8 @@ def init(context: 'Context') -> Iterable[Awaitable[Any]]:
Puppet.displayname_template = SimpleTemplate(config["bridge.displayname_template"],
"displayname")
secret = config["bridge.login_shared_secret"]
Puppet.login_shared_secret = secret.encode("utf-8") if secret else None
Puppet.login_device_name = "Telegram Bridge"
return (puppet.try_start() for puppet in Puppet.all_with_custom_mxid())
+17 -4
View File
@@ -199,14 +199,27 @@ class User(AbstractUser, BaseUser):
self.client.session.delete()
return self
async def post_login(self, info: TLUser = None) -> None:
async def post_login(self, info: TLUser = None, first_login: bool = False) -> None:
try:
await self.update_info(info)
if not self.is_bot and config["bridge.startup_sync"]:
except Exception:
self.log.exception("Failed to update telegram account info")
return
try:
puppet = pu.Puppet.get(self.tgid)
if puppet.custom_mxid != self.mxid and puppet.can_auto_login(self.mxid):
self.log.info(f"Automatically enabling custom puppet")
await puppet.switch_mxid(access_token="auto", mxid=self.mxid)
except Exception:
self.log.exception("Failed to automatically enable custom puppet")
if not self.is_bot and config["bridge.startup_sync"]:
try:
await self.sync_dialogs()
await self.sync_contacts()
except Exception:
self.log.exception("Failed to run post-login functions for %s", self.mxid)
except Exception:
self.log.exception("Failed to run post-login sync")
async def update(self, update: TypeUpdate) -> bool:
if not self.is_bot:
+1 -1
View File
@@ -119,7 +119,7 @@ class AuthAPI(abc.ABC):
existing_user = User.get_by_tgid(user_info.id)
if existing_user and existing_user != user:
await existing_user.log_out()
asyncio.ensure_future(user.post_login(user_info), loop=self.loop)
asyncio.ensure_future(user.post_login(user_info, first_login=True), loop=self.loop)
if user.command_status and user.command_status["action"] == "Login":
user.command_status = None
@@ -149,11 +149,9 @@ class ProvisioningAPI(AuthAPI):
force = request.query.get("force", None)
if force in ("delete", "unbridge"):
delete = force == "delete"
await portal.cleanup_room(portal.main_intent, portal.mxid, puppets_only=not delete,
message=("Portal deleted (moving to another room)"
if delete
else "Room unbridged (portal moving to another "
"room)"))
await portal.cleanup_portal("Portal deleted (moving to another room)" if delete
else "Room unbridged (portal moving to another room)",
puppets_only=not delete)
else:
return self.get_error_response(409, "chat_already_bridged",
"Telegram chat is already bridged to another "
+1 -1
View File
@@ -42,7 +42,7 @@ setuptools.setup(
install_requires=[
"aiohttp>=3.0.1,<4",
"mautrix>=0.4.0rc2,<0.5",
"mautrix>=0.4.0,<0.5",
"SQLAlchemy>=1.2.3,<2",
"alembic>=1.0.0,<2",
"commonmark>=0.8.1,<0.10",
+9 -1
View File
@@ -26,7 +26,7 @@ def context(request: FixtureRequest) -> Context:
"""
# Config(path, registration_path, base_path)
config = getattr(request.cls, 'config', Config("", "", ""))
return Context(az=Mock(), config=config, loop=Mock(), session_container=Mock(), bot=Mock())
return Context(az=Mock(), config=config, loop=Mock(), session_container=Mock(), bridge=Mock(), bot=Mock())
@pytest.fixture
@@ -52,6 +52,7 @@ class TestCommandEvent:
sender=u.User(UserID("@sender:example.org")),
command="help",
args=[],
content=Mock(),
is_management=True,
is_portal=False,
)
@@ -107,6 +108,7 @@ class TestCommandEvent:
sender=u.User(UserID("@sender:example.org")),
command="help",
args=[],
content=Mock(),
is_management=False,
is_portal=False,
)
@@ -133,6 +135,7 @@ class TestCommandEvent:
sender=u.User(UserID("@sender:example.org")),
command="help",
args=[],
content=Mock(),
is_management=True,
is_portal=False,
)
@@ -209,6 +212,7 @@ class TestCommandHandler:
sender=sender,
command=command,
args=[],
content=Mock(),
is_management=False,
is_portal=boolean,
)
@@ -271,6 +275,7 @@ class TestCommandHandler:
sender=sender,
command=command,
args=[],
content=Mock(),
is_management=is_management,
is_portal=boolean,
)
@@ -307,6 +312,7 @@ class TestCommandProcessor:
sender=sender,
command="hElp",
args=[],
content=Mock(),
is_management=boolean2[0],
is_portal=boolean2[1])
@@ -333,6 +339,7 @@ class TestCommandProcessor:
sender=sender,
command="foo",
args=[],
content=Mock(),
is_management=boolean2[0],
is_portal=boolean2[1],
)
@@ -361,6 +368,7 @@ class TestCommandProcessor:
sender=sender, # u.User
command="foo",
args=[],
content=Mock(),
is_management=boolean2[0],
is_portal=boolean2[1]
)