Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Discord messenger interface implementation started #323

Open
wants to merge 3 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions dff/messengers/common/modules.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
try:
import telebot as telegram
except ImportError:
telegram = None

try:
import discord
except ImportError:
discord = None
7 changes: 7 additions & 0 deletions dff/messengers/discord/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# -*- coding: utf-8 -*-

from dff.messengers.common.modules import discord
if discord is None:
raise ImportError("discord is not installed. Run `pip install dff[discord]`")

from .interface import DiscordInterface
63 changes: 63 additions & 0 deletions dff/messengers/discord/interface.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
from io import BytesIO
from logging import getLogger

from dff.messengers.common.modules import discord
from pydantic import HttpUrl

from dff.messengers.common import CallbackMessengerInterface
from dff.pipeline.types import PipelineRunnerFunction
from dff.script.core.message import Attachments, Audio, Document, Image, Message, Video

logger = getLogger(__name__)


def extract_message_from_discord(message: discord.Message) -> Message: # pragma: no cover
inn_mess = Message()
inn_mess.text = message.content

files = list()
if len(message.attachments) > 0:
first_attachment = message.attachments[0]
if first_attachment.content_type is not None:
content_type = first_attachment.content_type.split("/")[0]
if content_type == "audio":
files = [Audio(source=HttpUrl(first_attachment.url))]
elif content_type == "video":
files = [Video(source=HttpUrl(first_attachment.url))]
elif content_type == "image":
files = [Image(source=HttpUrl(first_attachment.url))]
elif content_type in ("application", "text"):
files = [Document(source=HttpUrl(first_attachment.url))]

inn_mess.attachments = Attachments(files=files)
pseusys marked this conversation as resolved.
Show resolved Hide resolved
return inn_mess


async def cast_message_to_discord_and_send(channel: discord.abc.Messageable, message: Message) -> None: # pragma: no cover
files = list()
if message.attachments is not None:
for file in message.attachments.files[:10]:
if file.source is not None:
files += [discord.File(BytesIO(file.get_bytes()), file.title)]

await channel.send(message.text, files=files)
RLKRo marked this conversation as resolved.
Show resolved Hide resolved


class DiscordInterface(CallbackMessengerInterface):
def __init__(self, token: str) -> None:
self._token = token

intents = discord.Intents.default()
intents.message_content = True
self._client = discord.Client(intents=intents)
self._client.event(self.on_message)

async def on_message(self, message: discord.Message):
if message.author != self._client.user:
resp = await self.on_request_async(extract_message_from_discord(message), message.author.id)
await cast_message_to_discord_and_send(message.channel, resp.last_response)

async def connect(self, callback: PipelineRunnerFunction):
await super().connect(callback)
await self._client.login(self._token)
await self._client.connect()
5 changes: 2 additions & 3 deletions dff/messengers/telegram/__init__.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
# -*- coding: utf-8 -*-

try:
import telebot
except ImportError:
from dff.messengers.common.modules import telegram
if telegram is None:
raise ImportError("telebot is not installed. Run `pip install dff[telegram]`")

from .messenger import TelegramMessenger
Expand Down
12 changes: 6 additions & 6 deletions dff/messengers/telegram/interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import asyncio
from typing import Any, Optional, List, Tuple, Callable

from telebot import types, apihelper
from dff.messengers.common.modules import telegram
RLKRo marked this conversation as resolved.
Show resolved Hide resolved

from dff.messengers.common import MessengerInterface, CallbackMessengerInterface
from dff.pipeline.types import PipelineRunnerFunction
Expand All @@ -24,11 +24,11 @@
request, abort = None, None


apihelper.ENABLE_MIDDLEWARE = True
telegram.apihelper.ENABLE_MIDDLEWARE = True


