Пример #1
0
from PIL import Image
from database import *
import base64
import io
import aiohttp
from kutana import Plugin
from utils.static_text import need_vip
from utils import priviligeshelper

plugin = Plugin(name="Жмыхает фоточки",
                cmds=[{
                    'command': 'жмых [фото]',
                    'desc': 'делает упоротую фотку',
                    'vip': True
                }])
ACCESS_TOKEN = "my-api-token he he boi"


@plugin.on_startswith_text("жмых")
async def on_message(message, attachments, env):
    privs = await priviligeshelper.getUserPriviliges(env, message.from_id)
    if not (privs & priviligeshelper.USER_VIP > 0):
        return await env.reply(need_vip)

    photo = False
    for x in attachments:
        if x.type == "photo":
            photo = True
            break

    if not photo:
Пример #2
0
from kutana import Plugin
import peewee_async, peewee, datetime, time, asyncio
from kutana.database import *
import traceback
from kutana.vksm import *

plugin = Plugin(category="Статистика")
plugin.desc = {'юзер стат': ['(имя)', 'считaет вcю cтaтистику пользователя - количествo соoбщeний, символов, матов и его рейтинг в топе'],
'стата чата': [0, 'аналогично команде юзер стат, только для чата'],
'топ бесед': [0, 'пoказывaет топ бeсeд (по cообщениям), в которых пpисутствует бoт'],
'актив': [0, 'пoказываeт ктo и кoгдa поcлeдний pаз что-тo пиcaл в чатe']}
plugin.category_desc = 'Считaeт всю статистику бeceды - кoличеcтво cоoбщений, символов, cтикeров, прикреплeний, голocoвыx cooбщeний, иcпoльзованных кoманд - кaк для пoльзoвaтеля, тaк и для всeгo чата, и пoкaзывает эту информацию'
class chat_stats_vlad(BaseModel):
    chat_id = peewee.IntegerField(default=0)
    messages = peewee.BigIntegerField(default=0)
    clear_messages = peewee.BigIntegerField(default=0)
    clear_symbols = peewee.BigIntegerField(default=0)
    symbols = peewee.BigIntegerField(default=0)
    voice_messages = peewee.BigIntegerField(default=0)
    resend_messages = peewee.BigIntegerField(default=0)
    photos = peewee.BigIntegerField(default=0)
    videos = peewee.BigIntegerField(default=0)
    audios = peewee.BigIntegerField(default=0)
    docs = peewee.BigIntegerField(default=0)
    posts = peewee.BigIntegerField(default=0)
    stickers = peewee.BigIntegerField(default=0)
    mentios = peewee.BigIntegerField(default=0)
    links = peewee.BigIntegerField(default=0)
    leaved = peewee.BigIntegerField(default=0)
    messages_with_sw = peewee.BigIntegerField(default=0)
    last_user_id = peewee.IntegerField(default=0)
Пример #3
0
from kutana import Plugin
import aiohttp, json
from kutana.vksm import *

plugin = Plugin()
async def get_rate(to):
    async with aiohttp.ClientSession() as sess:
        async with sess.get(f"https://www.cbr-xml-daily.ru/daily_json.js") as resp:
            try:
                data = await resp.text()
                res = json.loads(data)
                return res['Valute'][to]['Value']
            except (KeyError, IndexError):
                raise ValueError('Курса данной валюты не найдено')

async def get_btc():
    async with aiohttp.ClientSession() as sess:
        async with sess.get(f"https://blockchain.info/ru/ticker") as resp:
            res = await resp.json()
            return toFixed(res['RUB']['sell'], 2), toFixed(res['USD']['sell'], 2)

def toFixed(f: float, n=0):
    a, b = str(f).split('.')
    return '{}.{}{}'.format(a, b[:n], '0'*(n-len(b)))

@plugin.on_startswith_text("курс")
async def course(message, attachments, env):
    data = []
    for cur in ('USD', 'EUR'):
        data.append(await get_rate(cur))
    usd, eur = data
