Skip to content

WIP: Implemented support for normal keyboards #143

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

Open
wants to merge 6 commits into
base: master
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
1 change: 1 addition & 0 deletions AUTHORS
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ Contributors:
Ilya Otyutskiy <ilya.otyutskiy@icloud.com>
Stefano Teodorani <s.teodorani@gmail.com>
Francesco Zimbolo <dtrandom@randomdev.tk>
Mattia Effendi <contact@mattiaeffendi.me>
Daniele Ceribelli <danieleceribelli@gmail.com>

Original author:
Expand Down
1 change: 1 addition & 0 deletions botogram/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
from .objects import *
from .utils import usernames_in
from .callbacks import Buttons, ButtonsRow
from .keyboards import Keyboard, KeyboardRow
from .inline import (
InlineInputMessage,
InlineInputLocation,
Expand Down
84 changes: 84 additions & 0 deletions botogram/keyboards.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
# Copyright (c) 2015-2019 The Botogram Authors (see AUTHORS)
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER


class KeyboardRow:
"""A row of a keyboard"""

def __init__(self):
self._content = []

def text(self, text):
"""Sends a message when the button is pressed"""
self._content.append({"text": text})

def request_contact(self, label):
"""Ask the user if he wants to share his contact"""

self._content.append({
"text": label,
"request_contact": True,
})

def request_location(self, label):
"""Ask the user if he wants to share his location"""

self._content.append({
"text": label,
"request_location": True,
})

def _get_content(self, chat):
"""Get the content of this row"""
for item in self._content:
new = item.copy()

# Replace any callable with its value
# This allows to dynamically generate field values
for key, value in new.items():
if callable(value):
new[key] = value(chat)

yield new


class Keyboard:
"""Factory for keyboards"""

def __init__(self, resize=False, one_time=False, selective=False):
self.resize_keyboard = resize
self.one_time_keyboard = one_time
self.selective = selective
self._rows = {}

def __getitem__(self, index):
if index not in self._rows:
self._rows[index] = KeyboardRow()
return self._rows[index]

def _serialize_attachment(self, chat):
rows = [
list(row._get_content(chat)) for i, row in sorted(
tuple(self._rows.items()), key=lambda i: i[0]
)
]

return {"keyboard": rows, "resize_keyboard": self.resize_keyboard,
"one_time_keyboard": self.one_time_keyboard,
"selective": self.selective}
94 changes: 67 additions & 27 deletions botogram/objects/mixins.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,8 @@ def __(self, *args, **kwargs):
class ChatMixin:
"""Add some methods for chats"""

def _get_call_args(self, reply_to, extra, attach, notify):
def _get_call_args(self, reply_to, extra, attach, notify, remove_keyboard,
force_reply, selective):
"""Get default API call arguments"""
# Convert instance of Message to ids in reply_to
if hasattr(reply_to, "id"):
Expand All @@ -76,6 +77,20 @@ def _get_call_args(self, reply_to, extra, attach, notify):
if not notify:
args["disable_notification"] = True

if remove_keyboard is not None:
reply_markup = {}
reply_markup['remove_keyboard'] = remove_keyboard
if selective is not None:
reply_markup['selective'] = selective
args["reply_markup"] = json.dumps(reply_markup)

if force_reply is not None:
reply_markup = {}
reply_markup['force_reply'] = force_reply
if selective is not None:
reply_markup['selective'] = selective
args["reply_markup"] = json.dumps(reply_markup)

return args

@staticmethod
Expand All @@ -98,9 +113,11 @@ def _get_file_args(path, file_id, url):

@_require_api
def send(self, message, preview=True, reply_to=None, syntax=None,
extra=None, attach=None, notify=True):
extra=None, attach=None, notify=True, remove_keyboard=None,
force_reply=None, selective=None):
"""Send a message"""
args = self._get_call_args(reply_to, extra, attach, notify)
args = self._get_call_args(reply_to, extra, attach, notify,
remove_keyboard, force_reply, selective)
args["text"] = message
args["disable_web_page_preview"] = not preview

