A lot of changes
* New config file * Incapsulated db * Update notifier
This commit is contained in:
parent
4118265d00
commit
74bc59df12
|
@ -565,6 +565,5 @@ FodyWeavers.xsd
|
|||
|
||||
# Hide internal database and it's backups
|
||||
*.db
|
||||
# Hide token file
|
||||
token
|
||||
token.devel
|
||||
# Hide config file
|
||||
config.ini
|
342
bot.py
342
bot.py
|
@ -3,90 +3,31 @@
|
|||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import telebot
|
||||
import re
|
||||
import time
|
||||
import datetime as dt
|
||||
import sys
|
||||
import signal
|
||||
import os
|
||||
import subprocess
|
||||
import pickle as p
|
||||
import configparser
|
||||
from os import path
|
||||
from sys import stderr, stdout, stdin
|
||||
from threading import Thread
|
||||
|
||||
filename = "config.ini"
|
||||
config = configparser.ConfigParser()
|
||||
config.read(filename)
|
||||
|
||||
db = {}
|
||||
|
||||
def save_db(dbfo = ".db"):
|
||||
global db
|
||||
try:
|
||||
with open(dbfo, "wb") as fo:
|
||||
p.dump(db, fo)
|
||||
except:
|
||||
stderr.write("Не удалось записать базу!")
|
||||
sys.exit(1)
|
||||
|
||||
def load_db(dbfi = ".db"):
|
||||
global db
|
||||
if not path.exists(dbfi):
|
||||
save_db()
|
||||
return
|
||||
try:
|
||||
with open(dbfi, "rb") as fi:
|
||||
db = p.load(fi)
|
||||
except:
|
||||
stderr.write("Не удалось прочитать базу, пробуем сохранить…")
|
||||
save_db()
|
||||
|
||||
def write_db(field: str, value: any, data = None):
|
||||
global db
|
||||
head = False
|
||||
if data == None:
|
||||
data = db
|
||||
head = True
|
||||
field = field.split(".")
|
||||
if len(field) == 1:
|
||||
data[field[0]] = value
|
||||
else:
|
||||
if field[0] not in data or data[field[0]] is None:
|
||||
data[field[0]] = {}
|
||||
if type(data[field[0]]) is not dict:
|
||||
raise ValueError("Поле не является группой.")
|
||||
data[field[0]] = write_db('.'.join(field[1:]), value, data[field[0]])
|
||||
save_db()
|
||||
if head:
|
||||
db = data
|
||||
return data
|
||||
|
||||
def read_db(field: str, default = None, data = None) -> any:
|
||||
global db
|
||||
if data == None:
|
||||
data = db
|
||||
field = field.split(".")
|
||||
if field[0] not in data:
|
||||
return default
|
||||
if len(field) == 1:
|
||||
return data[field[0]]
|
||||
else:
|
||||
return read_db('.'.join(field[1:]), default, data[field[0]])
|
||||
|
||||
def pop_db(field: str):
|
||||
global db
|
||||
ret = read_db(field)
|
||||
field = field.split(".")
|
||||
val = read_db('.'.join(field[:-1]))
|
||||
if type(val) is not dict:
|
||||
return
|
||||
val.pop(field[-1])
|
||||
write_db('.'.join(field[:-1]), val)
|
||||
return ret
|
||||
|
||||
|
||||
# TODO more backends
|
||||
from pickle_db import *
|
||||
load_db()
|
||||
write_db("about.version", "v1.0rc4")
|
||||
__cur_version = "v1.0rc5"
|
||||
__version = read_db("about.version", __cur_version)
|
||||
write_db("about.updatedfrom", __version)
|
||||
write_db("about.version", __cur_version)
|
||||
write_db("about.author", "electromagneticcyclone")
|
||||
write_db("about.tester", "angelbeautifull")
|
||||
write_db("about.source", "https://git.disroot.org/electromagneticcyclone/duty-board-dog")
|
||||
if (read_db("about.host") is None) and __debug__:
|
||||
stdout.write("Введите username хоста: ")
|
||||
stdout.flush()
|
||||
|
@ -95,15 +36,32 @@ if (read_db("about.host") is None) and __debug__:
|
|||
|
||||
try:
|
||||
bot = None
|
||||
filename = "token.devel" if __debug__ else "token"
|
||||
with open(filename, encoding = "utf-8") as fi:
|
||||
for i in fi:
|
||||
i = i.strip()
|
||||
pattern = re.compile("^[\\d]{10}:[\\w\\d\\-\\+\\*]{35}$")
|
||||
matches = pattern.fullmatch(i) is not None
|
||||
if matches:
|
||||
bot = telebot.TeleBot(i, parse_mode = "MarkdownV2")
|
||||
break
|
||||
if __version != __cur_version:
|
||||
import re
|
||||
if path.exists("token"):
|
||||
with open("token", encoding = "utf-8") as fi:
|
||||
for i in fi:
|
||||
i = i.strip()
|
||||
pattern = re.compile("^[\\d]{10}:[\\w\\d\\-\\+\\*]{35}$")
|
||||
matches = pattern.fullmatch(i) is not None
|
||||
if matches:
|
||||
config['tokens']['prod'] = i
|
||||
config.write(filename)
|
||||
break
|
||||
os.remove("token")
|
||||
if path.exists("token.devel"):
|
||||
with open("token.devel", encoding = "utf-8") as fi:
|
||||
for i in fi:
|
||||
i = i.strip()
|
||||
pattern = re.compile("^[\\d]{10}:[\\w\\d\\-\\+\\*]{35}$")
|
||||
matches = pattern.fullmatch(i) is not None
|
||||
if matches:
|
||||
config['tokens']['devel'] = i
|
||||
config.write(filename)
|
||||
break
|
||||
os.remove("token.devel")
|
||||
bot = telebot.TeleBot( config['tokens']['devel' if __debug__ else 'prod'],
|
||||
parse_mode = "MarkdownV2")
|
||||
if bot is None:
|
||||
stderr.write("В файле нет токена\n")
|
||||
raise Exception
|
||||
|
@ -111,6 +69,7 @@ except:
|
|||
stderr.write("Ошибка чтения файла токена\n")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def get_time(forum: int):
|
||||
return dt.datetime.now(dt.UTC) \
|
||||
+ dt.timedelta(hours = read_db(str(forum) + ".settings.timezone", 3))
|
||||
|
@ -251,6 +210,89 @@ def insert_user_in_current_order(forum: int, uid) -> bool:
|
|||
write_db(str(forum) + ".rookies.order", list(order.keys())[1:])
|
||||
return True
|
||||
|
||||
def parse_dates(forum: int, args):
|
||||
dates = []
|
||||
OK = True
|
||||
for a in args:
|
||||
if a.lower() == "сегодня":
|
||||
dates.append(get_time(forum).date())
|
||||
continue
|
||||
if a.lower() == "завтра":
|
||||
dates.append(get_time(forum).date() + dt.timedelta(days=1))
|
||||
continue
|
||||
if a.lower() == "послезавтра":
|
||||
dates.append(get_time(forum).date() + dt.timedelta(days=2))
|
||||
continue
|
||||
if a.lower() == "вчера":
|
||||
dates.append(get_time(forum).date() - dt.timedelta(days=1))
|
||||
continue
|
||||
if a.lower() == "позавчера":
|
||||
dates.append(get_time(forum).date() - dt.timedelta(days=2))
|
||||
continue
|
||||
d = a.split('.')
|
||||
a_dates = []
|
||||
if len(d) in (2, 3):
|
||||
try:
|
||||
d = list(map(int, d))
|
||||
except:
|
||||
print("Ne ok")
|
||||
OK = False
|
||||
if OK:
|
||||
cur_date = get_time(forum).date() - dt.timedelta(days=1)
|
||||
cur_year = cur_date.year
|
||||
if len(d) == 2:
|
||||
years = [cur_year, cur_year + 1]
|
||||
else:
|
||||
years = [(cur_year // 100 * 100) + d[2] if (d[2] < 100) else d[2]]
|
||||
for y in years:
|
||||
try:
|
||||
a_dates.append(dt.datetime(y, d[1], d[0]).date())
|
||||
except:
|
||||
pass
|
||||
a_dates = sorted(filter(lambda x: cur_date + dt.timedelta(days=120) > x > cur_date, a_dates))
|
||||
if len(a_dates) == 0:
|
||||
OK = False
|
||||
else:
|
||||
OK = False
|
||||
if OK:
|
||||
dates.append(a_dates[0])
|
||||
else:
|
||||
return a
|
||||
return dates
|
||||
|
||||
def mod_days(message, target, neighbour):
|
||||
forum = message.chat.id
|
||||
chat = get_chat(message)
|
||||
if chat is not None:
|
||||
if antispam(message, chat, forum):
|
||||
return
|
||||
if check_if_admin(message):
|
||||
args = message.text.split()[1:]
|
||||
if len(args) == 0:
|
||||
dates = [get_time(forum).date()]
|
||||
else:
|
||||
dates = parse_dates(forum, args)
|
||||
if type(dates) is str:
|
||||
bot.reply_to(chat, telebot.formatting.escape_markdown(dates)
|
||||
+ " — это точно дата из ближайшего будущего?")
|
||||
return
|
||||
if dates is None:
|
||||
bot.reply_to(chat, "Нечего добавлять")
|
||||
return
|
||||
t = read_db(target)
|
||||
if t is None:
|
||||
t = []
|
||||
n = read_db(neighbour)
|
||||
if n is None:
|
||||
n = []
|
||||
write_db(neighbour, list(filter(lambda x: x not in dates, n)))
|
||||
write_db(target, list(sorted(set(t + dates))))
|
||||
if read_db(str(forum) + ".settings.delete_messages"):
|
||||
bot.delete_message(forum, message.id)
|
||||
bot.reply_to(chat,
|
||||
"Добавил " + telebot.formatting.escape_markdown(
|
||||
", ".join(['.'.join(map(str, (d.day, d.month, d.year))) for d in dates])))
|
||||
|
||||
timeout = None
|
||||
caution = True
|
||||
def antispam(message, chat, forum):
|
||||
|
@ -320,9 +362,26 @@ def help(message):
|
|||
+ "/stop — остановить дежурства\n"
|
||||
+ "/begin \\[@\\] — начать сначала с определённого студента")
|
||||
|
||||
@bot.message_handler(commands=['exec'])
|
||||
def exec_bot(message):
|
||||
if __debug__:
|
||||
if __debug__:
|
||||
def pretty(d, indent=0):
|
||||
for key, value in d.items():
|
||||
print(' ' * indent + str(key))
|
||||
if isinstance(value, dict):
|
||||
pretty(value, indent+1)
|
||||
else:
|
||||
print(' ' * (indent+1) + str(value))
|
||||
|
||||
@bot.message_handler(commands=['info'])
|
||||
def info(message):
|
||||
forum = message.chat.id
|
||||
chat = get_chat(message, True)
|
||||
if chat is not None:
|
||||
bot.delete_message(forum, message.id)
|
||||
if message.from_user.username == read_db("about.host"):
|
||||
pretty(db)
|
||||
|
||||
@bot.message_handler(commands=['exec'])
|
||||
def exec_bot(message):
|
||||
forum = message.chat.id
|
||||
chat = get_chat(message, True)
|
||||
if chat is not None:
|
||||
|
@ -332,24 +391,6 @@ def exec_bot(message):
|
|||
else:
|
||||
bot.delete_message(forum, message.id)
|
||||
|
||||
def pretty(d, indent=0):
|
||||
for key, value in d.items():
|
||||
print(' ' * indent + str(key))
|
||||
if isinstance(value, dict):
|
||||
pretty(value, indent+1)
|
||||
else:
|
||||
print(' ' * (indent+1) + str(value))
|
||||
|
||||
@bot.message_handler(commands=['info'])
|
||||
def info(message):
|
||||
if __debug__:
|
||||
forum = message.chat.id
|
||||
chat = get_chat(message, True)
|
||||
if chat is not None:
|
||||
bot.delete_message(forum, message.id)
|
||||
if message.from_user.username == read_db("about.host"):
|
||||
pretty(db)
|
||||
|
||||
@bot.message_handler(commands=['fuck', 'fuck_you', 'fuck-you', 'fuckyou'])
|
||||
def rude(message):
|
||||
forum = message.chat.id
|
||||
|
@ -762,89 +803,6 @@ def calendar(message):
|
|||
+ "*Рабочие:*\n" + telebot.formatting.escape_markdown(
|
||||
"\n".join(['.'.join(map(str, (d.day, d.month, d.year))) for d in work])))
|
||||
|
||||
def parse_dates(forum: int, args):
|
||||
dates = []
|
||||
OK = True
|
||||
for a in args:
|
||||
if a.lower() == "сегодня":
|
||||
dates.append(get_time(forum).date())
|
||||
continue
|
||||
if a.lower() == "завтра":
|
||||
dates.append(get_time(forum).date() + dt.timedelta(days=1))
|
||||
continue
|
||||
if a.lower() == "послезавтра":
|
||||
dates.append(get_time(forum).date() + dt.timedelta(days=2))
|
||||
continue
|
||||
if a.lower() == "вчера":
|
||||
dates.append(get_time(forum).date() - dt.timedelta(days=1))
|
||||
continue
|
||||
if a.lower() == "позавчера":
|
||||
dates.append(get_time(forum).date() - dt.timedelta(days=2))
|
||||
continue
|
||||
d = a.split('.')
|
||||
a_dates = []
|
||||
if len(d) in (2, 3):
|
||||
try:
|
||||
d = list(map(int, d))
|
||||
except:
|
||||
print("Ne ok")
|
||||
OK = False
|
||||
if OK:
|
||||
cur_date = get_time(forum).date() - dt.timedelta(days=1)
|
||||
cur_year = cur_date.year
|
||||
if len(d) == 2:
|
||||
years = [cur_year, cur_year + 1]
|
||||
else:
|
||||
years = [(cur_year // 100 * 100) + d[2] if (d[2] < 100) else d[2]]
|
||||
for y in years:
|
||||
try:
|
||||
a_dates.append(dt.datetime(y, d[1], d[0]).date())
|
||||
except:
|
||||
pass
|
||||
a_dates = sorted(filter(lambda x: cur_date + dt.timedelta(days=120) > x > cur_date, a_dates))
|
||||
if len(a_dates) == 0:
|
||||
OK = False
|
||||
else:
|
||||
OK = False
|
||||
if OK:
|
||||
dates.append(a_dates[0])
|
||||
else:
|
||||
return a
|
||||
return dates
|
||||
|
||||
def mod_days(message, target, neighbour):
|
||||
forum = message.chat.id
|
||||
chat = get_chat(message)
|
||||
if chat is not None:
|
||||
if antispam(message, chat, forum):
|
||||
return
|
||||
if check_if_admin(message):
|
||||
args = message.text.split()[1:]
|
||||
if len(args) == 0:
|
||||
dates = [get_time(forum).date()]
|
||||
else:
|
||||
dates = parse_dates(forum, args)
|
||||
if type(dates) is str:
|
||||
bot.reply_to(chat, telebot.formatting.escape_markdown(dates)
|
||||
+ " — это точно дата из ближайшего будущего?")
|
||||
return
|
||||
if dates is None:
|
||||
bot.reply_to(chat, "Нечего добавлять")
|
||||
return
|
||||
t = read_db(target)
|
||||
if t is None:
|
||||
t = []
|
||||
n = read_db(neighbour)
|
||||
if n is None:
|
||||
n = []
|
||||
write_db(neighbour, list(filter(lambda x: x not in dates, n)))
|
||||
write_db(target, list(sorted(set(t + dates))))
|
||||
if read_db(str(forum) + ".settings.delete_messages"):
|
||||
bot.delete_message(forum, message.id)
|
||||
bot.reply_to(chat,
|
||||
"Добавил " + telebot.formatting.escape_markdown(
|
||||
", ".join(['.'.join(map(str, (d.day, d.month, d.year))) for d in dates])))
|
||||
|
||||
@bot.message_handler(commands=['skip'])
|
||||
def skip_days(message):
|
||||
forum = message.chat.id
|
||||
|
@ -1169,13 +1127,6 @@ def set_timezone(message):
|
|||
def get_hours(forum: int) -> list:
|
||||
utc_range = (8, 20)
|
||||
return utc_range
|
||||
# Obsoleted by `get_time`
|
||||
# tz = read_db(str(forum) + ".settings.timezone", 3)
|
||||
# utc_range = tuple(map(lambda x: (x - tz) % 24, utc_range))
|
||||
# if utc_range[0] > utc_range[1]:
|
||||
# return list(range(utc_range[0], 24)) + list(range(0, utc_range[1]))
|
||||
# else:
|
||||
# return list(range(utc_range[0], utc_range[1]))
|
||||
|
||||
def stack_update(forum: int, force_reset = False):
|
||||
now = get_time(forum)
|
||||
|
@ -1261,6 +1212,17 @@ def update(forum: int):
|
|||
write_db(str(forum) + ".schedule.last_notification_date", now_date)
|
||||
remind_users(forum)
|
||||
|
||||
def update_notify(forum: int):
|
||||
bot.reply_to(get_chat(forum),
|
||||
"Обновился до версии " + telebot.formatting.escape_markdown(__version))
|
||||
|
||||
|
||||
if __version != __cur_version:
|
||||
for i in db.keys():
|
||||
try:
|
||||
update_notify(int(i))
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
def process1():
|
||||
bot.infinity_polling(none_stop=True)
|
||||
|
|
|
@ -2,5 +2,6 @@
|
|||
#
|
||||
# SPDX-License-Identifier: Unlicense
|
||||
|
||||
# Enter your token in the `token` file
|
||||
0000000000:ooooooooooooooooooooooooooooooooooo
|
||||
[tokens]
|
||||
# Enter your token in the `config.ini` file
|
||||
prod = 0000000000:ooooooooooooooooooooooooooooooooooo
|
|
@ -0,0 +1,74 @@
|
|||
# SPDX-FileCopyrightText: 2023 Egor Guslyancev <electromagneticcyclone@disroot.org>
|
||||
#
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import sys
|
||||
import pickle as p
|
||||
from os import path
|
||||
from sys import stderr, stdout, stdin
|
||||
|
||||
db = {}
|
||||
|
||||
def save_db(dbfo = ".db"):
|
||||
global db
|
||||
try:
|
||||
with open(dbfo, "wb") as fo:
|
||||
p.dump(db, fo)
|
||||
except:
|
||||
stderr.write("Не удалось записать базу!")
|
||||
sys.exit(1)
|
||||
|
||||
def load_db(dbfi = ".db"):
|
||||
global db
|
||||
if not path.exists(dbfi):
|
||||
save_db()
|
||||
return
|
||||
try:
|
||||
with open(dbfi, "rb") as fi:
|
||||
db = p.load(fi)
|
||||
except:
|
||||
stderr.write("Не удалось прочитать базу, пробуем сохранить…")
|
||||
save_db()
|
||||
|
||||
def write_db(field: str, value: any, data = None):
|
||||
global db
|
||||
head = False
|
||||
if data == None:
|
||||
data = db
|
||||
head = True
|
||||
field = field.split(".")
|
||||
if len(field) == 1:
|
||||
data[field[0]] = value
|
||||
else:
|
||||
if field[0] not in data or data[field[0]] is None:
|
||||
data[field[0]] = {}
|
||||
if type(data[field[0]]) is not dict:
|
||||
raise ValueError("Поле не является группой.")
|
||||
data[field[0]] = write_db('.'.join(field[1:]), value, data[field[0]])
|
||||
save_db()
|
||||
if head:
|
||||
db = data
|
||||
return data
|
||||
|
||||
def read_db(field: str, default = None, data = None) -> any:
|
||||
global db
|
||||
if data == None:
|
||||
data = db
|
||||
field = field.split(".")
|
||||
if field[0] not in data:
|
||||
return default
|
||||
if len(field) == 1:
|
||||
return data[field[0]]
|
||||
else:
|
||||
return read_db('.'.join(field[1:]), default, data[field[0]])
|
||||
|
||||
def pop_db(field: str):
|
||||
global db
|
||||
ret = read_db(field)
|
||||
field = field.split(".")
|
||||
val = read_db('.'.join(field[:-1]))
|
||||
if type(val) is not dict:
|
||||
return
|
||||
val.pop(field[-1])
|
||||
write_db('.'.join(field[:-1]), val)
|
||||
return ret
|
Loading…
Reference in New Issue