Пример #4
0
from kutana import Plugin
from peewee_async import Manager

from bot.db import User
from bot.roles import owner_global_role, UserRoles
from bot.utils import extract_users

plugin = Plugin('Add developers[develop]')


async def make_dev(mgr: Manager, users):
    added = []
    async with mgr.atomic():
        for user_id in users:
            user, created = await mgr.get_or_create(User, id=user_id)
            if user.role < UserRoles.DEVELOPER.value:
                user.role = UserRoles.DEVELOPER.value
                await mgr.update(user)
                added.append(user_id)
    return added


async def del_dev(mgr: Manager, users):
    deleted = []
    for user_id in users:
        user, created = await mgr.get_or_create(User, id=user_id)
        if user.role > UserRoles.USER.value:
            user.role = UserRoles.USER.value
            await mgr.update(user)
            deleted.append(user_id)
    return deleted
Пример #5
0
from kutana import Plugin

import random

plugin = Plugin(name="Рандом",
                cmds=[{
                    'command': 'рандом <от> <до>',
                    'desc': 'случайное число в диапазоне ОТ ДО'
                }])


@plugin.on_startswith_text("рандом")
async def on_message(message, attachments, env):
    try:
        args = [int(arg) for arg in env['args']]
    except ValueError:
        return await env.reply("Один из аргументов - не число")

    # Если у нас два аргумента - это диапазон
    if len(args) == 2:
        start, end = args
        # Конечное значение больше начального
        if abs(end - start) > 0:
            num = random.randint(start, end)
        # Конечное число меньше начального
        else:
            num = random.randint(end, start)

    # Если один аргумент, то диапазон будет - (1, число)
    elif len(args) == 1:
        num = random.randint(1, args[0])
Пример #6
0
from kutana import Plugin
import random

plugin = Plugin(name="Попытаемся сделать музочку?",
                cmds=[{
                    'command': 'randomsong <N>',
                    'desc': 'Получает <N> количество рандомных песен'
                }])


def get_n_songs(count):
    audios = []
    i = 0
    while (i < count):
        server_id = random.randint(1, 291461)
        audio_id = random.randint(456239017, 456239700)
        audios.append(f"audio2000{server_id}_{audio_id},")
        i += 1

    return ''.join(audios)


@plugin.on_startswith_text("randomsong")
async def random_song(message, attachments, env):
    if not env['args']:
        return await env.reply("<N> количество - от 1 до 10")

    if not env['args'][0].isdigit() or int(env['args'][0]) > 10:
        return await env.reply("<N> количество - от 1 до 10")

    att_songs = get_n_songs(int(env['args'][0]))
Пример #7
0
from kutana import Plugin
from kutana.structures import objdict
from utils import ddict, edict, parse_user_name
import asyncio
from operator import is_not
from functools import partial

plugin = Plugin(name="Статистика чата", cmds=[{'command': 'chatstats', 'desc': 'отображение статистики текущего чата.'},
                                              {'command': 'raiting', 'desc': 'отображение топа из чатов!'}], order=30)

async def getname(env, user_id):
    users = {}
    for u in env.eenv.meta_data.users:
        if 'name' in u:
            continue
        users[u["id"]] = u['first_name'] + " " + u["last_name"]
    resuser = users.get(user_id)
    if not resuser:
        if user_id < 0:
            return user_id
        us = await env.request('users.get', user_ids=user_id, fields="sex,screen_name,nickname")

        if not us:
            return user_id
        name1 = us.response[0]["first_name"] + " " + us[0]["last_name"]
        return name1
    return resuser

async def getrawname(env, user_id):
    us = await env.request('users.get', user_ids=user_id, fields="sex,screen_name,nickname")
