Add support for playing games. Fixes #256

This commit is contained in:
Tulir Asokan
2018-12-23 17:00:19 +02:00
parent 09676f8314
commit 2e20ae2148
3 changed files with 107 additions and 20 deletions
+61 -6
View File
@@ -14,17 +14,21 @@
# #
# You should have received a copy of the GNU Affero General Public License # You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>. # along with this program. If not, see <https://www.gnu.org/licenses/>.
from typing import Awaitable, Dict, List, Optional, Tuple from typing import Dict, List, Optional, Tuple
import base64
import re import re
from telethon.errors import ( from telethon.errors import (InviteHashInvalidError, InviteHashExpiredError,
InviteHashInvalidError, InviteHashExpiredError, UserAlreadyParticipantError) UserAlreadyParticipantError)
from telethon.tl.types import User as TLUser from telethon.tl.types import User as TLUser, TypeUpdates, MessageMediaGame, PeerChat
from telethon.tl.types import TypeUpdates from telethon.tl.types.messages import BotCallbackAnswer
from telethon.tl.functions.messages import ImportChatInviteRequest, CheckChatInviteRequest from telethon.tl.functions.messages import (ImportChatInviteRequest, CheckChatInviteRequest,
GetBotCallbackAnswerRequest)
from telethon.tl.functions.channels import JoinChannelRequest from telethon.tl.functions.channels import JoinChannelRequest
from .. import puppet as pu, portal as po from .. import puppet as pu, portal as po
from ..db import Message as DBMessage
from ..types import TelegramID
from . import command_handler, CommandEvent, SECTION_MISC, SECTION_CREATING_PORTALS from . import command_handler, CommandEvent, SECTION_MISC, SECTION_CREATING_PORTALS
@@ -158,3 +162,54 @@ async def sync(evt: CommandEvent) -> Optional[Dict]:
if not sync_only or sync_only == "me": if not sync_only or sync_only == "me":
await evt.sender.update_info() await evt.sender.update_info()
return await evt.reply("Synchronization complete.") return await evt.reply("Synchronization complete.")
@command_handler(help_section=SECTION_MISC,
help_args="<play ID>",
help_text="Play a Telegram game")
async def play(evt: CommandEvent) -> Optional[Dict]:
if len(evt.args) < 1:
return await evt.reply("**Usage:** `$cmdprefix+sp play <play ID>`")
elif not await evt.sender.is_logged_in():
return await evt.reply("You must be logged in with a real account to play games.")
elif evt.sender.is_bot:
return await evt.reply("Bots can't play games :(")
try:
space = None
peer_type, play_id = base64.b64decode(evt.args[0]).decode("utf-8").split("-", 1)
if peer_type == "chan" or peer_type == "user":
tgid, msg_id = play_id.split("-")
elif peer_type == "chat":
tgid, space, msg_id = play_id.split("-")
space = TelegramID(int(space))
else:
raise ValueError()
tgid = TelegramID(int(tgid))
msg_id = TelegramID(int(msg_id))
except ValueError:
return await evt.reply("Invalid play ID (format)")
if peer_type == "chat":
orig_msg = DBMessage.get_by_tgid(msg_id, space)
if not orig_msg:
return await evt.reply("Invalid play ID (original message not found in db)")
new_msg = DBMessage.get_by_mxid(orig_msg.mxid, orig_msg.mx_room, evt.sender.tgid)
if not new_msg:
return await evt.reply("Invalid play ID (your copy of message not found in db)")
msg_id = new_msg.tgid
try:
peer = await evt.sender.client.get_input_entity(tgid)
except ValueError as e:
return await evt.reply("Invalid play ID (chat not found)")
msg = await evt.sender.client.get_messages(entity=peer, ids=msg_id)
if not msg or not isinstance(msg.media, MessageMediaGame):
return await evt.reply("Invalid play ID (message doesn't look like a game)")
game = await evt.sender.client(GetBotCallbackAnswerRequest(peer=peer, msg_id=msg_id, game=True))
if not isinstance(game, BotCallbackAnswer):
return await evt.reply("Game request response invalid")
await evt.reply(f"Click [here]({game.url}) to play {msg.media.game.title}:\n\n"
f"{msg.media.game.description}")
+6 -3
View File
@@ -179,9 +179,12 @@ async def _add_reply_header(source: "AbstractUser", text: str, html: str, evt: M
async def telegram_to_matrix(evt: Message, source: "AbstractUser", async def telegram_to_matrix(evt: Message, source: "AbstractUser",
main_intent: Optional[IntentAPI] = None, main_intent: Optional[IntentAPI] = None,
is_edit: bool = False, prefix_text: Optional[str] = None, is_edit: bool = False, prefix_text: Optional[str] = None,
prefix_html: Optional[str] = None) -> Tuple[str, str, Dict]: prefix_html: Optional[str] = None, override_text: str = None,
text = add_surrogates(evt.message) override_entities: List[TypeMessageEntity] = None
html = _telegram_entities_to_matrix_catch(text, evt.entities) if evt.entities else None ) -> Tuple[str, str, Dict]:
text = add_surrogates(override_text or evt.message)
entities = override_entities or evt.entities
html = _telegram_entities_to_matrix_catch(text, entities) if entities else None
relates_to = {} # type: Dict relates_to = {} # type: Dict
if prefix_html: if prefix_html:
+40 -11
View File
@@ -23,6 +23,7 @@ import asyncio
import random import random
import mimetypes import mimetypes
import unicodedata import unicodedata
import base64
import hashlib import hashlib
import logging import logging
import json import json
@@ -55,12 +56,12 @@ from telethon.tl.types import (
MessageActionChatDeletePhoto, MessageActionChatDeleteUser, MessageActionChatEditPhoto, MessageActionChatDeletePhoto, MessageActionChatDeleteUser, MessageActionChatEditPhoto,
MessageActionChatEditTitle, MessageActionChatJoinedByLink, MessageActionChatMigrateTo, MessageActionChatEditTitle, MessageActionChatJoinedByLink, MessageActionChatMigrateTo,
MessageActionPinMessage, MessageMediaContact, MessageMediaDocument, MessageMediaGeo, MessageActionPinMessage, MessageMediaContact, MessageMediaDocument, MessageMediaGeo,
MessageMediaPhoto, MessageMediaUnsupported, MessageService, PeerChannel, PeerChat, PeerUser, MessageMediaPhoto, MessageMediaUnsupported, MessageMediaGame, MessageService, PeerChannel,
Photo, PhotoCachedSize, SendMessageCancelAction, SendMessageTypingAction, PeerChat, PeerUser, Photo, PhotoCachedSize, SendMessageCancelAction, SendMessageTypingAction,
TypeChannelParticipant, TypeChat, TypeChatParticipant, TypeDocumentAttribute, TypeInputPeer, TypeChannelParticipant, TypeChat, TypeChatParticipant, TypeDocumentAttribute, TypeInputPeer,
TypeMessageAction, TypeMessageEntity, TypePeer, TypePhotoSize, TypeUpdates, TypeUser, TypeMessageAction, TypeMessageEntity, TypePeer, TypePhotoSize, TypeUpdates, TypeUser,
TypeUserFull, UpdateChatUserTyping, UpdateNewChannelMessage, UpdateNewMessage, UpdateUserTyping, TypeUserFull, UpdateChatUserTyping, UpdateNewChannelMessage, UpdateNewMessage, UpdateUserTyping,
User, UserFull) User, UserFull, MessageEntityCode)
from mautrix_appservice import MatrixRequestError, IntentError, AppService, IntentAPI from mautrix_appservice import MatrixRequestError, IntentError, AppService, IntentAPI
from .types import MatrixEventID, MatrixRoomID, MatrixUserID, TelegramID from .types import MatrixEventID, MatrixRoomID, MatrixUserID, TelegramID
@@ -1414,13 +1415,11 @@ class Portal:
async def handle_telegram_unsupported(self, source: 'AbstractUser', intent: IntentAPI, async def handle_telegram_unsupported(self, source: 'AbstractUser', intent: IntentAPI,
evt: Message, relates_to: dict = None) -> dict: evt: Message, relates_to: dict = None) -> dict:
prev = evt.message override_text = ("This message is not supported on your version of Mautrix-Telegram. "
evt.message = ("This message is not supported on your version of Mautrix-Telegram. " "Please check https://github.com/tulir/mautrix-telegram or ask your "
"Please check https://github.com/tulir/mautrix-telegram or ask your " "bridge administrator about possible updates.")
"bridge administrator about possible updates.") text, html, relates_to = await formatter.telegram_to_matrix(
text, html, relates_to = await formatter.telegram_to_matrix(evt, source, evt, source, self.main_intent, override_text=override_text)
self.main_intent)
evt.message = prev
await intent.set_typing(self.mxid, is_typing=False) await intent.set_typing(self.mxid, is_typing=False)
return await intent.send_message(self.mxid, { return await intent.send_message(self.mxid, {
"body": text, "body": text,
@@ -1431,6 +1430,35 @@ class Portal:
"net.maunium.telegram.unsupported": True, "net.maunium.telegram.unsupported": True,
}, timestamp=evt.date, external_url=self.get_external_url(evt)) }, timestamp=evt.date, external_url=self.get_external_url(evt))
async def handle_telegram_game(self, source: 'AbstractUser', intent: IntentAPI,
evt: Message, relates_to: dict = None):
game = evt.media.game
if self.peer_type == "channel":
play_id = base64.b64encode(f"chan-{self.tgid}-{evt.id}".encode("utf-8"))
elif self.peer_type == "chat":
play_id = base64.b64encode(f"chat-{self.tgid}-{source.tgid}-{evt.id}".encode("utf-8"))
elif self.peer_type == "user":
play_id = base64.b64encode(f"user-{self.tgid}-{evt.id}".encode("utf-8"))
else:
raise ValueError("Portal has invalid peer type")
play_id = play_id.decode("utf-8")
command = f"!tg play {play_id}"
override_text = (f"Run {command} in your bridge management room to "
f"play {game.title}:\n\n{game.description}")
override_entities = [MessageEntityCode(offset=len("Run "), length=len(command))]
text, html, relates_to = await formatter.telegram_to_matrix(
evt, source, self.main_intent,
override_text=override_text, override_entities=override_entities)
await intent.set_typing(self.mxid, is_typing=False)
return await intent.send_message(self.mxid, {
"body": text,
"msgtype": "m.notice",
"format": "org.matrix.custom.html",
"formatted_body": html,
"m.relates_to": relates_to,
"net.maunium.telegram.game": play_id,
}, timestamp=evt.date, external_url=self.get_external_url(evt))
async def handle_telegram_edit(self, source: 'AbstractUser', sender: p.Puppet, async def handle_telegram_edit(self, source: 'AbstractUser', sender: p.Puppet,
evt: Message) -> None: evt: Message) -> None:
if not self.mxid: if not self.mxid:
@@ -1514,7 +1542,7 @@ class Portal:
entity = await source.client.get_entity(PeerUser(sender.tgid)) entity = await source.client.get_entity(PeerUser(sender.tgid))
await sender.update_info(source, entity) await sender.update_info(source, entity)
allowed_media = (MessageMediaPhoto, MessageMediaDocument, MessageMediaGeo, allowed_media = (MessageMediaPhoto, MessageMediaDocument, MessageMediaGeo, MessageMediaGame,
MessageMediaUnsupported) MessageMediaUnsupported)
media = evt.media if hasattr(evt, "media") and isinstance(evt.media, media = evt.media if hasattr(evt, "media") and isinstance(evt.media,
allowed_media) else None allowed_media) else None
@@ -1528,6 +1556,7 @@ class Portal:
MessageMediaDocument: self.handle_telegram_document, MessageMediaDocument: self.handle_telegram_document,
MessageMediaGeo: self.handle_telegram_location, MessageMediaGeo: self.handle_telegram_location,
MessageMediaUnsupported: self.handle_telegram_unsupported, MessageMediaUnsupported: self.handle_telegram_unsupported,
MessageMediaGame: self.handle_telegram_game,
}[type(media)](source, intent, evt, }[type(media)](source, intent, evt,
relates_to=formatter.telegram_reply_to_matrix(evt, source)) relates_to=formatter.telegram_reply_to_matrix(evt, source))
else: else: