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
# 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
from telethon.errors import (
InviteHashInvalidError, InviteHashExpiredError, UserAlreadyParticipantError)
from telethon.tl.types import User as TLUser
from telethon.tl.types import TypeUpdates
from telethon.tl.functions.messages import ImportChatInviteRequest, CheckChatInviteRequest
from telethon.errors import (InviteHashInvalidError, InviteHashExpiredError,
UserAlreadyParticipantError)
from telethon.tl.types import User as TLUser, TypeUpdates, MessageMediaGame, PeerChat
from telethon.tl.types.messages import BotCallbackAnswer
from telethon.tl.functions.messages import (ImportChatInviteRequest, CheckChatInviteRequest,
GetBotCallbackAnswerRequest)
from telethon.tl.functions.channels import JoinChannelRequest
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
@@ -158,3 +162,54 @@ async def sync(evt: CommandEvent) -> Optional[Dict]:
if not sync_only or sync_only == "me":
await evt.sender.update_info()
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",
main_intent: Optional[IntentAPI] = None,
is_edit: bool = False, prefix_text: Optional[str] = None,
prefix_html: Optional[str] = None) -> Tuple[str, str, Dict]:
text = add_surrogates(evt.message)
html = _telegram_entities_to_matrix_catch(text, evt.entities) if evt.entities else None
prefix_html: Optional[str] = None, override_text: str = None,
override_entities: List[TypeMessageEntity] = 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
if prefix_html:
+40 -11
View File
@@ -23,6 +23,7 @@ import asyncio
import random
import mimetypes
import unicodedata
import base64
import hashlib
import logging
import json
@@ -55,12 +56,12 @@ from telethon.tl.types import (
MessageActionChatDeletePhoto, MessageActionChatDeleteUser, MessageActionChatEditPhoto,
MessageActionChatEditTitle, MessageActionChatJoinedByLink, MessageActionChatMigrateTo,
MessageActionPinMessage, MessageMediaContact, MessageMediaDocument, MessageMediaGeo,
MessageMediaPhoto, MessageMediaUnsupported, MessageService, PeerChannel, PeerChat, PeerUser,
Photo, PhotoCachedSize, SendMessageCancelAction, SendMessageTypingAction,
MessageMediaPhoto, MessageMediaUnsupported, MessageMediaGame, MessageService, PeerChannel,
PeerChat, PeerUser, Photo, PhotoCachedSize, SendMessageCancelAction, SendMessageTypingAction,
TypeChannelParticipant, TypeChat, TypeChatParticipant, TypeDocumentAttribute, TypeInputPeer,
TypeMessageAction, TypeMessageEntity, TypePeer, TypePhotoSize, TypeUpdates, TypeUser,
TypeUserFull, UpdateChatUserTyping, UpdateNewChannelMessage, UpdateNewMessage, UpdateUserTyping,
User, UserFull)
User, UserFull, MessageEntityCode)
from mautrix_appservice import MatrixRequestError, IntentError, AppService, IntentAPI
from .types import MatrixEventID, MatrixRoomID, MatrixUserID, TelegramID
@@ -1414,13 +1415,11 @@ class Portal:
async def handle_telegram_unsupported(self, source: 'AbstractUser', intent: IntentAPI,
evt: Message, relates_to: dict = None) -> dict:
prev = evt.message
evt.message = ("This message is not supported on your version of Mautrix-Telegram. "
"Please check https://github.com/tulir/mautrix-telegram or ask your "
"bridge administrator about possible updates.")
text, html, relates_to = await formatter.telegram_to_matrix(evt, source,
self.main_intent)
evt.message = prev
override_text = ("This message is not supported on your version of Mautrix-Telegram. "
"Please check https://github.com/tulir/mautrix-telegram or ask your "
"bridge administrator about possible updates.")
text, html, relates_to = await formatter.telegram_to_matrix(
evt, source, self.main_intent, override_text=override_text)
await intent.set_typing(self.mxid, is_typing=False)
return await intent.send_message(self.mxid, {
"body": text,
@@ -1431,6 +1430,35 @@ class Portal:
"net.maunium.telegram.unsupported": True,
}, 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,
evt: Message) -> None:
if not self.mxid:
@@ -1514,7 +1542,7 @@ class Portal:
entity = await source.client.get_entity(PeerUser(sender.tgid))
await sender.update_info(source, entity)
allowed_media = (MessageMediaPhoto, MessageMediaDocument, MessageMediaGeo,
allowed_media = (MessageMediaPhoto, MessageMediaDocument, MessageMediaGeo, MessageMediaGame,
MessageMediaUnsupported)
media = evt.media if hasattr(evt, "media") and isinstance(evt.media,
allowed_media) else None
@@ -1528,6 +1556,7 @@ class Portal:
MessageMediaDocument: self.handle_telegram_document,
MessageMediaGeo: self.handle_telegram_location,
MessageMediaUnsupported: self.handle_telegram_unsupported,
MessageMediaGame: self.handle_telegram_game,
}[type(media)](source, intent, evt,
relates_to=formatter.telegram_reply_to_matrix(evt, source))
else: