Archived
1
0
Fork 0
This repository has been archived on 2024-04-26. You can view files and clone it, but cannot push or open issues or pull requests.
akari-bot/bot.py

205 lines
6 KiB
Python
Raw Normal View History

2022-01-21 13:10:24 +00:00
import datetime
2021-08-07 12:55:07 +00:00
import logging
2022-01-21 13:10:24 +00:00
import re
2021-08-07 12:55:07 +00:00
import subprocess
2021-08-23 12:44:31 +00:00
import traceback
2022-01-21 13:10:24 +00:00
from itertools import islice
2021-08-07 12:55:07 +00:00
from queue import Queue, Empty
from threading import Thread
2021-08-25 12:05:57 +00:00
from time import sleep
2021-08-21 07:45:21 +00:00
2022-01-20 12:13:03 +00:00
import psutil
2022-01-20 13:44:54 +00:00
import os
import shutil
from config import Config
from database import BotDBUtil
2021-08-23 12:44:31 +00:00
2021-08-21 07:45:21 +00:00
encode = 'UTF-8'
2020-09-19 10:35:13 +00:00
2021-08-21 15:58:07 +00:00
2022-01-13 15:50:17 +00:00
class RestartBot(Exception):
pass
2022-01-21 13:10:24 +00:00
class TimedPatternFileHandler(logging.FileHandler):
"""File handler that uses the current time fo the log filename,
by formating the current datetime, according to filename_pattern, using
the strftime function.
If backup_count is non-zero, then older filenames that match the base
filename are deleted to only leave the backup_count most recent copies,
whenever opening a new log file with a different name.
"""
def __init__(self, filename_pattern, mode, backup_count):
self.filename_pattern = os.path.abspath(filename_pattern)
self.backup_count = backup_count
self.filename = datetime.datetime.now().strftime(self.filename_pattern)
delete = islice(self._matching_files(), self.backup_count, None)
for entry in delete:
# print(entry)
os.remove(entry.path)
super().__init__(filename=self.filename, mode=mode)
@property
def filename(self):
"""Generate the 'current' filename to open"""
# use the start of *this* interval, not the next
return datetime.datetime.now().strftime(self.filename_pattern)
@filename.setter
def filename(self, _):
pass
def _matching_files(self):
"""Generate DirEntry entries that match the filename pattern.
The files are ordered by their last modification time, most recent
files first.
"""
matches = []
basename = os.path.basename(self.filename_pattern)
pattern = re.compile(re.sub('%[a-zA-z]', '.*', basename))
for entry in os.scandir(os.path.dirname(self.filename_pattern)):
if not entry.is_file():
continue
entry_basename = os.path.basename(entry.path)
if re.match(pattern, entry_basename):
matches.append(entry)
matches.sort(key=lambda e: e.stat().st_mtime, reverse=True)
return iter(matches)
2022-01-13 15:50:17 +00:00
def get_pid(name):
return [p.pid for p in psutil.process_iter() if p.name().find(name) != -1]
2021-08-07 12:55:07 +00:00
def enqueue_output(out, queue):
for line in iter(out.readline, b''):
queue.put(line)
out.close()
2021-02-01 15:13:11 +00:00
2021-08-21 15:58:07 +00:00
2022-01-20 13:44:54 +00:00
def init_bot():
cache_path = os.path.abspath('./cache/')
if os.path.exists(cache_path):
shutil.rmtree(cache_path)
os.mkdir(cache_path)
else:
os.mkdir(cache_path)
base_superuser = Config('base_superuser')
if base_superuser:
BotDBUtil.SenderInfo(base_superuser).edit('isSuperUser', True)
2021-03-05 16:19:06 +00:00
2021-08-20 16:32:46 +00:00
2022-01-13 15:50:17 +00:00
pidlst = []
def run_bot():
pid_cache = os.path.abspath('.pid_last')
if os.path.exists(pid_cache):
with open(pid_cache, 'r') as f:
pid_last = f.read().split('\n')
running_pids = get_pid('python')
print(running_pids)
print(pid_last)
for pid in pid_last:
if int(pid) in running_pids:
try:
os.kill(int(pid), 9)
2022-01-13 16:01:29 +00:00
except (PermissionError, ProcessLookupError):
2022-01-13 15:50:17 +00:00
pass
os.remove(pid_cache)
2022-01-20 14:03:07 +00:00
envs = os.environ.copy()
envs['PYTHONIOENCODING'] = 'UTF-8'
envs['PYTHONPATH'] = os.path.abspath('.')
2022-01-13 15:50:17 +00:00
botdir = './core/bots/'
lst = os.listdir(botdir)
runlst = []
for x in lst:
bot = os.path.abspath(f'{botdir}{x}/bot.py')
if os.path.exists(bot):
2022-01-13 15:58:19 +00:00
p = subprocess.Popen(['python', bot], shell=False, stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
2022-01-20 14:03:07 +00:00
cwd=os.path.abspath('.'), env=envs)
2022-01-13 15:50:17 +00:00
runlst.append(p)
pidlst.append(p.pid)
with open(pid_cache, 'w') as c:
c.write('\n'.join(str(p) for p in pidlst))
q = Queue()
threads = []
for p in runlst:
threads.append(Thread(target=enqueue_output, args=(p.stdout, q)))
for t in threads:
t.daemon = True
t.start()
2021-08-25 12:05:57 +00:00
2021-12-25 07:58:24 +00:00
while True:
2021-08-07 12:55:07 +00:00
try:
line = q.get_nowait()
2021-12-25 07:58:24 +00:00
except Empty:
pass
else:
try:
logging.info(line[:-1].decode(encode))
except Exception:
print(line)
2022-01-20 13:31:50 +00:00
logging.error(traceback.format_exc())
2021-08-07 12:55:07 +00:00
2021-12-25 07:58:24 +00:00
# break when all processes are done.
if all(p.poll() is not None for p in runlst):
break
2021-08-25 12:05:57 +00:00
2022-01-13 15:50:17 +00:00
for p in runlst:
if p.poll() == 233:
logging.warning(f'{p.pid} exited with code 233, restart all bots.')
2022-01-13 15:50:17 +00:00
pidlst.remove(p.pid)
raise RestartBot
sleep(0.001)
2022-01-13 15:50:17 +00:00
2022-01-20 13:44:54 +00:00
if __name__ == '__main__':
init_bot()
2022-02-06 12:06:23 +00:00
log_format = logging.Formatter(fmt="%(msg)s")
log_handler = logging.StreamHandler()
log_handler.setFormatter(log_format)
2022-01-21 13:10:24 +00:00
logger = logging.getLogger()
2022-02-06 12:06:23 +00:00
for h in logger.handlers:
logger.removeHandler(h)
logger.addHandler(log_handler)
2022-02-14 13:56:57 +00:00
logpath = os.path.abspath('./logs')
2022-01-21 13:10:24 +00:00
if not os.path.exists(logpath):
os.mkdir(logpath)
filehandler = TimedPatternFileHandler('{}_%Y-%m-%d.log'.format(logpath + '/log'), mode='a', backup_count=5)
logger.addHandler(filehandler)
2022-01-20 13:44:54 +00:00
try:
while True:
try:
run_bot()
logging.fatal('All bots exited unexpectedly, please check the output')
break
except RestartBot:
for x in pidlst:
try:
os.kill(x, 9)
except (PermissionError, ProcessLookupError):
pass
pidlst.clear()
sleep(5)
continue
except KeyboardInterrupt:
for x in pidlst:
try:
os.kill(x, 9)
except (PermissionError, ProcessLookupError):
pass