Akkoma and EjabberdAPI classes are now downloable from PyPi
This commit is contained in:
pare
a6a6ff3f15
commit
791ebc2cb9
S'han modificat 4 arxius amb 5 adicions i 1480 eliminacions
1106
akkoma.py
1106
akkoma.py
La diferencia del archivo ha sido suprimido porque es demasiado grande
Cargar Diff
373
ejabberdapi.py
373
ejabberdapi.py
|
@ -1,373 +0,0 @@
|
||||||
import os
|
|
||||||
import os.path
|
|
||||||
import requests
|
|
||||||
import string
|
|
||||||
import getpass
|
|
||||||
import secrets
|
|
||||||
from collections import OrderedDict
|
|
||||||
import pdb
|
|
||||||
|
|
||||||
###
|
|
||||||
# Dict helper class.
|
|
||||||
# Defined at top level so it can be pickled.
|
|
||||||
###
|
|
||||||
class AttribAccessDict(dict):
|
|
||||||
def __getattr__(self, attr):
|
|
||||||
if attr in self:
|
|
||||||
return self[attr]
|
|
||||||
else:
|
|
||||||
raise AttributeError("Attribute not found: " + str(attr))
|
|
||||||
|
|
||||||
def __setattr__(self, attr, val):
|
|
||||||
if attr in self:
|
|
||||||
raise AttributeError("Attribute-style access is read only")
|
|
||||||
super(AttribAccessDict, self).__setattr__(attr, val)
|
|
||||||
|
|
||||||
class Ejabberd:
|
|
||||||
|
|
||||||
name = 'Ejabberd API wrapper'
|
|
||||||
|
|
||||||
def __init__(self, api_base_url=None, local_vhost=None, admin_account=None, admin_pass=None):
|
|
||||||
|
|
||||||
self.__ejabberd_config_path = "secrets/ejabberd_secrets.txt"
|
|
||||||
|
|
||||||
is_setup = self.__check_setup(self)
|
|
||||||
|
|
||||||
if is_setup:
|
|
||||||
|
|
||||||
self.__api_base_url = self.__get_parameter("api_base_url", self.__ejabberd_config_path)
|
|
||||||
self.__local_vhost = self.__get_parameter("local_vhost", self.__ejabberd_config_path)
|
|
||||||
self.__admin_account = self.__get_parameter("admin_account", self.__ejabberd_config_path)
|
|
||||||
self.__admin_pass = self.__get_parameter("admin_pass", self.__ejabberd_config_path)
|
|
||||||
|
|
||||||
else:
|
|
||||||
|
|
||||||
self.__api_base_url, self.__local_vhost, self.__admin_account, self.__admin_pass = self.setup(self)
|
|
||||||
|
|
||||||
def generate_pass(self):
|
|
||||||
|
|
||||||
alphabet = string.ascii_letters + string.digits
|
|
||||||
|
|
||||||
while True:
|
|
||||||
|
|
||||||
password = ''.join(secrets.choice(alphabet) for i in range(10))
|
|
||||||
|
|
||||||
if (any(c.islower() for c in password)
|
|
||||||
and any(c.isupper() for c in password)
|
|
||||||
and sum(c.isdigit() for c in password) >= 3):
|
|
||||||
break
|
|
||||||
|
|
||||||
return password
|
|
||||||
|
|
||||||
def check_account(self, username, host):
|
|
||||||
|
|
||||||
data = {'user':username,
|
|
||||||
'host':self.__local_vhost,
|
|
||||||
}
|
|
||||||
|
|
||||||
endpoint = self.__api_base_url + '/api/check_account?'
|
|
||||||
|
|
||||||
response = self.__api_request(endpoint, data)
|
|
||||||
|
|
||||||
account_exists = True if response.json() == 0 else False
|
|
||||||
|
|
||||||
return account_exists
|
|
||||||
|
|
||||||
def register(self, username, host, user_password):
|
|
||||||
|
|
||||||
account_exists = self.check_account(username, host)
|
|
||||||
|
|
||||||
if not account_exists:
|
|
||||||
|
|
||||||
data = {'user':username,
|
|
||||||
'host':self.__local_vhost,
|
|
||||||
'password':user_password,
|
|
||||||
}
|
|
||||||
|
|
||||||
endpoint = self.__api_base_url + '/api/register?'
|
|
||||||
|
|
||||||
response = self.__api_request(endpoint, data)
|
|
||||||
|
|
||||||
is_registered = response.ok
|
|
||||||
|
|
||||||
if is_registered:
|
|
||||||
|
|
||||||
response_text = response.json()
|
|
||||||
|
|
||||||
else:
|
|
||||||
|
|
||||||
response_text = f"{response.json()['status']}: {response.json()['message']}"
|
|
||||||
|
|
||||||
else:
|
|
||||||
|
|
||||||
is_registered = False
|
|
||||||
|
|
||||||
response_text = f"el compte {username}@{host} ja existeix!"
|
|
||||||
|
|
||||||
return (is_registered, response_text)
|
|
||||||
|
|
||||||
def unregister(self, username, host):
|
|
||||||
|
|
||||||
is_unregistered = False
|
|
||||||
|
|
||||||
is_admin = False
|
|
||||||
|
|
||||||
if username+'@'+host == self.__admin_account:
|
|
||||||
|
|
||||||
is_admin = True
|
|
||||||
|
|
||||||
return (is_unregistered, is_admin)
|
|
||||||
|
|
||||||
data = {'user':username,
|
|
||||||
'host':self.__local_vhost,
|
|
||||||
}
|
|
||||||
|
|
||||||
endpoint = self.__api_base_url + '/api/unregister?'
|
|
||||||
|
|
||||||
response = self.__api_request(endpoint, data)
|
|
||||||
|
|
||||||
is_unregistered = response.ok
|
|
||||||
|
|
||||||
return (is_unregistered, is_admin)
|
|
||||||
|
|
||||||
def stats(self):
|
|
||||||
|
|
||||||
names_temp = ["registeredusers","onlineusers","onlineusersnode","uptimeseconds","processes"]
|
|
||||||
|
|
||||||
names = OrderedDict.fromkeys(names_temp).keys()
|
|
||||||
|
|
||||||
stats_dict = {}
|
|
||||||
|
|
||||||
for name in names:
|
|
||||||
|
|
||||||
data = {
|
|
||||||
"name": name
|
|
||||||
}
|
|
||||||
|
|
||||||
endpoint = self.__api_base_url + '/api/stats?'
|
|
||||||
|
|
||||||
response = self.__api_request(endpoint, data)
|
|
||||||
|
|
||||||
result = response.json()['stat']
|
|
||||||
|
|
||||||
stats_dict[name] = result
|
|
||||||
|
|
||||||
stats = self.__json_allow_dict_attrs(stats_dict)
|
|
||||||
|
|
||||||
return stats
|
|
||||||
|
|
||||||
def status(self):
|
|
||||||
|
|
||||||
data = {
|
|
||||||
}
|
|
||||||
|
|
||||||
endpoint = self.__api_base_url + '/api/status?'
|
|
||||||
|
|
||||||
response = self.__api_request(endpoint, data)
|
|
||||||
|
|
||||||
result = response.json()
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
def user_sessions_info(self, username, host):
|
|
||||||
|
|
||||||
temp_dict = {}
|
|
||||||
|
|
||||||
sessions_dict = {}
|
|
||||||
|
|
||||||
data = {'user':username,
|
|
||||||
'host':self.__local_vhost,
|
|
||||||
}
|
|
||||||
|
|
||||||
endpoint = self.__api_base_url + '/api/user_sessions_info?'
|
|
||||||
|
|
||||||
response = self.__api_request(endpoint, data)
|
|
||||||
|
|
||||||
i = 0
|
|
||||||
while i < len(response.json()):
|
|
||||||
|
|
||||||
temp_dict['connection'] = response.json()[i]['connection']
|
|
||||||
temp_dict['ip'] = response.json()[i]['ip']
|
|
||||||
temp_dict['port'] = response.json()[i]['port']
|
|
||||||
temp_dict['priority'] = response.json()[i]['priority']
|
|
||||||
temp_dict['node'] = response.json()[i]['node']
|
|
||||||
temp_dict['uptime'] = response.json()[i]['uptime']
|
|
||||||
temp_dict['status'] = response.json()[i]['status']
|
|
||||||
temp_dict['resource'] = response.json()[i]['resource']
|
|
||||||
temp_dict['statustext'] = response.json()[i]['statustext']
|
|
||||||
|
|
||||||
if len(sessions_dict) > 0:
|
|
||||||
|
|
||||||
ds = [temp_dict, sessions_dict]
|
|
||||||
sessions_temp = {}
|
|
||||||
for k in temp_dict.keys():
|
|
||||||
sessions_temp[k] = tuple(sessions_temp[k] for sessions_temp in ds)
|
|
||||||
|
|
||||||
else:
|
|
||||||
|
|
||||||
sessions_dict = temp_dict.copy()
|
|
||||||
|
|
||||||
sessions_temp = sessions_dict.copy()
|
|
||||||
|
|
||||||
i += 1
|
|
||||||
|
|
||||||
sessions = self.__json_allow_dict_attrs(sessions_temp)
|
|
||||||
|
|
||||||
return sessions
|
|
||||||
|
|
||||||
def __api_request(self, endpoint, data):
|
|
||||||
|
|
||||||
try:
|
|
||||||
|
|
||||||
response = requests.post(url = endpoint, json = data, auth=(self._Ejabberd__admin_account, self._Ejabberd__admin_pass))
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
|
|
||||||
raise EjabberdNetworkError(f"Could not complete request: {e}")
|
|
||||||
|
|
||||||
if response is None:
|
|
||||||
|
|
||||||
raise EjabberdIllegalArgumentError("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 = EjabberdNotFoundError
|
|
||||||
if not error_msg:
|
|
||||||
error_msg = 'Endpoint not found.'
|
|
||||||
# this is for compatibility with older versions
|
|
||||||
# which raised EjabberdAPIError('Endpoint not found.')
|
|
||||||
# on any 404
|
|
||||||
elif response.status_code == 401:
|
|
||||||
ex_type = EjabberdUnauthorizedError
|
|
||||||
elif response.status_code == 500:
|
|
||||||
ex_type = EjabberdInternalServerError
|
|
||||||
elif response.status_code == 502:
|
|
||||||
ex_type = EjabberdBadGatewayError
|
|
||||||
elif response.status_code == 503:
|
|
||||||
ex_type = EjabberdServiceUnavailableError
|
|
||||||
elif response.status_code == 504:
|
|
||||||
ex_type = EjabberdGatewayTimeoutError
|
|
||||||
elif response.status_code >= 500 and \
|
|
||||||
response.status_code <= 511:
|
|
||||||
ex_type = EjabberdServerError
|
|
||||||
else:
|
|
||||||
ex_type = EjabberdAPIError
|
|
||||||
|
|
||||||
raise ex_type(
|
|
||||||
'Ejabberd API returned error',
|
|
||||||
response.status_code,
|
|
||||||
response.reason,
|
|
||||||
error_msg)
|
|
||||||
|
|
||||||
else:
|
|
||||||
|
|
||||||
return response
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def __check_setup(self):
|
|
||||||
|
|
||||||
is_setup = False
|
|
||||||
|
|
||||||
if not os.path.isfile(self.__ejabberd_config_path):
|
|
||||||
print(f"File {self.__ejabberd_config_path} not found, running setup.")
|
|
||||||
else:
|
|
||||||
is_setup = True
|
|
||||||
|
|
||||||
return is_setup
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def setup(self):
|
|
||||||
|
|
||||||
if not os.path.exists('secrets'):
|
|
||||||
os.makedirs('secrets')
|
|
||||||
|
|
||||||
self.__api_base_url = input("api_base_url, in ex. 'http://127.0.0.1:5280': ")
|
|
||||||
self.__local_vhost = input("local_vhost, in ex. 'ejabberd.server': ")
|
|
||||||
self.__admin_account = input("admin_account, in ex. 'admin@ejabberd.server': ")
|
|
||||||
self.__admin_pass = getpass.getpass("admin_pass, in ex. 'my_very_hard_secret_pass': ")
|
|
||||||
|
|
||||||
if not os.path.exists(self.__ejabberd_config_path):
|
|
||||||
with open(self.__ejabberd_config_path, 'w'): pass
|
|
||||||
print(f"{self.__ejabberd_config_path} created!")
|
|
||||||
|
|
||||||
with open(self.__ejabberd_config_path, 'a') as the_file:
|
|
||||||
print("Writing ejabberd secrets parameters to " + self.__ejabberd_config_path)
|
|
||||||
the_file.write(f'api_base_url: {self.__api_base_url}\n'+f'local_vhost: {self.__local_vhost}\n'+f'admin_account: {self.__admin_account}\n'+f'admin_pass: {self.__admin_pass}\n')
|
|
||||||
|
|
||||||
return (self.__api_base_url, self.__local_vhost, self.__admin_account, self.__admin_pass)
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def __get_parameter(parameter, file_path ):
|
|
||||||
|
|
||||||
with open( file_path ) as f:
|
|
||||||
for line in f:
|
|
||||||
if line.startswith( parameter ):
|
|
||||||
return line.replace(parameter + ":", "").strip()
|
|
||||||
|
|
||||||
print(f'{file_path} Missing parameter {parameter}')
|
|
||||||
sys.exit(0)
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def __json_allow_dict_attrs(json_object):
|
|
||||||
"""
|
|
||||||
Makes it possible to use attribute notation to access a dicts
|
|
||||||
elements, while still allowing the dict to act as a dict.
|
|
||||||
"""
|
|
||||||
if isinstance(json_object, dict):
|
|
||||||
return AttribAccessDict(json_object)
|
|
||||||
return json_object
|
|
||||||
##
|
|
||||||
# Exceptions
|
|
||||||
##
|
|
||||||
class EjabberdError(Exception):
|
|
||||||
"""Base class for Mastodon.py exceptions"""
|
|
||||||
|
|
||||||
class EjabberdIOError(IOError, EjabberdError):
|
|
||||||
"""Base class for Mastodon.py I/O errors"""
|
|
||||||
|
|
||||||
class EjabberdNetworkError(EjabberdIOError):
|
|
||||||
"""Raised when network communication with the server fails"""
|
|
||||||
pass
|
|
||||||
class EjabberdAPIError(EjabberdError):
|
|
||||||
"""Raised when the mastodon API generates a response that cannot be handled"""
|
|
||||||
pass
|
|
||||||
class EjabberdServerError(EjabberdAPIError):
|
|
||||||
"""Raised if the Server is malconfigured and returns a 5xx error code"""
|
|
||||||
pass
|
|
||||||
class EjabberdInternalServerError(EjabberdServerError):
|
|
||||||
"""Raised if the Server returns a 500 error"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
class EjabberdBadGatewayError(EjabberdServerError):
|
|
||||||
"""Raised if the Server returns a 502 error"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
class EjabberdServiceUnavailableError(EjabberdServerError):
|
|
||||||
"""Raised if the Server returns a 503 error"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
class EjabberdGatewayTimeoutError(EjabberdServerError):
|
|
||||||
"""Raised if the Server returns a 504 error"""
|
|
||||||
pass
|
|
||||||
class EjabberdNotFoundError(EjabberdAPIError):
|
|
||||||
"""Raised when the ejabberd API returns a 404 Not Found error"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
class EjabberdUnauthorizedError(EjabberdAPIError):
|
|
||||||
"""Raised when the ejabberd API returns a 401 Unauthorized error
|
|
||||||
|
|
||||||
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
|
|
|
@ -3,3 +3,5 @@ requests
|
||||||
python-dateutil
|
python-dateutil
|
||||||
decorator
|
decorator
|
||||||
unidecode
|
unidecode
|
||||||
|
Akkoma.py
|
||||||
|
EjabberdAPI
|
||||||
|
|
4
xmpp.py
4
xmpp.py
|
@ -9,7 +9,9 @@ if __name__ == '__main__':
|
||||||
|
|
||||||
bot = Akkomabot()
|
bot = Akkomabot()
|
||||||
|
|
||||||
ejabberd = Ejabberd()
|
ejabberd_secrets_file = 'secrets/ejabberd_secrets.txt'
|
||||||
|
|
||||||
|
ejabberd = Ejabberd(ejabberd_secrets_file)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|
||||||
|
|
Loading…
Referencia en una nova incidència