Пример #8
0
def test_happy_path(mock_post):
    group_change_settings_update = {
        "type": "group_change_settings",
        "object": {
            "changes": {
                "screen_name": {
                    "old_value": "",
                    "new_value": "sdffff23f23"
                },
                "title": {
                    "old_value": "Спасибо",
                    "new_value": "Спасибо 2"
                }
            }
        }
    }

    raw_updates = [
        {},
        {
            "type": "present"
        },
        group_change_settings_update,
        MESSAGES["not_message"],
        MESSAGES["message"],
        MESSAGES[".echo"],
        MESSAGES[".echo chat"],
        MESSAGES[".echo wa"],
    ]

    answers = []
    updated_longpoll = []

    def acquire_updates(content_type=None):
        if not raw_updates:
            return {"updates": [], "ts": "100"}
        if updated_longpoll == [1]:
            return {"failed": 3}
        return {"updates": [raw_updates.pop(0)], "ts": "0"}

    mock_post.return_value.__aenter__.return_value.json = CoroutineMock(
        side_effect=acquire_updates)

    class _VkontakteLongpoll(VkontakteLongpoll):
        async def _get_response(self, method, kwargs={}):
            if method == "groups.setLongPollSettings":
                return {"response": 1}

            if method == "groups.getById":
                return {
                    "response": [
                        {
                            "id": 1,
                            "name": "group",
                            "screen_name": "grp"
                        },
                    ],
                }

            if method == "groups.getLongPollServer":
                updated_longpoll.append(1)
                return {
                    "response": {
                        "server": "s",
                        "key": "k",
                        "ts": "1",
                    },
                }

            if method == "execute":
                answers.extend(kwargs["code"].split("API.")[1:])
                return {
                    "response": [1] * kwargs["code"].count("API."),
                }

            print(method, kwargs)

    app = Kutana()

    vkontakte = _VkontakteLongpoll(token="token")

    app.add_backend(vkontakte)

    echo_plugin = Plugin("echo")

    @echo_plugin.on_commands(["echo", "ec"])
    async def _(message, ctx):
        assert ctx.resolve_screen_name
        assert ctx.reply

        await ctx.reply(message.text, attachments=message.attachments)

    app.add_plugin(echo_plugin)

    app.get_loop().call_later(
        delay=vkontakte.api_request_pause * 4,
        callback=app.stop,
    )

    app.run()

    assert vkontakte.group_name == "Спасибо 2"
    assert vkontakte.group_screen_name == "sdffff23f23"

    assert len(updated_longpoll) == 2

    answers.sort()
    assert len(answers) == 3
    assert '{"message": ".echo chat [michaelkrukov|Михаил]",' in answers[0]
    assert '{"message": ".echo wa",' in answers[1]
    assert 'attachment": ""' not in answers[1]
    assert '{"message": ".echo",' in answers[2]
Пример #9
0
from kutana import Plugin, HandlerResponse

# Plugin's global variable
statistics = {}

# Sub-plugin to collect statistics
plugin = Plugin(
    name="Statistics",
    description="Show word count in current dialog",
)


@plugin.on_messages(priority=5)
async def __(msg, ctx):
    words_count = statistics.get(msg.sender_id, 0)

    new_words_count = words_count + len(msg.text.split())

    statistics[msg.sender_id] = new_words_count

    return HandlerResponse.SKIPPED


@plugin.on_commands(["statistics"])
async def __(msg, ctx):
    await ctx.reply("You wrote: {} words.".format(
        statistics.get(msg.sender_id, 0)))
Пример #10
0
from kutana import Plugin, MemoryStorage

plugin = Plugin("Give image")

# Constants
WAITING_STATE = "give_image:waiting"


# Handlers
@plugin.on_commands(["give_image", "giveimage"])
@plugin.expect_sender(state="")
async def __(msg, ctx):
    if msg.attachments:
        await ctx.reply("You image:", attachments=msg.attachments[0])
        return

    await ctx.sender.update({"state": WAITING_STATE})
    await ctx.reply("Please, send you image")


