niho #2
5 changed files with 138 additions and 129 deletions
61
bot.py
Executable file
61
bot.py
Executable file
|
@ -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
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"nyan.nekoea.red": {
|
|
||||||
"name": "nyan.nekoea.red",
|
"name": "nyan.nekoea.red",
|
||||||
"url" : "https://nyan.nekoea.red",
|
"url" : "nyan.nekoea.red",
|
||||||
"token": "nyanyanyanyanyanyanya",
|
"token": "*****",
|
||||||
},
|
"verbosity": "debug"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
116
main.py
116
main.py
|
@ -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)
|
|
73
msk.py
Normal file
73
msk.py
Normal file
|
@ -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()
|
|
@ -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
|
|
Loading…
Reference in a new issue