Skip to content

Commit

Permalink
Updated to 4.1.0; Fixed bug in commands; Improved backends's public A…
Browse files Browse the repository at this point in the history
…PI (#48)

* Updated to 4.1.0; Fixed bug in commands; Improved backends's public API

* Made 'resolve_screen_name' public

* Changed commend in stream.py
  • Loading branch information
michaelkryukov authored Jan 15, 2020
1 parent adf92b5 commit 8c3ac1d
Show file tree
Hide file tree
Showing 11 changed files with 214 additions and 68 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@

> Changes to public API is marked as `^`
- v4.1.0
- Fixed case mistake in CommandsRouter
- Added better API for backends
- Added example for background actions

- v4.0.0
- ^ Dramatically changed API
- Fixed unknown memory leak
Expand Down
4 changes: 2 additions & 2 deletions example/plugins/quest.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
pl = Plugin("Quest")


@pl.on_commands(["start"], user_state="")
@pl.on_commands(["quest"], user_state="")
async def _(msg, ctx):
await ctx.set_state(user_state="quest:1")
await ctx.reply("Choose: left or right")
Expand Down Expand Up @@ -52,7 +52,7 @@ async def _(msg, ctx):
await ctx.reply("Bye")


@pl.on_any_message(user_state="quest:end")
@pl.on_any_unprocessed_message(user_state="quest:end")
async def _(msg, ctx):
await ctx.reply("Write '.OK'")

Expand Down
41 changes: 41 additions & 0 deletions example/plugins/stream.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import asyncio
from kutana import Plugin, Attachment, get_path


# This example shows how to access existing backend instance and perform
# some actions outside of handlers.


plugin = Plugin(name="Stream", description="Send images to subscribers")


subscribers = []


async def bg_loop(vk):
while True:
with open(get_path(__file__, "assets/pizza.png"), "rb") as fh:
temp_a = Attachment.new(fh.read(), "pizza.png")

a = await vk.upload_attachment(temp_a, peer_id=None)

for sub in subscribers:
await vk.send_message(sub, "", a)

await asyncio.sleep(5)

@plugin.on_start()
async def _(app):
vk = app.get_backends()[0]
asyncio.ensure_future(bg_loop(vk))

@plugin.on_commands(["stream sub"])
async def _(msg, ctx):
subscribers.append(msg.receiver_id)
await ctx.reply("OK")


@plugin.on_commands(["stream unsub"])
async def _(msg, ctx):
subscribers.remove(msg.receiver_id)
await ctx.reply("OK")
33 changes: 26 additions & 7 deletions kutana/backends/telegram.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ def __init__(
self.api_url = f"https://api.telegram.org/bot{token}/{{}}"
self.file_url = f"https://api.telegram.org/file/bot{token}/{{}}"

async def request(self, method, kwargs={}):
async def _request(self, method, kwargs={}):
if not self.session:
self.session = aiohttp.ClientSession()

Expand All @@ -55,7 +55,7 @@ async def request(self, method, kwargs={}):
return res

async def _request_file(self, file_id):
file = await self.request("getFile", {"file_id": file_id})
file = await self._request("getFile", {"file_id": file_id})

url = self.file_url.format(file["file_path"])

Expand Down Expand Up @@ -158,7 +158,7 @@ def _make_update(self, raw_update):

async def perform_updates_request(self, submit_update):
try:
response = await self.request(
response = await self._request(
"getUpdates", {"timeout": 25, "offset": self.offset}
)
except (json.JSONDecodeError, aiohttp.ClientError):
Expand All @@ -183,7 +183,7 @@ async def perform_send(self, target_id, message, attachments, kwargs):

async with self.api_messages_lock:
if message:
result.append(await self.request("sendMessage", {
result.append(await self._request("sendMessage", {
"chat_id": chat_id,
"text": message,
**kwargs,
Expand Down Expand Up @@ -215,7 +215,7 @@ async def perform_send(self, target_id, message, attachments, kwargs):
raise ValueError("Can't upload attachment '{atype}'")

result.append(
await self.request(
await self._request(
"send" + atype.capitalize(),
{"chat_id": chat_id, atype: acontent}
)
Expand All @@ -226,10 +226,10 @@ async def perform_send(self, target_id, message, attachments, kwargs):
return result

async def perform_api_call(self, method, kwargs):
return await self.request(method, kwargs)
return await self._request(method, kwargs)

async def on_start(self, app):
me = await self.request("getMe")
me = await self._request("getMe")

name = me["first_name"]
if me.get("last_name"):
Expand All @@ -243,6 +243,25 @@ async def on_start(self, app):

self.api_messages_lock = asyncio.Lock(loop=app.get_loop())

async def send_message(self, target_id, message, attachments=(), **kwargs):
"""
Send message to specified `target_id` with text `message` and
attachments `attachments`.
This method will forward all excessive keyword arguments to
sending method.
"""

return await self.perform_send(target_id, message, attachments, kwargs)

async def request(self, method, **kwargs):
"""
Call specified method from Telegram api with specified
kwargs and return response's data.
"""

return await self._request(method, kwargs)

async def on_shutdown(self, app):
if self._is_session_local:
await self.session.close()
78 changes: 49 additions & 29 deletions kutana/backends/vkontakte/backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,17 +134,7 @@ async def _execute_loop_perform_execute(self, code, requests):
else:
req.set_result(res)

async def request(self, method, kwargs, timeout=None):
"""
Call specified method from VKontakte api with specified
kwargs and return response's data.
This method respects limits.
This method raises RequestException if response
contains error.
"""

async def _request(self, method, kwargs, timeout=None):
req = VKRequest(method, kwargs)

self.requests_queue.append(req)
Expand All @@ -162,11 +152,11 @@ async def update_longpoll_data(self):

self.longpoll_data = longpoll.copy()

async def _resolve_screen_name(self, screen_name):
async def resolve_screen_name(self, screen_name):
if screen_name in NAIVE_CACHE:
return NAIVE_CACHE[screen_name]

result = await self.request(
result = await self._request(
"utils.resolveScreenName",
{"screen_name": screen_name}
)
Expand All @@ -188,7 +178,7 @@ async def _update_group_data(self):
self.group_screen_name = groups[0]["screen_name"]

def prepare_context(self, ctx):
ctx.resolve_screen_name = self._resolve_screen_name
ctx.resolve_screen_name = self.resolve_screen_name

def _make_getter(self, url):
async def getter():
Expand Down Expand Up @@ -301,7 +291,14 @@ async def _upload_file_to_vk(self, upload_url, data):
async with self.session.post(upload_url, data=data) as resp:
return await resp.json(content_type=None)

async def _upload_attachment(self, attachment, peer_id=None):
async def upload_attachment(self, attachment, peer_id=None):
"""
Upload specified attachment to VKontakte with specified peer_id and
return newly uploaded attachment.
This method doesn't change passed attachments.
"""

attachment_type = attachment.type

if attachment_type == "voice":
Expand All @@ -315,12 +312,12 @@ async def _upload_attachment(self, attachment, peer_id=None):

if attachment_type == "doc":
if peer_id and doctype != "graffiti":
upload_data = await self.request(
upload_data = await self._request(
"docs.getMessagesUploadServer",
{"peer_id": peer_id, "type": doctype},
)
else:
upload_data = await self.request(
upload_data = await self._request(
"docs.getWallUploadServer",
{"group_id": self.group_id, "type": doctype},
)
Expand All @@ -334,14 +331,14 @@ async def _upload_attachment(self, attachment, peer_id=None):
upload_data["upload_url"], data
)

attachment = await self.request(
attachment = await self._request(
"docs.save", upload_result
)

return self._make_attachment(attachment)

if attachment_type == "image":
upload_data = await self.request(
upload_data = await self._request(
"photos.getMessagesUploadServer", {"peer_id": peer_id}
)

Expand All @@ -355,14 +352,14 @@ async def _upload_attachment(self, attachment, peer_id=None):
)

try:
attachments = await self.request(
attachments = await self._request(
"photos.saveMessagesPhoto", upload_result
)
except RequestException as e:
if not peer_id or not e.error or e.error["error_code"] != 1:
raise

return await self._upload_attachment(attachment, peer_id=None)
return await self.upload_attachment(attachment, peer_id=None)

return self._make_attachment({
"type": "photo",
Expand Down Expand Up @@ -428,10 +425,8 @@ async def perform_updates_request(self, submit_update):

async def perform_send(self, target_id, message, attachments, kwargs):
# Form proper arguments
true_kwargs = kwargs.copy()

true_kwargs["message"] = message
true_kwargs["peer_id"] = target_id
true_kwargs = {"message": message, "peer_id": target_id}
true_kwargs.update(kwargs)

if "random_id" not in kwargs:
true_kwargs["random_id"] = int(random() * 4294967296) - 2147483648
Expand All @@ -449,7 +444,7 @@ async def perform_send(self, target_id, message, attachments, kwargs):
continue

if not a.uploaded:
a = await self._upload_attachment(a, peer_id=target_id)
a = await self.upload_attachment(a, peer_id=target_id)

if not a.id:
raise ValueError("Attachment has no ID")
Expand All @@ -466,12 +461,13 @@ async def perform_send(self, target_id, message, attachments, kwargs):
true_attachments += ","

# Add attachments to arguments
true_kwargs["attachment"] = true_attachments[:-1]
if true_attachments[:-1]:
true_kwargs["attachment"] = true_attachments[:-1]

return await self.request("messages.send", true_kwargs)
return await self._request("messages.send", true_kwargs)

async def perform_api_call(self, method, kwargs):
return await self.request(method, kwargs)
return await self._request(method, kwargs)

async def on_start(self, app):
if not self.session:
Expand Down Expand Up @@ -523,6 +519,30 @@ async def on_start(self, app):
loop=loop
)

async def send_message(self, target_id, message, attachments=(), **kwargs):
"""
Send message to specified `target_id` with text `message` and
attachments `attachments`.
This method will forward all excessive keyword arguments to
sending method.
"""

return await self.perform_send(target_id, message, attachments, kwargs)

async def request(self, method, _timeout=None, **kwargs):
"""
Call specified method from VKontakte api with specified
kwargs and return response's data.
This method respects limits.
This method raises RequestException if response
contains error.
"""

return await self._request(method, kwargs, _timeout)

async def on_shutdown(self, app):
if self._is_session_local:
await self.session.close()
3 changes: 3 additions & 0 deletions kutana/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,9 @@ def on_commands(
incoming update is message, starts with prefix and one of
provided commands.
If case_insensitive is True, commands will be processed with ignored
case.
Context is automatically populated with following values:
- prefix
Expand Down
7 changes: 5 additions & 2 deletions kutana/routers.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,12 @@ def _populate_cache(self, ctx):
prefix="|".join(re.escape(p) for p in prefixes),
command="|".join(re.escape(c) for c in commands),
),
flags=re.I,
flags=re.IGNORECASE,
)

def add_handler(self, handler, key):
return super().add_handler(handler, key.lower())

def _get_keys(self, update, ctx):
if update.type != UpdateType.MSG:
return ()
Expand All @@ -40,7 +43,7 @@ def _get_keys(self, update, ctx):
ctx.body = (match.group(3) or "").strip()
ctx.match = match

return (ctx.command,)
return (ctx.command.lower(),)


class AttachmentsRouter(MapRouter):
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import setuptools


VERSION = "4.0.0"
VERSION = "4.1.0"


with open("README.md", "r") as fh:
Expand Down
Loading

0 comments on commit 8c3ac1d

Please sign in to comment.