@plugin.on_attachments(["image"])
@plugin.expect_sender(state=WAITING_STATE)
async def __(msg, ctx):
    await ctx.sender.update({"state": ""})
    await ctx.reply("You image:", attachments=msg.attachments[0])


@plugin.on_unprocessed_messages()
@plugin.expect_sender(state=WAITING_STATE)
async def __(msg, ctx):
    await ctx.sender.update({"state": ""})
Пример #11
0
from kutana import Plugin
from database import *

from PIL import Image
from PIL import ImageDraw
from PIL import ImageFont
from PIL import ImageOps

from utils import priviligeshelper
from utils.static_text import need_vip

import io
import aiohttp

plugin = Plugin(name="Этот пользователь!", cmds=[{'command': 'этот пользователь <текст>', 'desc': 'делает карточку "этот пользователь"', 'vip': True}])

PATH = "plugins/this_user/"
textd = "Этот пользователь "
sizes = (668, 189)  # In-card space sizes
limit = 27  # Limit an symbols in one line
limit_lines = 4  # Limit lines!
fd = ImageFont.truetype(PATH + "font.ttf", 30)
color_font = (242, 174, 127)


def chuncked_text(l, n):
    for i in range(0, len(l), n + 2):
        yield l[i:i + n + 2]


@plugin.on_startswith_text("этот пользователь")
Пример #12
0
from kutana import Plugin, HandlerResponse


# Plugins for demonstrating how contexts works


plugin1 = Plugin(name="Contexts [Provider]")


@plugin1.on_commands(["contexts"], priority=5)
async def _(msg, ctx):
    # Do or produce something and save it to context.
    ctx.var = "val"

    # Let other plugins work
    return HandlerResponse.SKIPPED


plugin2 = Plugin(name="Contexts")


@plugin2.on_commands(["contexts"])
async def _(msg, ctx):
    await ctx.reply('ctx.var == "{}"'.format(
        ctx.var  # Use value saved in context by other plugin
    ))


plugins = [plugin1, plugin2]  # Order matters
Пример #13
0
from kutana import Plugin, Attachment, HandlerResponse, get_path
import os
import re

plugin = Plugin(name="Music", description="Send music")


@plugin.on_commands(["m"])
async def _(msg, ctx):
    files = files_find("/home/hord/Музыка/", ctx.body)

    if (len(files) == 0):
        await ctx.reply("Нет такой музыки...")

    else:
        filepath = files.pop()

        with open(get_path(__file__, filepath), "rb") as fh:
            audio_message = Attachment.new(fh.read(),
                                           os.path.basename(filepath), "voice")

        await ctx.reply(os.path.basename(filepath), attachments=audio_message)


def files_find(catalog, f):
    find_files = []
    for root, dirs, files in os.walk(catalog):
        find_files += [
            os.path.join(root, name) for name in files
            if re.search(f, name, re.IGNORECASE)
        ]
Пример #14
0
from kutana import Plugin
from database import *
from utils import check_admin, ddict, edict, parse_user_id, plural_form, priviligeshelper
import time
import datetime

plugin = Plugin(
    name="Активы чата",
    cmds=[{
        'command':
        'актив <кол-во дней>',
        'desc':
        'Показывает активных людей в чате за определённый период времени!'
    }, {
        'command': 'кик <имя>',
        'desc': 'кикает человека с чата'
    }])


@plugin.on_startswith_text("актив")
async def active_chat(message, attachments, env):
    if not env.eenv.is_multichat or not env.eenv.meta_data:
        return await env.reply(
            "Эту команду надо использовать в беседе и бот должен быть администратором!"
        )

    data_users = await ddict(await env.eenv.dbredis.get(
        f"honoka:active_users_multichat:{message.peer_id}"))
    if not data_users or data_users and len(data_users['users']) < 1:
        return await env.reply("Пока у меня нету данных для тебя.")
Пример #15
0
from kutana import Plugin
import romajitable
import transliterate

