2022-10-03 11:17:13 +02:00
|
|
|
import requests
|
|
|
|
import pdb
|
|
|
|
|
|
|
|
class MisskeyAPI:
|
|
|
|
|
|
|
|
name = 'Python Wrapper for Misskey API'
|
|
|
|
|
|
|
|
__DEFAULT_TIMEOUT = 300
|
|
|
|
__SCOPE_SETS = [
|
|
|
|
'read:account',
|
|
|
|
'read:blocks',
|
|
|
|
'read:drive',
|
|
|
|
'read:favorites',
|
|
|
|
'read:following',
|
|
|
|
'read:messaging',
|
|
|
|
'read:mutes',
|
|
|
|
'read:notifications',
|
|
|
|
'read:reactions',
|
|
|
|
'read:pages',
|
|
|
|
'read:page-likes',
|
|
|
|
'read:user-groups',
|
|
|
|
'read:channels',
|
|
|
|
'read:gallery',
|
|
|
|
'read:gallery-likes',
|
|
|
|
'write:account',
|
|
|
|
'write:blocks',
|
|
|
|
'write:drive',
|
|
|
|
'write:following',
|
|
|
|
'write:messaging',
|
|
|
|
'write:mutes',
|
|
|
|
'write:notes',
|
|
|
|
'write:notifications',
|
|
|
|
'write:reactions',
|
|
|
|
'write:votes',
|
|
|
|
'write:pages',
|
|
|
|
'write:page-likes',
|
|
|
|
'write:user-groups',
|
|
|
|
'write:channels',
|
|
|
|
'write:gallery',
|
|
|
|
'write:gallery-likes'
|
|
|
|
]
|
|
|
|
|
|
|
|
def __init__(
|
|
|
|
self,
|
2022-10-03 11:58:02 +02:00
|
|
|
api_base_url = None,
|
|
|
|
callback = None,
|
|
|
|
permission = __SCOPE_SETS,
|
|
|
|
name: str = None,
|
|
|
|
request_timeout = __DEFAULT_TIMEOUT,
|
|
|
|
session = None,
|
2022-10-03 11:17:13 +02:00
|
|
|
):
|
|
|
|
|
|
|
|
self.api_base_url = api_base_url
|
2022-10-03 11:58:02 +02:00
|
|
|
if self.api_base_url == None:
|
|
|
|
raise WrapperError("A Misskey instance's URL is required!")
|
|
|
|
if self.api_base_url != None:
|
|
|
|
self.callback = f'{self.api_base_url}/callback'
|
|
|
|
|
2022-10-03 11:17:13 +02:00
|
|
|
self.permission = permission
|
|
|
|
self.request_timeout = request_timeout
|
|
|
|
|
|
|
|
self.__name = name
|
|
|
|
|
|
|
|
if session:
|
|
|
|
self.session = session
|
|
|
|
else:
|
|
|
|
self.session = requests.Session()
|
|
|
|
|
|
|
|
self.__request_timeout = request_timeout
|
|
|
|
|
2022-10-06 18:48:49 +02:00
|
|
|
def i(self, i=None):
|
2022-10-03 11:17:13 +02:00
|
|
|
|
|
|
|
params = {
|
|
|
|
'i': i
|
|
|
|
}
|
|
|
|
|
|
|
|
endpoint = self.api_base_url + '/api/i'
|
2022-10-06 18:48:49 +02:00
|
|
|
|
|
|
|
response = self.__api_request(endpoint, params)
|
|
|
|
|
|
|
|
return response
|
|
|
|
|
|
|
|
def i_notifications(self, i=None, limit=None, sinceId=None, untilId=None, following=False, unreadOnly=False, markAsRead=True, includeTypes=None, excludeTypes=None):
|
|
|
|
|
|
|
|
params = {
|
|
|
|
'i': i,
|
|
|
|
'limit': limit,
|
|
|
|
'sinceId': sinceId,
|
|
|
|
'untilId': untilId,
|
|
|
|
'following': following,
|
|
|
|
'unreadOnly': unreadOnly,
|
|
|
|
'markAsRead': markAsRead,
|
|
|
|
'includeTypes': includeTypes,
|
|
|
|
'excludeTypes': excludeTypes
|
|
|
|
}
|
|
|
|
|
|
|
|
endpoint = self.api_base_url + '/api/i/notifications'
|
|
|
|
|
|
|
|
response = self.__api_request(endpoint, params)
|
|
|
|
|
|
|
|
return response
|
|
|
|
|
|
|
|
def my_apps(self, i=None, limit=None, offset=0):
|
|
|
|
|
|
|
|
params = {
|
|
|
|
'i': i,
|
|
|
|
'limit': limit,
|
|
|
|
'offset': offset
|
|
|
|
}
|
|
|
|
|
|
|
|
endpoint = self.api_base_url + '/api/my/apps'
|
|
|
|
|
2022-10-03 11:17:13 +02:00
|
|
|
response = self.__api_request(endpoint, params)
|
|
|
|
|
|
|
|
return response
|
|
|
|
|
|
|
|
def app_create(self, name=None, description=None, permission=None, callbackUrl=None, session=None):
|
|
|
|
|
|
|
|
params = {
|
|
|
|
'name': name,
|
|
|
|
'description': description,
|
|
|
|
'permission': permission,
|
|
|
|
'callbackUrl': callbackUrl
|
|
|
|
}
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
|
|
|
if session:
|
2022-10-04 19:03:07 +02:00
|
|
|
ret = session.post(self.api_base_url + '/api/app/create', json=params, timeout=self.request_timeout)
|
2022-10-03 11:17:13 +02:00
|
|
|
response = ret.json()
|
|
|
|
else:
|
|
|
|
response = requests.post(self.api_base_url + '/api/app/create', json=params, timeout=self.request_timeout)
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
|
|
raise MisskeyNetworkError("Could not complete request: %s" % e)
|
|
|
|
|
|
|
|
return response
|
|
|
|
|
|
|
|
def app_show(self, app_id=None, session=None):
|
2022-10-03 11:58:02 +02:00
|
|
|
|
2022-10-03 11:17:13 +02:00
|
|
|
params = {
|
|
|
|
'appId': app_id
|
|
|
|
}
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
|
|
|
if session:
|
2022-10-04 19:03:07 +02:00
|
|
|
ret = session.post(self.api_base_url + '/api/app/show', json=params, timeout=self.request_timeout)
|
2022-10-03 11:17:13 +02:00
|
|
|
response = ret.json()
|
|
|
|
else:
|
|
|
|
response = requests.post(self.api_base_url + '/api/app/show', json=params, timeout=self.request_timeout)
|
|
|
|
#response = response.json()
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
|
|
raise MisskeyNetworkError("Could not complete request: %s" % e)
|
|
|
|
|
|
|
|
return response
|
|
|
|
|
2022-10-06 18:48:49 +02:00
|
|
|
def auth_accept(self, token=None):
|
|
|
|
|
|
|
|
params = {
|
|
|
|
'token': token,
|
|
|
|
}
|
|
|
|
|
|
|
|
endpoint = self.api_base_url + '/api/auth/accept'
|
|
|
|
|
|
|
|
response = self.__api_request(endpoint, params)
|
|
|
|
|
|
|
|
return response
|
|
|
|
|
2022-10-03 11:17:13 +02:00
|
|
|
def auth_session_generate(self, app_secret=None):
|
|
|
|
|
|
|
|
params = {
|
|
|
|
'appSecret': app_secret,
|
|
|
|
}
|
|
|
|
|
|
|
|
endpoint = self.api_base_url + '/api/auth/session/generate'
|
|
|
|
|
|
|
|
response = self.__api_request(endpoint, params)
|
2022-10-06 18:48:49 +02:00
|
|
|
|
2022-10-03 11:17:13 +02:00
|
|
|
return response
|
|
|
|
|
|
|
|
def auth_session_show(self, token=None):
|
|
|
|
|
|
|
|
params = {
|
|
|
|
'token': token
|
|
|
|
}
|
|
|
|
|
|
|
|
endpoint = self.api_base_url + '/api/auth/session/show'
|
2022-10-06 18:48:49 +02:00
|
|
|
|
2022-10-03 11:17:13 +02:00
|
|
|
response = self.__api_request(endpoint, params)
|
|
|
|
|
|
|
|
return response
|
|
|
|
|
|
|
|
def auth_session_userkey(self, app_secret=None, token=None):
|
|
|
|
|
|
|
|
params = {
|
|
|
|
'appSecret': app_secret,
|
|
|
|
'token': token
|
|
|
|
}
|
|
|
|
|
|
|
|
endpoint = self.api_base_url + '/api/auth/session/userkey'
|
2022-10-06 18:48:49 +02:00
|
|
|
|
|
|
|
response = self.__api_request(endpoint, params)
|
|
|
|
|
|
|
|
return response
|
|
|
|
|
|
|
|
def charts_federation(self, span=None, limit=None, offset=None):
|
|
|
|
|
|
|
|
params = {
|
|
|
|
'span': span,
|
|
|
|
'limit': limit,
|
|
|
|
'offset': None
|
|
|
|
}
|
|
|
|
|
|
|
|
endpoint = self.api_base_url + '/api/charts/federation'
|
|
|
|
|
|
|
|
response = self.__api_request(endpoint, params)
|
|
|
|
|
|
|
|
return response
|
|
|
|
|
|
|
|
def ap_get(self, i=None, uri=None):
|
|
|
|
|
|
|
|
params = {
|
|
|
|
'i': i,
|
|
|
|
'uri': uri
|
|
|
|
}
|
|
|
|
|
|
|
|
endpoint = self.api_base_url + '/api/ap/get'
|
|
|
|
|
|
|
|
response = self.__api_request(endpoint, params)
|
|
|
|
|
|
|
|
return response
|
|
|
|
|
|
|
|
def ap_show(self, i=None, uri=None):
|
|
|
|
|
|
|
|
params = {
|
|
|
|
'i': i,
|
|
|
|
'uri': uri
|
|
|
|
}
|
|
|
|
|
|
|
|
endpoint = self.api_base_url + '/api/ap/show'
|
|
|
|
|
|
|
|
response = self.__api_request(endpoint, params)
|
|
|
|
|
|
|
|
return response
|
|
|
|
|
|
|
|
def federation_instances(self):
|
|
|
|
|
|
|
|
params = {
|
|
|
|
}
|
|
|
|
|
|
|
|
endpoint = self.api_base_url + '/api/federation/instances'
|
|
|
|
|
|
|
|
response = self.__api_request(endpoint, params)
|
|
|
|
|
|
|
|
return response
|
|
|
|
|
|
|
|
def federation_stats(self, limit=None):
|
|
|
|
|
|
|
|
params = {
|
|
|
|
'limit': limit
|
|
|
|
}
|
|
|
|
|
|
|
|
endpoint = self.api_base_url + '/api/federation/stats'
|
|
|
|
|
|
|
|
response = self.__api_request(endpoint, params)
|
|
|
|
|
|
|
|
return response
|
|
|
|
|
|
|
|
def endpoint(self, endpoint=None):
|
|
|
|
|
|
|
|
params = {
|
|
|
|
'endpoint': endpoint
|
|
|
|
}
|
|
|
|
|
|
|
|
endpoint = self.api_base_url + '/api/endpoint'
|
|
|
|
|
|
|
|
response = self.__api_request(endpoint, params)
|
|
|
|
|
|
|
|
return response
|
|
|
|
|
|
|
|
def endpoints(self):
|
|
|
|
|
|
|
|
params = {
|
|
|
|
}
|
|
|
|
|
|
|
|
endpoint = self.api_base_url + '/api/endpoints'
|
|
|
|
|
|
|
|
response = self.__api_request(endpoint, params)
|
|
|
|
|
|
|
|
return response
|
|
|
|
|
|
|
|
def serverinfo(self):
|
|
|
|
|
|
|
|
params = {
|
|
|
|
}
|
|
|
|
|
|
|
|
endpoint = self.api_base_url + '/api/server-info'
|
|
|
|
|
|
|
|
response = self.__api_request(endpoint, params)
|
|
|
|
|
|
|
|
return response
|
|
|
|
|
|
|
|
def stats(self):
|
|
|
|
|
|
|
|
params = {
|
|
|
|
}
|
|
|
|
|
|
|
|
endpoint = self.api_base_url + '/api/stats'
|
|
|
|
|
2022-10-03 11:17:13 +02:00
|
|
|
response = self.__api_request(endpoint, params)
|
|
|
|
|
|
|
|
return response
|
|
|
|
|
|
|
|
def notes_create(self, i=None, visibility=None, text=None, local_only=True):
|
|
|
|
|
|
|
|
params = {
|
|
|
|
'i': i,
|
|
|
|
'visibility': visibility,
|
|
|
|
'text': text,
|
|
|
|
'localOnly': local_only
|
|
|
|
}
|
|
|
|
|
|
|
|
endpoint = self.api_base_url + '/api/notes/create'
|
2022-10-06 18:48:49 +02:00
|
|
|
|
2022-10-03 11:17:13 +02:00
|
|
|
response = self.__api_request(endpoint, params)
|
|
|
|
|
2022-10-06 18:48:49 +02:00
|
|
|
return response
|
|
|
|
|
|
|
|
def notifications_create(self, i=None, body=None, header=None, icon=None):
|
|
|
|
|
|
|
|
params = {
|
|
|
|
'i': i,
|
|
|
|
'body': body,
|
|
|
|
'header': header,
|
|
|
|
'icon': icon
|
|
|
|
}
|
|
|
|
|
|
|
|
endpoint = self.api_base_url + '/api/notifications/create'
|
|
|
|
|
|
|
|
response = self.__api_request(endpoint, params)
|
|
|
|
|
|
|
|
return response
|
|
|
|
|
|
|
|
def notifications_read(self, i=None, notificationIds=[]):
|
|
|
|
|
|
|
|
params = {
|
|
|
|
'i': i,
|
|
|
|
'notificationIds': notificationIds
|
|
|
|
}
|
|
|
|
|
|
|
|
endpoint = self.api_base_url + '/api/notifications/read'
|
|
|
|
|
|
|
|
response = self.__api_request(endpoint, params)
|
|
|
|
|
|
|
|
return response
|
|
|
|
|
2022-10-03 11:17:13 +02:00
|
|
|
def users_groups_create(self, i=None, name=None):
|
|
|
|
|
|
|
|
params = {
|
|
|
|
'i': i,
|
|
|
|
'name': name
|
|
|
|
}
|
|
|
|
|
|
|
|
endpoint = self.api_base_url + '/api/users/groups/create'
|
|
|
|
|
|
|
|
response = self.__api_request(endpoint, params)
|
|
|
|
|
|
|
|
return response
|
|
|
|
|
|
|
|
def users_groups_delete(self, i=None, groupId=None):
|
|
|
|
|
|
|
|
params = {
|
|
|
|
'i': i,
|
|
|
|
'groupId': groupId
|
|
|
|
}
|
|
|
|
|
|
|
|
endpoint = self.api_base_url + '/api/users/groups/delete'
|
|
|
|
|
|
|
|
response = self.__api_request(endpoint, params)
|
|
|
|
|
|
|
|
return response
|
|
|
|
|
|
|
|
def __api_request(self, endpoint, params):
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
|
|
|
response = requests.post(url = endpoint, json=params)
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
|
|
raise MisskeyNetworkError(f"Could not complete request: {e}")
|
|
|
|
|
|
|
|
if response is None:
|
|
|
|
|
|
|
|
raise MisskeyIllegalArgumentError("Illegal request.")
|
|
|
|
|
|
|
|
if not response.ok:
|
|
|
|
|
|
|
|
try:
|
|
|
|
if isinstance(response, dict) and 'error' in response:
|
|
|
|
error_msg = response['error']
|
|
|
|
elif isinstance(response, str):
|
|
|
|
error_msg = response
|
|
|
|
else:
|
|
|
|
error_msg = None
|
|
|
|
except ValueError:
|
|
|
|
error_msg = None
|
|
|
|
|
|
|
|
if response.status_code == 404:
|
|
|
|
ex_type = MisskeyNotFoundError
|
|
|
|
if not error_msg:
|
|
|
|
error_msg = 'Endpoint not found.'
|
|
|
|
# this is for compatibility with older versions
|
|
|
|
# which raised MisskeyAPIError('Endpoint not found.')
|
|
|
|
# on any 404
|
2022-10-06 18:48:49 +02:00
|
|
|
elif response.status_code == 400:
|
|
|
|
ex_type = MisskeyBadRequestError
|
|
|
|
if not error_msg:
|
|
|
|
error_msg = response.json()['error']['info']
|
2022-10-03 11:17:13 +02:00
|
|
|
elif response.status_code == 401:
|
|
|
|
ex_type = MisskeyUnauthorizedError
|
|
|
|
elif response.status_code == 500:
|
|
|
|
ex_type = MisskeyInternalServerError
|
|
|
|
elif response.status_code == 502:
|
|
|
|
ex_type = MisskeyBadGatewayError
|
|
|
|
elif response.status_code == 503:
|
|
|
|
ex_type = MisskeyServiceUnavailableError
|
|
|
|
elif response.status_code == 504:
|
|
|
|
ex_type = MisskeyGatewayTimeoutError
|
|
|
|
elif response.status_code >= 500 and \
|
|
|
|
response.status_code <= 511:
|
|
|
|
ex_type = MisskeyServerError
|
|
|
|
else:
|
|
|
|
ex_type = MisskeyAPIError
|
|
|
|
|
|
|
|
raise ex_type(
|
|
|
|
'Misskey API returned error',
|
|
|
|
response.status_code,
|
|
|
|
response.reason,
|
|
|
|
error_msg)
|
|
|
|
|
|
|
|
else:
|
|
|
|
|
|
|
|
return response
|
|
|
|
|
|
|
|
##
|
|
|
|
# Exceptions
|
|
|
|
##
|
2022-10-03 11:58:02 +02:00
|
|
|
class WrapperError(Exception):
|
|
|
|
"""Wrapper base class"""
|
|
|
|
|
2022-10-06 18:48:49 +02:00
|
|
|
class MisskeyBadRequestError(Exception):
|
|
|
|
"""Bad Request class"""
|
|
|
|
|
2022-10-03 11:17:13 +02:00
|
|
|
class MisskeyAPIConfigError(Exception):
|
|
|
|
"""This class exception"""
|
|
|
|
|
|
|
|
class MisskeyError(Exception):
|
|
|
|
"""Base class for MisskeyAPI exceptions"""
|
|
|
|
|
|
|
|
class MisskeyIllegalArgumentError(ValueError, MisskeyError):
|
|
|
|
"""Raised when an incorrect parameter is passed to a function"""
|
|
|
|
pass
|
|
|
|
|
|
|
|
class MisskeyIOError(IOError, MisskeyError):
|
|
|
|
"""Base class for MisskeyAPI I/O errors"""
|
|
|
|
|
|
|
|
class MisskeyNetworkError(MisskeyIOError):
|
|
|
|
"""Raised when network communication with the server fails"""
|
|
|
|
pass
|
|
|
|
class MisskeyReadTimeout(MisskeyNetworkError):
|
|
|
|
"""Raised when a stream times out"""
|
|
|
|
pass
|
|
|
|
class MisskeyAPIError(MisskeyError):
|
2022-10-04 19:03:07 +02:00
|
|
|
"""Raised when the Misskey API generates a response that cannot be handled"""
|
2022-10-03 11:17:13 +02:00
|
|
|
pass
|
|
|
|
class MisskeyServerError(MisskeyAPIError):
|
|
|
|
"""Raised if the Server is malconfigured and returns a 5xx error code"""
|
|
|
|
pass
|
|
|
|
class MisskeyInternalServerError(MisskeyServerError):
|
|
|
|
"""Raised if the Server returns a 500 error"""
|
|
|
|
pass
|
|
|
|
|
|
|
|
class MisskeyBadGatewayError(MisskeyServerError):
|
|
|
|
"""Raised if the Server returns a 502 error"""
|
|
|
|
pass
|
|
|
|
|
|
|
|
class MisskeyServiceUnavailableError(MisskeyServerError):
|
|
|
|
"""Raised if the Server returns a 503 error"""
|
|
|
|
pass
|
|
|
|
|
|
|
|
class MisskeyGatewayTimeoutError(MisskeyServerError):
|
|
|
|
"""Raised if the Server returns a 504 error"""
|
|
|
|
pass
|
|
|
|
class MisskeyNotFoundError(MisskeyAPIError):
|
2022-10-04 19:03:07 +02:00
|
|
|
"""Raised when the Misskey API returns a 404 Not Found error"""
|
2022-10-03 11:17:13 +02:00
|
|
|
pass
|
|
|
|
|
|
|
|
class MisskeyUnauthorizedError(MisskeyAPIError):
|
2022-10-04 19:03:07 +02:00
|
|
|
"""Raised when the Misskey API returns a 401 Unauthorized error
|
2022-10-03 11:17:13 +02:00
|
|
|
|
|
|
|
This happens when an OAuth token is invalid or has been revoked,
|
|
|
|
or when trying to access an endpoint that can't be used without
|
|
|
|
authentication without providing credentials."""
|
|
|
|
pass
|