This commit is contained in:
ScuroNeko 2024-03-24 04:05:54 +03:00
parent 26f511535a
commit c5e0c91828
Signed by: ScuroNeko
GPG Key ID: A75AA155FE93EBC0
24 changed files with 132 additions and 322 deletions

View File

@ -1,4 +1,9 @@
**1.1**
-
- Replaced `Message` with `MessageContext`. Now API and database should will accessed by context.
-
**1.0**
-
---
Initial release

View File

@ -1,6 +1,6 @@
**Kuro Core**
-
Требуется Python 3.6-3.11
Требуется Python 3.6-3.12
Ниже возможно работает.

View File

@ -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

View File

@ -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()

View File

@ -0,0 +1,3 @@
from .plugins import Plugin, MethodWithPriority
from .message import Message, MessageContext, Attachment
from .event import Event, ChatEvent

View File

@ -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)})

View File

@ -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):

View File

@ -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'<MessageContext{self.msg}({self.args})>'
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

View File

@ -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

View File

@ -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'

View File

@ -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

View File

@ -1,6 +0,0 @@
from json import JSONEncoder
class EnumEncoder(JSONEncoder):
def default(self, obj):
return obj.value

3
kurocore/vk/__init__.py Normal file
View File

@ -0,0 +1,3 @@
from .vk import VK, VKApiException
from .longpoll import VKLongPoll, VkBotEventType
from .keyboard import VkKeyboard

View File

@ -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

View File

@ -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:

View File

@ -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:

View File

@ -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 = [

View File

@ -1,9 +1,4 @@
asyncio
aiohttp
aiopg
psycopg2-binary
peewee
peewee-async
sentry-sdk
py-aiovk