diff --git a/bot.py b/bot.py new file mode 100755 index 0000000..379fa7a --- /dev/null +++ b/bot.py @@ -0,0 +1,61 @@ +#!/usr/bin/python3 +import logging as log +import requests +import json +import msk +import websockets +import asyncio, aiohttp + +def json_read(file): + print("Reading config file: {}".format(file)) + with open(file) as f: + config = json.load(f) + return config + +# Temp +config = json_read('config.json') + + +#try: +# config = json_read('config.json') +#except FileNotFoundError: +# print("Config file not found. Maybe we should launch setup.py?") + + +if config['verbosity'].upper() == 'DEBUG': + log.basicConfig(format="%(levelname)s: %(message)s", level=log.DEBUG) + log.info("Verbose output") + +msk = msk.Misskey(i=config['token'], url=config['url']) + +async def main(): + log.info("Connecting to {}".format(msk.url)) + async with websockets.connect(msk.ws_url) as ws: + log.info('Sucessfully connected to {}'.format(msk.url)) + log.info('Attemping to watch timeline') + p = { + 'type': 'connect', + 'body': { + 'channel': 'main' + } + } + await ws.send(json.dumps(p)) + + log.info('Listening to timeline') + while True: + data = await ws.recv() + j = json.loads(data) + if j['type'] == 'channel': + if j['body']['type'] == 'followed': + print("Follow!") + msk.following_create(j['body']['body']['id']) + if j['body']['type'] == 'mention': + if not j['body']['body']['replyId']: + msk.notes_create(renoteId=j['body']['body']['id']) + print(j) +while True: + try: + asyncio.run(main()) + except KeyboardInterrupt: + break + diff --git a/config.json.example b/config.json.example index a47ce1e..c207b1d 100644 --- a/config.json.example +++ b/config.json.example @@ -1,7 +1,7 @@ { - "nyan.nekoea.red": { "name": "nyan.nekoea.red", - "url" : "https://nyan.nekoea.red", - "token": "nyanyanyanyanyanyanya", - }, + "url" : "nyan.nekoea.red", + "token": "*****", + "verbosity": "debug" } + diff --git a/main.py b/main.py deleted file mode 100644 index 64a7637..0000000 --- a/main.py +++ /dev/null @@ -1,116 +0,0 @@ -#!/usr/bin/python3 - -import requests # Для отправки http-запросов -import json -import time - -# Функция для чтения JSON-файла (например, конфига), аргумент - адрес файла в ФС -# Возвращает словарь -def json_read(file): - # Создается локальное окружение (или как эта хуйня зовется), где присутствует дескриптор для нашего файла - with open(file) as f: - config = json.load(f) - return config - -# Функция для получения уведомлений пользователя из Misskey -# Возвращает список словарей -# 2do: includeTypes как аргумент -def get_notifications(url, i): - print("Getting notifications from", url) - # Формируем URL для доступа к конкретной функции API - req_url = url + "/api/i/notifications" - - body = { - "i": i, - # Можно включить лишь определенные уведомления, всю эту залупу следует вынести также в аргументы функции, чтоб реагировать на конкретные события, типа подписки или упоминания, по-разному - #"includeTypes": [ - # "reply", - # "mention" - # ], - # Количество уведомлений, которые вытянем - "limit": 3, - "unreadOnly": False, - } - # Отправляем запрос, в тело запроса суем словарь-JSON, которые объявили выше - r = requests.post(req_url, json=body) - - # Если все прошло збс, код HTTP 200, то отдаем получанный список уведомлений - if r.status_code == 200: - # Можешь раскомментить, глянуть, что это за черт - #print(r.json()) - return r.json() - # Иначе, если не збс, то ругаемся - else: - print("Fuck") - -# Функция, создающая пост -# 2do: посты с картинками, ... -def create_post(content, url, i, visibility="public", channel="", fileIds=[]): - print("Post to", url, ":", content) - # Аналогично, адрес нужной функции API - req_url = url + "/api/notes/create" - body = { - # Не ебу, что за noExtract*, надо полуркать, но работает и збс пока что - "noExtractMentions": True, - "noExtractHashtags": True, - "noExtractEmojis": True, - "visibility": visibility, - "text": content, - "fileIds": fileIds, - # Если поставлен канал, то пост только локальный для инстанса - "localOnly": channel != "", - "i": i - } - if channel != "": - body["channelId"] = channel - # Отправляем запрос - r = requests.post(req_url, json=body) - # Аналогично, проверка на 200, в случае успеха 1 - if r.status_code == 200: - return 0 - else: - print("Failed to post:", r.text) - return 1 - -# Пока что можешь не пытаться разобраться, я сам отчасти хз, как это работает, лол - -def file_upload(file, url, i, isSensitive=False): - print("Uploading file to", url) - req_url = url + "/api/drive/files/create" - with open(file, "rb") as f: - fileo = f.read() - body = { - "isSensitive": False, - "force": True, - "i":i - } - body = json.dumps(body) - - payload = {'json_payload': body, 'i': i } - - files = {"file": (file, fileo)} - r = requests.post(req_url, data=payload, files=files) - if r.status_code == 200: - return r.json() - else: - print("Upload failed with code", r.status_code) - print(r.text) - -def get_file_list(url, i): - req_url = url + "/api/drive/files" - r = requests.post(req_url, json={"i":i}) - print(r.json()) -### Сюда надо захуярить еще 100500 функций### - -# Собсна, содержательная часть программы начинается тут - -# Читаем конфиг, получаем словарь -config = json_read('config.json') - -#notif_list = get_notifications(config['url'], config['token']) -#print(notif_list) -#file_upload('test.jpg', config['url'], config['token']) -im_info = file_upload('test.jpg', config['url'], config['token']) - -create_post("Nya~", config['url'], config['token'], fileIds=[im_info['id']]) -#print(notif_list) diff --git a/msk.py b/msk.py new file mode 100644 index 0000000..02aef4e --- /dev/null +++ b/msk.py @@ -0,0 +1,73 @@ +import requests +import logging as log + +class Misskey: + ''' + Class for interaction with Misskey instance + Currently, this class ignores most of the Misskey specific shit like channels =3 + ''' + + def __init__(self, url: str, i: str): + self.url = f"https://{url}" + self.i = i + self.ws_url=f"wss://{url}/streaming?i={i}" + + def notes_create(self, text=None, visibility = "public", cw = None, fileIds = list(), replyId = None, renoteId = None): + req_url = self.url + "/api/notes/create" + + if renoteId: + body = { + "renoteId": renoteId, + } + else: + body = { + "text": text, + "visibility": visibility, + } + for item in [(cw, "cw"), (fileIds,"fileIds"), (replyId,"replyId")]: + if item[0]: + body[item[1]] = item[0] + + body['i'] = self.i + + log.info("Trying to post note to {}".format(self.url)) + r = requests.post(req_url, json=body) + print(body) + if r.status_code == 200: + log.info("Successfully posted") + else: + log.error("Posting failed with {} error!".format(r.status_code)) + + return r.json() + + def following_create(self, userId: str): + req_url = self.url + "/api/following/create" + r = requests.post(req_url, json={"i":self.i, "userId": userId}) + if r.status_code == 200: + res = r.json() + log.info("Successfully followed user {}@{}[{}]".format(res['username'],res['host'] ,userId)) + else: + log.error("Failed to follow user [{}] with {} error!".format(userId, r.status_code)) + + return r.json() + + def drive_files_create(self, file, isSensitive=False): + req_url = self.url + "/api/drive/files/create" + + with open(file, "rb") as f: + files = {"file": (file, f)} + body = { + "isSensitive": isSensitive, + "i":self.i + } + payload = {'json_payload': body, 'i': self.i } + + r = requests.post(req_url, data=payload, files=files) + + if r.status_code == 200: + res = r.json() + log.info("Successfully uploaded file {}[{}]".format(res['name'], res['id'])) + else: + log.error("Failed to upload file {} with {} error!".format(file, r.status_code)) + + return r.json() diff --git a/sources.md b/sources.md deleted file mode 100644 index f648ea6..0000000 --- a/sources.md +++ /dev/null @@ -1,9 +0,0 @@ -## API -https://misskey.io/api-doc - -## Примеры ботов -https://github.com/yupix/Mi.py - -https://github.com/TennousuAthena/RSSToMisskey - -https://github.com/theskanthunt42/misskeyInstanceCheckBot