plugin = Plugin(name="Кана",
                cmds=[{
                    'command': 'jp <текст>',
                    'desc': 'переводит текст на япу(фан)'
                }])


@plugin.on_startswith_text("jp")
async def on_message(message, attachments, env):

    if not env['args']:
        return await env.reply("Введите текст пожалуйста")

    text = ' '.join(env['args'])
    try:
        text = transliterate.translit(text, reversed=True)
    except Exception:
        pass

    rt = romajitable.to_kana(text)
    return await env.reply("А вот и результат подъехал\n\n"
                           f"🔑🇯🇵|Хирагана: {rt.hiragana}\n"
                           f"🔑🇯🇵|Катакана: {rt.katakana}")
Пример #16
0
from kutana import Plugin

plugin = Plugin("echo")


@plugin.on_commands(["echo", "ec"])
async def _(message, ctx):
    await ctx.reply(ctx.route["args"])
Пример #17
0
                    {
                        "action": {
                            "type": "text",
                            "payload": "4",
                            "label": "Four"
                        },
                        "color": "primary",
                    },
                ]],
}

# Keyboard that will be send to VKONTAKTE is a STRING!
KEYBOARD_STRING = json.dumps(KEYBOARD_OBJECT)

# Plugins for sending keyboard.
plugin1 = Plugin(name="Keyboard", description="Keyboard for vkontakte")


@plugin1.on_text("keyboard")
async def _(message, env):
    if env.manager_type != "vkontakte":
        await env.reply("This example works only for vk.com")
        return

    await env.reply("Keyboard", keyboard=KEYBOARD_STRING)


# Plugin for intercepting messages with payload.
plugin2 = Plugin(name="_Keyboard_listener", priority=10)

Пример #18
0
from kutana import Plugin


plugin = Plugin(name="Plugins")


plugins = []


@plugin.on_startup()
async def startup(app):
    for pl in app.registered_plugins:
        if isinstance(pl, Plugin) and pl.name[:1] != "_":
            plugins.append(pl.name)


@plugin.on_text("list")
async def on_list(message, env):
    await env.reply(
        "Plugins:\n" + "; ".join(plugins)
    )
Пример #19
0
from kutana import Plugin

plugin = Plugin(name="Stop", description="Turns application off")


@plugin.on_start()
async def __(app):
    plugin.stop = app.stop


@plugin.on_commands(["stop"])
async def __(msg, ctx):
    plugin.stop()
Пример #20
0
from kutana import Plugin, Message

plugin = Plugin(name="Prefix", priority=5)  # default priority is 0

PREFIXES = (".", "/")


@plugin.on_has_text()
async def on_has_text(message, env):
    for prefix in PREFIXES:
        if message.text[:len(prefix)] == prefix:
            break

    else:
        return "DONE"

    env.parent.set_message(
        Message(message.text[len(prefix):], message.attachments,
                message.from_id, message.peer_id, message.date,
                message.raw_update))

    return "GOON"
Пример #21
0
from kutana import Plugin
import random
from random import sample
from database import *
from utils import priviligeshelper

plugin = Plugin(name="Кто? Кто в кого влюблён",
                cmds=[{
                    'command':
                    'кто <определение>',
                    'desc':
                    'кто в конференции является обладателем определения.'
                }, {
                    'command': 'кто кого',
                    'desc': 'кто кого же любит в беседе? Хмммммм'
                }, {
                    'command': 'ктогей',
                    'desc': 'поиск петушков'
                }])