Expand All @@ -113,9 +130,11 @@ def send(self, message, preview=True, reply_to=None, syntax=None,
@_require_api
def send_photo(self, path=None, file_id=None, url=None, caption=None,
syntax=None, reply_to=None, extra=None, attach=None,
notify=True):
notify=True, remove_keyboard=None, force_reply=None,
selective=None):
"""Send a photo"""
args = self._get_call_args(reply_to, extra, attach, notify)
args = self._get_call_args(reply_to, extra, attach, notify,
remove_keyboard, force_reply, selective)
if caption is not None:
args["caption"] = caption
if syntax is not None:
Expand All @@ -134,10 +153,12 @@ def send_photo(self, path=None, file_id=None, url=None, caption=None,
@_require_api
def send_audio(self, path=None, file_id=None, url=None, duration=None,
thumb=None, performer=None, title=None, reply_to=None,
extra=None, attach=None, notify=True, caption=None, *,
extra=None, attach=None, notify=True, remove_keyboard=None,
force_reply=None, selective=None, caption=None, *,
syntax=None):
"""Send an audio track"""
args = self._get_call_args(reply_to, extra, attach, notify)
args = self._get_call_args(reply_to, extra, attach, notify,
remove_keyboard, force_reply, selective)
if caption is not None:
args["caption"] = caption
if syntax is not None:
Expand Down Expand Up @@ -165,9 +186,11 @@ def send_audio(self, path=None, file_id=None, url=None, duration=None,
@_require_api
def send_voice(self, path=None, file_id=None, url=None, duration=None,
title=None, reply_to=None, extra=None, attach=None,
notify=True, caption=None, *, syntax=None):
notify=True, remove_keyboard=None, force_reply=None,
selective=None, caption=None, *, syntax=None):
"""Send a voice message"""
args = self._get_call_args(reply_to, extra, attach, notify)
args = self._get_call_args(reply_to, extra, attach, notify,
remove_keyboard, force_reply, selective)
if caption is not None:
args["caption"] = caption
if syntax is not None:
Expand Down Expand Up @@ -195,9 +218,11 @@ def send_voice(self, path=None, file_id=None, url=None, duration=None,
def send_video(self, path=None, file_id=None, url=None,
duration=None, caption=None, streaming=True, thumb=None,
reply_to=None, extra=None, attach=None,
notify=True, *, syntax=None):
notify=True, remove_keyboard=None, force_reply=None,
selective=None, *, syntax=None):
"""Send a video"""
args = self._get_call_args(reply_to, extra, attach, notify)
args = self._get_call_args(reply_to, extra, attach, notify,
remove_keyboard, force_reply, selective)
args["supports_streaming"] = streaming
if duration is not None:
args["duration"] = duration
Expand All @@ -222,9 +247,11 @@ def send_video(self, path=None, file_id=None, url=None,
@_require_api
def send_video_note(self, path=None, file_id=None, duration=None,
diameter=None, thumb=None, reply_to=None, extra=None,
attach=None, notify=True):
attach=None, notify=True, remove_keyboard=None,
force_reply=None, selective=None):
"""Send a video note"""
args = self._get_call_args(reply_to, extra, attach, notify)
args = self._get_call_args(reply_to, extra, attach, notify,
remove_keyboard, force_reply, selective)
if duration is not None:
args["duration"] = duration
if diameter is not None:
Expand All @@ -246,9 +273,11 @@ def send_video_note(self, path=None, file_id=None, duration=None,
def send_gif(self, path=None, file_id=None, url=None, duration=None,
width=None, height=None, caption=None, thumb=None,
reply_to=None, extra=None, attach=None,
notify=True, syntax=None):
notify=True, remove_keyboard=None, force_reply=None,
selective=None, syntax=None):
"""Send an animation"""
args = self._get_call_args(reply_to, extra, attach, notify)
args = self._get_call_args(reply_to, extra, attach, notify,
remove_keyboard, force_reply, selective)
if duration is not None:
args["duration"] = duration
if caption is not None:
Expand Down Expand Up @@ -276,9 +305,11 @@ def send_gif(self, path=None, file_id=None, url=None, duration=None,
@_require_api
def send_file(self, path=None, file_id=None, url=None, thumb=None,
reply_to=None, extra=None, attach=None,
notify=True, caption=None, *, syntax=None):
notify=True, remove_keyboard=None, force_reply=None,
selective=None, caption=None, *, syntax=None):
"""Send a generic file"""
args = self._get_call_args(reply_to, extra, attach, notify)
args = self._get_call_args(reply_to, extra, attach, notify,
remove_keyboard, force_reply, selective)
if caption is not None:
args["caption"] = caption
if syntax is not None:
Expand All @@ -299,10 +330,12 @@ def send_file(self, path=None, file_id=None, url=None, thumb=None,

@_require_api
def send_location(self, latitude, longitude, live_period=None,
reply_to=None, extra=None, attach=None, notify=True):
reply_to=None, extra=None, attach=None, notify=True,
remove_keyboard=None, force_reply=None, selective=None):
"""Send a geographic location, set live_period to a number between 60
and 86400 if it's a live location"""
args = self._get_call_args(reply_to, extra, attach, notify)
args = self._get_call_args(reply_to, extra, attach, notify,
remove_keyboard, force_reply, selective)
args["latitude"] = latitude
args["longitude"] = longitude

Expand All @@ -317,9 +350,11 @@ def send_location(self, latitude, longitude, live_period=None,

@_require_api
def send_venue(self, latitude, longitude, title, address, foursquare=None,
reply_to=None, extra=None, attach=None, notify=True):
reply_to=None, extra=None, attach=None, notify=True,
remove_keyboard=None, force_reply=None, selective=None):
"""Send a venue"""
args = self._get_call_args(reply_to, extra, attach, notify)
args = self._get_call_args(reply_to, extra, attach, notify,
remove_keyboard, force_reply, selective)
args["latitude"] = latitude
args["longitude"] = longitude
args["title"] = title
Expand All @@ -331,7 +366,8 @@ def send_venue(self, latitude, longitude, title, address, foursquare=None,

@_require_api
def send_sticker(self, sticker=None, reply_to=None, extra=None,
attach=None, notify=True, *,
attach=None, notify=True, remove_keyboard=None,
force_reply=None, selective=None, *,
path=None, file_id=None, url=None):
"""Send a sticker"""
if sticker is not None:
Expand All @@ -343,7 +379,8 @@ def send_sticker(self, sticker=None, reply_to=None, extra=None,
"The sticker parameter", "1.0", "use the path parameter", -3
)

args = self._get_call_args(reply_to, extra, attach, notify)
args = self._get_call_args(reply_to, extra, attach, notify,
remove_keyboard, force_reply, selective)

files = dict()
args["sticker"], files["sticker"] = self._get_file_args(path,
Expand All @@ -358,9 +395,10 @@ def send_sticker(self, sticker=None, reply_to=None, extra=None,
@_require_api
def send_contact(self, phone, first_name, last_name=None,
vcard=None, *, reply_to=None,
extra=None, attach=None, notify=True):
extra=None, attach=None, notify=True, selective=None):
"""Send a contact"""
args = self._get_call_args(reply_to, extra, attach, notify)
args = self._get_call_args(reply_to, extra, attach, notify,
remove_keyboard, force_reply, selective)
args["phone_number"] = phone
args["first_name"] = first_name

Expand All @@ -373,9 +411,11 @@ def send_contact(self, phone, first_name, last_name=None,

@_require_api
def send_poll(self, question, *kargs, reply_to=None, extra=None,
attach=None, notify=True):
attach=None, notify=True, remove_keyboard=None,
force_reply=None, selective=None):
"""Send a poll"""
args = self._get_call_args(reply_to, extra, attach, notify)
args = self._get_call_args(reply_to, extra, attach, notify,
remove_keyboard, force_reply, selective)
args["question"] = question
args["options"] = json.dumps(list(kargs))

Expand Down