Skip to content
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
50 changes: 50 additions & 0 deletions bot.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import vk
import random


class Bot:
def __init__(self):
self.session = vk.Session(access_token=open("key.txt").read().strip())
self.api = vk.API(self.session)
self.vk_version = "5.103"

def get_user(self, user_id):
return self.api.users.get(
v=self.vk_version,
user_id=user_id
)

def send(self, user, message, *args, **kwargs):
return self.api.messages.send(
v=self.vk_version,
peer_id=user.id,
random_id=random.random(),
message=message,
*args, **kwargs
)

def get_conversations(self, filter="unread"):
return self.api.messages.getConversations(
v=self.vk_version,
filter=filter
)

def get_history(self, peer_id, start_message, count):
return self.api.messages.getHistory(
v=self.vk_version,
peer_id=peer_id,
start_message_id=start_message,
count=count,
)

def get_messages(self, message_ids):
return self.api.messages.getById(
v=self.vk_version,
message_ids=message_ids
)['items']

def mark_as_read(self, peer_id):
return self.api.messages.markAsRead(
v=self.vk_version,
peer_id=peer_id
)
159 changes: 84 additions & 75 deletions group.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,8 @@ def to_json(o):
return o.to_json()


class Group:
class BaseGroup:
def __init__(self):
self.session = vk.Session(access_token=open("key.txt").read().strip())
self.api = vk.API(self.session)
self.vk_version = "5.103"
self.users = []
self.users_by_name = dict()
self.users_by_id = dict()
Expand All @@ -38,15 +35,12 @@ def load(self, stream):
res = json.load(stream)

for u in res["users"]:
u = User(
self.add_user(User(
self,
u["name"],
u["id"],
u["balance"]
)
self.users.append(u)
self.users_by_name[u.name] = u
self.users_by_id[u.id] = u
))

for t in res["transactions"]:
t = Transaction(
Expand All @@ -57,6 +51,11 @@ def load(self, stream):
t["message_id"]
)
self.transactions_by_id[t.message_id] = t

def add_user(self, user: User):
self.users.append(user)
self.users_by_name[user.name] = user
self.users_by_id[user.id] = user

def save(self, stream):
json.dump(
Expand All @@ -67,21 +66,76 @@ def save(self, stream):
default=to_json
)

def save_data(self):
with open("data_tmp.json", "w", encoding="utf-8") as file:
self.save(file)
os.remove("data.json")
os.rename("data_tmp.json", "data.json")

def create_transaction(self, purchaser, products, names, message_id):
consumers = []

for name in names:
if name in self.users_by_name:
consumers.append(self.users_by_name[name])
elif name in ("все", "всем"):
consumers = self.users
break
else:
return # TODO: add error raise and handle in subclass

confirmers = consumers.copy()
if purchaser not in consumers:
confirmers.append(purchaser)
transaction = Transaction(purchaser, products, consumers, confirmers, message_id)
self.transactions_by_id[message_id] = transaction
self.save_data()
return transaction

def decline_transaction(self, user, transaction_id):
transaction = self.transactions_by_id[transaction_id]
del self.transactions_by_id[transaction_id]
self.save_data()
return transaction

def confirm_transaction(self, user, transaction_id):
transaction = self.transactions_by_id[transaction_id]
if user not in transaction.confirmers:
return # TODO: add error raise and handle in subclass
transaction.confirmers.remove(user)

if not transaction.confirmers:
self.make_transaction(transaction)
del self.transactions_by_id[transaction.message_id]
self.save_data()
return transaction

def make_transaction(self, transaction):
val_per_usr = transaction.value / len(transaction.consumers)
transaction.purchaser.balance += val_per_usr * len(transaction.consumers)
for user in transaction.consumers:
user.balance -= val_per_usr
self.save_data()


class Group(BaseGroup):
def __init__(self, bot):
super().__init__()
self.bot = bot

def save_data(self):
try:
self.save(open("data_tmp.json", "w", encoding='utf-8'))
super().save_data()
except Exception as e:
self.send_admins(e)
else:
os.remove("data.json")
os.rename("data_tmp.json", "data.json")

def add_user(self, peer_id):
usr = self.api.users.get(
v=self.vk_version,
user_id=peer_id
)
user = User(self, usr[0]["first_name"], peer_id)
def add_user(self, user):
if isinstance(user, User):
return super().add_user(user)

peer_id = user
user = self.bot.get_user(peer_id)
user = User(self, user[0]["first_name"], peer_id)

self.users.append(user)
self.users_by_name[user.name] = user
Expand Down Expand Up @@ -109,8 +163,7 @@ def run(self):
self.save_data()