def extract_telegram_request_and_id(
update: types.Update, messenger: Optional[TelegramMessenger] = None
update: telegram.types.Update, messenger: Optional[TelegramMessenger] = None
) -> Tuple[TelegramMessage, int]: # pragma: no cover
"""
Utility function that extracts parameters from a telegram update.
Expand Down Expand Up @@ -62,10 +62,10 @@ def extract_telegram_request_and_id(
raise RuntimeError(f"Two update fields. First: {message.update_type}; second: {update_field}")
message.update_type = update_field
message.update = update_value
if isinstance(update_value, types.Message):
if isinstance(update_value, telegram.types.Message):
message.text = update_value.text

if isinstance(update_value, types.CallbackQuery):
if isinstance(update_value, telegram.types.CallbackQuery):
data = update_value.data
if data is not None:
message.callback_query = data
Expand Down Expand Up @@ -204,7 +204,7 @@ async def endpoint():
abort(403)

json_string = request.get_data().decode("utf-8")
update = types.Update.de_json(json_string)
update = telegram.types.Update.de_json(json_string)
resp = await self.on_request_async(*extract_telegram_request_and_id(update, self.messenger))
self.messenger.send_response(resp.id, resp.last_response)
return ""
Expand Down
38 changes: 12 additions & 26 deletions dff/messengers/telegram/message.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,7 @@
from typing import Optional, Union
from enum import Enum

from telebot.types import (
ReplyKeyboardRemove,
ReplyKeyboardMarkup,
InlineKeyboardMarkup,
Message as tlMessage,
InlineQuery,
ChosenInlineResult,
CallbackQuery as tlCallbackQuery,
ShippingQuery,
PreCheckoutQuery,
Poll,
PollAnswer,
ChatMemberUpdated,
ChatJoinRequest,
)
from dff.messengers.common.modules import telegram

from dff.script.core.message import Message, Location, Keyboard, DataModel
from pydantic import model_validator
Expand Down Expand Up @@ -68,22 +54,22 @@ class ParseMode(Enum):

class TelegramMessage(Message):
ui: Optional[
Union[TelegramUI, RemoveKeyboard, ReplyKeyboardRemove, ReplyKeyboardMarkup, InlineKeyboardMarkup]
Union[TelegramUI, RemoveKeyboard, telegram.types.ReplyKeyboardRemove, telegram.types.ReplyKeyboardMarkup, telegram.types.InlineKeyboardMarkup]
] = None
location: Optional[Location] = None
callback_query: Optional[Union[str, _ClickButton]] = None
update: Optional[
Union[
tlMessage,
InlineQuery,
ChosenInlineResult,
tlCallbackQuery,
ShippingQuery,
PreCheckoutQuery,
Poll,
PollAnswer,
ChatMemberUpdated,
ChatJoinRequest,
telegram.types.Message,
telegram.types.InlineQuery,
telegram.types.ChosenInlineResult,
telegram.types.CallbackQuery,
telegram.types.ShippingQuery,
telegram.types.PreCheckoutQuery,
telegram.types.Poll,
telegram.types.PollAnswer,
telegram.types.ChatMemberUpdated,
telegram.types.ChatJoinRequest,
]
] = None
"""This field stores an update representing this message."""
Expand Down
26 changes: 13 additions & 13 deletions dff/messengers/telegram/messenger.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from typing import Union, List, Optional, Callable
from enum import Enum

from telebot import types, TeleBot
from dff.messengers.common.modules import telegram

from dff.script import Context
from dff.pipeline import Pipeline
Expand All @@ -22,7 +22,7 @@
from dff.script.core.message import Audio, Video, Image, Document


class TelegramMessenger(TeleBot): # pragma: no cover
class TelegramMessenger(telegram.TeleBot): # pragma: no cover
"""
This class inherits from `Telebot` and implements framework-specific functionality
like sending generic responses.
Expand Down Expand Up @@ -90,13 +90,13 @@ def send_response(self, chat_id: Union[str, int], response: Union[str, dict, Mes

def cast(file):
if isinstance(file, Image):
cast_to_media_type = types.InputMediaPhoto
cast_to_media_type = telegram.types.InputMediaPhoto
elif isinstance(file, Audio):
cast_to_media_type = types.InputMediaAudio
cast_to_media_type = telegram.types.InputMediaAudio
elif isinstance(file, Document):
cast_to_media_type = types.InputMediaDocument
cast_to_media_type = telegram.types.InputMediaDocument
elif isinstance(file, Video):
cast_to_media_type = types.InputMediaVideo
cast_to_media_type = telegram.types.InputMediaVideo
else:
raise TypeError(type(file))
return cast_to_media_type(media=str(file.source or file.id), caption=file.title)
Expand All @@ -114,22 +114,22 @@ def cast(file):

if ready_response.ui is not None:
if isinstance(ready_response.ui, RemoveKeyboard):
keyboard = types.ReplyKeyboardRemove()
keyboard = telegram.types.ReplyKeyboardRemove()
elif isinstance(ready_response.ui, TelegramUI):
if ready_response.ui.is_inline:
keyboard = types.InlineKeyboardMarkup(row_width=ready_response.ui.row_width)
keyboard = telegram.types.InlineKeyboardMarkup(row_width=ready_response.ui.row_width)
buttons = [
types.InlineKeyboardButton(
telegram.types.InlineKeyboardButton(
text=item.text,
url=item.source,
callback_data=item.payload,
)
for item in ready_response.ui.buttons
]
else:
keyboard = types.ReplyKeyboardMarkup(row_width=ready_response.ui.row_width)
keyboard = telegram.types.ReplyKeyboardMarkup(row_width=ready_response.ui.row_width)
buttons = [
types.KeyboardButton(
telegram.types.KeyboardButton(
text=item.text,
)
for item in ready_response.ui.buttons
Expand All @@ -156,7 +156,7 @@ def cast(file):
)


_default_messenger = TeleBot("")
_default_messenger = telegram.TeleBot("")


class UpdateType(Enum):
Expand Down Expand Up @@ -184,7 +184,7 @@ class UpdateType(Enum):


def telegram_condition(
messenger: TeleBot = _default_messenger,
messenger: telegram.TeleBot = _default_messenger,
update_type: UpdateType = UpdateType.MESSAGE,
commands: Optional[List[str]] = None,
regexp: Optional[str] = None,
Expand Down
8 changes: 4 additions & 4 deletions dff/messengers/telegram/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@
from pathlib import Path
from io import IOBase

from telebot import types
from dff.messengers.common.modules import telegram


def open_io(item: types.InputMedia):
def open_io(item: telegram.types.InputMedia):
"""
Returns `InputMedia` with an opened file descriptor instead of path.

Expand All @@ -22,7 +22,7 @@ def open_io(item: types.InputMedia):
return item


def close_io(item: types.InputMedia):
def close_io(item: telegram.types.InputMedia):
"""
Closes an IO in an `InputMedia` object to perform the cleanup.

Expand All @@ -33,7 +33,7 @@ def close_io(item: types.InputMedia):


@contextmanager
def batch_open_io(item: Union[types.InputMedia, Iterable[types.InputMedia]]):
def batch_open_io(item: Union[telegram.types.InputMedia, Iterable[telegram.types.InputMedia]]):
"""
Context manager that controls the state of file descriptors inside `InputMedia`.
Can be used both for single objects and collections.
Expand Down
Loading