diff --git a/CHANGELOG.md b/CHANGELOG.md index 26e55c5..4541805 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,9 @@ +**1.1** +- +- Replaced `Message` with `MessageContext`. Now API and database should will accessed by context. +- + + **1.0** - ---- Initial release \ No newline at end of file diff --git a/README.md b/README.md index b524124..165de30 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ **Kuro Core** - -Требуется Python 3.6-3.11 +Требуется Python 3.6-3.12 Ниже возможно работает. diff --git a/kurocore/__init__.py b/kurocore/__init__.py index ed22c57..d9f2f22 100644 --- a/kurocore/__init__.py +++ b/kurocore/__init__.py @@ -1,8 +1,5 @@ from .config import Config from .bot import Bot from .logger import BotLogger, RequestLogger, ClientLogger - -from main.plugins import Plugin, MethodWithPriority - -from main.event import ChatEvent, Event -from main.message import Message, MessageArgs +from .database import Database +from .main import Plugin, MethodWithPriority, ChatEvent, Event, MessageContext, Message, Attachment diff --git a/kurocore/bot.py b/kurocore/bot.py index d86201e..312c811 100644 --- a/kurocore/bot.py +++ b/kurocore/bot.py @@ -1,24 +1,27 @@ import sys -from asyncio import WindowsSelectorEventLoopPolicy, set_event_loop_policy +from asyncio import set_event_loop_policy from platform import system from .logger import BotLogger, RequestLogger, ClientLogger from .main.handler import Handler +from kurocore.database import Database class Bot: __slots__ = ('config', 'is_running', 'middlewares', 'tasks') - def __init__(self, config): + def __init__(self, config, load_db=False): BotLogger(config) RequestLogger(config) ClientLogger(config) self.config = config - # Database(self.config) + if load_db: + Database(self.config) self.is_running = False self.middlewares = [] self.tasks = [] if sys.version_info >= (3, 8) and sys.platform.lower().startswith("win"): + from asyncio import WindowsSelectorEventLoopPolicy set_event_loop_policy(WindowsSelectorEventLoopPolicy()) def add_plugin(self, plugin): @@ -55,5 +58,5 @@ class Bot: handler.run() except KeyboardInterrupt: handler.shutdown() - # if Database.db and not Database.db.is_closed(): - # Database.db.close() + if Database.db and not Database.db.is_closed(): + Database.db.close() diff --git a/kurocore/utils/database/database.py b/kurocore/database.py similarity index 100% rename from kurocore/utils/database/database.py rename to kurocore/database.py diff --git a/kurocore/main/__init__.py b/kurocore/main/__init__.py index e69de29..63d12e6 100644 --- a/kurocore/main/__init__.py +++ b/kurocore/main/__init__.py @@ -0,0 +1,3 @@ +from .plugins import Plugin, MethodWithPriority +from .message import Message, MessageContext, Attachment +from .event import Event, ChatEvent diff --git a/kurocore/main/event.py b/kurocore/main/event.py index 76a2a3b..ac3ac64 100644 --- a/kurocore/main/event.py +++ b/kurocore/main/event.py @@ -1,11 +1,10 @@ -from kurocore.utils.vk_utils import generate_random_id +from kurocore.vk.utils import generate_random_id class ChatEvent: __slots__ = ('session', 'api', 'raw', 'type', 'member_id', 'text', 'photo') - def __init__(self, session, api, raw: dict): - self.session = session + def __init__(self, api, raw: dict): self.api = api self.raw: dict = raw @@ -18,8 +17,7 @@ class ChatEvent: class Event: __slots__ = ('session', 'api', 'raw', 'type') - def __init__(self, session, api, raw: dict): - self.session = session + def __init__(self, api, raw: dict): self.api = api self.raw: dict = raw['object'] @@ -35,7 +33,7 @@ class Event: if text: data.update({'message': text}) if attachments: - if type(attachments) == str: + if type(attachments) is str: data.update({'attachment': attachments}) else: data.update({'attachment': ','.join(attachments)}) diff --git a/kurocore/main/handler.py b/kurocore/main/handler.py index 6be3f61..5c43f12 100644 --- a/kurocore/main/handler.py +++ b/kurocore/main/handler.py @@ -4,11 +4,11 @@ import traceback from aiohttp.web import run_app from sentry_sdk import init as sentry_init, capture_exception, set_user +from kurocore.logger import BotLogger +from kurocore.vk import VK, VKLongPoll, VkBotEventType from .event import ChatEvent, Event -from kurocore.utils.database.database import Database -from kurocore.utils.vk.longpoll import VKLongPoll, VkBotEventType -from kurocore.utils.vk.vk import VK -from ..logger import BotLogger +from .message import MessageContext, Message +from kurocore.database import Database class Handler: @@ -145,7 +145,10 @@ class Handler: for before_process in p.before_process_methods: before_process.call() - await p._process_command(command, msg, args) + ctx = MessageContext(self.api, msg, None) + if Database.db: + ctx.db = Database + await p._process_command(command, ctx, args) return except Exception as e: if self.config.debug: @@ -175,9 +178,9 @@ class Handler: if not await self.check_payload(msg): await self.check_command(msg) - # if db: - # db.close() - # BotLogger.log.debug('Connection closed!') + if db: + db.close() + BotLogger.log.debug('Connection closed!') async def check_event(self, event: (ChatEvent, Event), msg): event_type = event.type @@ -220,23 +223,22 @@ class Handler: # self.shutdown() async def handle_event(self, event): - from kurocore import Message if ((event.type == VkBotEventType.MESSAGE_NEW and 'action' not in event.obj) or event.type == VkBotEventType.MESSAGE_EVENT): - msg = Message(self.session, self.api, event.obj) + msg = Message(self.api, event.obj) if msg.user_id > 0: set_user({'id': msg.user_id}) await self.check(msg) elif event.type == VkBotEventType.MESSAGE_NEW and 'action' in event.obj: - e = ChatEvent(self.session, self.api, event.obj['action']) - msg = Message(self.session, self.api, event.obj) + e = ChatEvent(self.api, event.obj['action']) + msg = Message(self.api, event.obj) if msg.user_id > 0: set_user({'id': msg.user_id}) await self.check_event(e, msg) else: - e = Event(self.session, self.api, event.raw) + e = Event(self.api, event.raw) await self.check_event(e, None) async def _run(self): diff --git a/kurocore/main/message.py b/kurocore/main/message.py index 682f691..0f3b5b9 100644 --- a/kurocore/main/message.py +++ b/kurocore/main/message.py @@ -2,9 +2,9 @@ import json from enum import Enum from typing import Union, Type, Tuple -from kurocore.utils.vk.keyboard import VkKeyboard -from kurocore.utils.vk.vk import VKApiException -from kurocore.utils.vk_utils import generate_random_id +from kurocore.database import Database +from kurocore.vk import VkKeyboard, VKApiException, VK +from kurocore.vk.utils import generate_random_id class MessageArgs(dict): @@ -137,41 +137,6 @@ class Document(VkObject): Attachment = Type[Photo], Type[Document], Type[Sticker] -def load_attachments(raw: list[dict]) -> list[Attachment]: - attachments = [] - for attachment in raw: - match attachment['type']: - case 'photo': - attachments.append(Photo(attachment)) - case 'doc': - attachments.append(Document(attachment)) - case 'sticker': - attachments.append(Sticker(attachment)) - case _: - attachments.append(VkObject(attachment)) - return attachments - - -def dump_attachments(raw_attachments: list[Attachment]) -> str: - return ','.join(map(repr, raw_attachments)) - - -class EventActions: - SHOW_SNACKBAR = 'show_snackbar' - OPEN_LINK = 'open_link' - OPEN_APP = 'open_app' - - -class MessageID: - def __init__(self, body): - if type(body) is dict: - self.message_id = body.get('message_id', 0) - self.cmid = body.get('cmid', 0) - else: - self.message_id = body - self.cmid = body - - class Message(VkObject): __slots__ = ( 'vk', 'api', 'raw', 'id', 'conversation_message_id', 'cmid', @@ -181,10 +146,9 @@ class Message(VkObject): 'meta' ) - def __init__(self, vk, api, raw): + def __init__(self, api, raw): super().__init__(raw) - self.vk = vk self.api = api if type(raw) is Message: @@ -218,8 +182,7 @@ class Message(VkObject): self.event_id: str = self.raw.get('event_id', '') self.forwarded_messages: list = self.raw.get('fwd_messages', []) - self.reply_message = Message(self.vk, self.api, self.raw['reply_message']) \ - if 'reply_message' in self.raw else None + self.reply_message = Message(self.api, self.raw['reply_message']) if 'reply_message' in self.raw else None self.meta: dict = {} @@ -325,7 +288,56 @@ class Message(VkObject): 'conversation_message_ids': [cmid] } res = await self.api.messages.getByConversationMessageId(**data) - return Message(self.vk, self.api, res['items'][0]) + return Message(self.api, res['items'][0]) def __repr__(self): return str(self.raw) + + +class MessageContext: + __slots__ = ('api', 'msg', 'db', 'args', 'meta') + + def __init__(self, api, msg, db): + self.api: VK = api + self.msg: Message = msg + self.db: Database = db + self.args = None + self.meta = {} + + def __repr__(self): + return f'' + + +def load_attachments(raw: list[dict]) -> list[Attachment]: + attachments = [] + for attachment in raw: + match attachment['type']: + case 'photo': + attachments.append(Photo(attachment)) + case 'doc': + attachments.append(Document(attachment)) + case 'sticker': + attachments.append(Sticker(attachment)) + case _: + attachments.append(VkObject(attachment)) + return attachments + + +def dump_attachments(raw_attachments: list[Attachment]) -> str: + return ','.join(map(repr, raw_attachments)) + + +class EventActions: + SHOW_SNACKBAR = 'show_snackbar' + OPEN_LINK = 'open_link' + OPEN_APP = 'open_app' + + +class MessageID: + def __init__(self, body): + if type(body) is dict: + self.message_id = body.get('message_id', 0) + self.cmid = body.get('cmid', 0) + else: + self.message_id = body + self.cmid = body diff --git a/kurocore/main/plugins.py b/kurocore/main/plugins.py index 69e4660..0f8acad 100644 --- a/kurocore/main/plugins.py +++ b/kurocore/main/plugins.py @@ -1,6 +1,7 @@ -import inspect import re +from kurocore.main.message import MessageArgs + class MethodWithPriority: __slots__ = ('priority', 'method') @@ -160,12 +161,10 @@ class Plugin: self.tasks.append(f) return f - async def _process_command(self, command: str, msg, args): - sig = inspect.signature(self.commands[command]) - if len(sig.parameters) == 1: - await self.commands[command](msg) - elif len(sig.parameters) == 2: - await self.commands[command](msg, args) + async def _process_command(self, command: str, ctx, args): + # sig = inspect.signature(self.commands[command]) + ctx.args = args + await self.commands[command](ctx) async def _process_payload(self, payload: str, msg): await self.payloads[payload](msg) @@ -180,7 +179,6 @@ class Plugin: return command in self.admin_commands async def _validate_command_args(self, command: str, cmd_args: tuple): - from kurocore import MessageArgs commands_args = self.commands_args if command not in commands_args: return True, MessageArgs({}) @@ -215,7 +213,6 @@ class Plugin: return True, MessageArgs(args) async def _validate_payload_args(self, payload: str, msg_args: dict): - from kurocore import MessageArgs payloads_args = self.payloads_args if payload not in payloads_args: return True, None diff --git a/kurocore/utils/__init__.py b/kurocore/utils/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/kurocore/utils/database/__init__.py b/kurocore/utils/database/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/kurocore/utils/database/models.py b/kurocore/utils/database/models.py deleted file mode 100644 index 20726a2..0000000 --- a/kurocore/utils/database/models.py +++ /dev/null @@ -1,173 +0,0 @@ -from peewee import Model, IntegerField, BigIntegerField, DecimalField, TextField, ForeignKeyField, DateTimeField, \ - BooleanField, CompositeKey, CharField, AutoField - -# from plugins import models -from utils.database.database import Database - - -class BaseModel(Model): - class Meta: - database = Database.db - - -class Groups(BaseModel): - id = IntegerField(null=False) - name = TextField(default='') - is_vip = BooleanField(default=False) - is_admin = BooleanField(default=False) - is_tester = BooleanField(default=False) - multiplier = DecimalField(default=1) - sale = DecimalField(default=1) - max_multigen = IntegerField(default=3) - - -class Works(BaseModel): - id = IntegerField(null=False) - name = TextField(default='') - required_lvl = IntegerField(default=1) - money_income = DecimalField(default=0) - min_exp = IntegerField(default=0) - max_exp = IntegerField(default=0) - - -class Fractions(BaseModel): - id = IntegerField() - name = TextField(default='Без названия', null=False) - owner_id = IntegerField(null=False) - money = DecimalField() - exp = IntegerField() - level = IntegerField() - - -class Auto(BaseModel): - id = IntegerField() - name = TextField() - price = DecimalField() - - class Meta: - table_name = 'shop_auto' - - -class Business(BaseModel): - id = IntegerField() - name = TextField() - price = DecimalField() - income = DecimalField() - - class Meta: - table_name = 'shop_business' - - -class Maid(BaseModel): - id = IntegerField() - name = TextField() - price = DecimalField() - income = DecimalField() - - class Meta: - table_name = 'shop_maid' - - -class Miner(BaseModel): - id = IntegerField() - name = TextField() - price = DecimalField() - income = DecimalField() - - class Meta: - table_name = 'shop_miner' - - -class Users(BaseModel): - id = IntegerField() - user_id = BigIntegerField() - - name = TextField() - greeting = TextField() - pair = IntegerField() - donat = IntegerField() - - balance = DecimalField(default=10000, max_digits=20) - invested = DecimalField(default=0, max_digits=20) - btc = DecimalField(default=0, max_digits=20, decimal_places=6) - level = IntegerField() - exp = DecimalField(default=0) - - group_id = IntegerField() - group = ForeignKeyField(Groups) - fraction = ForeignKeyField(Fractions, null=True, lazy_load=True) - - work = ForeignKeyField(Works) - work_time = DateTimeField() - - income_time = DateTimeField() - - auto = ForeignKeyField(Auto) - business = ForeignKeyField(Business) - maid = ForeignKeyField(Maid) - miner = ForeignKeyField(Miner) - - subscribed = BooleanField() - - -class Reports(BaseModel): - id = IntegerField(unique=True, null=False, primary_key=True) - text = TextField(null=False) - admin_answer = TextField(null=True) - from_id = BigIntegerField(null=False) - date = DateTimeField() - status = CharField(null=False) - attachments = TextField(null=True) - - -class FractionMember(BaseModel): - fraction = ForeignKeyField(Fractions) - user = ForeignKeyField(Users) - is_moderator = BooleanField() - is_admin = BooleanField() - - class Meta: - table_name = 'fraction_member' - primary_key = CompositeKey('fraction', 'user') - - -# WD -class WDModels(BaseModel): - model_id = IntegerField(null=False) - name = TextField(null=False) - image = TextField(null=False) - description = TextField(null=False) - - class Meta: - table_name = 'wd_models' - - -class WDUsers(BaseModel): - user_id = IntegerField(null=False) - model = ForeignKeyField(WDModels) - model_id = IntegerField(null=False, default=1) - orientation = TextField(null=False, default='PORTRAIT') - - class Meta: - table_name = 'wd_users' - primary_key = CompositeKey('user_id', 'model_id') - - -class WDLoras(BaseModel): - id = IntegerField(null=False) - name = TextField(null=False, default='NOT SET') - trigger_prompt = TextField(null=False, default='') - prompt_mixin = TextField(null=False, default='') - - class Meta: - table_name = 'wd_loras' - primary_key = CompositeKey('id', 'trigger_prompt') - - -class WDPrompts(BaseModel): - id = AutoField(null=False) - user_id = IntegerField(null=False) - prompt = TextField(null=False) - - class Meta: - table_name = 'wd_prompts' diff --git a/kurocore/utils/utils.py b/kurocore/utils/utils.py deleted file mode 100644 index c7e1262..0000000 --- a/kurocore/utils/utils.py +++ /dev/null @@ -1,42 +0,0 @@ -import re - - -def chunk_array(array, chunk_number) -> list: - if len(array) <= chunk_number: - return [array] - out, tmp, index = [], [], 0 - for el in array: - tmp.append(el) - index += 1 - if index == chunk_number: - out.append(tmp) - tmp = [] - index = 0 - out.append(tmp) - return out - - -def parse_attachments(attachments) -> tuple: - out = [] - for attach in attachments: - t = attach['type'] - if t == 'wall': - out.append(f'{t}{attach[t]["from_id"]}_{attach[t]["id"]}') - else: - out.append(f'{t}{attach[t]["owner_id"]}_{attach[t]["id"]}') - return tuple(out) - - -def parse_mention(mention: (str, None)) -> int: - if not mention: - return 0 - reg = r'\[id(\d+)\|.+\]' - match = re.match(reg, mention) - if not match: - return 0 - return int(match.group(1)) - - -async def get_user_sex(user_id, api): - query = await api.users.get(user_ids=user_id, fields='sex') - return query[0]['sex'] if len(query) > 0 else 2 diff --git a/kurocore/utils/vk/__init__.py b/kurocore/utils/vk/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/kurocore/utils/vk/utils.py b/kurocore/utils/vk/utils.py deleted file mode 100644 index fe5381f..0000000 --- a/kurocore/utils/vk/utils.py +++ /dev/null @@ -1,6 +0,0 @@ -from json import JSONEncoder - - -class EnumEncoder(JSONEncoder): - def default(self, obj): - return obj.value diff --git a/kurocore/vk/__init__.py b/kurocore/vk/__init__.py new file mode 100644 index 0000000..25bb12f --- /dev/null +++ b/kurocore/vk/__init__.py @@ -0,0 +1,3 @@ +from .vk import VK, VKApiException +from .longpoll import VKLongPoll, VkBotEventType +from .keyboard import VkKeyboard diff --git a/kurocore/utils/vk/keyboard.py b/kurocore/vk/keyboard.py similarity index 100% rename from kurocore/utils/vk/keyboard.py rename to kurocore/vk/keyboard.py diff --git a/kurocore/utils/vk/longpoll.py b/kurocore/vk/longpoll.py similarity index 96% rename from kurocore/utils/vk/longpoll.py rename to kurocore/vk/longpoll.py index 0495cb3..b7caa71 100644 --- a/kurocore/utils/vk/longpoll.py +++ b/kurocore/vk/longpoll.py @@ -1,9 +1,8 @@ from enum import Enum -from logging import Logger from kurocore import Config -from kurocore.logger import RequestLogger -from kurocore.utils.vk.vk import VK +from kurocore.logger import RequestLogger, BotLogger +from kurocore.vk.vk import VK class DotDict(dict): @@ -54,7 +53,7 @@ class VKLongPoll: async def listen(self): while True: async for event in self.check(): - Logger.log.debug(f'new event: {event.raw}') + BotLogger.log.debug(f'new event: {event.raw}') yield event diff --git a/kurocore/utils/vk/upload.py b/kurocore/vk/upload.py similarity index 92% rename from kurocore/utils/vk/upload.py rename to kurocore/vk/upload.py index e6735ec..92313c6 100644 --- a/kurocore/utils/vk/upload.py +++ b/kurocore/vk/upload.py @@ -2,9 +2,9 @@ import json from aiohttp import FormData -from handler.message import load_attachments -from kurocore import LoggingClientSession -from utils.vk.vk import VK, VkApiMethod +from kurocore.logger import LoggingClientSession +from kurocore.main.message import load_attachments +from kurocore.vk.vk import VK, VkApiMethod class JsonParser: diff --git a/kurocore/utils/vk_utils.py b/kurocore/vk/utils.py similarity index 57% rename from kurocore/utils/vk_utils.py rename to kurocore/vk/utils.py index 202d7cc..180d272 100644 --- a/kurocore/utils/vk_utils.py +++ b/kurocore/vk/utils.py @@ -1,9 +1,15 @@ +from json import JSONEncoder import io from random import randint from aiohttp import ClientSession +class EnumEncoder(JSONEncoder): + def default(self, obj): + return obj.value + + def get_self_id(api): return api.groups.getById()[0]['id'] @@ -12,11 +18,22 @@ def generate_random_id(): return randint(-9 ** 99, 9 ** 99) -async def get_user_name(id, api, name_case='nom'): - user = (await api.users.get(user_ids=id, name_case=name_case))[0] +async def get_user_name(user_id, api, name_case='nom'): + user = (await api.users.get(user_ids=user_id, name_case=name_case))[0] return f'{user["first_name"]} {user["last_name"]}' +def parse_attachments(attachments) -> tuple: + out = [] + for attach in attachments: + t = attach['type'] + if t == 'wall': + out.append(f'{t}{attach[t]["from_id"]}_{attach[t]["id"]}') + else: + out.append(f'{t}{attach[t]["owner_id"]}_{attach[t]["id"]}') + return tuple(out) + + async def reupload_attachments(attachments, upload): new_attachments = [] for a in attachments: diff --git a/kurocore/utils/vk/vk.py b/kurocore/vk/vk.py similarity index 100% rename from kurocore/utils/vk/vk.py rename to kurocore/vk/vk.py diff --git a/pyproject.toml b/pyproject.toml index 997f55e..930ce4e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,10 +1,10 @@ [project] name = "kurocore" -version = "1.0" +version = "1.1" authors = [ - { name="ScuroNeko", email="author@example.com" }, + { name="ScuroNeko" }, ] -description = "A small example package" +description = "A basic library for VK bot" readme = "README.md" requires-python = ">=3.6" classifiers = [ diff --git a/requirements.txt b/requirements.txt index bf0a36a..77f0154 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,9 +1,4 @@ asyncio aiohttp -aiopg -psycopg2-binary peewee -peewee-async -sentry-sdk - -py-aiovk \ No newline at end of file +peewee-async \ No newline at end of file