@plugin.on_startswith_text("кто кого", "ктокого")
async def on_message(message, attachments, env):
    if env.eenv.is_multichat and env.eenv.meta_data:
        love1, love2 = sample(env.eenv.meta_data.users, 2)
        await env.reply(
            f"[id{love1['id']}|{love1['first_name']} {love1['last_name']}] - ❤ Любит ❤ - [id{love2['id']}|{love2['first_name']} {love2['last_name']}]"
        )
    else:
        await env.reply(
            "Эту команду можно использовать только в беседе, и при условии что у бота есть права администратора."
Пример #22
0
from kutana import Plugin

# Plugin's global variable
statistics = {}

# Sub-plugin to collect statistics
p1 = Plugin(name="_Statistics collector", priority=10)


@p1.on_has_text()
async def _(message, env):
    words_count = statistics.get(message.from_id, 0)

    new_words_count = words_count + len(message.text.split())

    statistics[message.from_id] = new_words_count

    return "GOON"


# Sub-plugin to show statistics
p2 = Plugin(name="Statistics", description="Show word count in current dialog")


@p2.on_text("statistics")
async def _(message, env):
    await env.reply("You wrote: {} words.".format(
        statistics.get(message.from_id, 0)))


# List of plugins to export
Пример #23
0
import random
from utils import parse_user_id, VKKeyboard, get_nekos_attach

plugin = Plugin(name="Отношения",
                cmds=[{
                    'command': 'отношения встречаться <id>',
                    'desc': 'начать встречаться с <id>'
                }, {
                    'command': 'отношения поцеловать',
                    'desc': 'поцеловать свою девушку/парня'
                }, {
                    'command': 'отношения обнять',
                    'desc': 'обнять свою девушку/парня'
                }, {
                    'command': 'отношения погладить',
                    'desc': 'погладить свою девушку/парня'
                }, {
                    'command': 'отношения порвать',
                    'desc': 'порвать с отношениями'
                }, {
                    'command': 'отношения заняться сексом',
                    'desc': 'ну тут я думаю вы всё поняли ;D'
                }, {
                    'command': 'отношения шлёпнуть',
                    'desc': 'шлёпнуть своего партнёра по попе ;D'
                }, {
                    'command': 'отношения инфа',
                    'desc': 'информация об отношениях'
                }])

relationtemp = {}
Пример #24
0
from kutana import Plugin
import psutil
import time
import os

plugin = Plugin(name="Metrics", description="Send some information")


@plugin.on_commands(["metrics"])
async def __(msg, ctx):
    process = psutil.Process(os.getpid())

    taken_memory = int(process.memory_info().rss / 2**20)
    taken_time = time.time() - msg.date

    await ctx.reply("mem: ~{}mib; tim: {}s".format(taken_memory, taken_time))
Пример #25
0
import pytest
from kutana import (
    Plugin,
    Message,
    Update,
    UpdateType,
    Attachment,
    HandlerResponse as hr,
)
from testing_tools import make_kutana_no_run

# --------------------------------------- QUEST
pl = Plugin("Quest")


@pl.on_commands(["start"], user_state="")
async def _(msg, ctx):
    await ctx.set_state(user_state="quest:1")
    await ctx.reply("Choose: left or right")


@pl.on_commands(["left"], user_state="quest:1")
async def _(msg, ctx):
    await ctx.set_state(user_state="quest:end")
    await ctx.reply("You have chosen: left\nWrite '.ok'")


@pl.on_commands(["right"], user_state="quest:1")
async def _(msg, ctx):
    await ctx.set_state(user_state="quest:end")
    await ctx.reply("You have chosen: right\nWrite '.ok'")
Пример #26
0
from kutana import Plugin

plugin = Plugin(name="Echo")


@plugin.on_startswith_text("echo")
async def on_echo(message, env):
    await env.reply("{}".format(env.body), attachment=message.attachments)
Пример #27
0
from utils.static_text import need_vip
from utils import VKKeyboard
import utils.logs as Logs

plugin = Plugin(name="Economy-Games",
                cmds=[{
                    'command': 'double [r|g|b] [ставка]',
                    'desc': 'ну тип рулетка'
                }, {
                    'command': 'казино [ставка]',
                    'desc': 'сыграем?'
                }, {
                    'command': 'бин [вверх/вниз] [ставка]',
                    'desc': 'бинарные опционы'
                }, {
                    'command': 'ловушка [ставка]',
                    'desc': 'попробуй достать сокровища!'
                }, {
                    'command': 'эко бонус',
                    'desc': 'получитс бонус от бота',
                    'vip': True
                }, {
                    'command': 'игра [ставка]',
                    'desc': 'просто игрулька)'
                }, {
                    'command': 'дснять',
                    'desc': 'снять мани, за то что писал!'
                }])


# 31give
# 32cazino
Пример #28
0
from kutana import Plugin, Message

plugin = Plugin(name="Система префиксов!", priority=500)


@plugin.on_has_text()
async def on_has_text(message, attachments, env):
    if not message.text.startswith(env.eenv.prefix):
        return "DONE"  # "GOON" if you want to just keep message

    env.eenv['prefixes'] = env.eenv.prefix

    env.eenv._cached_message = Message(message.text[len(env.eenv.prefix):],
                                       message.attachments, message.from_id,
                                       message.peer_id, message.raw_update)

    return "GOON"
Пример #29
0
import os
import sys
import time

from kutana import Plugin, Context

from bot.roles import developer_global_role
from bot.utils import get_users

plugin = Plugin('Manage process', '(re)start bot')


@plugin.on_start()
async def _(app):
    if len(sys.argv) == 5 and sys.argv[1] == '--restarted':
        for backend in app.get_backends():
            if backend.get_identity() == 'vkontakte':
                elapsed_time = time.time() - float(sys.argv[2])
                chat_id = int(sys.argv[3])
                user_id = int(sys.argv[4])
                user = (await get_users(backend, user_id, 'ins'))[0]
                user_name = user['first_name'] + ' ' + user['last_name']
                await backend.send_message(
                    chat_id,
                    f"Бот был перезапущен [id{user_id}|{user_name}] за {elapsed_time:.2f} сек",
                    disable_mentions=1)


@plugin.on_commands(['рестарт', 'restart'])
@developer_global_role
async def restart_command(msg, ctx: Context):
Пример #30
0
    def test_vk_full(self):
        plugin = Plugin()

        self.called = False
        self.called_on_raw = False
        self.called_on_attachment = False

        async def on_attachment(message, attachments, env):
            self.called_on_attachment = True
            return "GOON"

        plugin.on_attachment("photo")(on_attachment)

        async def on_regexp(message, attachments, env):
            # Test receiving
            self.assertEqual(env.match.group(1), "message")
            self.assertEqual(env.match.group(0), "echo message")

            self.assertEqual(message.attachments, attachments)
            self.assertEqual(len(attachments), 2)

            self.assertTrue(attachments[0].link)
            self.assertTrue(attachments[1].link)

            # Test sending
            a_image = await env.upload_photo("test/test_assets/author.png")

            a_image = await env.upload_photo("test/test_assets/author.png",
                                             peer_id=False)

            a_audio = await env.upload_doc("test/test_assets/girl.ogg",
                                           doctype="audio_message",
                                           filename="file.ogg")

            self.assertTrue(a_image.id)
            self.assertTrue(a_audio.id)

            resps = await env.reply("Спасибо.", attachment=a_image)

            self.assertTrue(resps[0].response)

            for resp in resps:
                resp = await env.request("messages.delete",
                                         message_ids=str(resp.response),
                                         delete_for_all=1)

                self.assertTrue(resp.response)

            # Test failed request
            resp = await env.request("messages.send")

            self.assertTrue(resp.error)
            self.assertTrue(resp.errors[0][1])
            self.assertEqual(resp.errors[0][0], "VK_req")
            self.assertFalse(resp.response)

            self.called = True

        plugin.on_regexp_text(r"echo (.+)")(on_regexp)

        async def on_raw(update, env):
            self.called_on_raw = True

            return "GOON"

        plugin.on_raw()(on_raw)

        self.kutana.executor.register_plugins(plugin)

        self.kutana.run()

        self.assertTrue(self.called)
        self.assertTrue(self.called_on_raw)
        self.assertTrue(self.called_on_attachment)