Compare commits

..

13 Commits

Author SHA1 Message Date
Tulir Asokan 4d8d332732 Bump version to 0.8.0rc2 2020-05-20 19:13:54 +03:00
Tulir Asokan 7fb771b992 Fix copying example config on first run of docker image 2020-05-20 19:13:45 +03:00
Tulir Asokan d0900a95a7 Send uk.half-shot.bridge in addition to m.bridge 2020-05-19 11:37:17 +03:00
Tulir Asokan 8552d463a1 Add missing receiver_id when syncing direct chat dialogs (ref #425) 2020-05-19 11:30:45 +03:00
Tulir Asokan 74d130644c Fix tempfile usage 2020-05-17 15:01:03 +03:00
Tulir Asokan 976e0dd2b7 Fix !tg version command for non-release versions in docker 2020-05-13 23:34:43 +03:00
Tulir Asokan 340c25ba0b Use stdlib tempfile for video thumbnail temp files 2020-05-13 23:33:24 +03:00
Tulir Asokan 7e8d4bc9a8 Include readme, license and requirements.txt in PyPI tarballs 2020-05-13 23:33:08 +03:00
Tulir Asokan 429544373a Bump mautrix-python and send m.bridge events 2020-05-05 21:40:57 +03:00
Tulir Asokan 80dd6fa9e1 Fix typo in unbridge permission error 2020-04-27 13:21:49 +03:00
Tulir Asokan 45ac120407 Add error message if backfill is ran in non-portal room 2020-04-25 23:24:39 +03:00
Tulir Asokan 2c100ca1e5 Fix minor mistake in logging 2020-04-25 19:31:12 +03:00
Tulir Asokan c54bd9e1ce Log the source and reason of user displayname changes 2020-04-25 19:29:12 +03:00
12 changed files with 62 additions and 41 deletions
+3 -1
View File
@@ -62,7 +62,9 @@ RUN apk add --virtual .build-deps \
&& apk del .build-deps
COPY . /opt/mautrix-telegram
RUN apk add git && pip3 install .[speedups,hq_thumbnails,metrics,e2be] && apk del git
RUN apk add git && pip3 install .[speedups,hq_thumbnails,metrics,e2be] && apk del git \
# This doesn't make the image smaller, but it's needed so that the `version` command works properly
&& cp mautrix_telegram/example-config.yaml . && rm -rf mautrix_telegram
VOLUME /data
ENV UID=1337 GID=1337 \
+4
View File
@@ -0,0 +1,4 @@
include README.md
include LICENSE
include requirements.txt
include optional-requirements.txt
+1 -1
View File
@@ -15,7 +15,7 @@ if [ -f /data/mx-state.json ]; then
fi
if [ ! -f /data/config.yaml ]; then
cp mautrix_telegram/example-config.yaml /data/config.yaml
cp example-config.yaml /data/config.yaml
echo "Didn't find a config file."
echo "Copied default config file to /data/config.yaml"
echo "Modify that config file to your liking."
+1 -1
View File
@@ -1,2 +1,2 @@
__version__ = "0.8.0rc1"
__version__ = "0.8.0rc2"
__author__ = "Tulir Asokan <tulir@maunium.net>"
+5 -8
View File
@@ -22,9 +22,7 @@ from .. import command_handler, CommandEvent, SECTION_PORTAL_MANAGEMENT
from .util import user_has_power_level
async def _get_portal_and_check_permission(evt: CommandEvent, permission: str,
action: Optional[str] = None
) -> Optional[po.Portal]:
async def _get_portal_and_check_permission(evt: CommandEvent) -> Optional[po.Portal]:
room_id = RoomID(evt.args[0]) if len(evt.args) > 0 else evt.room_id
portal = po.Portal.get_by_mxid(room_id)
@@ -33,9 +31,8 @@ async def _get_portal_and_check_permission(evt: CommandEvent, permission: str,
await evt.reply(f"{that_this} is not a portal room.")
return None
if not await user_has_power_level(portal.mxid, evt.az.intent, evt.sender, permission):
action = action or f"{permission.replace('_', ' ')}s"
await evt.reply(f"You do not have the permissions to {action} that portal.")
if not await user_has_power_level(portal.mxid, evt.az.intent, evt.sender, "unbridge"):
await evt.reply("You do not have the permissions to unbridge that portal.")
return None
return portal
@@ -64,7 +61,7 @@ def _get_portal_murder_function(action: str, room_id: str, function: Callable, c
"Only works for group chats; to delete a private chat portal, simply "
"leave the room.")
async def delete_portal(evt: CommandEvent) -> Optional[EventID]:
portal = await _get_portal_and_check_permission(evt, "unbridge")
portal = await _get_portal_and_check_permission(evt)
if not portal:
return None
@@ -85,7 +82,7 @@ async def delete_portal(evt: CommandEvent) -> Optional[EventID]:
help_section=SECTION_PORTAL_MANAGEMENT,
help_text="Remove puppets from the current portal room and forget the portal.")
async def unbridge(evt: CommandEvent) -> Optional[EventID]:
portal = await _get_portal_and_check_permission(evt, "unbridge")
portal = await _get_portal_and_check_permission(evt)
if not portal:
return None
+3 -1
View File
@@ -328,9 +328,11 @@ async def random(evt: CommandEvent) -> EventID:
@command_handler(help_section=SECTION_PORTAL_MANAGEMENT,
help_args="<_number of messages_> [--takeout]",
help_text="Backfill messages from Telegram history.")
async def backfill(evt: CommandEvent) -> None:
if not evt.is_portal:
await evt.reply("You can only use backfill in portal rooms")
return
portal = po.Portal.get_by_mxid(evt.room_id)
try:
await portal.backfill(evt.sender)
+2
View File
@@ -402,6 +402,8 @@ class BasePortal(ABC):
@classmethod
def get_by_tgid(cls, tgid: TelegramID, tg_receiver: Optional[TelegramID] = None,
peer_type: str = None) -> Optional['Portal']:
if peer_type == "user" and tg_receiver is None:
raise ValueError("tg_receiver is required when peer_type is \"user\"")
tg_receiver = tg_receiver or tgid
tgid_full = (tgid, tg_receiver)
try:
+21
View File
@@ -307,9 +307,30 @@ class PortalMetadata(BasePortal, ABC):
for invite in invites:
power_levels.users.setdefault(invite, 100)
self.title = puppet.displayname
bridge_info = {
"bridgebot": self.az.bot_mxid,
"creator": self.main_intent.mxid,
"protocol": {
"id": "telegram",
"displayname": "Telegram",
"avatar_url": config["appservice.bot_avatar"],
},
"channel": {
"id": self.tgid
}
}
initial_state = [{
"type": EventType.ROOM_POWER_LEVELS.serialize(),
"content": power_levels.serialize(),
}, {
"type": "m.bridge",
"state_key": f"net.maunium.telegram://telegram/{self.tgid}",
"content": bridge_info
}, {
# TODO remove this once https://github.com/matrix-org/matrix-doc/pull/2346 is in spec
"type": "uk.half-shot.bridge",
"state_key": f"net.maunium.telegram://telegram/{self.tgid}",
"content": bridge_info
}]
if config["bridge.encryption.default"] and self.matrix.e2ee:
self.encrypted = True
+13 -8
View File
@@ -256,19 +256,24 @@ class Puppet(CustomPuppetMixin):
) -> bool:
if self.disable_updates:
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:
if source.is_relaybot or source.is_bot:
allow_because = "user is bot"
elif self.displayname_source == source.tgid:
allow_because = "user is the primary source"
elif not info.contact:
allow_because = "user is not a contact"
elif self.displayname_source is None:
allow_because = "no primary source set"
else:
return False
elif isinstance(info, UpdateUserName):
if isinstance(info, UpdateUserName):
info = await source.client.get_entity(PeerUser(self.tgid))
displayname = self.get_displayname(info)
if displayname != self.displayname:
self.log.debug(f"Updating displayname of {self.id} (src: {source.tgid}, allowed "
f"because {allow_because}) from {self.displayname} to {displayname}")
self.displayname = displayname
self.displayname_source = source.tgid
try:
+1 -1
View File
@@ -348,7 +348,7 @@ class User(AbstractUser, BaseUser):
continue
elif isinstance(entity, TLUser) and not config["bridge.sync_direct_chats"]:
continue
portal = po.Portal.get_by_entity(entity)
portal = po.Portal.get_by_entity(entity, receiver_id=self.tgid)
self.portals[portal.tgid_full] = portal
creators.append(
portal.create_matrix_room(self, entity, invites=[self.mxid],
+7 -19
View File
@@ -18,6 +18,7 @@ from io import BytesIO
import time
import logging
import asyncio
import tempfile
import magic
from sqlalchemy.exc import IntegrityError, InvalidRequestError
@@ -44,12 +45,8 @@ except ImportError:
try:
from moviepy.editor import VideoFileClip
import random
import string
import os
import mimetypes
except ImportError:
VideoFileClip = random = string = os = mimetypes = None
VideoFileClip = None
try:
from nio.crypto import encrypt_attachment
@@ -80,32 +77,23 @@ def convert_image(file: bytes, source_mime: str = "image/webp", target_type: str
return source_mime, file, None, None
def _temp_file_name(ext: str) -> str:
return ("/tmp/mxtg-video-"
+ "".join(random.choice(string.ascii_uppercase + string.digits) for _ in range(10))
+ ext)
def _read_video_thumbnail(data: bytes, video_ext: str = "mp4", frame_ext: str = "png",
max_size: Tuple[int, int] = (1024, 720)) -> Tuple[bytes, int, int]:
# We don't have any way to read the video from memory, so save it to disk.
temp_file = _temp_file_name(video_ext)
with open(temp_file, "wb") as file:
with tempfile.NamedTemporaryFile(prefix="mxtg_video_", suffix=f".{video_ext}") as file:
# We don't have any way to read the video from memory, so save it to disk.
file.write(data)
# Read temp file and get frame
clip = VideoFileClip(temp_file)
frame = clip.get_frame(0)
# Read temp file and get frame
frame = VideoFileClip(file.name).get_frame(0)
# Convert to png and save to BytesIO
image = Image.fromarray(frame).convert("RGBA")
thumbnail_file = BytesIO()
if max_size:
image.thumbnail(max_size, Image.ANTIALIAS)
image.save(thumbnail_file, frame_ext)
os.remove(temp_file)
w, h = image.size
return thumbnail_file.getvalue(), w, h
+1 -1
View File
@@ -4,6 +4,6 @@ ruamel.yaml>=0.15.35,<0.17
python-magic>=0.4,<0.5
commonmark>=0.8,<0.10
aiohttp>=3,<4
mautrix==0.5.0.beta13
mautrix==0.5.0.beta15
telethon>=1.13,<1.14
telethon-session-sqlalchemy>=0.2.14,<0.3