Add support for playing games. Fixes #256
This commit is contained in:
@@ -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}")
|
||||||
|
|||||||
@@ -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
@@ -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:
|
||||||
|
|||||||
Reference in New Issue
Block a user