def send(self, user, message, *args, **kwargs):
return self.api.messages.send(
v=self.vk_version,
return self.bot.send(
peer_id=user.id,
random_id=random.random(),
message=message,
Expand All @@ -121,25 +174,10 @@ def send_admins(self, message):
for id in self.admins_id:
self.send(self.users_by_id[id], message)

def make_transaction(self, transaction):
val_per_usr = transaction.value / len(transaction.consumers)
transaction.purchaser.balance += val_per_usr * len(transaction.consumers)
for user in transaction.consumers:
user.balance -= val_per_usr
self.save_data()

def confirm_transaction(self, user, transaction_id):
transaction = self.transactions_by_id[transaction_id]
if user not in transaction.confirmers:
self.send(
user,
"Покупка уже была подтверждена\n"
)
return
transaction.confirmers.remove(user)
transaction = super().confirm_transaction(user, transaction_id)

if not transaction.confirmers:
self.make_transaction(transaction)
self.send(
transaction.purchaser,
"Покупка подтверждена\n",
Expand All @@ -152,13 +190,9 @@ def confirm_transaction(self, user, transaction_id):
for user in self.users_by_name.values()
)
)
del self.transactions_by_id[transaction.message_id]
self.save_data()

def decline_transaction(self, user, transaction_id):
transaction = self.transactions_by_id[transaction_id]
del self.transactions_by_id[transaction_id]
self.save_data()
transaction = super().decline_transaction(user, transaction_id)
for consumer in transaction.consumers:
self.send(
consumer,
Expand All @@ -173,25 +207,8 @@ def decline_transaction(self, user, transaction_id):
)

def create_transaction(self, purchaser, products, names, message_id):
consumers = []

for name in names:
if name in self.users_by_name:
consumers.append(self.users_by_name[name])
elif name in ("все", "всем"):
consumers = self.users
break
else:
purchaser.send("Пользователя с именем \"%s\" нет в вашей группе" % name)
return

confirmers = consumers.copy()
if purchaser not in consumers:
confirmers.append(purchaser)
transaction = Transaction(purchaser, products, consumers, confirmers, message_id)
self.transactions_by_id[message_id] = transaction
self.save_data()
for user in confirmers:
transaction = super().create_transaction(purchaser, products, names, message_id)
for user in transaction.confirmers:
self.send(
user,
message="Подтверждаете покупку? Всего {}₽ (да/нет)".format(transaction.value),
Expand All @@ -200,10 +217,7 @@ def create_transaction(self, purchaser, products, names, message_id):

def idle(self):
try:
result = self.api.messages.getConversations(
v=self.vk_version,
filter="unread"
)
result = self.bot.get_conversations("unread")
except requests.exceptions.ConnectionError:
time.sleep(300)
except requests.exceptions.ReadTimeout:
Expand All @@ -215,9 +229,8 @@ def idle(self):
conv = conv["conversation"]
peer_id = conv["peer"]["id"]

history = self.api.messages.getHistory(
v=self.vk_version,
peer_id=peer_id,
history = self.bot.get_history(
peer_id,
start_message_id=max(conv["in_read"] + 1, conv["out_read"] + 1),
count=conv["unread_count"] + 1,
)
Expand All @@ -232,13 +245,9 @@ def idle(self):
if 'reply_message' not in messages[i]:
messages[i]['reply_message'] = messages[i - 1]
else:
messages[i]['reply_message'] = self.api.messages.getById(
v=self.vk_version,
message_ids=[messages[i]['reply_message']['id']]
messages[i]['reply_message'] = self.bot.get_messages(
[messages[i]['reply_message']['id']]
)['items'][0]
user.answer(messages[i])

self.api.messages.markAsRead(
v=self.vk_version,
peer_id=peer_id
)
self.bot.mark_as_read(peer_id)
6 changes: 5 additions & 1 deletion main.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
from group import Group
from bot import Bot
import sys
import os


def lowpriority():
""" Set the priority of the process to below-normal."""
try:
Expand All @@ -20,13 +22,15 @@ def lowpriority():
else:
os.nice(19)


def main():
if len(sys.argv) != 2:
print("Error. Write data directory")
return
lowpriority()
os.chdir(sys.argv[1])
app = Group()
bot = Bot()
app = Group(bot)
app.run()


Expand Down
68 changes: 68 additions & 0 deletions route.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
from functools import partial
from typing import List, Callable, Tuple, Optional
from itertools import chain
from functools import wraps


class Route:
def __init__(self, func: Callable, name: str, description: str = None, help: str = None, aliases: List[str] = None):
if description is None:
description = ""
if help is None:
help = ""
if aliases is None:
aliases = []

self.name = name
self.description = description
self.help = help
self.aliases = aliases
self.func = func

def __set_name__(self, owner, _):
# register route in user
owner.routes.append(self)

def __get__(self, obj, cls):
# AAAAAAAAAA
# bound method emulation
class BoundRoute:
def __init__(self, obj, route: Route):
object.__setattr__(self, 'obj', obj)
object.__setattr__(self, 'route', route)

def __call__(self, *args, **kwargs):
return self.func(object.__getattribute__(self, 'obj'), *args, **kwargs)

def __getattr__(self, attr):
return getattr(object.__getattribute__(self, 'route'), attr)

def __setattr__(self, attr, value):
return setattr(object.__getattribute__(self, 'route'), attr, value)

return BoundRoute(obj, self)

def __repr__(self):
return "Route {{ name = {name}, description = {description}, help = {help}, aliases = {aliases} }}".format(self.__dict__)


@wraps(Route)
def route(*args, **kwargs):
return partial(Route, *args, **kwargs)


class Router:
routes: List[Route] = []

def find_route(self, line: str) -> Optional[Tuple[Route, str]]:
'''
Checks if line starts with any of known routes
Returns: route and rest of the line if found, None otherwise
'''

low = line.lower()
for route in self.routes:
for variant in chain([route.name], route.aliases):
if low.startswith(variant):
return route, line[len(variant):].strip()

Loading