From ed0515d93006863e7bf6eded966885c60c9b63c9 Mon Sep 17 00:00:00 2001 From: Dedaldino3D Date: Wed, 4 Nov 2020 01:34:49 +0100 Subject: [PATCH 01/99] Create client to run commands --- client.py | 1 + 1 file changed, 1 insertion(+) create mode 100644 client.py diff --git a/client.py b/client.py new file mode 100644 index 0000000..22eccff --- /dev/null +++ b/client.py @@ -0,0 +1 @@ +# Client \ No newline at end of file From 53bae9e77f30dcc5c68d02b16e19b70c22a1837d Mon Sep 17 00:00:00 2001 From: Dedaldino3D Date: Wed, 4 Nov 2020 01:35:21 +0100 Subject: [PATCH 02/99] Serialize response --- serializers.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 serializers.py diff --git a/serializers.py b/serializers.py new file mode 100644 index 0000000..e69de29 From 48be35620ff742d19fdabfc468a58c70cbf20fda Mon Sep 17 00:00:00 2001 From: Dedaldino3D Date: Wed, 4 Nov 2020 01:35:48 +0100 Subject: [PATCH 03/99] Authenticate request --- auth.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 auth.py diff --git a/auth.py b/auth.py new file mode 100644 index 0000000..e69de29 From a75c5c31e816027f29f1366e5f18da7cc5d6be4b Mon Sep 17 00:00:00 2001 From: Dedaldino3D Date: Wed, 4 Nov 2020 01:36:04 +0100 Subject: [PATCH 04/99] After initial commit --- __init__.py | 0 core/__init__.py | 0 core/abc.py | 1 + defaults/__init__.py | 0 4 files changed, 1 insertion(+) create mode 100644 __init__.py create mode 100644 core/__init__.py create mode 100644 core/abc.py create mode 100644 defaults/__init__.py diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/core/__init__.py b/core/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/core/abc.py b/core/abc.py new file mode 100644 index 0000000..18be313 --- /dev/null +++ b/core/abc.py @@ -0,0 +1 @@ +# Abstract Base Class \ No newline at end of file diff --git a/defaults/__init__.py b/defaults/__init__.py new file mode 100644 index 0000000..e69de29 From b5f24b01d823ea630a5a0ea415d2f59f66ef2c82 Mon Sep 17 00:00:00 2001 From: Dedaldino3D Date: Wed, 4 Nov 2020 02:00:20 +0100 Subject: [PATCH 05/99] Add README.md --- README.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..5d252b7 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +A python lib to run ejabberd XML-RPC commands From ca4bc48cdd95c4c7eeb96d209ca33a8aebc044ea Mon Sep 17 00:00:00 2001 From: Dedaldino3D Date: Wed, 4 Nov 2020 02:09:01 +0100 Subject: [PATCH 06/99] Changed LICENSE --- LICENSE | 142 +++++++++----------------------------------------------- 1 file changed, 21 insertions(+), 121 deletions(-) diff --git a/LICENSE b/LICENSE index 0e259d4..99e51be 100644 --- a/LICENSE +++ b/LICENSE @@ -1,121 +1,21 @@ -Creative Commons Legal Code - -CC0 1.0 Universal - - CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE - LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN - ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS - INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES - REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS - PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM - THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED - HEREUNDER. - -Statement of Purpose - -The laws of most jurisdictions throughout the world automatically confer -exclusive Copyright and Related Rights (defined below) upon the creator -and subsequent owner(s) (each and all, an "owner") of an original work of -authorship and/or a database (each, a "Work"). - -Certain owners wish to permanently relinquish those rights to a Work for -the purpose of contributing to a commons of creative, cultural and -scientific works ("Commons") that the public can reliably and without fear -of later claims of infringement build upon, modify, incorporate in other -works, reuse and redistribute as freely as possible in any form whatsoever -and for any purposes, including without limitation commercial purposes. -These owners may contribute to the Commons to promote the ideal of a free -culture and the further production of creative, cultural and scientific -works, or to gain reputation or greater distribution for their Work in -part through the use and efforts of others. - -For these and/or other purposes and motivations, and without any -expectation of additional consideration or compensation, the person -associating CC0 with a Work (the "Affirmer"), to the extent that he or she -is an owner of Copyright and Related Rights in the Work, voluntarily -elects to apply CC0 to the Work and publicly distribute the Work under its -terms, with knowledge of his or her Copyright and Related Rights in the -Work and the meaning and intended legal effect of CC0 on those rights. - -1. Copyright and Related Rights. A Work made available under CC0 may be -protected by copyright and related or neighboring rights ("Copyright and -Related Rights"). Copyright and Related Rights include, but are not -limited to, the following: - - i. the right to reproduce, adapt, distribute, perform, display, - communicate, and translate a Work; - ii. moral rights retained by the original author(s) and/or performer(s); -iii. publicity and privacy rights pertaining to a person's image or - likeness depicted in a Work; - iv. rights protecting against unfair competition in regards to a Work, - subject to the limitations in paragraph 4(a), below; - v. rights protecting the extraction, dissemination, use and reuse of data - in a Work; - vi. database rights (such as those arising under Directive 96/9/EC of the - European Parliament and of the Council of 11 March 1996 on the legal - protection of databases, and under any national implementation - thereof, including any amended or successor version of such - directive); and -vii. other similar, equivalent or corresponding rights throughout the - world based on applicable law or treaty, and any national - implementations thereof. - -2. Waiver. To the greatest extent permitted by, but not in contravention -of, applicable law, Affirmer hereby overtly, fully, permanently, -irrevocably and unconditionally waives, abandons, and surrenders all of -Affirmer's Copyright and Related Rights and associated claims and causes -of action, whether now known or unknown (including existing as well as -future claims and causes of action), in the Work (i) in all territories -worldwide, (ii) for the maximum duration provided by applicable law or -treaty (including future time extensions), (iii) in any current or future -medium and for any number of copies, and (iv) for any purpose whatsoever, -including without limitation commercial, advertising or promotional -purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each -member of the public at large and to the detriment of Affirmer's heirs and -successors, fully intending that such Waiver shall not be subject to -revocation, rescission, cancellation, termination, or any other legal or -equitable action to disrupt the quiet enjoyment of the Work by the public -as contemplated by Affirmer's express Statement of Purpose. - -3. Public License Fallback. Should any part of the Waiver for any reason -be judged legally invalid or ineffective under applicable law, then the -Waiver shall be preserved to the maximum extent permitted taking into -account Affirmer's express Statement of Purpose. In addition, to the -extent the Waiver is so judged Affirmer hereby grants to each affected -person a royalty-free, non transferable, non sublicensable, non exclusive, -irrevocable and unconditional license to exercise Affirmer's Copyright and -Related Rights in the Work (i) in all territories worldwide, (ii) for the -maximum duration provided by applicable law or treaty (including future -time extensions), (iii) in any current or future medium and for any number -of copies, and (iv) for any purpose whatsoever, including without -limitation commercial, advertising or promotional purposes (the -"License"). The License shall be deemed effective as of the date CC0 was -applied by Affirmer to the Work. Should any part of the License for any -reason be judged legally invalid or ineffective under applicable law, such -partial invalidity or ineffectiveness shall not invalidate the remainder -of the License, and in such case Affirmer hereby affirms that he or she -will not (i) exercise any of his or her remaining Copyright and Related -Rights in the Work or (ii) assert any associated claims and causes of -action with respect to the Work, in either case contrary to Affirmer's -express Statement of Purpose. - -4. Limitations and Disclaimers. - - a. No trademark or patent rights held by Affirmer are waived, abandoned, - surrendered, licensed or otherwise affected by this document. - b. Affirmer offers the Work as-is and makes no representations or - warranties of any kind concerning the Work, express, implied, - statutory or otherwise, including without limitation warranties of - title, merchantability, fitness for a particular purpose, non - infringement, or the absence of latent or other defects, accuracy, or - the present or absence of errors, whether or not discoverable, all to - the greatest extent permissible under applicable law. - c. Affirmer disclaims responsibility for clearing rights of other persons - that may apply to the Work or any use thereof, including without - limitation any person's Copyright and Related Rights in the Work. - Further, Affirmer disclaims responsibility for obtaining any necessary - consents, permissions or other rights required for any use of the - Work. - d. Affirmer understands and acknowledges that Creative Commons is not a - party to this document and has no duty or obligation with respect to - this CC0 or use of the Work. +The MIT License (MIT) + +Copyright (c) 2015 Dirk Moors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. From bdc17b4b2561495ebe7845e710e36656b06c5ad4 Mon Sep 17 00:00:00 2001 From: Dedaldino3D Date: Wed, 4 Nov 2020 14:31:08 +0100 Subject: [PATCH 07/99] Create & move files to src and defaults --- client.py | 1 - auth.py => defaults/constants.py | 0 serializers.py => src/__init__.py | 0 src/auth.py | 0 src/client.py | 76 +++++++++++++++++++++++++++++++ src/serializers.py | 0 6 files changed, 76 insertions(+), 1 deletion(-) delete mode 100644 client.py rename auth.py => defaults/constants.py (100%) rename serializers.py => src/__init__.py (100%) create mode 100644 src/auth.py create mode 100644 src/client.py create mode 100644 src/serializers.py diff --git a/client.py b/client.py deleted file mode 100644 index 22eccff..0000000 --- a/client.py +++ /dev/null @@ -1 +0,0 @@ -# Client \ No newline at end of file diff --git a/auth.py b/defaults/constants.py similarity index 100% rename from auth.py rename to defaults/constants.py diff --git a/serializers.py b/src/__init__.py similarity index 100% rename from serializers.py rename to src/__init__.py diff --git a/src/auth.py b/src/auth.py new file mode 100644 index 0000000..e69de29 diff --git a/src/client.py b/src/client.py new file mode 100644 index 0000000..aaac9c9 --- /dev/null +++ b/src/client.py @@ -0,0 +1,76 @@ +import xmlrpc + + + + + +class EjabberdAPI(object): + """Pytho client for ejabberd XML-RPC Adminitration API.""" + + def __init__(self, host, username, password, + protocol='http', server='127.0.0.1', port=4560, + admin=True, verbose=False): + """XML-RPC server proxy.""" + + self.params = {'user': username, + 'password': password, + 'server': host, 'admin':admin} + self.errors = { + 'connect': 'ERROR: cannot connect to the server', + 'access': 'ERROR: access denied, account unprevilged', + 'bad_arg': 'ERROR: call failed, bad input argument', + 'missing_arg': 'ERROR: call failed, missing input argument' + } + self._proxy = None + self.xmlrpc_server = xmlrpc.ServerProxy(uri, verbose=verbose) + + @property + def service_url(self): + """ + Return the FQDN to the ejabberd server's XML-RPC endpoint + """ + return "{}://{}:{}".format(protocol, host, port) + + @property + def proxy(self) : + """ + Retun the proxy object that is used to perform the calls to the + XML-RPC endpoint + :return xmlrpc.ServerProxy + """ + if self._proxy is None: + self._proxy = xmlrpc.client.ServerProxy(self.service_url, verbose=self.verbose) + return self._proxy + + @property + def auth(self): + """ + Return a dictionay containing the basic authorization info + """ + return { + 'user': self.username, + 'server': self.host, + 'password': self.password + } + + def call_api(self, command, payload=None): + """ + Run the ejabberd command. + """ + fn = getattr(self.xmlrpc_server, command) + try: + if payload: + return fn(self.params, payload) + return fn(self.params) + except BadStatusLine as e: + raise Exception("{}\n{}".format(self.errors['connect'], + e.message)) + except xmlrpc.Fault as e: + if 'account_unprivileged' in e.message: + raise Exception('{}\n{}'.format(self.errors['access'], e.message)) + if 'bad_argument' in e.message: + raise Exception("{}\n{}".format(self.errors['bad_arg'], e.message)) + if 'Required attribute' in e.message and 'not found' in e.message: + raise Exception("{}\n{}".format(self.errors[ 'missing_arg'], e.message)) + raise Exception(e) + diff --git a/src/serializers.py b/src/serializers.py new file mode 100644 index 0000000..e69de29 From bad77508c4c96f01273f814fab094b45135406ad Mon Sep 17 00:00:00 2001 From: Dedaldino3D Date: Wed, 4 Nov 2020 14:41:09 +0100 Subject: [PATCH 08/99] Implement methods --- src/client.py | 859 ++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 799 insertions(+), 60 deletions(-) diff --git a/src/client.py b/src/client.py index aaac9c9..705effc 100644 --- a/src/client.py +++ b/src/client.py @@ -1,76 +1,815 @@ -import xmlrpc - +from __future__ import print_function +import xmlrpc +from httplib import BadStatusLine class EjabberdAPI(object): - """Pytho client for ejabberd XML-RPC Adminitration API.""" - - def __init__(self, host, username, password, - protocol='http', server='127.0.0.1', port=4560, - admin=True, verbose=False): - """XML-RPC server proxy.""" - - self.params = {'user': username, - 'password': password, - 'server': host, 'admin':admin} + ''' + Python client for Ejabberd XML-RPC Administration API. + ''' + def __init__(self, + host, username, password, + protocol='http', server='127.0.0.1', port=4560, + admin=True, verbose=False): + ''' + Init XML-RPC server proxy. + ''' + self.params = {'user': username, + 'password': password, + 'server': host, + 'admin': admin} self.errors = { - 'connect': 'ERROR: cannot connect to the server', - 'access': 'ERROR: access denied, account unprevilged', + 'connect': 'ERROR: cannot connect to the server/the call crashed', + 'access': 'ERROR: access denied, account unprivileged', 'bad_arg': 'ERROR: call failed, bad input argument', 'missing_arg': 'ERROR: call failed, missing input argument' - } - self._proxy = None + } + uri = '{}://{}:{}'.format(protocol, server, port) self.xmlrpc_server = xmlrpc.ServerProxy(uri, verbose=verbose) - - @property - def service_url(self): - """ - Return the FQDN to the ejabberd server's XML-RPC endpoint - """ - return "{}://{}:{}".format(protocol, host, port) - - @property - def proxy(self) : - """ - Retun the proxy object that is used to perform the calls to the - XML-RPC endpoint - :return xmlrpc.ServerProxy - """ - if self._proxy is None: - self._proxy = xmlrpc.client.ServerProxy(self.service_url, verbose=self.verbose) - return self._proxy - - @property - def auth(self): - """ - Return a dictionay containing the basic authorization info - """ - return { - 'user': self.username, - 'server': self.host, - 'password': self.password - } - - def call_api(self, command, payload=None): - """ - Run the ejabberd command. - """ + + def _call_api(self, command, **kwargs): + ''' + Run ejabberd command. + ''' fn = getattr(self.xmlrpc_server, command) try: - if payload: - return fn(self.params, payload) + if kwargs: + return fn(self.params, **kwargs) return fn(self.params) - except BadStatusLine as e: - raise Exception("{}\n{}".format(self.errors['connect'], - e.message)) - except xmlrpc.Fault as e: + except BadStatusLine, e: + raise Exception('{}\n{}'.format(self.errors['connect'], + e.message)) + except xmlrpc.Fault, e: if 'account_unprivileged' in e.message: - raise Exception('{}\n{}'.format(self.errors['access'], e.message)) + raise Exception('{}\n{}'.format(self.errors['access'], + e.message)) if 'bad_argument' in e.message: - raise Exception("{}\n{}".format(self.errors['bad_arg'], e.message)) + raise Exception('{}\n{}'.format(self.errors['bad_arg'], + e.message)) if 'Required attribute' in e.message and 'not found' in e.message: - raise Exception("{}\n{}".format(self.errors[ 'missing_arg'], e.message)) + raise Exception('{}\n{}'.format(self.errors['missing_arg'], + e.message)) raise Exception(e) - + + def add_rosteritem(self, + localuser, localserver, + user, server, + nick, group, subs): + ''' + Add an item to a user's roster (self,supports ODBC): + ''' + return self._call_api('add_rosteritem', {'localuser': localuser, + 'localserver': localserver, + 'user': user, + 'server': server, + 'nick': nick, + 'group': group, + 'subs': subs}) + + # TODO def backup(self, file): Store the database to backup file + + def ban_account(self, user, host, reason): + ''' + Ban an account: kick sessions and set random password + ''' + return self._call_api('ban_account', {'user': user, + 'host': host, + 'reason': reason}) + + def change_password(self, user, host, newpass): + ''' + Change the password of an account + ''' + return self._call_api('change_password', {'user': user, + 'host': host, + 'newpass': newpass}) + + # TODO def change_room_option(self, name, service, option, value) + # Change an option in a MUC room + + def check_account(self, user, host): + ''' + Check if an account exists or not + ''' + return self._call_api('check_account', {'user': user, 'host': host}) + + def check_password(self, user, host, password): + ''' + Check if a password is correct + ''' + return self._call_api('check_password', {'user': user, + 'host': host, + 'password': password}) + + def check_password_hash(self, user, host, passwordhash, hashmethod): + ''' + Check if the password hash is correct + ''' + return self._call_api('check_password_hash', {'user': user, + 'host': host, + 'passwordhash': passwordhash, + 'hashmethod': hashmethod}) + + # TODO def compile(self, file): + # Recompile and reload Erlang source code file + + def connected_users(self): + ''' + List all established sessions + ''' + return self._call_api('connected_users') + + def connected_users_info(self): + ''' + List all established sessions and their information + ''' + return self._call_api('connected_users_info') + + def connected_users_number(self): + ''' + Get the number of established sessions + ''' + return self._call_api('connected_users_number') + + def connected_users_vhost(self, host): + ''' + Get the list of established sessions in a vhost + ''' + return self._call_api('connected_users_vhost', {'host': host}) + + # TODO def convert_to_scram(self, host): + # Convert the passwords in ‘users’ SQL table to SCRAM + + # TODO def convert_to_yaml(self, in, out): + # Convert the input file from Erlang to YAML format + + # TODO def create_room(self, name, service, host): + # Create a MUC room name@service in host + + # TODO def create_room_with_opts(self, name, service, host, options): + # Create a MUC room name@service in host with given options + + # TODO def create_rooms_file(self, file): + # Create the rooms indicated in file + + def delete_expired_messages(self): + ''' + Delete expired offline messages from database + ''' + return self._call_api('delete_expired_messages') + + # TODO def delete_mnesia(self, host): + # Export all tables as SQL queries to a file + + # TODO def delete_old_mam_messages(self, type, days): + # Delete MAM messages older than DAYS + + def delete_old_messages(self, days): + ''' + Delete offline messages older than DAYS + ''' + return self._call_api('delete_old_messages', {'days': days}) + + def delete_old_users(self, days): + ''' + Delete users that didn't log in last days, or that never logged + ''' + return self._call_api('delete_old_users', {'days': days}) + + def delete_old_users_vhost(self, host, days): + ''' + Delete users that didn't log in last days in vhost, + or that never logged + ''' + return self._call_api('delete_old_users_vhost', + {'host': host, 'days': days}) + + def delete_rosteritem(self, localuser, localserver, user, server): + ''' + Delete an item from a user's roster (self,supports ODBC): + ''' + return self._call_api('delete_rosteritem', {'localuser': localuser, + 'localserver': localserver, + 'user': user, + 'server': server}) + + # TODO def destroy_room(self, name, service): + # Destroy a MUC room + + # TODO def destroy_rooms_file(self, file): + # Destroy the rooms indicated in file. Provide one room JID per line. + + # TODO def dump(self, file): + # Dump the database to text file + + # TODO def dump_table(self, file, table): + # Dump a table to text file + + # TODO def export2sql(self, host, file): + # Export virtual host information from Mnesia tables to SQL files + + # TODO def export_piefxis(self, dir): + # Export data of all users in the server to PIEFXIS files (XEP-0227) + + # TODO def export_piefxis_host(self, dir, host): + # Export data of users in a host to PIEFXIS files (XEP-0227) + + # TODO def gen_html_doc_for_commands(self, file, regexp, examples): + # Generates html documentation for ejabberd_commands + + # TODO def gen_markdown_doc_for_commands(self, file, regexp, examples): + # Generates markdown documentation for ejabberd_commands + + def get_cookie(self): + ''' + Get the Erlang cookie of this node + ''' + return self._call_api('get_cookie') + + def get_last(self, user, host): + ''' + Get last activity information (self,timestamp and status): + ''' + return self._call_api('get_last', {'user': user, 'host': host}) + + def get_loglevel(self): + ''' + Get the current loglevel + ''' + return self._call_api('get_loglevel') + + # TODO def get_offline_count(self): + # Get the number of unread offline messages + + # TODO def get_room_affiliations(self, name, service): + # Get the list of affiliations of a MUC room + + # TODO def get_room_occupants(self, name, service): + # Get the list of occupants of a MUC room + + # TODO def get_room_occupants_number(self, name, service): + # Get the number of occupants of a MUC room + + # TODO def get_room_options(self, name, service): + # Get options from a MUC room + + def get_roster(self, user, server): + ''' + Get roster of a local user. + + Note, parameters changed in 15.09 + from ``user, host`` + to ``user, server``. + + Arguments: + + user :: binary + server :: binary + + Result: + + {contacts,{list,{contact,{tuple,[{jid,string}, + {nick,string}, + {subscription,string}, + {ask,string}, + {group,string}]}}}} + + ''' + try: + return self._call_api('get_roster', {'user': user, 'server': server}) + except: + return self._call_api('get_roster', {'user': user, 'host': server}) + + # TODO get_subscribers(self, name, service): + # List subscribers of a MUC conference + + # TODO get_user_rooms(self, user, host): + # Get the list of rooms where this user is occupant + + def get_vcard(self, user, host, name): + ''' + Get content from a vCard field + ''' + return self._call_api('get_vcard', {'user': user, + 'host': host, + 'name': name}) + + def get_vcard2(self, user, host, name, subname): + ''' + Get content from a vCard field + ''' + return self._call_api('get_vcard2', {'user': user, + 'host': host, + 'name': name, + 'subname': subname}) + + def get_vcard2_multi(self, user, host, name, subname): + ''' + Get multiple contents from a vCard field + ''' + return self._call_api('get_vcard2_multi', {'user': user, + 'host': host, + 'name': name, + 'subname': subname}) + + # TODO def import_dir(self, file): + # Import users data from jabberd14 spool dir + + # TODO def import_file(self, file): + # Import users data from jabberd14 spool file + + # TODO def import_piefxis(self, file): + # Import users data from a PIEFXIS file (XEP-0227) + + # TODO def import_prosody(self, dir) Import data from Prosody + + def incoming_s2s_number(self): + ''' + Number of incoming s2s connections on the node + ''' + return self._call_api('incoming_s2s_number') + + # TODO def install_fallback(self, file): + # Install the database from a fallback file + + # TODO def join_cluster(self, node): + # Join this node into the cluster handled by Node + + def kick_session(self, user, host, resource, reason): + ''' + Kick a user session + ''' + return self._call_api('kick_session', {'user': user, + 'host': host, + 'resource': resource, + 'reason': reason}) + + def kick_user(self, user, host): + ''' + Disconnect user's active sessions + ''' + return self._call_api('kick_user', {'user': user, 'host': host}) + + # TODO def leave_cluster(self, node): + # Remove node handled by Node from the cluster + + def list_cluster(self): + ''' + List nodes that are part of the cluster handled by Node + + Result: + + {nodes,{list,{node,atom}}} + + ''' + try: + return self._call_api('list_cluster') + except xmlrpc.Fault, e: + msg = 'list_cluster is NOT available in your version of ejabberd' + raise Exception('{}\n{}\n'.format(msg, e.message)) + + # TODO def load(self, file): + # Restore the database from text file + + # TODO def mnesia_change_nodename(self, + # oldnodename, + # newnodename, + # oldbackup, + # newbackup): + # Change the erlang node name in a backup file + + # TODO def module_check(self, module): + + # TODO def module_install(self, module): + + # TODO def module_uninstall(self, module): + + # TODO def module_upgrade(self, module): + + def modules_available(self): + ''' + List available modules + ''' + return self._call_api('modules_available') + + def modules_installed(self): + ''' + List installed modules + ''' + return self._call_api('modules_installed') + + # TODO def modules_update_specs(self): + + # TODO def muc_online_rooms(self, host): + # List existing rooms (‘global’ to get all vhosts) + + # TODO def muc_unregister_nick(self, nick): + # Unregister the nick in the MUC service + + def num_active_users(self, host, days): + ''' + Get number of users active in the last days + ''' + return self._call_api('num_active_users', {'host': host, 'days': days}) + + def num_resources(self, user, host): + ''' + Get the number of resources of a user + ''' + return self._call_api('num_resources', {'user': user, 'host': host}) + + def outgoing_s2s_number(self): + ''' + Number of outgoing s2s connections on the node + ''' + return self._call_api('outgoing_s2s_number') + + # TODO def privacy_set(self, user, host, xmlquery): + # Send a IQ set privacy stanza for a local account + + # TODO def private_get(self, user, host, element, ns): + # Get some information from a user private storage + + # TODO def private_set(self, user, host, element): + # Set to the user private storage + + def process_rosteritems(self, action, subs, asks, users, contacts): + ''' + List or delete rosteritems that match filtering options + ''' + return self._call_api('process_rosteritems', {'action': action, + 'subs': subs, + 'asks': asks, + 'users': users, + 'contacts': contacts}) + + def push_alltoall(self, host, group): + ''' + Add all the users to all the users of Host in Group + ''' + return self._call_api('push_alltoall', {'host': host, 'group': group}) + + # TODO def push_roster(self, file, user, host): + # Push template roster from file to a user + + # TODO def push_roster_all(self, file): + # Push template roster from file to all those users + + def register(self, user, host, password): + ''' + Register a user + ''' + return self._call_api('register', {'user': user, + 'host': host, + 'password': password}) + + def registered_users(self, host): + ''' + List all registered users in HOST + ''' + return self._call_api('registered_users', {'host': host}) + + def registered_vhosts(self): + ''' + List all registered vhosts in SERVER + ''' + return self._call_api('registered_vhosts') + + def reload_config(self): + ''' + Reload ejabberd configuration file into memory + + (only affects ACL and Access) + ''' + return self._call_api('reload_config') + + def remove_node(self, node): + ''' + Remove an ejabberd node from Mnesia clustering config + ''' + return self._call_api('remove_node', {'node': node}) + + def reopen_log(self): + ''' + Reopen the log files + ''' + return self._call_api('reopen_log') + + def resource_num(self, user, host, num): + ''' + Resource string of a session number + ''' + return self._call_api('resource_num', {'user': user, + 'host': host, + 'num': num}) + + def restart(self): + ''' + Restart ejabberd + ''' + return self._call_api('restart') + + # TODO def restore(self, file): + # Restore the database from backup file + + # TODO def rooms_unused_destroy(self, host, days): + # Destroy the rooms that are unused for many days in host + + # TODO def rooms_unused_list(self, host, days): + # List the rooms that are unused for many days in host + + # TODO def rotate_log(self): + # Rotate the log files + + # TODO def send_direct_invitation(self, + # name, + # service, + # password, + # reason, + # users): + # Send a direct invitation to several destinations + + def send_message(self, type, from_jid, to, subject, body): + ''' + Send a message to a local or remote bare of full JID + ''' + return self._call_api('send_message', {'type': type, + 'from': from_jid, + 'to': to, + 'subject': subject, + 'body': body}) + + # TODO def send_stanza(self, from, to, stanza): + # Send a stanza; provide From JID and valid To JID + + def send_stanza_c2s(self, user, host, resource, stanza): + ''' + Send a stanza as if sent from a c2s session + ''' + return self._call_api('send_stanza_c2s', {'user': user, + 'host': host, + 'resource': resource, + 'stanza': stanza}) + + def set_last(self, user, host, timestamp, status): + ''' + Set last activity information + ''' + return self._call_api('set_last', {'user': user, + 'host': host, + 'timestamp': timestamp, + 'status': status}) + + def set_loglevel(self, loglevel): + ''' + Set the loglevel (0 to 5) + + Arguments: + + loglevel :: integer + + Result: + + {logger,atom} + + ''' + try: + return self._call_api('set_loglevel', {'loglevel': loglevel}) + except xmlrpc.Fault, e: + msg = 'set_loglevel is NOT available in your version of ejabberd' + raise Exception('{}\n{}\n'.format(msg, e.message)) + + def set_master(self, nodename): + ''' + Set master node of the clustered Mnesia tables + ''' + return self._call_api('set_master', {'nodename': nodename}) + + def set_nickname(self, user, host, nickname): + ''' + Set nickname in a user's vCard + ''' + return self._call_api('set_nickname', {'user': user, + 'host': host, + 'nickname': nickname}) + + def set_presence(self, user, host, resource, type, show, status, priority): + ''' + Set presence of a session + ''' + return self._call_api('set_presence', {'user': user, + 'host': host, + 'resource': resource, + 'type': type, + 'show': show, + 'status': status, + 'priority': priority}) + + # TODO def set_room_affiliation(self, name, service, jid, affiliation): + # Change an affiliation in a MUC room + + def set_vcard(self, user, host, name, content): + ''' + Set content in a vCard field + ''' + return self._call_api('set_vcard', {'user': user, + 'host': host, + 'name': name, + 'content': content}) + + def set_vcard2(self, user, host, name, subname, content): + ''' + Set content in a vCard subfield + ''' + return self._call_api('set_vcard2', {'user': user, + 'host': host, + 'name': name, + 'subname': subname, + 'content': content}) + + def set_vcard2_multi(self, user, host, name, subname, contents): + ''' + *Set multiple contents in a vCard subfield + ''' + return self._call_api('set_vcard2_multi', {'user': user, + 'host': host, + 'name': name, + 'subname': subname, + 'contents': contents}) + + def srg_create(self, group, host, name, description, display): + ''' + Create a Shared Roster Group + ''' + return self._call_api('srg_create', {'group': group, + 'host': host, + 'name': name, + 'description': description, + 'display': display}) + + def srg_delete(self, group, host): + ''' + Delete a Shared Roster Group + ''' + return self._call_api('srg_delete', {'group': group, 'host': host}) + + def srg_get_info(self, group, host): + ''' + Get info of a Shared Roster Group + ''' + return self._call_api('srg_get_info', {'group': group, 'host': host}) + + def srg_get_members(self, group, host): + ''' + Get members of a Shared Roster Group + ''' + return self._call_api('srg_get_members', {'group': group, 'host': host}) + + def srg_list(self, host): + ''' + List the Shared Roster Groups in Host + ''' + return self._call_api('srg_list', {'host': host}) + + def srg_user_add(self, user, host, group, grouphost): + ''' + Add the JID user@host to the Shared Roster Group + ''' + return self._call_api('srg_user_add', {'user': user, + 'host': host, + 'group': group, + 'grouphost': grouphost}) + + def srg_user_del(self, user, host, group, grouphost): + ''' + Delete this JID user@host from the Shared Roster Group + ''' + return self._call_api('srg_user_del', {'user': user, + 'host': host, + 'group': group, + 'grouphost': grouphost}) + + def stats(self, name): + ''' + Get statistical value: + + * ``registeredusers`` + * ``onlineusers`` + * ``onlineusersnode`` + * ``uptimeseconds`` + * ``processes`` - Introduced sometime after Ejabberd 15.07 + ''' + try: + return self._call_api('stats', {'name': name}) + except Exception, e: + msg = 'processes stats NOT available in this version of Ejabberd' + if e.message == self.errors['connect']: + raise Exception('{}\n{}\n'.format(msg, e.message)) + raise Exception(e) + + def stats_host(self, name, host): + ''' + Get statistical value for this host: + + * ``registeredusers`` + * ``onlineusers`` + ''' + return self._call_api('stats_host', {'name': name, 'host': host}) + + def status(self): + ''' + Get ejabberd status + ''' + return self._call_api('status') + + def status_list(self, status): + ''' + List of logged users with this status + ''' + return self._call_api('status_list', {'status': status}) + + def status_list_host(self, host, status): + ''' + List of users logged in host with their statuses + ''' + return self._call_api('status_list_host', {'host': host, 'status': status}) + + def status_num(self, status): + ''' + Number of logged users with this status + ''' + return self._call_api('status_num', {'status': status}) + + def status_num_host(self, host, status): + ''' + Number of logged users with this status in host + ''' + return self._call_api('status_num_host', {'host': host, 'status': status}) + + def stop(self): + ''' + Stop ejabberd + ''' + return self._call_api('stop') + + def stop_kindly(self, delay, announcement): + ''' + Inform users and rooms, wait, and stop the server + ''' + return self._call_api('stop_kindly', + {'delay': delay, 'announcement': announcement}) + + # TODO def subscribe_room(self, user, nick, room, nodes): + # Subscribe to a MUC conference + + def unregister(self, user, host): + ''' + Unregister a user + ''' + return self._call_api('unregister', {'user': user, 'host': host}) + + # TODO def unsubscribe_room(self, user, room): + # Unsubscribe from a MUC conference + + def update(self, module): + ''' + Update the given module, or use the keyword: all + ''' + return self._call_api('update', {'module': module}) + + def update_list(self): + ''' + List modified modules that can be updated + ''' + return self._call_api('update_list') + + def user_resources(self, user, server): + ''' + List user's connected resources + + Note, parameters changed in 15.09 + from ``user, host`` + to ``user, server``. + + Arguments: + + user :: binary + server :: binary + + Result: + + {resources,{list,{resource,string}}} + + ''' + try: + return self._call_api('user_resources', {'user': user, 'server': server}) + except: + return self._call_api('user_resources', {'user': user, 'host': server}) + + def user_sessions_info(self, user, host): + ''' + Get information about all sessions of a user + ''' + return self._call_api('user_sessions_info', {'user': user, 'host': host}) From 362fb8031570cdec6674f3e1beccd63a1892994a Mon Sep 17 00:00:00 2001 From: Dedaldino3D Date: Wed, 4 Nov 2020 14:41:47 +0100 Subject: [PATCH 09/99] Add custom exceptions --- core/errors.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 core/errors.py diff --git a/core/errors.py b/core/errors.py new file mode 100644 index 0000000..7ea5408 --- /dev/null +++ b/core/errors.py @@ -0,0 +1,20 @@ + + + +class ConnectionError(Exception): + """Error when connecting to API""" + pass + + +class AccessDeniedError(Exception): + """Access denied, account unprevileged""" + pass + + +class MissingArguments(Exception): + """Missing arguments in call""" + + +class BadArgument(Exception): + """Wrong Argument""" + pass \ No newline at end of file From f82f2e00e8547fb2b49c7d5f0b528fa077728222 Mon Sep 17 00:00:00 2001 From: Dedaldino3D Date: Wed, 4 Nov 2020 15:24:09 +0100 Subject: [PATCH 10/99] Create API as base class --- defaults/arguments.py | 79 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 defaults/arguments.py diff --git a/defaults/arguments.py b/defaults/arguments.py new file mode 100644 index 0000000..4bf33d8 --- /dev/null +++ b/defaults/arguments.py @@ -0,0 +1,79 @@ +from __future__ import unicode_literals + +from abc import ABCMeta, abstractmethod + + + +class APIArgumentSerializer(ABCMeta): + @abstractmethod + def to_api(self, value): + pass + + @abstractmethod + def to_builtin(self, value): + pass + + + +class APIArgument(ABCMeta): + def __init__(self, name, description=None, required=True): + self.name = name + self.description = description + self.required = required + + @abstractmethod + @property + def serializer_class(self): + pass + + + +class API(ABCMeta): + @abstractmethod + @property + def method(self): + """ + Return the exact name of the XML-RPC API mthod to call + """ + pass + + @abstractmethod + @property + def arguments(self): + """ + Return an (orderd) list of APIArrgument objects + """ + pass + + @property + def authenticate(self): + """ + Defines whether or not we should authenticate when calling API + """ + return True + + def transform_arguments(self, **kwargs): + """ + Handler methos to trasnform an argument before processing + :param kwargs: Named argument dictionary + """ + return kwargs + + def validate_response(self, api, arguments, response): + """ + Handler to validate the API response, Can be used + to raise an Exception to indicate fail, the pipeline will continue with the + 'transform_response' method + :param argumentd: The dictionary containing the arguments that have been used to perfom the call + :parma response: object + """ + pass + + def transform_response(self, api, arguments, response): + """ + Handler method to process the response, The output of this method + will be returnd as the outpu of the API + :param api: The api object that has been used fot the call + :param argumentd: The dictionary containing the arguments + """ + return response From 175a7b64423db001fad98b9350501a4002a4d731 Mon Sep 17 00:00:00 2001 From: Dedaldino3D Date: Wed, 4 Nov 2020 15:28:40 +0100 Subject: [PATCH 11/99] Add Enum class in arguments --- defaults/arguments.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/defaults/arguments.py b/defaults/arguments.py index 4bf33d8..778e8ac 100644 --- a/defaults/arguments.py +++ b/defaults/arguments.py @@ -1,7 +1,7 @@ from __future__ import unicode_literals from abc import ABCMeta, abstractmethod - +from enum import Enum as BaseClassEnum class APIArgumentSerializer(ABCMeta): @@ -28,6 +28,17 @@ def serializer_class(self): +class Enum(BaseClassEnum): + @classmethod + def get_by_name(cls, name): + return getattr(cls, name, None) + + @classmethod + def get_by_value(cls, value): + return cls(value) + + + class API(ABCMeta): @abstractmethod @property From 966ef68a4788978e149af37ba7d67463599b4ab0 Mon Sep 17 00:00:00 2001 From: Dedaldino3D Date: Wed, 4 Nov 2020 15:40:29 +0100 Subject: [PATCH 12/99] Add api.py(use for APIArgument) --- defaults/arguments.py | 90 +++++-------------------------------------- src/api.py | 90 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 100 insertions(+), 80 deletions(-) create mode 100644 src/api.py diff --git a/defaults/arguments.py b/defaults/arguments.py index 778e8ac..5af0c10 100644 --- a/defaults/arguments.py +++ b/defaults/arguments.py @@ -1,90 +1,20 @@ from __future__ import unicode_literals -from abc import ABCMeta, abstractmethod -from enum import Enum as BaseClassEnum +from ..api import APIArgument +from ..api import StringSerializer, IntegerSerializer, PositiveIntegerSerializer, BooleanSerializer -class APIArgumentSerializer(ABCMeta): - @abstractmethod - def to_api(self, value): - pass - - @abstractmethod - def to_builtin(self, value): - pass +class StringArgument(APIArgument): + serializer_class = StringSerializer +class IntegerArgument(APIArgument): + serializer_class = IntegerSerializer -class APIArgument(ABCMeta): - def __init__(self, name, description=None, required=True): - self.name = name - self.description = description - self.required = required - - @abstractmethod - @property - def serializer_class(self): - pass +class PositiveIntegerArgument(APIArgument): + serializer_class = PositiveIntegerSerializer -class Enum(BaseClassEnum): - @classmethod - def get_by_name(cls, name): - return getattr(cls, name, None) - - @classmethod - def get_by_value(cls, value): - return cls(value) - - - -class API(ABCMeta): - @abstractmethod - @property - def method(self): - """ - Return the exact name of the XML-RPC API mthod to call - """ - pass - - @abstractmethod - @property - def arguments(self): - """ - Return an (orderd) list of APIArrgument objects - """ - pass - - @property - def authenticate(self): - """ - Defines whether or not we should authenticate when calling API - """ - return True - - def transform_arguments(self, **kwargs): - """ - Handler methos to trasnform an argument before processing - :param kwargs: Named argument dictionary - """ - return kwargs - - def validate_response(self, api, arguments, response): - """ - Handler to validate the API response, Can be used - to raise an Exception to indicate fail, the pipeline will continue with the - 'transform_response' method - :param argumentd: The dictionary containing the arguments that have been used to perfom the call - :parma response: object - """ - pass - - def transform_response(self, api, arguments, response): - """ - Handler method to process the response, The output of this method - will be returnd as the outpu of the API - :param api: The api object that has been used fot the call - :param argumentd: The dictionary containing the arguments - """ - return response +class BooleanArgument(APIArgument): + serializer_class = BooleanSerializer diff --git a/src/api.py b/src/api.py new file mode 100644 index 0000000..778e8ac --- /dev/null +++ b/src/api.py @@ -0,0 +1,90 @@ +from __future__ import unicode_literals + +from abc import ABCMeta, abstractmethod +from enum import Enum as BaseClassEnum + + +class APIArgumentSerializer(ABCMeta): + @abstractmethod + def to_api(self, value): + pass + + @abstractmethod + def to_builtin(self, value): + pass + + + +class APIArgument(ABCMeta): + def __init__(self, name, description=None, required=True): + self.name = name + self.description = description + self.required = required + + @abstractmethod + @property + def serializer_class(self): + pass + + + +class Enum(BaseClassEnum): + @classmethod + def get_by_name(cls, name): + return getattr(cls, name, None) + + @classmethod + def get_by_value(cls, value): + return cls(value) + + + +class API(ABCMeta): + @abstractmethod + @property + def method(self): + """ + Return the exact name of the XML-RPC API mthod to call + """ + pass + + @abstractmethod + @property + def arguments(self): + """ + Return an (orderd) list of APIArrgument objects + """ + pass + + @property + def authenticate(self): + """ + Defines whether or not we should authenticate when calling API + """ + return True + + def transform_arguments(self, **kwargs): + """ + Handler methos to trasnform an argument before processing + :param kwargs: Named argument dictionary + """ + return kwargs + + def validate_response(self, api, arguments, response): + """ + Handler to validate the API response, Can be used + to raise an Exception to indicate fail, the pipeline will continue with the + 'transform_response' method + :param argumentd: The dictionary containing the arguments that have been used to perfom the call + :parma response: object + """ + pass + + def transform_response(self, api, arguments, response): + """ + Handler method to process the response, The output of this method + will be returnd as the outpu of the API + :param api: The api object that has been used fot the call + :param argumentd: The dictionary containing the arguments + """ + return response From 506d1a902b1708f050bc1f7eb74eee21001df1c3 Mon Sep 17 00:00:00 2001 From: Dedaldino3D Date: Wed, 4 Nov 2020 15:41:41 +0100 Subject: [PATCH 13/99] Move core & defaults to src --- {core => src/core}/__init__.py | 0 {core => src/core}/abc.py | 0 {core => src/core}/errors.py | 0 {defaults => src/defaults}/__init__.py | 0 {defaults => src/defaults}/arguments.py | 0 {defaults => src/defaults}/constants.py | 0 6 files changed, 0 insertions(+), 0 deletions(-) rename {core => src/core}/__init__.py (100%) rename {core => src/core}/abc.py (100%) rename {core => src/core}/errors.py (100%) rename {defaults => src/defaults}/__init__.py (100%) rename {defaults => src/defaults}/arguments.py (100%) rename {defaults => src/defaults}/constants.py (100%) diff --git a/core/__init__.py b/src/core/__init__.py similarity index 100% rename from core/__init__.py rename to src/core/__init__.py diff --git a/core/abc.py b/src/core/abc.py similarity index 100% rename from core/abc.py rename to src/core/abc.py diff --git a/core/errors.py b/src/core/errors.py similarity index 100% rename from core/errors.py rename to src/core/errors.py diff --git a/defaults/__init__.py b/src/defaults/__init__.py similarity index 100% rename from defaults/__init__.py rename to src/defaults/__init__.py diff --git a/defaults/arguments.py b/src/defaults/arguments.py similarity index 100% rename from defaults/arguments.py rename to src/defaults/arguments.py diff --git a/defaults/constants.py b/src/defaults/constants.py similarity index 100% rename from defaults/constants.py rename to src/defaults/constants.py From 2cb9ce2177e8577f804f55205d4ffb4a2cadda73 Mon Sep 17 00:00:00 2001 From: Dedaldino3D Date: Wed, 4 Nov 2020 15:43:10 +0100 Subject: [PATCH 14/99] Base Exception to ValueError --- src/core/errors.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/core/errors.py b/src/core/errors.py index 7ea5408..a64d12f 100644 --- a/src/core/errors.py +++ b/src/core/errors.py @@ -1,6 +1,5 @@ - class ConnectionError(Exception): """Error when connecting to API""" pass @@ -11,10 +10,10 @@ class AccessDeniedError(Exception): pass -class MissingArguments(Exception): +class MissingArguments(ValueError): """Missing arguments in call""" -class BadArgument(Exception): +class BadArgument(ValueError): """Wrong Argument""" - pass \ No newline at end of file + pass From 3edc4f2dad7f70b70a236dc93cea265df3437af7 Mon Sep 17 00:00:00 2001 From: Dedaldino3D Date: Wed, 4 Nov 2020 16:03:52 +0100 Subject: [PATCH 15/99] Create Serializers definitions --- src/serializers.py | 82 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) diff --git a/src/serializers.py b/src/serializers.py index e69de29..8fb504a 100644 --- a/src/serializers.py +++ b/src/serializers.py @@ -0,0 +1,82 @@ +from abc import ABCMeta, abstractmethod +from six import string_types + +from .api import Enum, APIArgumentSerializer + + + +class StringSerializer(APIArgumentSerializer): + def to_api(self, value): + if not isinstance(value, string_types): + raise ValueError("Expects str or unicode, but got {}".format(type(value))) + return value + + def to_builtin(self, value): + if not isinstance(value, string_types): + raise ValueError("Expects str or unicode, but got {}".format(type(value))) + return value + + + +class IntegerSerializer(APIArgumentSerializer): + def to_api(self, value): + if not isinstance(value, int): + raise ValueError("Expects int or long, but got {}".format(type(value))) + return str(value) + + def to_builtin(self, value): + return int(value) + + + +class PositiveIntegerSerializer(IntegerSerializer): + def to_api(self, value): + if not isinstance(value, int) or value < 0: + raise ValueError("Expects positive int or long, but got {}".format(type(value))) + return super(PositiveIntegerSerializer, self).to_api(value) + + def to_builtin(self, value): + res = super(PositiveIntegerSerializer, self).to_builtin(value) + if res < 0: + raise ValueError("Expects positive int or long, but got {}".format(type(value))) + return res + + + +class BooleanSerializer(APIArgumentSerializer): + def to_api(self, value): + if not isinstance(value, bool): + raise ValueError("Expects boolean") + return 'true' if value else 'false + + def to_builtin(self, value): + if value not in ('true', 'false'): + raise ValueError("Expects true|false, but got {}".format(type(value))) + return value == 'true' + + + +class EnumSerializer(ABCMeta, StringSerializer): + @abstractmethod + @property + def enum_class(self): + pass + + def to_api(self, value): + assert issubclass(self.enum_class, Enum) + if isinstance(value, self.enum_class): + return value.name + elif isinstance(value, string_types): + return value + elif isinstance(value, int): + return self.enum_class.get_by_value(value).name + raise ValueError("Invalid value for enum %s: %s" % (self.enum_class, value)) + + def to_builtin(self, value): + assert issubclass(self.enum_class, Enum) + if not isinstance(value, string_types): + raise ValueError("Expects str or unicode , but got {}".format(type(value))) + res = self.enum_class.get_by_name(value) + if res is None: + raise ValueError("Expects enum value for {}, but got {}".format(self.enum_class, type(value))) + \ No newline at end of file From b1a52bd22593b6521c2858f9a707c306606725a0 Mon Sep 17 00:00:00 2001 From: Dedaldino3D Date: Wed, 4 Nov 2020 16:08:37 +0100 Subject: [PATCH 16/99] Add constants and change LICENSE --- LICENSE | 2 +- src/defaults/constants.py | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index 99e51be..e902b60 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2015 Dirk Moors +Copyright (c) 2020 Dedaldino Antonio Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/src/defaults/constants.py b/src/defaults/constants.py index e69de29..5036014 100644 --- a/src/defaults/constants.py +++ b/src/defaults/constants.py @@ -0,0 +1,6 @@ + + + +XMLRPC_API_PROTOCOL = 'https' +XMLRPC_API_PORT = 4560 +XMLRPC_API_SERVER = '127.0.0.1' From f6623e5747122b1f810d175bd6a9d9ddf78814d3 Mon Sep 17 00:00:00 2001 From: Dedaldino3D Date: Thu, 5 Nov 2020 22:36:21 +0100 Subject: [PATCH 17/99] Create a package to contain abstract base classes --- src/{ => abc}/api.py | 0 src/core/abc.py | 1 - src/defaults/arguments.py | 4 ++-- src/serializers.py | 2 +- 4 files changed, 3 insertions(+), 4 deletions(-) rename src/{ => abc}/api.py (100%) delete mode 100644 src/core/abc.py diff --git a/src/api.py b/src/abc/api.py similarity index 100% rename from src/api.py rename to src/abc/api.py diff --git a/src/core/abc.py b/src/core/abc.py deleted file mode 100644 index 18be313..0000000 --- a/src/core/abc.py +++ /dev/null @@ -1 +0,0 @@ -# Abstract Base Class \ No newline at end of file diff --git a/src/defaults/arguments.py b/src/defaults/arguments.py index 5af0c10..9711420 100644 --- a/src/defaults/arguments.py +++ b/src/defaults/arguments.py @@ -1,7 +1,7 @@ from __future__ import unicode_literals -from ..api import APIArgument -from ..api import StringSerializer, IntegerSerializer, PositiveIntegerSerializer, BooleanSerializer +from ..abc.api import APIArgument +from ..serializers import StringSerializer, IntegerSerializer, PositiveIntegerSerializer, BooleanSerializer class StringArgument(APIArgument): diff --git a/src/serializers.py b/src/serializers.py index 8fb504a..9e8de97 100644 --- a/src/serializers.py +++ b/src/serializers.py @@ -1,7 +1,7 @@ from abc import ABCMeta, abstractmethod from six import string_types -from .api import Enum, APIArgumentSerializer +from .abc.api import Enum, APIArgumentSerializer From 2846573519634ea2df5e58d5b69fa17f1d7eb09f Mon Sep 17 00:00:00 2001 From: Dedaldino3D Date: Fri, 6 Nov 2020 12:45:54 +0100 Subject: [PATCH 18/99] Add implementation of a base class for API --- src/abc/api.py | 90 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 90 insertions(+) diff --git a/src/abc/api.py b/src/abc/api.py index 778e8ac..8488f5c 100644 --- a/src/abc/api.py +++ b/src/abc/api.py @@ -88,3 +88,93 @@ def transform_response(self, api, arguments, response): :param argumentd: The dictionary containing the arguments """ return response + + +class EjabberdBaseAPI(ABCMeta): + @abstractmethod + def echo(self, sentence): + pass + + @abstractmethod + def registered_users(self, host): + pass + + @abstractmethod + def register(self, user, host, password): + pass + + @abstractmethod + def unregister(self, user, host): + pass + + @abstractmethod + def change_password(self, user, host, password, newpass): + pass + + @abstractmethod + def check_password_hash(self, user, host, password): + pass + + @abstractmethod + def set_nickname(sel, user, host, nickname): + pass + + @abstractmethod + def conneected_users(self): + pass + + @abstractmethod + def conneected_users_info(self): + pass + + @abstractmethod + def conneected_users_number(self): + pass + + @abstractmethod + def user_sessions_info(self): + pass + + @abstractmethod + def muc_online_rooms(self, host=None): + pass + + @abstractmethod + def muc_online_rooms(self. host=None): + pass + + @abstractmethod + def create_room(self, name, service, host): + pass + + @abstractmethod + def destroy_room(self, name, service, host): + pass + + @abstractmethod + def get_room_options(self, name, service): + pass + + @abstractmethod + def change_room_option(self, name, service, option, value): + pass + + @abstractmethod + def set_room_affiliation(self, name, service, jid, affiliation): + pass + + @abstractmethod + def get_room_affiliations(self, name, service): + pass + + @abstractmethod + def add_roster_item(self, localuser, localserver, user, server, nick, group, subs): + pass + + @abstractmethod + def remove_rosteritem(self, localuser, localserver, user, server): + pass + + @abstractmethod + def get_roster(self, user, host): + pass From ee749af560c34837ec95016c6818eb8f4f474536 Mon Sep 17 00:00:00 2001 From: Dedaldino3D Date: Fri, 6 Nov 2020 12:49:55 +0100 Subject: [PATCH 19/99] Add muc serializers --- src/muc/serializers.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 src/muc/serializers.py diff --git a/src/muc/serializers.py b/src/muc/serializers.py new file mode 100644 index 0000000..1087cfa --- /dev/null +++ b/src/muc/serializers.py @@ -0,0 +1,17 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from ..serializers import EnumSerializer +from .enums import MUCRoomOption, AllowVisitorPrivateMessage, Affiliation + + +class MUCRoomOptionSerializer(EnumSerializer): + enum_class = MUCRoomOption + + +class AllowVisitorPrivateMessageSerializer(EnumSerializer): + enum_class = AllowVisitorPrivateMessage + + +class AffiliationSerializer(EnumSerializer): + enum_class = Affiliation From 1d8c3ad2caccbd291b819685ec852876c97d1420 Mon Sep 17 00:00:00 2001 From: Dedaldino3D Date: Fri, 6 Nov 2020 12:51:44 +0100 Subject: [PATCH 20/99] Add muc enums --- src/muc/enums.py | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 src/muc/enums.py diff --git a/src/muc/enums.py b/src/muc/enums.py new file mode 100644 index 0000000..46fe95d --- /dev/null +++ b/src/muc/enums.py @@ -0,0 +1,40 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals +from ..abc.api import Enum + + +class MUCRoomOption(Enum): + allow_change_subj = 1 + allow_private_messages = 2 + allow_private_messages_from_visitors = 3 + allow_query_users = 4 + allow_user_invites = 5 + allow_visitor_nickchange = 6 + allow_visitor_status = 7 + anonymous = 8 + captcha_protected = 9 + logging = 10 + max_users = 11 + members_by_default = 12 + members_only = 13 + moderated = 14 + password = 15 + password_protected = 16 + persistent = 17 + public = 18 + public_list = 19 + title = 20 + + +class AllowVisitorPrivateMessage(Enum): + anyone = 1 + moderators = 2 + nobody = 3 + + +class Affiliation(Enum): + outcast = 1 + none = 2 + member = 3 + admin = 4 + owner = 5 From 12cc443e37e6d2ac416843ee299f3be23ac151f5 Mon Sep 17 00:00:00 2001 From: Dedaldino3D Date: Fri, 6 Nov 2020 12:52:31 +0100 Subject: [PATCH 21/99] Add muc arguments --- src/muc/arguments.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 src/muc/arguments.py diff --git a/src/muc/arguments.py b/src/muc/arguments.py new file mode 100644 index 0000000..3166d8e --- /dev/null +++ b/src/muc/arguments.py @@ -0,0 +1,13 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from ..abc.api import APIArgument +from .serializers import MUCRoomOptionSerializer, AffiliationSerializer + + +class MUCRoomArgument(APIArgument): + serializer_class = MUCRoomOptionSerializer + + +class AffiliationArgument(APIArgument): + serializer_class = AffiliationSerializer From f3239bde8248cdab8fb1e4fd04747a77626c016e Mon Sep 17 00:00:00 2001 From: Dedaldino3D Date: Fri, 6 Nov 2020 12:54:45 +0100 Subject: [PATCH 22/99] Declare muc room options serializers in __init__ --- src/muc/__init__.py | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 src/muc/__init__.py diff --git a/src/muc/__init__.py b/src/muc/__init__.py new file mode 100644 index 0000000..7584bdb --- /dev/null +++ b/src/muc/__init__.py @@ -0,0 +1,30 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from ..serializers import BooleanSerializer, StringSerializer, PositiveIntegerSerializer +from .serializers import AllowVisitorPrivateMessageSerializer +from .enums import MUCRoomOption + + +muc_room_options_serializers = { + MUCRoomOption.allow_change_subj: BooleanSerializer, + MUCRoomOption.allow_private_messages: BooleanSerializer, + MUCRoomOption.allow_private_messages_from_visitors: AllowVisitorPrivateMessageSerializer, + MUCRoomOption.allow_query_users: BooleanSerializer, + MUCRoomOption.allow_user_invites: BooleanSerializer, + MUCRoomOption.allow_visitor_nickchange: BooleanSerializer, + MUCRoomOption.allow_visitor_status: BooleanSerializer, + MUCRoomOption.anonymous: BooleanSerializer, + MUCRoomOption.captcha_protected: BooleanSerializer, + MUCRoomOption.logging: BooleanSerializer, + MUCRoomOption.max_users: PositiveIntegerSerializer, + MUCRoomOption.members_by_default: BooleanSerializer, + MUCRoomOption.members_only: BooleanSerializer, + MUCRoomOption.moderated: BooleanSerializer, + MUCRoomOption.password: StringSerializer, + MUCRoomOption.password_protected: BooleanSerializer, + MUCRoomOption.persistent: BooleanSerializer, + MUCRoomOption.public: BooleanSerializer, + MUCRoomOption.public_list: BooleanSerializer, + MUCRoomOption.title: StringSerializer +} From 332432c207c9f13bf8b0ca70d744d3452ab49e75 Mon Sep 17 00:00:00 2001 From: Dedaldino3D Date: Fri, 6 Nov 2020 13:58:14 +0100 Subject: [PATCH 23/99] Implement base methods for xml-rpc calls in top APIClient --- src/abc/methods.py | 231 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 231 insertions(+) create mode 100644 src/abc/methods.py diff --git a/src/abc/methods.py b/src/abc/methods.py new file mode 100644 index 0000000..8b6a983 --- /dev/null +++ b/src/abc/methods.py @@ -0,0 +1,231 @@ +from __future__ import unicode_literals + +from ..defaults.arguments import StringArgument +from ..abc.api import api +from ..serializers import StringSerializer +from ..muc import muc_room_options_serializers +from ..muc.arguments imprt MUCRoomArgument, AffiliationArgument + +# TODO: implement this methos +from ..core.errors import UserAlreadyRegisteredError +from ..muc.enums import Affiliation +# TODO: implement this method +from ..core.utils import format_password_hash_sha + + + +class Echo(API): + method = 'echothisnew' + arguments = [StringArgument('sentence')] + + def transform_response(self, api, arguments, response): + return response.get('repeated') + + +class RegisteredUsers(API): + method = 'registered_users' + arguments = [StringArgument('host')] + + def transform_response(self, api, arguments, response): + return response.get('users', []) + + +class Register(API): + method = 'register' + arguments = [StringArgument('user'), StringArgument('host'),StringArgument('password')] + + def validate_response(self, api, arguments, response): + if response.get('res') == 1: + username = arguments.get('uer') + raise UserAlreadyRegisteredError('User with username %s already exist' % username) + + def transform_response(self, api, arguments, response): + return response.get('res') == 0 + + +class Unregister(API): + method = 'unregister' + arguments = [StringArgument('user'), StringArgument('host')] + + def transform_response(self, api, arguments, response): + return response.get('res') == 0 + + + +class ChangePassword(API): + method = 'change_password' + arguments = [StringArgument('user'), StringArgument('host'), StringArgument('newpass')] + + def transform_response(self, api, arguments, response): + return response.get('res') == 0 + + +class CheckPasswordHash(API): + method = 'check_password_hash' + arguments = [StringArgument('user'), StringArgument('host'), StringArgument('passwordhash'), + StringArgument('hashmethod')] + + def transform_arguments(self, **kwargs): + passwordhash = format_password_hash_sha(password=kwargs.pop('password')) + kwargs.update({ + 'passwordhash': passwordhash, + 'hashmethod': 'sha' + }) + return kwargs + + def transform_response(self, api, arguments, response): + return response.get('res') == 0 + + +class SetNickname(API): + method = 'set_nickname' + arguments = [StringArgument('user'), StringArgument('host'), StringArgument('nickname')] + + def transform_response(self, api, arguments, response): + return response.get('res') == 0 + +class ConnectedUsers(API): + method = 'connected_users' + arguments = [] + + def transform_response(self, api, arguments, response): + connected_users = response.get('connected_users', []) + + return [user["sessions"] for user in connected_users] + +class ConnectedUsersInfo(API): + method = 'connected_users_info' + arguments = [] + + def transform_response(self, api, arguments, response): + connected_users_info = response.get('connected_users_info', []) + + return [user["sessions"] for user in connected_users_info] + +class ConnectedUsersNumber(API): + method = 'connected_users_number' + arguments = [] + + def transform_response(self, api, arguments, response): + return response.get('num_sessions') + +class UserSessionInfo(API): + method = 'user_sessions_info' + arguments = [StringArgument('user'), StringArgument('host')] + + def transform_response(self, api, arguments, response): + sessions_info = response.get('sessions_info', []) + return [ + dict((k, v) for property_k_v in session["session"] for k, v in property_k_v.items()) + for session in sessions_info + ] + +class MucOnlineRooms(API): + method = 'muc_online_rooms' + arguments = [StringArgument('host')] + + def transform_response(self, api, arguments, response): + return [result_dict.get('room') for result_dict in response.get('rooms', {})] + + +class CreateRoom(API): + method = 'create_room' + arguments = [StringArgument('name'), StringArgument('service'), StringArgument('host')] + + def transform_response(self, api, arguments, response): + return response.get('res') == 0 + + +class DestroyRoom(API): + method = 'destroy_room' + arguments = [StringArgument('name'), StringArgument('service'), StringArgument('host')] + + def transform_response(self, api, arguments, response): + return response.get('res') == 0 + + +class GetRoomOptions(API): + method = 'get_room_options' + arguments = [StringArgument('name'), StringArgument('service')] + + def transform_response(self, api, arguments, response): + result = {} + for option_dict in response.get('options', []): + option = option_dict.get('option') + if option is None: + raise ValueError('Unexpected option in response: ' % str(option_dict)) + name_dict, value_dict = option + result[name_dict['name']] = value_dict['value'] + return result + + +class ChangeRoomOption(API): + method = 'change_room_option' + arguments = [StringArgument('name'), StringArgument('service'), MUCRoomArgument('option'), StringArgument('value')] + + def transform_arguments(self, **kwargs): + option = kwargs.get('option') + assert isinstance(option, MUCRoomOption) + serializer_class = muc_room_options_serializers.get(option, StringSerializer) + kwargs['value'] = serializer_class().to_api(kwargs['value']) + return kwargs + + def transform_response(self, api, arguments, response): + return response.get('res') == 0 + + +class SetRoomAffiliation(API): + method = 'set_room_affiliation' + arguments = [StringArgument('name'), StringArgument('service'), StringArgument('jid'), + AffiliationArgument('affiliation')] + + def transform_response(self, api, arguments, response): + return response.get('res') == 0 + + +class GetRoomAffiliations(API): + method = 'get_room_affiliations' + arguments = [StringArgument('name'), StringArgument('service')] + + def transform_response(self, api, arguments, response): + affiliations = response.get('affiliations', []) + return [{ + 'username': subdict['affiliation'][0]['username'], + 'domain': subdict['affiliation'][1]['domain'], + 'affiliation': Affiliation.get_by_name(subdict['affiliation'][2]['affiliation']), + 'reason': subdict['affiliation'][3]['reason'], + } for subdict in affiliations] + + +class AddRosterItem(API): + method = 'add_rosteritem' + arguments = [StringArgument('localuser'), StringArgument('localserver'), + StringArgument('user'), StringArgument('server'), + StringArgument('nick'), StringArgument('group'), StringArgument('subs')] + + def transform_response(self, api, arguments, response): + return response.get('res') == 0 + + +class DeleteRosterItem(API): + method = 'delete_rosteritem' + arguments = [StringArgument('localuser'), StringArgument('localserver'), + StringArgument('user'), StringArgument('server')] + + def transform_response(self, api, arguments, response): + return response.get('res') == 0 + + +class GetRoster(API): + method = 'get_roster' + arguments = [StringArgument('user'), StringArgument('host')] + + def transform_response(self, api, arguments, response): + roster = [] + for contact in response.get('contacts', []): + contact_details = {} + for parameter in contact['contact']: + for key, value in parameter.items(): + contact_details[key] = value + roster.append(contact_details) + return roster From ee34ae336781f67da580792341efa02f84ed20cd Mon Sep 17 00:00:00 2001 From: Dedaldino3D Date: Fri, 6 Nov 2020 13:59:52 +0100 Subject: [PATCH 24/99] Add UserAlreadyRegistered Exception --- src/core/errors.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/core/errors.py b/src/core/errors.py index a64d12f..b31bcaa 100644 --- a/src/core/errors.py +++ b/src/core/errors.py @@ -12,8 +12,14 @@ class AccessDeniedError(Exception): class MissingArguments(ValueError): """Missing arguments in call""" - + pass class BadArgument(ValueError): """Wrong Argument""" pass + + +class UserAlreadyRegisteredError(Exception): + """User already registered""" + pass + \ No newline at end of file From fcc6d09b5280d95d4bdc220276d3237e9dec6391 Mon Sep 17 00:00:00 2001 From: Dedaldino3D Date: Fri, 6 Nov 2020 14:01:53 +0100 Subject: [PATCH 25/99] Add critical methods in client to make xml-rpc calls --- src/client.py | 291 +++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 230 insertions(+), 61 deletions(-) diff --git a/src/client.py b/src/client.py index 705effc..a22232a 100644 --- a/src/client.py +++ b/src/client.py @@ -1,57 +1,247 @@ -from __future__ import print_function - import xmlrpc - +import copy +from __future__ import print_function from httplib import BadStatusLine +from urllib.parse import urlparse +from .abc import api, methods +from .defaults import XMLRPC_API_PROTOCOL, XMLRPC_API_PORT -class EjabberdAPI(object): + +class EjabberdAPIClient(api.EjabberdBaseAPI): ''' Python client for Ejabberd XML-RPC Administration API. ''' def __init__(self, host, username, password, - protocol='http', server='127.0.0.1', port=4560, + server='127.0.0.1', port=4560, protocol='http', admin=True, verbose=False): ''' Init XML-RPC server proxy. ''' - self.params = {'user': username, - 'password': password, - 'server': host, - 'admin': admin} - self.errors = { - 'connect': 'ERROR: cannot connect to the server/the call crashed', - 'access': 'ERROR: access denied, account unprivileged', - 'bad_arg': 'ERROR: call failed, bad input argument', - 'missing_arg': 'ERROR: call failed, missing input argument' - } - uri = '{}://{}:{}'.format(protocol, server, port) - self.xmlrpc_server = xmlrpc.ServerProxy(uri, verbose=verbose) + self.host = host + self.username = username + self.password = password + self.server = server + self.protocol = protocol or XMLRPC_API_PROTOCOL + self.verbose = verbose + self._server_proxy = None + + @staticmethod + def get_instance(service_url, verbose=Fase): + """ + Return a EjabberdAPIClient instance based on a '12factor app' compliant service_url + + :param service_url: A connection string in the format: + ://:@(:port)/user_domain + :type service_url: str|unicode + :param verbose: + :type verbose: bool + :return: EjabberdAPIClient instance + """ + format_error = "expects service_url like https://username:password@HOST:PORT/DOMAIN" + + o = urlparse(service_url) + protocol = o.scheme + assert protocol in ('http', 'https'), format_error + + netloc_parts = o.netloc.split('@') + assert len(netloc_parts) == 2, format_error + + auth, server = netloc_parts + auth_parts = auth.split(':') + assert len(auth_parts) == 2, format_error + + username, password = auth_parts + server_parts = server.split(':') + assert len(server_parts) <= 2, format_error + + if len(server_parts) == 2: + host, port = server_parts + port = int(port) + else: + host, port = server_parts[0],XMLRPC_API_PORT + path_parts = o.path.lstrip('/').split('/') + assert len(path_parts) == 1, format_error + + server =path_parts[0] + return EjabberdAPIClient(host, username, password,server, port,protocol=protocol, verbose=verbose) + + + @property + def service_url(self): + """ + Returns the FQDN to the Ejabberd server's XML-RPC endpoint + :return: + """ + return "{}://{}:{}/".format(self.protocol, self.host, self.port) + + + @property + def server_proxy(self): + """ + Returns the proxy object that is used to perform the calls to the XML-RPC endpoint + """ + if self._server_proxy is None: + self._server_proxy = xmlrpc.client.ServerProxy(self.service_url, verbose(1 if self.verbose else 0)) + return self._server_proxy + + @property + def auth(self): + """ + Returns a dictionary containing the basic authorization info + """ + return { + 'user': self.username, + 'server': self.server, + 'password': self.password + } + + + def _validate_and_serialize_arguments(self, api, arguments): + """ + Internal method to validate and serialize arguments + :param api: An instance of an API class + :param arguments: A dictionary of arguments that will be passed to the method + :type arguments: dict + :rtype: dict + :return: The serialized arguments + """ + ser_args = {} + + for i in range(len(api.arguments)): + arg_desc = api.arguments[i] + assert isinstance(arg_desc, api.APIArgument) + + # validate argument presence + arg_name = str(arg_desc.name) + if arg_desc.required and arg_name not in arguments: + raise MissingArguments("Missing required argument '%s'" % arg_name) + + # serialize argument value + ser_args[arg_desc.name] = arg_desc.serializer_class().to_api(arguments.get(arg_name)) + + return ser_args + + def _report_method_call(self, method, arguments): + """ + Internal method to print info about a method call + :param method: The name oft hem ethod to call + :type method: str|unicode + :param arguments: A dictionary of arguments that will be passed to the method + :type: arguments: dict + :return: + """ + if self.verbose: + print("===> %s(%s)" %(method, ', '.join(['%s=%s' % (k,v) for k,v in arguments.items()]))) + + def _call_api(self, api_class, **kwargs): + """ + Internal method used to perform api calls + :param api_class: + :type api_class: py:class:API + :param kwargs: + :type kwargs: dict + :rtype: object + :return: Returns return value of the XMLRPC Method call + """ + # validate api_class + assert issubclass(api_class, API) + + # create api instance + api = api_class() + # copy arguments + args = copy.copy(kwargs) + + # transform arguments + args = api.transform_arguments(**args) + # validate and serializer arguments + args = self._validate_and_serialize_arguments(api, args) + # retrive method + method = getattr(self.server_proxy, str(api.method)) + + # print method call with arguments + self._report_method_call(api.method, args) + + # perform call + if not api.authenticate: + response = method(args) + else: + response = method(self.auth, args) + + # validate response + api.validate_response(api, args, response) + # tranform response + result = api.transform_response(api, args, response) + return result + + def echo(self, sentence): + """Echo the input back""" + return self._call_api(methods.Echo, sentence=sentence) - def _call_api(self, command, **kwargs): - ''' - Run ejabberd command. - ''' - fn = getattr(self.xmlrpc_server, command) - try: - if kwargs: - return fn(self.params, **kwargs) - return fn(self.params) - except BadStatusLine, e: - raise Exception('{}\n{}'.format(self.errors['connect'], - e.message)) - except xmlrpc.Fault, e: - if 'account_unprivileged' in e.message: - raise Exception('{}\n{}'.format(self.errors['access'], - e.message)) - if 'bad_argument' in e.message: - raise Exception('{}\n{}'.format(self.errors['bad_arg'], - e.message)) - if 'Required attribute' in e.message and 'not found' in e.message: - raise Exception('{}\n{}'.format(self.errors['missing_arg'], - e.message)) - raise Exception(e) + def registered_users(self, host): + """ + List all registered users in the xmpp_host + :param host: The XMPP_DOMAIN + :type host: str|unicode + :rtype: Iterable + :return: A list of registered users in the xmpp_host + """ + return self._call_api(methods.RegisteredUsers, host=host) + + def register(self, user, host, password): + """ + Registers a user to the ejabberd server + :param user: The username for the new user + :type user: str|unicode + :param host: The XMPP_DOMAIN + :type host: str|unicode + :param password: The password for the new user + :type password: str|unicode + :rtype: bool + :return: A boolean indicating if the registration has succeeded + """ + return self._call_api(methods.Register, user=user, host=host, password=password) + + def unregister(self, user, host): + """ + UnRegisters a user from the ejabberd server + :param user: The username for the new user + :type user: str|unicode + :param host: The XMPP_DOMAIN + :type host: str|unicode + :rtype: bool + :return: A boolean indicating if the unregistration has succeeded + """ + return self._call_api(methods.UnRegister, user=user, host=host) + + def change_password(self, user, host, newpass): + """ + Change the password for a given user + :param user: The username for the user we want to change the password for + :type user: str|unicode + :param host: The XMPP_DOMAIN + :type host: str|unicode + :param newpass: The new password + :type newpass: str|unicode + :rtype: bool + :return: A boolean indicating if the password change has succeeded + """ + return self._call_api(methods.ChangePassword, user=user, host=host, newpass=newpass) + + def check_password_hash(self, user, host, password): + """ + Checks whether a password is correct for a given user. The used hash-method is fixed to sha1. + :param user: The username for the user we want to check the password for + :type user: str|unicode + :param host: The XMPP_DOMAIN + :type host: str|unicode + :param password: The password we want to check for the user + :type password: str|unicode + :rtype: bool + :return: A boolean indicating if the given password matches the user's password + """ + return self._call_api(methods.CheckPasswordHash, user=user, host=host, password=password) def add_rosteritem(self, localuser, localserver, @@ -78,14 +268,6 @@ def ban_account(self, user, host, reason): 'host': host, 'reason': reason}) - def change_password(self, user, host, newpass): - ''' - Change the password of an account - ''' - return self._call_api('change_password', {'user': user, - 'host': host, - 'newpass': newpass}) - # TODO def change_room_option(self, name, service, option, value) # Change an option in a MUC room @@ -457,19 +639,6 @@ def push_alltoall(self, host, group): # TODO def push_roster_all(self, file): # Push template roster from file to all those users - def register(self, user, host, password): - ''' - Register a user - ''' - return self._call_api('register', {'user': user, - 'host': host, - 'password': password}) - - def registered_users(self, host): - ''' - List all registered users in HOST - ''' - return self._call_api('registered_users', {'host': host}) def registered_vhosts(self): ''' From 8045aed5f21e41207de2e51b1e18d14a90664669 Mon Sep 17 00:00:00 2001 From: Dedaldino3D Date: Fri, 6 Nov 2020 14:10:21 +0100 Subject: [PATCH 26/99] Rename src to ejabberd_python3d. - Add import in __init__ --- ejabberd_python3d/__init__.py | 2 ++ {src => ejabberd_python3d}/abc/api.py | 0 {src => ejabberd_python3d}/abc/methods.py | 0 {src => ejabberd_python3d}/auth.py | 0 {src => ejabberd_python3d}/client.py | 0 __init__.py => ejabberd_python3d/core/__init__.py | 0 {src => ejabberd_python3d}/core/errors.py | 0 {src => ejabberd_python3d/defaults}/__init__.py | 0 {src => ejabberd_python3d}/defaults/arguments.py | 0 {src => ejabberd_python3d}/defaults/constants.py | 0 {src => ejabberd_python3d}/muc/__init__.py | 0 {src => ejabberd_python3d}/muc/arguments.py | 0 {src => ejabberd_python3d}/muc/enums.py | 0 {src => ejabberd_python3d}/muc/serializers.py | 0 {src => ejabberd_python3d}/serializers.py | 0 src/core/__init__.py | 0 src/defaults/__init__.py | 0 17 files changed, 2 insertions(+) create mode 100644 ejabberd_python3d/__init__.py rename {src => ejabberd_python3d}/abc/api.py (100%) rename {src => ejabberd_python3d}/abc/methods.py (100%) rename {src => ejabberd_python3d}/auth.py (100%) rename {src => ejabberd_python3d}/client.py (100%) rename __init__.py => ejabberd_python3d/core/__init__.py (100%) rename {src => ejabberd_python3d}/core/errors.py (100%) rename {src => ejabberd_python3d/defaults}/__init__.py (100%) rename {src => ejabberd_python3d}/defaults/arguments.py (100%) rename {src => ejabberd_python3d}/defaults/constants.py (100%) rename {src => ejabberd_python3d}/muc/__init__.py (100%) rename {src => ejabberd_python3d}/muc/arguments.py (100%) rename {src => ejabberd_python3d}/muc/enums.py (100%) rename {src => ejabberd_python3d}/muc/serializers.py (100%) rename {src => ejabberd_python3d}/serializers.py (100%) delete mode 100644 src/core/__init__.py delete mode 100644 src/defaults/__init__.py diff --git a/ejabberd_python3d/__init__.py b/ejabberd_python3d/__init__.py new file mode 100644 index 0000000..3bf06b5 --- /dev/null +++ b/ejabberd_python3d/__init__.py @@ -0,0 +1,2 @@ +from ejabberd_python3d import client, serializers +from ejabberd_python3d import muc, abc, defaults \ No newline at end of file diff --git a/src/abc/api.py b/ejabberd_python3d/abc/api.py similarity index 100% rename from src/abc/api.py rename to ejabberd_python3d/abc/api.py diff --git a/src/abc/methods.py b/ejabberd_python3d/abc/methods.py similarity index 100% rename from src/abc/methods.py rename to ejabberd_python3d/abc/methods.py diff --git a/src/auth.py b/ejabberd_python3d/auth.py similarity index 100% rename from src/auth.py rename to ejabberd_python3d/auth.py diff --git a/src/client.py b/ejabberd_python3d/client.py similarity index 100% rename from src/client.py rename to ejabberd_python3d/client.py diff --git a/__init__.py b/ejabberd_python3d/core/__init__.py similarity index 100% rename from __init__.py rename to ejabberd_python3d/core/__init__.py diff --git a/src/core/errors.py b/ejabberd_python3d/core/errors.py similarity index 100% rename from src/core/errors.py rename to ejabberd_python3d/core/errors.py diff --git a/src/__init__.py b/ejabberd_python3d/defaults/__init__.py similarity index 100% rename from src/__init__.py rename to ejabberd_python3d/defaults/__init__.py diff --git a/src/defaults/arguments.py b/ejabberd_python3d/defaults/arguments.py similarity index 100% rename from src/defaults/arguments.py rename to ejabberd_python3d/defaults/arguments.py diff --git a/src/defaults/constants.py b/ejabberd_python3d/defaults/constants.py similarity index 100% rename from src/defaults/constants.py rename to ejabberd_python3d/defaults/constants.py diff --git a/src/muc/__init__.py b/ejabberd_python3d/muc/__init__.py similarity index 100% rename from src/muc/__init__.py rename to ejabberd_python3d/muc/__init__.py diff --git a/src/muc/arguments.py b/ejabberd_python3d/muc/arguments.py similarity index 100% rename from src/muc/arguments.py rename to ejabberd_python3d/muc/arguments.py diff --git a/src/muc/enums.py b/ejabberd_python3d/muc/enums.py similarity index 100% rename from src/muc/enums.py rename to ejabberd_python3d/muc/enums.py diff --git a/src/muc/serializers.py b/ejabberd_python3d/muc/serializers.py similarity index 100% rename from src/muc/serializers.py rename to ejabberd_python3d/muc/serializers.py diff --git a/src/serializers.py b/ejabberd_python3d/serializers.py similarity index 100% rename from src/serializers.py rename to ejabberd_python3d/serializers.py diff --git a/src/core/__init__.py b/src/core/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/defaults/__init__.py b/src/defaults/__init__.py deleted file mode 100644 index e69de29..0000000 From 9004f33eb143fa209281670298a3bde0bcfafede Mon Sep 17 00:00:00 2001 From: Dedaldino3D Date: Fri, 6 Nov 2020 15:50:54 +0100 Subject: [PATCH 27/99] Add external authentication for ejabberd from django based on ejabberd-bridge --- .../{auth.py => auth/__init__.py} | 0 ejabberd_python3d/auth/management/__init__.py | 0 .../auth/management/commands/__init__.py | 0 .../auth/management/commands/ejabberd_auth.py | 99 +++++++++++++++++++ 4 files changed, 99 insertions(+) rename ejabberd_python3d/{auth.py => auth/__init__.py} (100%) create mode 100644 ejabberd_python3d/auth/management/__init__.py create mode 100644 ejabberd_python3d/auth/management/commands/__init__.py create mode 100644 ejabberd_python3d/auth/management/commands/ejabberd_auth.py diff --git a/ejabberd_python3d/auth.py b/ejabberd_python3d/auth/__init__.py similarity index 100% rename from ejabberd_python3d/auth.py rename to ejabberd_python3d/auth/__init__.py diff --git a/ejabberd_python3d/auth/management/__init__.py b/ejabberd_python3d/auth/management/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/ejabberd_python3d/auth/management/commands/__init__.py b/ejabberd_python3d/auth/management/commands/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/ejabberd_python3d/auth/management/commands/ejabberd_auth.py b/ejabberd_python3d/auth/management/commands/ejabberd_auth.py new file mode 100644 index 0000000..cf63c31 --- /dev/null +++ b/ejabberd_python3d/auth/management/commands/ejabberd_auth.py @@ -0,0 +1,99 @@ +import logging +import struct +import sys +from django.contrib.auth import authenticate, get_user_model +from django.contrib.auth.models import User +from django.core.management.base import BaseCommand + + + +class Command(BaseCommand): + logger = logging.getLogger(__name__) + + def from_ejabberd(self, encoding="utf-8"): + """ + Reads data from stdin as passed by eJabberd + """ + input_length = sys.stdin.read(2).encode(encoding) + (size,) = struct.unpack(">h", input_length) + return sys.stdin.read(size).split(":") + + def to_ejabberd(self, answer=False): + """ + Converts the response into eJabberd format + """ + b = struct.pack('>hh', + 2, + 1 if answer else 0) + self.logger.debug("To jabber: %s" % b) + sys.stdout.write(b.decode("utf-8")) + sys.stdout.flush() + + def auth(self, username=None, server="localhost", password=None): + self.logger.debug("Authenticating %s with password %s on server %s" % (username, password, server)) + #TODO: would be nice if this could take server into account + user = authenticate(username=username, password=password) + return user and user.is_active + + def isuser(self, username=None, server="localhost"): + """ + Checks if the user exists and is active + """ + self.logger.debug("Validating %s on server %s" % (username, server)) + #TODO: would be nice if this could take server into account + try: + user = get_user_model().objects.get(username=username) + if user.is_active: + return True + else: + self.logger.warning("User %s is disabled" % username) + return False + except User.DoesNotExist: + return False + + def setpass(self, username=None, server="localhost", password=None): + """ + Handles password change + """ + self.logger.debug("Changing password to %s with new password %s on server %s" % (username, password, server)) + #TODO: would be nice if this could take server into account + try: + user = get_user_model().objects.get(username=username) + user.set_password(password) + user.save() + return True + except User.DoesNotExist: + return False + + def handle(self, *args, **options): + """ + Gathers parameters from eJabberd and executes authentication + against django backend + """ + #logging.basicConfig( + # level="DEBUG", + # format='%(asctime)s %(levelname)s %(message)s', + # filename="/usr/local/var/log/ejabberd/django-bridge.log", + # filemode='a') + + self.logger.debug("Starting serving authentication requests for eJabberd") + print("Starting serving authentication requests for eJabberd") + success = False + try: + while True: + data = self.from_ejabberd() + self.logger.debug("Command is %s" % data[0]) + print("Command is %s" % data[0]) + if data[0] == "auth": + success = self.auth(data[1], data[2], data[3]) + elif data[0] == "isuser": + success = self.isuser(data[1], data[2]) + elif data[0] == "setpass": + success = self.setpass(data[1], data[2], data[3]) + self.to_ejabberd(success) + if not options.get("run_forever", True): + break + except Exception as e: + self.logger.error("An error has occurred during eJabberd external authentication: %s" % e) + print("An error has occurred during eJabberd external authentication: %s" % e) + self.to_ejabberd(success) From 269123fcadd6860e3de3668e36b0ad2b41fb39a8 Mon Sep 17 00:00:00 2001 From: Dedaldino3D Date: Fri, 13 Nov 2020 19:38:34 +0100 Subject: [PATCH 28/99] Add idea in gitignore --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index b6e4761..38a6c99 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,8 @@ __pycache__/ *.py[cod] *$py.class +# PyCharm +.idea/ # C extensions *.so From 8929e9c045b856d6452d5aca0d18251bf17f99a5 Mon Sep 17 00:00:00 2001 From: Dedaldino3D Date: Fri, 13 Nov 2020 21:27:40 +0100 Subject: [PATCH 29/99] Add __init__ in abc --- ejabberd_python3d/abc/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 ejabberd_python3d/abc/__init__.py diff --git a/ejabberd_python3d/abc/__init__.py b/ejabberd_python3d/abc/__init__.py new file mode 100644 index 0000000..e69de29 From 04787d8457acc6d672fc2fad653d038368596539 Mon Sep 17 00:00:00 2001 From: Dedaldino3D Date: Fri, 13 Nov 2020 21:29:25 +0100 Subject: [PATCH 30/99] Implement new base methods in EjabberdBaseAPI --- ejabberd_python3d/abc/api.py | 740 ++++++++++++++++++++++++++++++++--- 1 file changed, 695 insertions(+), 45 deletions(-) diff --git a/ejabberd_python3d/abc/api.py b/ejabberd_python3d/abc/api.py index 8488f5c..4e9e603 100644 --- a/ejabberd_python3d/abc/api.py +++ b/ejabberd_python3d/abc/api.py @@ -8,43 +8,40 @@ class APIArgumentSerializer(ABCMeta): @abstractmethod def to_api(self, value): pass - + @abstractmethod def to_builtin(self, value): pass - class APIArgument(ABCMeta): - def __init__(self, name, description=None, required=True): + def __init__(self, name, description=None, required=True, **kwargs): self.name = name self.description = description self.required = required - + @abstractmethod @property def serializer_class(self): pass - class Enum(BaseClassEnum): @classmethod def get_by_name(cls, name): return getattr(cls, name, None) - + @classmethod def get_by_value(cls, value): return cls(value) - class API(ABCMeta): @abstractmethod @property def method(self): """ - Return the exact name of the XML-RPC API mthod to call + Return the exact name of the XML-RPC API method to call """ pass @@ -52,40 +49,41 @@ def method(self): @property def arguments(self): """ - Return an (orderd) list of APIArrgument objects + Return an (ordered) list of APIArgument objects """ pass - + @property def authenticate(self): """ Defines whether or not we should authenticate when calling API """ return True - + def transform_arguments(self, **kwargs): """ - Handler methos to trasnform an argument before processing + Handler methods to transform an argument before processing :param kwargs: Named argument dictionary """ return kwargs - + def validate_response(self, api, arguments, response): """ Handler to validate the API response, Can be used to raise an Exception to indicate fail, the pipeline will continue with the 'transform_response' method - :param argumentd: The dictionary containing the arguments that have been used to perfom the call - :parma response: object + :param arguments: The dictionary containing the arguments that have been used to perform the call + :param response: object """ pass - + def transform_response(self, api, arguments, response): """ Handler method to process the response, The output of this method - will be returnd as the outpu of the API + will be return as the output of the API + :param response: :param api: The api object that has been used fot the call - :param argumentd: The dictionary containing the arguments + :param arguments: The dictionary containing the arguments """ return response @@ -94,75 +92,71 @@ class EjabberdBaseAPI(ABCMeta): @abstractmethod def echo(self, sentence): pass - + @abstractmethod def registered_users(self, host): pass - + @abstractmethod def register(self, user, host, password): pass - + @abstractmethod def unregister(self, user, host): pass - + @abstractmethod def change_password(self, user, host, password, newpass): pass - + @abstractmethod def check_password_hash(self, user, host, password): pass - + @abstractmethod - def set_nickname(sel, user, host, nickname): + def set_nickname(self, user, host, nickname): pass - + @abstractmethod - def conneected_users(self): + def connected_users(self): pass - + @abstractmethod - def conneected_users_info(self): + def connected_users_info(self): pass - + @abstractmethod - def conneected_users_number(self): + def connected_users_number(self): pass - + @abstractmethod def user_sessions_info(self): pass - + @abstractmethod def muc_online_rooms(self, host=None): pass - - @abstractmethod - def muc_online_rooms(self. host=None): - pass @abstractmethod def create_room(self, name, service, host): pass - + @abstractmethod def destroy_room(self, name, service, host): pass - + @abstractmethod def get_room_options(self, name, service): pass - + @abstractmethod def change_room_option(self, name, service, option, value): pass - + @abstractmethod def set_room_affiliation(self, name, service, jid, affiliation): pass - + @abstractmethod def get_room_affiliations(self, name, service): pass @@ -170,11 +164,667 @@ def get_room_affiliations(self, name, service): @abstractmethod def add_roster_item(self, localuser, localserver, user, server, nick, group, subs): pass - + @abstractmethod def remove_rosteritem(self, localuser, localserver, user, server): pass - + + @abstractmethod + def add_rosteritem(self, + localuser, localserver, + user, server, + nick, group, subs): + """ + Add an item to a user's roster (self,supports ODBC): + """ + pass + + # TODO def backup(self, file): Store the database to backup file + + @abstractmethod + def ban_account(self, user, host, reason): + """ + Ban an account: kick sessions and set random password + """ + pass + + # TODO def change_room_option(self, name, service, option, value) + # Change an option in a MUC room + + @abstractmethod + def check_account(self, user, host): + """ + Check if an account exists or not + """ + pass + + @abstractmethod + def check_password(self, user, host, password): + """ + Check if a password is correct + """ + pass + + # TODO def compile(self, file): + # Recompile and reload Erlang source code file + + @abstractmethod + def connected_users_vhost(self, host): + """ + Get the list of established sessions in a vhost + """ + pass + + # TODO def convert_to_scram(self, host): + # Convert the passwords in ‘users’ SQL table to SCRAM + + # TODO def convert_to_yaml(self, in, out): + # Convert the input file from Erlang to YAML format + + # TODO def create_room(self, name, service, host): + # Create a MUC room name@service in host + + # TODO def create_room_with_opts(self, name, service, host, options): + # Create a MUC room name@service in host with given options + + # TODO def create_rooms_file(self, file): + # Create the rooms indicated in file + + @abstractmethod + def delete_expired_messages(self): + """ + Delete expired offline messages from database + """ + pass + + # TODO def delete_mnesia(self, host): + # Export all tables as SQL queries to a file + + # TODO def delete_old_mam_messages(self, type, days): + # Delete MAM messages older than DAYS + + @abstractmethod + def delete_old_messages(self, days): + """ + Delete offline messages older than DAYS + """ + pass + + @abstractmethod + def delete_old_users(self, days): + """ + Delete users that didn't log in last days, or that never logged + """ + pass + + @abstractmethod + def delete_old_users_vhost(self, host, days): + """ + Delete users that didn't log in last days in vhost, + or that never logged + """ + pass + + @abstractmethod + def delete_rosteritem(self, localuser, localserver, user, server): + """ + Delete an item from a user's roster (self,supports ODBC): + """ + pass + + # TODO def destroy_room(self, name, service): + # Destroy a MUC room + + # TODO def destroy_rooms_file(self, file): + # Destroy the rooms indicated in file. Provide one room JID per line. + + # TODO def dump(self, file): + # Dump the database to text file + + # TODO def dump_table(self, file, table): + # Dump a table to text file + + # TODO def export2sql(self, host, file): + # Export virtual host information from Mnesia tables to SQL files + + # TODO def export_piefxis(self, dir): + # Export data of all users in the server to PIEFXIS files (XEP-0227) + + # TODO def export_piefxis_host(self, dir, host): + # Export data of users in a host to PIEFXIS files (XEP-0227) + + # TODO def gen_html_doc_for_commands(self, file, regexp, examples): + # Generates html documentation for ejabberd_commands + + # TODO def gen_markdown_doc_for_commands(self, file, regexp, examples): + # Generates markdown documentation for ejabberd_commands + @abstractmethod + def get_cookie(self): + """ + Get the Erlang cookie of this node + """ + pass + + @abstractmethod + def get_last(self, user, host): + """ + Get last activity information (self,timestamp and status): + """ + pass + + @abstractmethod + def get_loglevel(self): + """ + Get the current loglevel + """ + pass + + # TODO def get_offline_count(self): + # Get the number of unread offline messages + + # TODO def get_room_affiliations(self, name, service): + # Get the list of affiliations of a MUC room + + # TODO def get_room_occupants(self, name, service): + # Get the list of occupants of a MUC room + + # TODO def get_room_occupants_number(self, name, service): + # Get the number of occupants of a MUC room + + # TODO def get_room_options(self, name, service): + # Get options from a MUC room + @abstractmethod + def get_roster(self, user, server): + """ + Get roster of a local user. + + Note, parameters changed in 15.09 + from ``user, host`` + to ``user, server``. + + Arguments: + + user :: binary + server :: binary + + Result: + + {contacts,{list,{contact,{tuple,[{jid,string}, + {nick,string}, + {subscription,string}, + {ask,string}, + {group,string}]}}}} + + """ + pass + + # TODO get_subscribers(self, name, service): + # List subscribers of a MUC conference + + # TODO get_user_rooms(self, user, host): + # Get the list of rooms where this user is occupant + @abstractmethod + def get_vcard(self, user, host, name): + """ + Get content from a vCard field + """ + pass + + @abstractmethod + def get_vcard2(self, user, host, name, subname): + """ + Get content from a vCard field + """ + pass + + @abstractmethod + def get_vcard2_multi(self, user, host, name, subname): + """ + Get multiple contents from a vCard field + """ + pass + + # TODO def import_dir(self, file): + # Import users data from jabberd14 spool dir + + # TODO def import_file(self, file): + # Import users data from jabberd14 spool file + + # TODO def import_piefxis(self, file): + # Import users data from a PIEFXIS file (XEP-0227) + + # TODO def import_prosody(self, dir) Import data from Prosody + @abstractmethod + def incoming_s2s_number(self): + """ + Number of incoming s2s connections on the node + """ + pass + + # TODO def install_fallback(self, file): + # Install the database from a fallback file + + # TODO def join_cluster(self, node): + # Join this node into the cluster handled by Node + @abstractmethod + def kick_session(self, user, host, resource, reason): + """ + Kick a user session + """ + pass + + @abstractmethod + def kick_user(self, user, host): + """ + Disconnect user's active sessions + """ + pass + + # TODO def leave_cluster(self, node): + # Remove node handled by Node from the cluster + @abstractmethod + def list_cluster(self): + """ + List nodes that are part of the cluster handled by Node + + Result: + + {nodes,{list,{node,atom}}} + + """ + pass + + # TODO def load(self, file): + # Restore the database from text file + + # TODO def mnesia_change_nodename(self, + # oldnodename, + # newnodename, + # oldbackup, + # newbackup): + # Change the erlang node name in a backup file + + # TODO def module_check(self, module): + + # TODO def module_install(self, module): + + # TODO def module_uninstall(self, module): + + # TODO def module_upgrade(self, module): + @abstractmethod + def modules_available(self): + """ + List available modules + """ + pass + + @abstractmethod + def modules_installed(self): + """ + List installed modules + """ + pass + + # TODO def modules_update_specs(self): + + # TODO def muc_online_rooms(self, host): + # List existing rooms (‘global’ to get all vhosts) + + # TODO def muc_unregister_nick(self, nick): + # Unregister the nick in the MUC service + @abstractmethod + def num_active_users(self, host, days): + """ + Get number of users active in the last days + """ + pass + + @abstractmethod + def num_resources(self, user, host): + """ + Get the number of resources of a user + """ + pass + + @abstractmethod + def outgoing_s2s_number(self): + """ + Number of outgoing s2s connections on the node + """ + pass + + # TODO def privacy_set(self, user, host, xmlquery): + # Send a IQ set privacy stanza for a local account + + # TODO def private_get(self, user, host, element, ns): + # Get some information from a user private storage + + # TODO def private_set(self, user, host, element): + # Set to the user private storage @abstractmethod - def get_roster(self, user, host): + def process_rosteritems(self, action, subs, asks, users, contacts): + """ + List or delete rosteritems that match filtering options + """ + pass + + @abstractmethod + def push_alltoall(self, host, group): + """ + Add all the users to all the users of Host in Group + """ + pass + + # TODO def push_roster(self, file, user, host): + # Push template roster from file to a user + + # TODO def push_roster_all(self, file): + # Push template roster from file to all those users + @abstractmethod + def registered_vhosts(self): + """ + List all registered vhosts in SERVER + """ + pass + + @abstractmethod + def reload_config(self): + """ + Reload ejabberd configuration file into memory + + (only affects ACL and Access) + """ + pass + + @abstractmethod + def remove_node(self, node): + """ + Remove an ejabberd node from Mnesia clustering config + """ + pass + + @abstractmethod + def reopen_log(self): + """ + Reopen the log files + """ + pass + + @abstractmethod + def resource_num(self, user, host, num): + """ + Resource string of a session number + """ + pass + + @abstractmethod + def restart(self): + """ + Restart ejabberd + """ + pass + + # TODO def restore(self, file): + # Restore the database from backup file + + # TODO def rooms_unused_destroy(self, host, days): + # Destroy the rooms that are unused for many days in host + + # TODO def rooms_unused_list(self, host, days): + # List the rooms that are unused for many days in host + + # TODO def rotate_log(self): + # Rotate the log files + + # TODO def send_direct_invitation(self, + # name, + # service, + # password, + # reason, + # users): + # Send a direct invitation to several destinations + @abstractmethod + def send_message(self, type, from_jid, to, subject, body): + """ + Send a message to a local or remote bare of full JID + """ + pass + + # TODO def send_stanza(self, from, to, stanza): + # Send a stanza; provide From JID and valid To JID + @abstractmethod + def send_stanza_c2s(self, user, host, resource, stanza): + """ + Send a stanza as if sent from a c2s session + """ + pass + + @abstractmethod + def set_last(self, user, host, timestamp, status): + """ + Set last activity information + """ + pass + + @abstractmethod + def set_loglevel(self, loglevel): + """ + Set the loglevel (0 to 5) + + Arguments: + + loglevel :: integer + + Result: + + {logger,atom} + + """ + pass + + @abstractmethod + def set_master(self, nodename): + """ + Set master node of the clustered Mnesia tables + """ + pass + + @abstractmethod + def set_nickname(self, user, host, nickname): + """ + Set nickname in a user's vCard + """ + pass + + @abstractmethod + def set_presence(self, user, host, resource, type, show, status, priority): + """ + Set presence of a session + """ + pass + + # TODO def set_room_affiliation(self, name, service, jid, affiliation): + # Change an affiliation in a MUC room + + @abstractmethod + def set_vcard(self, user, host, name, content): + """ + Set content in a vCard field + """ + pass + + @abstractmethod + def set_vcard2(self, user, host, name, subname, content): + """ + Set content in a vCard subfield + """ + pass + + @abstractmethod + def set_vcard2_multi(self, user, host, name, subname, contents): + """ + *Set multiple contents in a vCard subfield + """ + pass + + @abstractmethod + def srg_create(self, group, host, name, description, display): + """ + Create a Shared Roster Group + """ + pass + + @abstractmethod + def srg_delete(self, group, host): + """ + Delete a Shared Roster Group + """ + pass + + @abstractmethod + def srg_get_info(self, group, host): + """ + Get info of a Shared Roster Group + """ + pass + + @abstractmethod + def srg_get_members(self, group, host): + """ + Get members of a Shared Roster Group + """ + pass + + @abstractmethod + def srg_list(self, host): + """ + List the Shared Roster Groups in Host + """ + pass + + @abstractmethod + def srg_user_add(self, user, host, group, grouphost): + """ + Add the JID user@host to the Shared Roster Group + """ + pass + + @abstractmethod + def srg_user_del(self, user, host, group, grouphost): + """ + Delete this JID user@host from the Shared Roster Group + """ + pass + + @abstractmethod + def stats(self, name): + """ + Get statistical value: + + * ``registeredusers`` + * ``onlineusers`` + * ``onlineusersnode`` + * ``uptimeseconds`` + * ``processes`` - Introduced sometime after Ejabberd 15.07 + """ + pass + + @abstractmethod + def stats_host(self, name, host): + """ + Get statistical value for this host: + + * ``registeredusers`` + * ``onlineusers`` + """ + pass + + @abstractmethod + def status(self): + """ + Get ejabberd status + """ + pass + + @abstractmethod + def status_list(self, status): + """ + List of logged users with this status + """ + pass + + @abstractmethod + def status_list_host(self, host, status): + """ + List of users logged in host with their statuses + """ + pass + + @abstractmethod + def status_num(self, status): + """ + Number of logged users with this status + """ + pass + + @abstractmethod + def status_num_host(self, host, status): + """ + Number of logged users with this status in host + """ + pass + + @abstractmethod + def stop(self): + """ + Stop ejabberd + """ + pass + + @abstractmethod + def stop_kindly(self, delay, announcement): + """ + Inform users and rooms, wait, and stop the server + """ + pass + + # TODO def subscribe_room(self, user, nick, room, nodes): + # Subscribe to a MUC conference + + # TODO def unsubscribe_room(self, user, room): + # Unsubscribe from a MUC conference + + @abstractmethod + def update(self, module): + """ + Update the given module, or use the keyword: all + """ + pass + + @abstractmethod + def update_list(self): + """ + List modified modules that can be updated + """ + pass + + @abstractmethod + def user_resources(self, user, server): + """ + List user's connected resources + + Note, parameters changed in 15.09 + from ``user, host`` + to ``user, server``. + + Arguments: + + user :: binary + server :: binary + + Result: + + {resources,{list,{resource,string}}} + + """ pass From 1aa405514dc9edb6982b7fb4c6fcf2d04659d44e Mon Sep 17 00:00:00 2001 From: Dedaldino3D Date: Fri, 13 Nov 2020 22:54:33 +0100 Subject: [PATCH 31/99] Update LICENSE (author codename). :( --- LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index e902b60..bfd5c15 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2020 Dedaldino Antonio +Copyright (c) 2020 Dedaldino Antonio, a.k.a 3D Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal From 216004c8f88b520b072cad4961cf8a13bf0407f7 Mon Sep 17 00:00:00 2001 From: Dedaldino3D Date: Fri, 13 Nov 2020 22:55:32 +0100 Subject: [PATCH 32/99] Add utils --- ejabberd_python3d/core/errors.py | 6 ++---- ejabberd_python3d/core/utils.py | 33 ++++++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 4 deletions(-) create mode 100644 ejabberd_python3d/core/utils.py diff --git a/ejabberd_python3d/core/errors.py b/ejabberd_python3d/core/errors.py index b31bcaa..07d379e 100644 --- a/ejabberd_python3d/core/errors.py +++ b/ejabberd_python3d/core/errors.py @@ -1,12 +1,10 @@ - - class ConnectionError(Exception): """Error when connecting to API""" pass class AccessDeniedError(Exception): - """Access denied, account unprevileged""" + """Access denied, account unprivileged""" pass @@ -14,6 +12,7 @@ class MissingArguments(ValueError): """Missing arguments in call""" pass + class BadArgument(ValueError): """Wrong Argument""" pass @@ -22,4 +21,3 @@ class BadArgument(ValueError): class UserAlreadyRegisteredError(Exception): """User already registered""" pass - \ No newline at end of file diff --git a/ejabberd_python3d/core/utils.py b/ejabberd_python3d/core/utils.py new file mode 100644 index 0000000..7059fd2 --- /dev/null +++ b/ejabberd_python3d/core/utils.py @@ -0,0 +1,33 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +import hashlib +from builtins import range, int + +from six import b + + +def _format_digest(hexdigest): # pragma: no cover + parts = [] + hexdigest_size = int(len(hexdigest) / 2) + for i in range(hexdigest_size): + part = hexdigest[i * 2:(i * 2) + 2] + if part == '00': + part = '' + elif part.startswith('0'): + part = part[1:] + parts.append(part) + return (''.join(parts)).upper() + + +def _format_password_hash(password, hash_method): # pragma: no cover + hash_method.update(b(password)) + return _format_digest(hash_method.hexdigest()) + + +def format_password_hash_sha(password): + return _format_password_hash(password, hashlib.sha1()) + + +def format_password_hash_md5(password): + return _format_password_hash(password, hashlib.md5()) From 9c74ed18398970733eff37d673e8b016373d84f3 Mon Sep 17 00:00:00 2001 From: Dedaldino3D Date: Fri, 13 Nov 2020 23:02:40 +0100 Subject: [PATCH 33/99] Override new methods --- ejabberd_python3d/client.py | 524 +++++++++++++++++++----------------- 1 file changed, 275 insertions(+), 249 deletions(-) diff --git a/ejabberd_python3d/client.py b/ejabberd_python3d/client.py index a22232a..d0efb0c 100644 --- a/ejabberd_python3d/client.py +++ b/ejabberd_python3d/client.py @@ -1,34 +1,37 @@ +from __future__ import print_function + import xmlrpc import copy -from __future__ import print_function -from httplib import BadStatusLine from urllib.parse import urlparse +from ejabberd_python3d.core.errors import MissingArguments from .abc import api, methods -from .defaults import XMLRPC_API_PROTOCOL, XMLRPC_API_PORT +from .defaults.constants import XMLRPC_API_PROTOCOL, XMLRPC_API_PORT class EjabberdAPIClient(api.EjabberdBaseAPI): - ''' + """ Python client for Ejabberd XML-RPC Administration API. - ''' - def __init__(self, - host, username, password, - server='127.0.0.1', port=4560, protocol='http', - admin=True, verbose=False): - ''' + """ + + def __init__(self, host, username, password, server='127.0.0.1', port=4560, protocol='http', admin=True, + verbose=False): + """ Init XML-RPC server proxy. - ''' + """ + super().__init__(self) self.host = host self.username = username self.password = password self.server = server + self.port = port + self.admin = admin self.protocol = protocol or XMLRPC_API_PROTOCOL self.verbose = verbose self._server_proxy = None @staticmethod - def get_instance(service_url, verbose=Fase): + def get_instance(service_url, verbose=False): """ Return a EjabberdAPIClient instance based on a '12factor app' compliant service_url @@ -60,13 +63,12 @@ def get_instance(service_url, verbose=Fase): host, port = server_parts port = int(port) else: - host, port = server_parts[0],XMLRPC_API_PORT + host, port = server_parts[0], XMLRPC_API_PORT path_parts = o.path.lstrip('/').split('/') assert len(path_parts) == 1, format_error - server =path_parts[0] - return EjabberdAPIClient(host, username, password,server, port,protocol=protocol, verbose=verbose) - + server = path_parts[0] + return EjabberdAPIClient(host, username, password, server, port, protocol=protocol, verbose=verbose) @property def service_url(self): @@ -76,16 +78,15 @@ def service_url(self): """ return "{}://{}:{}/".format(self.protocol, self.host, self.port) - @property def server_proxy(self): """ Returns the proxy object that is used to perform the calls to the XML-RPC endpoint """ if self._server_proxy is None: - self._server_proxy = xmlrpc.client.ServerProxy(self.service_url, verbose(1 if self.verbose else 0)) + self._server_proxy = xmlrpc.client.ServerProxy(self.service_url, verbose=(1 if self.verbose else 0)) return self._server_proxy - + @property def auth(self): """ @@ -96,7 +97,6 @@ def auth(self): 'server': self.server, 'password': self.password } - def _validate_and_serialize_arguments(self, api, arguments): """ @@ -117,24 +117,24 @@ def _validate_and_serialize_arguments(self, api, arguments): arg_name = str(arg_desc.name) if arg_desc.required and arg_name not in arguments: raise MissingArguments("Missing required argument '%s'" % arg_name) - + # serialize argument value ser_args[arg_desc.name] = arg_desc.serializer_class().to_api(arguments.get(arg_name)) - + return ser_args def _report_method_call(self, method, arguments): """ Internal method to print info about a method call - :param method: The name oft hem ethod to call + :param method: The name oft hem method to call :type method: str|unicode :param arguments: A dictionary of arguments that will be passed to the method :type: arguments: dict :return: """ if self.verbose: - print("===> %s(%s)" %(method, ', '.join(['%s=%s' % (k,v) for k,v in arguments.items()]))) - + print("===> %s(%s)" % (method, ', '.join(['%s=%s' % (k, v) for k, v in arguments.items()]))) + def _call_api(self, api_class, **kwargs): """ Internal method used to perform api calls @@ -146,7 +146,7 @@ def _call_api(self, api_class, **kwargs): :return: Returns return value of the XMLRPC Method call """ # validate api_class - assert issubclass(api_class, API) + assert issubclass(api_class, api.API) # create api instance api = api_class() @@ -155,9 +155,9 @@ def _call_api(self, api_class, **kwargs): # transform arguments args = api.transform_arguments(**args) - # validate and serializer arguments + # validate and serialize arguments args = self._validate_and_serialize_arguments(api, args) - # retrive method + # retrieve method method = getattr(self.server_proxy, str(api.method)) # print method call with arguments @@ -168,10 +168,10 @@ def _call_api(self, api_class, **kwargs): response = method(args) else: response = method(self.auth, args) - + # validate response api.validate_response(api, args, response) - # tranform response + # transform response result = api.transform_response(api, args, response) return result @@ -211,9 +211,9 @@ def unregister(self, user, host): :param host: The XMPP_DOMAIN :type host: str|unicode :rtype: bool - :return: A boolean indicating if the unregistration has succeeded + :return: A boolean indicating if user unregistered has succeeded """ - return self._call_api(methods.UnRegister, user=user, host=host) + return self._call_api(methods.Unregister, user=user, host=host) def change_password(self, user, host, newpass): """ @@ -247,78 +247,78 @@ def add_rosteritem(self, localuser, localserver, user, server, nick, group, subs): - ''' + """ Add an item to a user's roster (self,supports ODBC): - ''' + """ return self._call_api('add_rosteritem', {'localuser': localuser, - 'localserver': localserver, - 'user': user, - 'server': server, - 'nick': nick, - 'group': group, - 'subs': subs}) + 'localserver': localserver, + 'user': user, + 'server': server, + 'nick': nick, + 'group': group, + 'subs': subs}) # TODO def backup(self, file): Store the database to backup file def ban_account(self, user, host, reason): - ''' + """ Ban an account: kick sessions and set random password - ''' + """ return self._call_api('ban_account', {'user': user, - 'host': host, - 'reason': reason}) + 'host': host, + 'reason': reason}) # TODO def change_room_option(self, name, service, option, value) # Change an option in a MUC room def check_account(self, user, host): - ''' + """ Check if an account exists or not - ''' + """ return self._call_api('check_account', {'user': user, 'host': host}) def check_password(self, user, host, password): - ''' + """ Check if a password is correct - ''' + """ return self._call_api('check_password', {'user': user, - 'host': host, - 'password': password}) + 'host': host, + 'password': password}) def check_password_hash(self, user, host, passwordhash, hashmethod): - ''' + """ Check if the password hash is correct - ''' + """ return self._call_api('check_password_hash', {'user': user, - 'host': host, - 'passwordhash': passwordhash, - 'hashmethod': hashmethod}) + 'host': host, + 'passwordhash': passwordhash, + 'hashmethod': hashmethod}) # TODO def compile(self, file): # Recompile and reload Erlang source code file def connected_users(self): - ''' + """ List all established sessions - ''' + """ return self._call_api('connected_users') def connected_users_info(self): - ''' + """ List all established sessions and their information - ''' + """ return self._call_api('connected_users_info') def connected_users_number(self): - ''' + """ Get the number of established sessions - ''' + """ return self._call_api('connected_users_number') def connected_users_vhost(self, host): - ''' + """ Get the list of established sessions in a vhost - ''' + """ return self._call_api('connected_users_vhost', {'host': host}) # TODO def convert_to_scram(self, host): @@ -337,9 +337,9 @@ def connected_users_vhost(self, host): # Create the rooms indicated in file def delete_expired_messages(self): - ''' + """ Delete expired offline messages from database - ''' + """ return self._call_api('delete_expired_messages') # TODO def delete_mnesia(self, host): @@ -349,33 +349,33 @@ def delete_expired_messages(self): # Delete MAM messages older than DAYS def delete_old_messages(self, days): - ''' + """ Delete offline messages older than DAYS - ''' + """ return self._call_api('delete_old_messages', {'days': days}) def delete_old_users(self, days): - ''' + """ Delete users that didn't log in last days, or that never logged - ''' + """ return self._call_api('delete_old_users', {'days': days}) def delete_old_users_vhost(self, host, days): - ''' + """ Delete users that didn't log in last days in vhost, or that never logged - ''' + """ return self._call_api('delete_old_users_vhost', - {'host': host, 'days': days}) + {'host': host, 'days': days}) def delete_rosteritem(self, localuser, localserver, user, server): - ''' + """ Delete an item from a user's roster (self,supports ODBC): - ''' + """ return self._call_api('delete_rosteritem', {'localuser': localuser, - 'localserver': localserver, - 'user': user, - 'server': server}) + 'localserver': localserver, + 'user': user, + 'server': server}) # TODO def destroy_room(self, name, service): # Destroy a MUC room @@ -404,22 +404,49 @@ def delete_rosteritem(self, localuser, localserver, user, server): # TODO def gen_markdown_doc_for_commands(self, file, regexp, examples): # Generates markdown documentation for ejabberd_commands + def muc_online_rooms(self, host=None): + pass + + def create_room(self, name, service, host): + pass + + def destroy_room(self, name, service, host): + pass + + def get_room_options(self, name, service): + pass + + def change_room_option(self, name, service, option, value): + pass + + def set_room_affiliation(self, name, service, jid, affiliation): + pass + + def get_room_affiliations(self, name, service): + pass + + def add_roster_item(self, localuser, localserver, user, server, nick, group, subs): + pass + + def remove_rosteritem(self, localuser, localserver, user, server): + pass + def get_cookie(self): - ''' + """ Get the Erlang cookie of this node - ''' + """ return self._call_api('get_cookie') def get_last(self, user, host): - ''' + """ Get last activity information (self,timestamp and status): - ''' + """ return self._call_api('get_last', {'user': user, 'host': host}) def get_loglevel(self): - ''' + """ Get the current loglevel - ''' + """ return self._call_api('get_loglevel') # TODO def get_offline_count(self): @@ -438,7 +465,7 @@ def get_loglevel(self): # Get options from a MUC room def get_roster(self, user, server): - ''' + """ Get roster of a local user. Note, parameters changed in 15.09 @@ -458,7 +485,7 @@ def get_roster(self, user, server): {ask,string}, {group,string}]}}}} - ''' + """ try: return self._call_api('get_roster', {'user': user, 'server': server}) except: @@ -471,30 +498,30 @@ def get_roster(self, user, server): # Get the list of rooms where this user is occupant def get_vcard(self, user, host, name): - ''' + """ Get content from a vCard field - ''' + """ return self._call_api('get_vcard', {'user': user, - 'host': host, - 'name': name}) + 'host': host, + 'name': name}) def get_vcard2(self, user, host, name, subname): - ''' + """ Get content from a vCard field - ''' + """ return self._call_api('get_vcard2', {'user': user, - 'host': host, - 'name': name, - 'subname': subname}) + 'host': host, + 'name': name, + 'subname': subname}) def get_vcard2_multi(self, user, host, name, subname): - ''' + """ Get multiple contents from a vCard field - ''' + """ return self._call_api('get_vcard2_multi', {'user': user, - 'host': host, - 'name': name, - 'subname': subname}) + 'host': host, + 'name': name, + 'subname': subname}) # TODO def import_dir(self, file): # Import users data from jabberd14 spool dir @@ -508,9 +535,9 @@ def get_vcard2_multi(self, user, host, name, subname): # TODO def import_prosody(self, dir) Import data from Prosody def incoming_s2s_number(self): - ''' + """ Number of incoming s2s connections on the node - ''' + """ return self._call_api('incoming_s2s_number') # TODO def install_fallback(self, file): @@ -520,35 +547,35 @@ def incoming_s2s_number(self): # Join this node into the cluster handled by Node def kick_session(self, user, host, resource, reason): - ''' + """ Kick a user session - ''' + """ return self._call_api('kick_session', {'user': user, - 'host': host, - 'resource': resource, - 'reason': reason}) + 'host': host, + 'resource': resource, + 'reason': reason}) def kick_user(self, user, host): - ''' + """ Disconnect user's active sessions - ''' + """ return self._call_api('kick_user', {'user': user, 'host': host}) # TODO def leave_cluster(self, node): # Remove node handled by Node from the cluster def list_cluster(self): - ''' + """ List nodes that are part of the cluster handled by Node Result: {nodes,{list,{node,atom}}} - ''' + """ try: return self._call_api('list_cluster') - except xmlrpc.Fault, e: + except xmlrpc.client.Fault as e: msg = 'list_cluster is NOT available in your version of ejabberd' raise Exception('{}\n{}\n'.format(msg, e.message)) @@ -571,15 +598,15 @@ def list_cluster(self): # TODO def module_upgrade(self, module): def modules_available(self): - ''' + """ List available modules - ''' + """ return self._call_api('modules_available') def modules_installed(self): - ''' + """ List installed modules - ''' + """ return self._call_api('modules_installed') # TODO def modules_update_specs(self): @@ -591,21 +618,21 @@ def modules_installed(self): # Unregister the nick in the MUC service def num_active_users(self, host, days): - ''' + """ Get number of users active in the last days - ''' + """ return self._call_api('num_active_users', {'host': host, 'days': days}) def num_resources(self, user, host): - ''' + """ Get the number of resources of a user - ''' + """ return self._call_api('num_resources', {'user': user, 'host': host}) def outgoing_s2s_number(self): - ''' + """ Number of outgoing s2s connections on the node - ''' + """ return self._call_api('outgoing_s2s_number') # TODO def privacy_set(self, user, host, xmlquery): @@ -618,19 +645,19 @@ def outgoing_s2s_number(self): # Set to the user private storage def process_rosteritems(self, action, subs, asks, users, contacts): - ''' + """ List or delete rosteritems that match filtering options - ''' + """ return self._call_api('process_rosteritems', {'action': action, - 'subs': subs, - 'asks': asks, - 'users': users, - 'contacts': contacts}) + 'subs': subs, + 'asks': asks, + 'users': users, + 'contacts': contacts}) def push_alltoall(self, host, group): - ''' + """ Add all the users to all the users of Host in Group - ''' + """ return self._call_api('push_alltoall', {'host': host, 'group': group}) # TODO def push_roster(self, file, user, host): @@ -639,45 +666,44 @@ def push_alltoall(self, host, group): # TODO def push_roster_all(self, file): # Push template roster from file to all those users - def registered_vhosts(self): - ''' + """ List all registered vhosts in SERVER - ''' + """ return self._call_api('registered_vhosts') def reload_config(self): - ''' + """ Reload ejabberd configuration file into memory (only affects ACL and Access) - ''' + """ return self._call_api('reload_config') def remove_node(self, node): - ''' + """ Remove an ejabberd node from Mnesia clustering config - ''' + """ return self._call_api('remove_node', {'node': node}) def reopen_log(self): - ''' + """ Reopen the log files - ''' + """ return self._call_api('reopen_log') def resource_num(self, user, host, num): - ''' + """ Resource string of a session number - ''' + """ return self._call_api('resource_num', {'user': user, - 'host': host, - 'num': num}) + 'host': host, + 'num': num}) def restart(self): - ''' + """ Restart ejabberd - ''' + """ return self._call_api('restart') # TODO def restore(self, file): @@ -701,38 +727,38 @@ def restart(self): # Send a direct invitation to several destinations def send_message(self, type, from_jid, to, subject, body): - ''' + """ Send a message to a local or remote bare of full JID - ''' + """ return self._call_api('send_message', {'type': type, - 'from': from_jid, - 'to': to, - 'subject': subject, - 'body': body}) + 'from': from_jid, + 'to': to, + 'subject': subject, + 'body': body}) # TODO def send_stanza(self, from, to, stanza): # Send a stanza; provide From JID and valid To JID def send_stanza_c2s(self, user, host, resource, stanza): - ''' + """ Send a stanza as if sent from a c2s session - ''' + """ return self._call_api('send_stanza_c2s', {'user': user, - 'host': host, - 'resource': resource, - 'stanza': stanza}) + 'host': host, + 'resource': resource, + 'stanza': stanza}) def set_last(self, user, host, timestamp, status): - ''' + """ Set last activity information - ''' + """ return self._call_api('set_last', {'user': user, - 'host': host, - 'timestamp': timestamp, - 'status': status}) + 'host': host, + 'timestamp': timestamp, + 'status': status}) def set_loglevel(self, loglevel): - ''' + """ Set the loglevel (0 to 5) Arguments: @@ -743,125 +769,125 @@ def set_loglevel(self, loglevel): {logger,atom} - ''' + """ try: return self._call_api('set_loglevel', {'loglevel': loglevel}) - except xmlrpc.Fault, e: + except xmlrpc.Fault as e: msg = 'set_loglevel is NOT available in your version of ejabberd' raise Exception('{}\n{}\n'.format(msg, e.message)) def set_master(self, nodename): - ''' + """ Set master node of the clustered Mnesia tables - ''' + """ return self._call_api('set_master', {'nodename': nodename}) def set_nickname(self, user, host, nickname): - ''' + """ Set nickname in a user's vCard - ''' + """ return self._call_api('set_nickname', {'user': user, - 'host': host, - 'nickname': nickname}) + 'host': host, + 'nickname': nickname}) def set_presence(self, user, host, resource, type, show, status, priority): - ''' + """ Set presence of a session - ''' + """ return self._call_api('set_presence', {'user': user, - 'host': host, - 'resource': resource, - 'type': type, - 'show': show, - 'status': status, - 'priority': priority}) + 'host': host, + 'resource': resource, + 'type': type, + 'show': show, + 'status': status, + 'priority': priority}) # TODO def set_room_affiliation(self, name, service, jid, affiliation): # Change an affiliation in a MUC room def set_vcard(self, user, host, name, content): - ''' + """ Set content in a vCard field - ''' + """ return self._call_api('set_vcard', {'user': user, - 'host': host, - 'name': name, - 'content': content}) + 'host': host, + 'name': name, + 'content': content}) def set_vcard2(self, user, host, name, subname, content): - ''' + """ Set content in a vCard subfield - ''' + """ return self._call_api('set_vcard2', {'user': user, - 'host': host, - 'name': name, - 'subname': subname, - 'content': content}) + 'host': host, + 'name': name, + 'subname': subname, + 'content': content}) def set_vcard2_multi(self, user, host, name, subname, contents): - ''' + """ *Set multiple contents in a vCard subfield - ''' + """ return self._call_api('set_vcard2_multi', {'user': user, - 'host': host, - 'name': name, - 'subname': subname, - 'contents': contents}) + 'host': host, + 'name': name, + 'subname': subname, + 'contents': contents}) def srg_create(self, group, host, name, description, display): - ''' + """ Create a Shared Roster Group - ''' + """ return self._call_api('srg_create', {'group': group, - 'host': host, - 'name': name, - 'description': description, - 'display': display}) + 'host': host, + 'name': name, + 'description': description, + 'display': display}) def srg_delete(self, group, host): - ''' + """ Delete a Shared Roster Group - ''' + """ return self._call_api('srg_delete', {'group': group, 'host': host}) def srg_get_info(self, group, host): - ''' + """ Get info of a Shared Roster Group - ''' + """ return self._call_api('srg_get_info', {'group': group, 'host': host}) def srg_get_members(self, group, host): - ''' + """ Get members of a Shared Roster Group - ''' + """ return self._call_api('srg_get_members', {'group': group, 'host': host}) def srg_list(self, host): - ''' + """ List the Shared Roster Groups in Host - ''' + """ return self._call_api('srg_list', {'host': host}) def srg_user_add(self, user, host, group, grouphost): - ''' + """ Add the JID user@host to the Shared Roster Group - ''' + """ return self._call_api('srg_user_add', {'user': user, - 'host': host, - 'group': group, - 'grouphost': grouphost}) + 'host': host, + 'group': group, + 'grouphost': grouphost}) def srg_user_del(self, user, host, group, grouphost): - ''' + """ Delete this JID user@host from the Shared Roster Group - ''' + """ return self._call_api('srg_user_del', {'user': user, - 'host': host, - 'group': group, - 'grouphost': grouphost}) + 'host': host, + 'group': group, + 'grouphost': grouphost}) def stats(self, name): - ''' + """ Get statistical value: * ``registeredusers`` @@ -869,93 +895,93 @@ def stats(self, name): * ``onlineusersnode`` * ``uptimeseconds`` * ``processes`` - Introduced sometime after Ejabberd 15.07 - ''' + """ try: return self._call_api('stats', {'name': name}) - except Exception, e: + except Exception as e: msg = 'processes stats NOT available in this version of Ejabberd' if e.message == self.errors['connect']: raise Exception('{}\n{}\n'.format(msg, e.message)) raise Exception(e) def stats_host(self, name, host): - ''' + """ Get statistical value for this host: * ``registeredusers`` * ``onlineusers`` - ''' + """ return self._call_api('stats_host', {'name': name, 'host': host}) def status(self): - ''' + """ Get ejabberd status - ''' + """ return self._call_api('status') def status_list(self, status): - ''' + """ List of logged users with this status - ''' + """ return self._call_api('status_list', {'status': status}) def status_list_host(self, host, status): - ''' + """ List of users logged in host with their statuses - ''' + """ return self._call_api('status_list_host', {'host': host, 'status': status}) def status_num(self, status): - ''' + """ Number of logged users with this status - ''' + """ return self._call_api('status_num', {'status': status}) def status_num_host(self, host, status): - ''' + """ Number of logged users with this status in host - ''' + """ return self._call_api('status_num_host', {'host': host, 'status': status}) def stop(self): - ''' + """ Stop ejabberd - ''' + """ return self._call_api('stop') def stop_kindly(self, delay, announcement): - ''' + """ Inform users and rooms, wait, and stop the server - ''' + """ return self._call_api('stop_kindly', - {'delay': delay, 'announcement': announcement}) + {'delay': delay, 'announcement': announcement}) # TODO def subscribe_room(self, user, nick, room, nodes): # Subscribe to a MUC conference def unregister(self, user, host): - ''' + """ Unregister a user - ''' + """ return self._call_api('unregister', {'user': user, 'host': host}) # TODO def unsubscribe_room(self, user, room): # Unsubscribe from a MUC conference def update(self, module): - ''' + """ Update the given module, or use the keyword: all - ''' + """ return self._call_api('update', {'module': module}) def update_list(self): - ''' + """ List modified modules that can be updated - ''' + """ return self._call_api('update_list') def user_resources(self, user, server): - ''' + """ List user's connected resources Note, parameters changed in 15.09 @@ -971,14 +997,14 @@ def user_resources(self, user, server): {resources,{list,{resource,string}}} - ''' + """ try: return self._call_api('user_resources', {'user': user, 'server': server}) - except: + except Exception: return self._call_api('user_resources', {'user': user, 'host': server}) def user_sessions_info(self, user, host): - ''' + """ Get information about all sessions of a user - ''' + """ return self._call_api('user_sessions_info', {'user': user, 'host': host}) From bd5a86c202bca39fcf48f5e4781a7d3671f473fd Mon Sep 17 00:00:00 2001 From: Dedaldino3D Date: Fri, 13 Nov 2020 23:03:26 +0100 Subject: [PATCH 34/99] Implement new methods --- ejabberd_python3d/abc/api.py | 4 - ejabberd_python3d/abc/methods.py | 245 ++++++++++++++++++++++++++++--- 2 files changed, 221 insertions(+), 28 deletions(-) diff --git a/ejabberd_python3d/abc/api.py b/ejabberd_python3d/abc/api.py index 4e9e603..d19dfca 100644 --- a/ejabberd_python3d/abc/api.py +++ b/ejabberd_python3d/abc/api.py @@ -113,10 +113,6 @@ def change_password(self, user, host, password, newpass): def check_password_hash(self, user, host, password): pass - @abstractmethod - def set_nickname(self, user, host, nickname): - pass - @abstractmethod def connected_users(self): pass diff --git a/ejabberd_python3d/abc/methods.py b/ejabberd_python3d/abc/methods.py index 8b6a983..7bc4a99 100644 --- a/ejabberd_python3d/abc/methods.py +++ b/ejabberd_python3d/abc/methods.py @@ -1,21 +1,17 @@ from __future__ import unicode_literals -from ..defaults.arguments import StringArgument -from ..abc.api import api -from ..serializers import StringSerializer -from ..muc import muc_room_options_serializers -from ..muc.arguments imprt MUCRoomArgument, AffiliationArgument - -# TODO: implement this methos +from ..abc.api import API from ..core.errors import UserAlreadyRegisteredError -from ..muc.enums import Affiliation -# TODO: implement this method from ..core.utils import format_password_hash_sha - +from ..defaults.arguments import StringArgument +from ..muc import muc_room_options_serializers +from ..muc.arguments import MUCRoomArgument, AffiliationArgument +from ..muc.enums import Affiliation, MUCRoomOption +from ..serializers import StringSerializer, IntegerSerializer class Echo(API): - method = 'echothisnew' + method = 'dedaldino_denis_3D' arguments = [StringArgument('sentence')] def transform_response(self, api, arguments, response): @@ -32,16 +28,16 @@ def transform_response(self, api, arguments, response): class Register(API): method = 'register' - arguments = [StringArgument('user'), StringArgument('host'),StringArgument('password')] + arguments = [StringArgument('user'), StringArgument('host'), StringArgument('password')] def validate_response(self, api, arguments, response): if response.get('res') == 1: - username = arguments.get('uer') + username = arguments.get('user') raise UserAlreadyRegisteredError('User with username %s already exist' % username) - + def transform_response(self, api, arguments, response): return response.get('res') == 0 - + class Unregister(API): method = 'unregister' @@ -51,7 +47,6 @@ def transform_response(self, api, arguments, response): return response.get('res') == 0 - class ChangePassword(API): method = 'change_password' arguments = [StringArgument('user'), StringArgument('host'), StringArgument('newpass')] @@ -84,6 +79,7 @@ class SetNickname(API): def transform_response(self, api, arguments, response): return response.get('res') == 0 + class ConnectedUsers(API): method = 'connected_users' arguments = [] @@ -93,6 +89,7 @@ def transform_response(self, api, arguments, response): return [user["sessions"] for user in connected_users] + class ConnectedUsersInfo(API): method = 'connected_users_info' arguments = [] @@ -102,6 +99,7 @@ def transform_response(self, api, arguments, response): return [user["sessions"] for user in connected_users_info] + class ConnectedUsersNumber(API): method = 'connected_users_number' arguments = [] @@ -109,6 +107,7 @@ class ConnectedUsersNumber(API): def transform_response(self, api, arguments, response): return response.get('num_sessions') + class UserSessionInfo(API): method = 'user_sessions_info' arguments = [StringArgument('user'), StringArgument('host')] @@ -120,13 +119,6 @@ def transform_response(self, api, arguments, response): for session in sessions_info ] -class MucOnlineRooms(API): - method = 'muc_online_rooms' - arguments = [StringArgument('host')] - - def transform_response(self, api, arguments, response): - return [result_dict.get('room') for result_dict in response.get('rooms', {})] - class CreateRoom(API): method = 'create_room' @@ -153,7 +145,7 @@ def transform_response(self, api, arguments, response): for option_dict in response.get('options', []): option = option_dict.get('option') if option is None: - raise ValueError('Unexpected option in response: ' % str(option_dict)) + raise ValueError('Unexpected option in response: {}'.format(str(option_dict))) name_dict, value_dict = option result[name_dict['name']] = value_dict['value'] return result @@ -174,6 +166,14 @@ def transform_response(self, api, arguments, response): return response.get('res') == 0 +class GetRoomAffiliation(API): + method = 'get_room_affiliation' + arguments = [StringArgument('name'), StringArgument('service'), StringArgument('jid')] + + def transform_response(self, api, arguments, response): + return response.get('affiliation') == 0 + + class SetRoomAffiliation(API): method = 'set_room_affiliation' arguments = [StringArgument('name'), StringArgument('service'), StringArgument('jid'), @@ -229,3 +229,200 @@ def transform_response(self, api, arguments, response): contact_details[key] = value roster.append(contact_details) return roster + + +class Backup(API): + method = 'backup' + arguments = [StringArgument('file')] + + def transform_response(self, api, arguments, response): + return response.get('res').lower() == "success" + + +class BanAccount(API): + method = 'ban_account' + arguments = [StringArgument('user'), StringArgument('host'), StringArgument('reason')] + + def transform_response(self, api, arguments, response): + return response.get('res') == 0 + + +class BookmarksToPEP(API): + method = 'bookmarks_to_pep' + arguments = [StringArgument('user'), StringArgument('host')] + + def transform_response(self, api, arguments, response): + return response.get('res') + + +class CheckAccount(API): + method = 'check_account' + arguments = [StringArgument('user'), StringArgument('host')] + + def transform_response(self, api, arguments, response): + return response.get('res') == 0 + + +class ClearCache(API): + method = 'clear_cache' + arguments = [] + + def transform_response(self, api, arguments, response): + return response.get('res') == 0 + + +class Compile(API): + method = 'compile' + arguments = [StringArgument('file')] + + def transform_response(self, api, arguments, response): + return response.get('res') == 0 + + +class ConnectedUsersVhost(API): + method = 'connected_users_vhost' + arguments = [StringArgument('host')] + + def transform_response(self, api, arguments, response): + return response.get('connected_users_vhost', []) + + +class ConvertToSCRAM(API): + method = 'convert_to_scram' + arguments = [StringArgument('host')] + + def transform_response(self, api, arguments, response): + return response.get('res') == 0 + + +class CreateRoomWithOPTS(API): + method = "create_room_with_opts" + # TODO: add argument options: [{name::string,value::string}]: List of options + arguments = [StringArgument('name'), StringArgument('service'), StringArgument('host')] + + def transform_response(self, api, arguments, response): + return response.get('res') == 0 + + +class GetLast(API): + method = 'get_last' + arguments = [StringArgument('user'), StringArgument('host')] + + def transform_response(self, api, arguments, response): + return response.get('last_activity', {}) + + +class GetOfflineCount(API): + method = "get_offline_count" + arguments = [StringArgument('user'), StringArgument('server')] + + def transform_response(self, api, arguments, response): + return response.get('value') + + +class GetPresence(API): + method = "get_presence" + arguments = [StringArgument('user'), StringArgument('host')] + + def transform_response(self, api, arguments, response): + return response.get('presence') + + +class GetRoomOccupants(API): + method = 'get_room_occupants' + arguments = [StringArgument('name'), StringArgument('service')] + + def transform_response(self, api, arguments, response): + return response.get('occupants') + + +class GetRoomOccupantsNumber(API): + method = 'get_room_occupants_number' + arguments = [StringArgument('name'), StringArgument('service')] + + def transform_response(self, api, arguments, response): + return response.get('occupants') + + +class GetSubscrivers(API): + method = 'get_subscribers' + arguments = [StringArgument('name'), StringArgument('service')] + + def transform_response(self, api, arguments, response): + return response.get('subscribers') + + +class GetUserRooms(API): + method = 'get_user_rooms' + arguments = [StringArgument('user'), StringArgument('host')] + + def transform_response(self, api, arguments, response): + return response.get('rooms') + + +class MucOnlineRooms(API): + method = "muc_online_rooms" + arguments = [StringArgument('service')] + + def transform_response(self, api, arguments, response): + return response.get('rooms') + + +class MucOnlineRoomsByRegex(API): + method = "muc_online_rooms_bt_regex" + arguments = [StringArgument('service'), StringArgument('regex')] + + def transform_response(self, api, arguments, response): + return response.get('rooms') + + +class MucRegisterNick(API): + method = "muc_register_nick" + arguments = [StringArgument('nick'), StringArgument('service'), StringArgument('service')] + + def transform_response(self, api, arguments, response): + return response.get('res') == 0 + + +class MucUnRegisterNick(API): + method = "muc_unregister_nick" + arguments = [StringArgument('service'), StringArgument('service')] + + def transform_response(self, api, arguments, response): + return response.get('res') == 0 + + +class SendMessage(API): + method = "send_message" + arguments = [StringArgument('type'), StringArgument('from'), StringArgument('to'), StringArgument('subject'), + StringArgument('body')] + + def transform_response(self, api, arguments, response): + return response.get('res') == 0 + + +class SetLast(API): + method = "set_last" + arguments = [StringArgument('user'), StringArgument('host'), IntegerSerializer('timestamp'), + StringArgument('status')] + + def transform_response(self, api, arguments, response): + return response.get('res') == 0 + + +class SubscribeRoom(API): + method = "subscribe_room" + # TODO: nodes must be separated by commas, so therefore you can use an array and before send transform arguments + arguments = [StringArgument('user'), StringArgument('nick'), StringArgument('room'), + StringArgument('nodes')] + + def transform_response(self, api, arguments, response): + return response.get('nodes') + + +class UnSubscribeRoom(API): + method = "unsubscribe_room" + arguments = [StringArgument('user'), StringArgument('room')] + + def transform_response(self, api, arguments, response): + return response.get('res') == 0 From b0e92a141f8b279d0946ce256a49abed4ad315be Mon Sep 17 00:00:00 2001 From: Dedaldino3D Date: Fri, 13 Nov 2020 23:04:01 +0100 Subject: [PATCH 35/99] Refactor code (code cleanup) --- ejabberd_python3d/__init__.py | 2 +- ejabberd_python3d/defaults/constants.py | 3 --- ejabberd_python3d/serializers.py | 18 ++++++------------ 3 files changed, 7 insertions(+), 16 deletions(-) diff --git a/ejabberd_python3d/__init__.py b/ejabberd_python3d/__init__.py index 3bf06b5..fa53baf 100644 --- a/ejabberd_python3d/__init__.py +++ b/ejabberd_python3d/__init__.py @@ -1,2 +1,2 @@ from ejabberd_python3d import client, serializers -from ejabberd_python3d import muc, abc, defaults \ No newline at end of file +from ejabberd_python3d import muc, abc, defaults diff --git a/ejabberd_python3d/defaults/constants.py b/ejabberd_python3d/defaults/constants.py index 5036014..dccd439 100644 --- a/ejabberd_python3d/defaults/constants.py +++ b/ejabberd_python3d/defaults/constants.py @@ -1,6 +1,3 @@ - - - XMLRPC_API_PROTOCOL = 'https' XMLRPC_API_PORT = 4560 XMLRPC_API_SERVER = '127.0.0.1' diff --git a/ejabberd_python3d/serializers.py b/ejabberd_python3d/serializers.py index 9e8de97..0a03d8d 100644 --- a/ejabberd_python3d/serializers.py +++ b/ejabberd_python3d/serializers.py @@ -4,37 +4,34 @@ from .abc.api import Enum, APIArgumentSerializer - class StringSerializer(APIArgumentSerializer): def to_api(self, value): if not isinstance(value, string_types): raise ValueError("Expects str or unicode, but got {}".format(type(value))) return value - + def to_builtin(self, value): if not isinstance(value, string_types): raise ValueError("Expects str or unicode, but got {}".format(type(value))) return value - class IntegerSerializer(APIArgumentSerializer): def to_api(self, value): if not isinstance(value, int): raise ValueError("Expects int or long, but got {}".format(type(value))) return str(value) - + def to_builtin(self, value): return int(value) - class PositiveIntegerSerializer(IntegerSerializer): def to_api(self, value): if not isinstance(value, int) or value < 0: raise ValueError("Expects positive int or long, but got {}".format(type(value))) return super(PositiveIntegerSerializer, self).to_api(value) - + def to_builtin(self, value): res = super(PositiveIntegerSerializer, self).to_builtin(value) if res < 0: @@ -42,26 +39,24 @@ def to_builtin(self, value): return res - class BooleanSerializer(APIArgumentSerializer): def to_api(self, value): if not isinstance(value, bool): raise ValueError("Expects boolean") - return 'true' if value else 'false - + return 'true' if value else 'false' + def to_builtin(self, value): if value not in ('true', 'false'): raise ValueError("Expects true|false, but got {}".format(type(value))) return value == 'true' - class EnumSerializer(ABCMeta, StringSerializer): @abstractmethod @property def enum_class(self): pass - + def to_api(self, value): assert issubclass(self.enum_class, Enum) if isinstance(value, self.enum_class): @@ -79,4 +74,3 @@ def to_builtin(self, value): res = self.enum_class.get_by_name(value) if res is None: raise ValueError("Expects enum value for {}, but got {}".format(self.enum_class, type(value))) - \ No newline at end of file From 49f6b8b6398a12943371bb7332622b63da2e1566 Mon Sep 17 00:00:00 2001 From: Dedaldino3D Date: Fri, 13 Nov 2020 23:35:38 +0100 Subject: [PATCH 36/99] Add requirements --- requirements.txt | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 requirements.txt diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..c4821f4 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,10 @@ +asgiref==3.3.0 +certifi==2020.6.20 +chardet==3.0.4 +coverage==5.3 +Django==3.1.3 +idna==2.10 +pytz==2020.4 +requests==2.24.0 +sqlparse==0.4.1 +urllib3==1.25.11 From 9dd22f7aace822b41fc1111eb4b00ed32ef08b0e Mon Sep 17 00:00:00 2001 From: Dedaldino3D Date: Fri, 13 Nov 2020 23:42:23 +0100 Subject: [PATCH 37/99] Add AUTHORS --- AUTHORS | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 AUTHORS diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 0000000..aa0e6fb --- /dev/null +++ b/AUTHORS @@ -0,0 +1,4 @@ +## AUTHORS +====================================== + +* Dedaldino Antonio, 3D - dedaldinoantonio7@gmail.com \ No newline at end of file From 68c4a760245e17b39edd6288d7045ccc67df84ed Mon Sep 17 00:00:00 2001 From: Dedaldino3D Date: Fri, 13 Nov 2020 23:42:36 +0100 Subject: [PATCH 38/99] Add MANIFEST.in --- MANIFEST.in | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 MANIFEST.in diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..0385206 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,3 @@ +include LICENSE +include README.md +include requirements.txt \ No newline at end of file From 10cc87a5d7201b88dd4dcff72088b037d38666b7 Mon Sep 17 00:00:00 2001 From: Dedaldino3D Date: Sat, 14 Nov 2020 00:03:51 +0100 Subject: [PATCH 39/99] setup project --- setup.cfg | 41 +++++++++++++++++++++++++++++++++++++++++ setup.py | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 91 insertions(+) create mode 100644 setup.cfg create mode 100644 setup.py diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..2a12f9b --- /dev/null +++ b/setup.cfg @@ -0,0 +1,41 @@ +[bdist_wheel] +universal = 1 + +[aliases] +release = register clean --all sdist bdist_wheel +publish = register clean --all sdist bdist_wheel upload + +[flake8] +max-line-length = 160 +exclude = tests/*,*/migrations/*,*/south_migrations/* + +[pytest] +norecursedirs = + .git + .tox + .env + .eggs + venv + dist + build + south_migrations + migrations +python_files = + test_*.py + *_test.py + tests.py +addopts = + -rxEfs + --strict + --ignore=docs/conf.py + --ignore=setup.py + --ignore=ci + --doctest-modules + --doctest-glob=\*.rst + --tb=short + +[isort] +force_single_line=True +line_length=120 +known_first_party=ejabberd_python3d +default_section=THIRDPARTY diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..d19deb7 --- /dev/null +++ b/setup.py @@ -0,0 +1,50 @@ +from glob import glob +from os.path import basename, splitext + +import setuptools +from pip._internal.req import parse_requirements + +__version__ = "0.0.1" + +with open("README.md", "r") as fh: + long_description = fh.read() + +# parse_requirements() returns generator of pip.req.InstallRequirement objects +requirements = [str(i.requirement) for i in parse_requirements('./requirements.txt', session=False)] + +setuptools.setup( + name="ejabberd_python3d", + version=__version__, + author="Dedaldino Antonio", + author_email="dedaldinoantonio7@gmail.com", + description="A library to make XML-RPC calls to ejabberd", + long_description=long_description, + long_description_content_type="text/markdown", + url="https://github.com/Dedaldino3D/ejabberd-python3D", + license="MIT", + packages=setuptools.find_packages(), + py_modules=[splitext(basename(path))[0] for path in glob('src/*.py')], + include_package_data=True, + zip_safe=False, + classifiers=[ + "License :: OSI Approved :: MIT License", + "Operating System :: OS Independent", + "Environment :: All Environment", + "Intended Audience :: Developers", + "Programming Language :: Python", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3 :: Only", + "Programming Language :: Python :: 3.6", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + 'Programming Language :: Python :: Implementation :: CPython', + 'Programming Language :: Python :: Implementation :: PyPy', + ], + python_requires='>=3.6', keywords=[ + 'python', 'mix', 'django-ejabberd', 'django-auth', 'ejabberd', 'xmlrpc', 'api', 'client', 'xmpp', 'chat', 'muc' + + ], + install_requires=requirements, + extras_require={ + } +) From 08c8826450b0472f64d6a9096bfc4874133ccb03 Mon Sep 17 00:00:00 2001 From: Dedaldino3D Date: Sat, 14 Nov 2020 15:27:08 +0100 Subject: [PATCH 40/99] use methods, first argument in _call_api --- ejabberd_python3d/client.py | 282 ++++++++++++++++++------------------ 1 file changed, 138 insertions(+), 144 deletions(-) diff --git a/ejabberd_python3d/client.py b/ejabberd_python3d/client.py index d0efb0c..3a37604 100644 --- a/ejabberd_python3d/client.py +++ b/ejabberd_python3d/client.py @@ -5,8 +5,8 @@ from urllib.parse import urlparse from ejabberd_python3d.core.errors import MissingArguments -from .abc import api, methods -from .defaults.constants import XMLRPC_API_PROTOCOL, XMLRPC_API_PORT +from ejabberd_python3d.abc import api, methods +from ejabberd_python3d.defaults.constants import XMLRPC_API_PROTOCOL, XMLRPC_API_PORT class EjabberdAPIClient(api.EjabberdBaseAPI): @@ -264,9 +264,9 @@ def ban_account(self, user, host, reason): """ Ban an account: kick sessions and set random password """ - return self._call_api('ban_account', {'user': user, - 'host': host, - 'reason': reason}) + return self._call_api(methods.BanAccount, user=user, + host=host, + reason=reason) # TODO def change_room_option(self, name, service, option, value) # Change an option in a MUC room @@ -275,24 +275,24 @@ def check_account(self, user, host): """ Check if an account exists or not """ - return self._call_api('check_account', {'user': user, 'host': host}) + return self._call_api(methods.CheckAccount, user=user, host=host) def check_password(self, user, host, password): """ Check if a password is correct """ - return self._call_api('check_password', {'user': user, - 'host': host, - 'password': password}) + return self._call_api(methods.CheckPassword, user=user, + host=host, + password=password) def check_password_hash(self, user, host, passwordhash, hashmethod): """ Check if the password hash is correct """ - return self._call_api('check_password_hash', {'user': user, - 'host': host, - 'passwordhash': passwordhash, - 'hashmethod': hashmethod}) + return self._call_api(methods.CheckPasswordHash, user=user, + host=host, + passwordhash=passwordhash, + hashmethod=hashmethod) # TODO def compile(self, file): # Recompile and reload Erlang source code file @@ -301,25 +301,25 @@ def connected_users(self): """ List all established sessions """ - return self._call_api('connected_users') + return self._call_api(methods.ConnectedUsers) def connected_users_info(self): """ List all established sessions and their information """ - return self._call_api('connected_users_info') + return self._call_api(methods.ConnectedUsersInfo) def connected_users_number(self): """ Get the number of established sessions """ - return self._call_api('connected_users_number') + return self._call_api(methods.ConnectedUsersNumber) def connected_users_vhost(self, host): """ Get the list of established sessions in a vhost """ - return self._call_api('connected_users_vhost', {'host': host}) + return self._call_api(methods.ConnectedUsersVhost, host=host) # TODO def convert_to_scram(self, host): # Convert the passwords in ‘users’ SQL table to SCRAM @@ -340,7 +340,7 @@ def delete_expired_messages(self): """ Delete expired offline messages from database """ - return self._call_api('delete_expired_messages') + return self._call_api(methods.DeleteExpiredMessages) # TODO def delete_mnesia(self, host): # Export all tables as SQL queries to a file @@ -352,30 +352,30 @@ def delete_old_messages(self, days): """ Delete offline messages older than DAYS """ - return self._call_api('delete_old_messages', {'days': days}) + return self._call_api(methods.DeleteOldMessages, days=days) def delete_old_users(self, days): """ Delete users that didn't log in last days, or that never logged """ - return self._call_api('delete_old_users', {'days': days}) + return self._call_api(methods.DeleteOldUsers, days=days) def delete_old_users_vhost(self, host, days): """ Delete users that didn't log in last days in vhost, or that never logged """ - return self._call_api('delete_old_users_vhost', - {'host': host, 'days': days}) + return self._call_api(methods.DeleteOldUsersVhost, + host=host, days=days) def delete_rosteritem(self, localuser, localserver, user, server): """ Delete an item from a user's roster (self,supports ODBC): """ - return self._call_api('delete_rosteritem', {'localuser': localuser, - 'localserver': localserver, - 'user': user, - 'server': server}) + return self._call_api(methods.DeleteRosterItem, localuser=localuser, + localserver=localserver, + user=user, + server=server) # TODO def destroy_room(self, name, service): # Destroy a MUC room @@ -435,19 +435,19 @@ def get_cookie(self): """ Get the Erlang cookie of this node """ - return self._call_api('get_cookie') + return self._call_api(methods.GetCookie) def get_last(self, user, host): """ Get last activity information (self,timestamp and status): """ - return self._call_api('get_last', {'user': user, 'host': host}) + return self._call_api(methods.GetLast, user=user, host=host) def get_loglevel(self): """ Get the current loglevel """ - return self._call_api('get_loglevel') + return self._call_api(methods.GetLogLevel) # TODO def get_offline_count(self): # Get the number of unread offline messages @@ -486,10 +486,8 @@ def get_roster(self, user, server): {group,string}]}}}} """ - try: - return self._call_api('get_roster', {'user': user, 'server': server}) - except: - return self._call_api('get_roster', {'user': user, 'host': server}) + + return self._call_api(methods.GetRoster, user=user, server=server) # TODO get_subscribers(self, name, service): # List subscribers of a MUC conference @@ -501,27 +499,27 @@ def get_vcard(self, user, host, name): """ Get content from a vCard field """ - return self._call_api('get_vcard', {'user': user, - 'host': host, - 'name': name}) + return self._call_api(methods.GetVcard, user=user, + host=host, + name=name) def get_vcard2(self, user, host, name, subname): """ Get content from a vCard field """ - return self._call_api('get_vcard2', {'user': user, - 'host': host, - 'name': name, - 'subname': subname}) + return self._call_api(methods.GetVcard2, user=user, + host=host, + name=name, + subname=subname) def get_vcard2_multi(self, user, host, name, subname): """ Get multiple contents from a vCard field """ - return self._call_api('get_vcard2_multi', {'user': user, - 'host': host, - 'name': name, - 'subname': subname}) + return self._call_api(methods.GetVcard2Multi, user=user, + host=host, + name=name, + subname=subname) # TODO def import_dir(self, file): # Import users data from jabberd14 spool dir @@ -538,7 +536,7 @@ def incoming_s2s_number(self): """ Number of incoming s2s connections on the node """ - return self._call_api('incoming_s2s_number') + return self._call_api(methods.IncomingS2SNumber) # TODO def install_fallback(self, file): # Install the database from a fallback file @@ -550,16 +548,16 @@ def kick_session(self, user, host, resource, reason): """ Kick a user session """ - return self._call_api('kick_session', {'user': user, - 'host': host, - 'resource': resource, - 'reason': reason}) + return self._call_api(methods.KickSession, user=user, + host=host, + resource=resource, + reason=reason) def kick_user(self, user, host): """ Disconnect user's active sessions """ - return self._call_api('kick_user', {'user': user, 'host': host}) + return self._call_api(methods.KickUsers, user=user, host=host) # TODO def leave_cluster(self, node): # Remove node handled by Node from the cluster @@ -574,7 +572,7 @@ def list_cluster(self): """ try: - return self._call_api('list_cluster') + return self._call_api(methods.ListCluster) except xmlrpc.client.Fault as e: msg = 'list_cluster is NOT available in your version of ejabberd' raise Exception('{}\n{}\n'.format(msg, e.message)) @@ -601,13 +599,13 @@ def modules_available(self): """ List available modules """ - return self._call_api('modules_available') + return self._call_api(methods.ModulesAvailable) def modules_installed(self): """ List installed modules """ - return self._call_api('modules_installed') + return self._call_api(methods.ModulesInstalled) # TODO def modules_update_specs(self): @@ -621,19 +619,19 @@ def num_active_users(self, host, days): """ Get number of users active in the last days """ - return self._call_api('num_active_users', {'host': host, 'days': days}) + return self._call_api(methods.NumActiveUsers, host=host, days=days) def num_resources(self, user, host): """ Get the number of resources of a user """ - return self._call_api('num_resources', {'user': user, 'host': host}) + return self._call_api(methods.NumResources, user=user, host=host) def outgoing_s2s_number(self): """ Number of outgoing s2s connections on the node """ - return self._call_api('outgoing_s2s_number') + return self._call_api(methods.OutgoingS2SNumber) # TODO def privacy_set(self, user, host, xmlquery): # Send a IQ set privacy stanza for a local account @@ -648,17 +646,17 @@ def process_rosteritems(self, action, subs, asks, users, contacts): """ List or delete rosteritems that match filtering options """ - return self._call_api('process_rosteritems', {'action': action, - 'subs': subs, - 'asks': asks, - 'users': users, - 'contacts': contacts}) + return self._call_api(methods.ProcessRosterItems, action=action, + subs=subs, + asks=asks, + users=users, + contacts=contacts) def push_alltoall(self, host, group): """ Add all the users to all the users of Host in Group """ - return self._call_api('push_alltoall', {'host': host, 'group': group}) + return self._call_api(methods.PushAllToAll, host=host, group=group) # TODO def push_roster(self, file, user, host): # Push template roster from file to a user @@ -670,7 +668,7 @@ def registered_vhosts(self): """ List all registered vhosts in SERVER """ - return self._call_api('registered_vhosts') + return self._call_api(methods.RegisteredVhosts) def reload_config(self): """ @@ -678,33 +676,33 @@ def reload_config(self): (only affects ACL and Access) """ - return self._call_api('reload_config') + return self._call_api(methods.ReloadConfig) def remove_node(self, node): """ Remove an ejabberd node from Mnesia clustering config """ - return self._call_api('remove_node', {'node': node}) + return self._call_api(methods.RemoveNodes, node=node) def reopen_log(self): """ Reopen the log files """ - return self._call_api('reopen_log') + return self._call_api(methods.ReopenLog) def resource_num(self, user, host, num): """ Resource string of a session number """ - return self._call_api('resource_num', {'user': user, - 'host': host, - 'num': num}) + return self._call_api(methods.ResourceNum, user=user, + host=host, + num=num) def restart(self): """ Restart ejabberd """ - return self._call_api('restart') + return self._call_api(methods.Restart) # TODO def restore(self, file): # Restore the database from backup file @@ -730,11 +728,10 @@ def send_message(self, type, from_jid, to, subject, body): """ Send a message to a local or remote bare of full JID """ - return self._call_api('send_message', {'type': type, - 'from': from_jid, - 'to': to, - 'subject': subject, - 'body': body}) + return self._call_api(methods.SendMessage, type=type, + from=from_jid, to=to, + subject=subject, + body=body) # TODO def send_stanza(self, from, to, stanza): # Send a stanza; provide From JID and valid To JID @@ -743,19 +740,19 @@ def send_stanza_c2s(self, user, host, resource, stanza): """ Send a stanza as if sent from a c2s session """ - return self._call_api('send_stanza_c2s', {'user': user, - 'host': host, - 'resource': resource, - 'stanza': stanza}) + return self._call_api(methods.SendStanzaC2S, user=user, + host=host, + resource=resource, + stanza=stanza) def set_last(self, user, host, timestamp, status): """ Set last activity information """ - return self._call_api('set_last', {'user': user, - 'host': host, - 'timestamp': timestamp, - 'status': status}) + return self._call_api(methods.SetLast, user=user, + host=host, + timestamp=timestamp, + status=status) def set_loglevel(self, loglevel): """ @@ -771,7 +768,7 @@ def set_loglevel(self, loglevel): """ try: - return self._call_api('set_loglevel', {'loglevel': loglevel}) + return self._call_api(methods.SetLogLevel, loglevel=loglevel) except xmlrpc.Fault as e: msg = 'set_loglevel is NOT available in your version of ejabberd' raise Exception('{}\n{}\n'.format(msg, e.message)) @@ -780,27 +777,27 @@ def set_master(self, nodename): """ Set master node of the clustered Mnesia tables """ - return self._call_api('set_master', {'nodename': nodename}) + return self._call_api(methods.SetMaster, nodename=nodename) def set_nickname(self, user, host, nickname): """ Set nickname in a user's vCard """ - return self._call_api('set_nickname', {'user': user, - 'host': host, - 'nickname': nickname}) + return self._call_api(methods.SetNickname, user=user, + host=host, + nickname=nickname) def set_presence(self, user, host, resource, type, show, status, priority): """ Set presence of a session """ - return self._call_api('set_presence', {'user': user, - 'host': host, - 'resource': resource, - 'type': type, - 'show': show, - 'status': status, - 'priority': priority}) + return self._call_api(methods.SetPresence, user=user, + host=host, + resource=resource, + type=type, + show=show, + status=status, + priority=priority) # TODO def set_room_affiliation(self, name, service, jid, affiliation): # Change an affiliation in a MUC room @@ -809,82 +806,82 @@ def set_vcard(self, user, host, name, content): """ Set content in a vCard field """ - return self._call_api('set_vcard', {'user': user, - 'host': host, - 'name': name, - 'content': content}) + return self._call_api(methods.SetVcard, user=user, + host=host, + name=name, + content=content) def set_vcard2(self, user, host, name, subname, content): """ Set content in a vCard subfield """ - return self._call_api('set_vcard2', {'user': user, - 'host': host, - 'name': name, - 'subname': subname, - 'content': content}) + return self._call_api(methods.SetVcard2, user=user, + host=host, + name=name, + subname=subname, + content=content) def set_vcard2_multi(self, user, host, name, subname, contents): """ *Set multiple contents in a vCard subfield """ - return self._call_api('set_vcard2_multi', {'user': user, - 'host': host, - 'name': name, - 'subname': subname, - 'contents': contents}) + return self._call_api(methods.SetVcardMulti, user=user, + host=host, + name=name, + subname=subname, + contents=contents) def srg_create(self, group, host, name, description, display): """ Create a Shared Roster Group """ - return self._call_api('srg_create', {'group': group, - 'host': host, - 'name': name, - 'description': description, - 'display': display}) + return self._call_api(methods.SrgCreate, group=group, + host=host, + name=name, + description=description, + display=display) def srg_delete(self, group, host): """ Delete a Shared Roster Group """ - return self._call_api('srg_delete', {'group': group, 'host': host}) + return self._call_api(methods.SrgDelete, group=group, host=host) def srg_get_info(self, group, host): """ Get info of a Shared Roster Group """ - return self._call_api('srg_get_info', {'group': group, 'host': host}) + return self._call_api(methods.SrgGetInfo, group=group, host=host) def srg_get_members(self, group, host): """ Get members of a Shared Roster Group """ - return self._call_api('srg_get_members', {'group': group, 'host': host}) + return self._call_api(methods.SrgGetMembers, group=group, host=host) def srg_list(self, host): """ List the Shared Roster Groups in Host """ - return self._call_api('srg_list', {'host': host}) + return self._call_api(methods.SrgList, host=host) def srg_user_add(self, user, host, group, grouphost): """ Add the JID user@host to the Shared Roster Group """ - return self._call_api('srg_user_add', {'user': user, - 'host': host, - 'group': group, - 'grouphost': grouphost}) + return self._call_api(methods.SrgUserAdd, user=user, + host=host, + group=group, + grouphost=grouphost) def srg_user_del(self, user, host, group, grouphost): """ Delete this JID user@host from the Shared Roster Group """ - return self._call_api('srg_user_del', {'user': user, - 'host': host, - 'group': group, - 'grouphost': grouphost}) + return self._call_api(methods.SrgUserDel, user=user, + host=host, + group=group, + grouphost=grouphost) def stats(self, name): """ @@ -897,7 +894,7 @@ def stats(self, name): * ``processes`` - Introduced sometime after Ejabberd 15.07 """ try: - return self._call_api('stats', {'name': name}) + return self._call_api(methods.Stats, name=name) except Exception as e: msg = 'processes stats NOT available in this version of Ejabberd' if e.message == self.errors['connect']: @@ -911,50 +908,50 @@ def stats_host(self, name, host): * ``registeredusers`` * ``onlineusers`` """ - return self._call_api('stats_host', {'name': name, 'host': host}) + return self._call_api(methods.StatsHost, name=name, host=host) def status(self): """ Get ejabberd status """ - return self._call_api('status') + return self._call_api(methods.Status) def status_list(self, status): """ List of logged users with this status """ - return self._call_api('status_list', {'status': status}) + return self._call_api(methods.StatusList, status=status) def status_list_host(self, host, status): """ List of users logged in host with their statuses """ - return self._call_api('status_list_host', {'host': host, 'status': status}) + return self._call_api(methods.StatusListHost, host=host, status=status) def status_num(self, status): """ Number of logged users with this status """ - return self._call_api('status_num', {'status': status}) + return self._call_api(methods.StatusNum, status=status) def status_num_host(self, host, status): """ Number of logged users with this status in host """ - return self._call_api('status_num_host', {'host': host, 'status': status}) + return self._call_api(methods.StatusNumHost, host=host, status=status) def stop(self): """ Stop ejabberd """ - return self._call_api('stop') + return self._call_api(methods.Stop) def stop_kindly(self, delay, announcement): """ Inform users and rooms, wait, and stop the server """ - return self._call_api('stop_kindly', - {'delay': delay, 'announcement': announcement}) + return self._call_api(methods.StopKindly, + delay=delay, announcement=announcement) # TODO def subscribe_room(self, user, nick, room, nodes): # Subscribe to a MUC conference @@ -963,7 +960,7 @@ def unregister(self, user, host): """ Unregister a user """ - return self._call_api('unregister', {'user': user, 'host': host}) + return self._call_api(methods.Unregister, user=user, host=host) # TODO def unsubscribe_room(self, user, room): # Unsubscribe from a MUC conference @@ -972,13 +969,13 @@ def update(self, module): """ Update the given module, or use the keyword: all """ - return self._call_api('update', {'module': module}) + return self._call_api(methods.Update, module=module) def update_list(self): """ List modified modules that can be updated """ - return self._call_api('update_list') + return self._call_api(methods.UpdateList) def user_resources(self, user, server): """ @@ -998,13 +995,10 @@ def user_resources(self, user, server): {resources,{list,{resource,string}}} """ - try: - return self._call_api('user_resources', {'user': user, 'server': server}) - except Exception: - return self._call_api('user_resources', {'user': user, 'host': server}) + return self._call_api(methods.UserResources, user=user, server=server) def user_sessions_info(self, user, host): """ Get information about all sessions of a user """ - return self._call_api('user_sessions_info', {'user': user, 'host': host}) + return self._call_api(methods.UserSessionInfo, user=user, host=host) From dfd8d9da1d89fe23eb1eb97bd48b3ebb0e14ee31 Mon Sep 17 00:00:00 2001 From: Dedaldino3D Date: Sat, 14 Nov 2020 15:40:45 +0100 Subject: [PATCH 41/99] Update imports, change argument in GetRoster --- ejabberd_python3d/abc/methods.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/ejabberd_python3d/abc/methods.py b/ejabberd_python3d/abc/methods.py index 7bc4a99..d401a20 100644 --- a/ejabberd_python3d/abc/methods.py +++ b/ejabberd_python3d/abc/methods.py @@ -1,13 +1,13 @@ from __future__ import unicode_literals -from ..abc.api import API -from ..core.errors import UserAlreadyRegisteredError -from ..core.utils import format_password_hash_sha -from ..defaults.arguments import StringArgument -from ..muc import muc_room_options_serializers -from ..muc.arguments import MUCRoomArgument, AffiliationArgument -from ..muc.enums import Affiliation, MUCRoomOption -from ..serializers import StringSerializer, IntegerSerializer +from ejabberd_python3d.abc.api import API +from ejabberd_python3d.core.errors import UserAlreadyRegisteredError +from ejabberd_python3d.core.utils import format_password_hash_sha +from ejabberd_python3d.defaults.arguments import StringArgument +from ejabberd_python3d.muc import muc_room_options_serializers +from ejabberd_python3d.muc.arguments import MUCRoomArgument, AffiliationArgument +from ejabberd_python3d.muc.enums import Affiliation, MUCRoomOption +from ejabberd_python3d.serializers import StringSerializer, IntegerSerializer class Echo(API): @@ -218,7 +218,7 @@ def transform_response(self, api, arguments, response): class GetRoster(API): method = 'get_roster' - arguments = [StringArgument('user'), StringArgument('host')] + arguments = [StringArgument('user'), StringArgument('server')] def transform_response(self, api, arguments, response): roster = [] From 33a849be8000cccf37a4d2ba91e60612758e1486 Mon Sep 17 00:00:00 2001 From: Dedaldino3D Date: Mon, 16 Nov 2020 09:11:18 +0100 Subject: [PATCH 42/99] Change superclass from ABCMeta to ABC, update imports --- ejabberd_python3d/abc/api.py | 13 +++++-------- ejabberd_python3d/abc/methods.py | 6 +++--- ejabberd_python3d/serializers.py | 5 ++--- 3 files changed, 10 insertions(+), 14 deletions(-) diff --git a/ejabberd_python3d/abc/api.py b/ejabberd_python3d/abc/api.py index d19dfca..5be3d9e 100644 --- a/ejabberd_python3d/abc/api.py +++ b/ejabberd_python3d/abc/api.py @@ -1,10 +1,10 @@ from __future__ import unicode_literals -from abc import ABCMeta, abstractmethod +from abc import ABC, abstractmethod from enum import Enum as BaseClassEnum -class APIArgumentSerializer(ABCMeta): +class APIArgumentSerializer(ABC): @abstractmethod def to_api(self, value): pass @@ -14,14 +14,13 @@ def to_builtin(self, value): pass -class APIArgument(ABCMeta): +class APIArgument(ABC): def __init__(self, name, description=None, required=True, **kwargs): self.name = name self.description = description self.required = required @abstractmethod - @property def serializer_class(self): pass @@ -36,9 +35,8 @@ def get_by_value(cls, value): return cls(value) -class API(ABCMeta): +class API(ABC): @abstractmethod - @property def method(self): """ Return the exact name of the XML-RPC API method to call @@ -46,7 +44,6 @@ def method(self): pass @abstractmethod - @property def arguments(self): """ Return an (ordered) list of APIArgument objects @@ -88,7 +85,7 @@ def transform_response(self, api, arguments, response): return response -class EjabberdBaseAPI(ABCMeta): +class EjabberdBaseAPI(ABC): @abstractmethod def echo(self, sentence): pass diff --git a/ejabberd_python3d/abc/methods.py b/ejabberd_python3d/abc/methods.py index d401a20..59f155e 100644 --- a/ejabberd_python3d/abc/methods.py +++ b/ejabberd_python3d/abc/methods.py @@ -3,11 +3,11 @@ from ejabberd_python3d.abc.api import API from ejabberd_python3d.core.errors import UserAlreadyRegisteredError from ejabberd_python3d.core.utils import format_password_hash_sha -from ejabberd_python3d.defaults.arguments import StringArgument +from ejabberd_python3d.defaults.arguments import StringArgument, IntegerArgument from ejabberd_python3d.muc import muc_room_options_serializers from ejabberd_python3d.muc.arguments import MUCRoomArgument, AffiliationArgument from ejabberd_python3d.muc.enums import Affiliation, MUCRoomOption -from ejabberd_python3d.serializers import StringSerializer, IntegerSerializer +from ejabberd_python3d.serializers import StringSerializer class Echo(API): @@ -403,7 +403,7 @@ def transform_response(self, api, arguments, response): class SetLast(API): method = "set_last" - arguments = [StringArgument('user'), StringArgument('host'), IntegerSerializer('timestamp'), + arguments = [StringArgument('user'), StringArgument('host'), IntegerArgument('timestamp'), StringArgument('status')] def transform_response(self, api, arguments, response): diff --git a/ejabberd_python3d/serializers.py b/ejabberd_python3d/serializers.py index 0a03d8d..1371dec 100644 --- a/ejabberd_python3d/serializers.py +++ b/ejabberd_python3d/serializers.py @@ -1,7 +1,7 @@ from abc import ABCMeta, abstractmethod from six import string_types -from .abc.api import Enum, APIArgumentSerializer +from ejabberd_python3d.abc.api import Enum, APIArgumentSerializer class StringSerializer(APIArgumentSerializer): @@ -51,9 +51,8 @@ def to_builtin(self, value): return value == 'true' -class EnumSerializer(ABCMeta, StringSerializer): +class EnumSerializer(StringSerializer): @abstractmethod - @property def enum_class(self): pass From af48c3c0d231d9ba677fd6adf16db1fec4e61064 Mon Sep 17 00:00:00 2001 From: Dedaldino3D Date: Mon, 16 Nov 2020 09:37:08 +0100 Subject: [PATCH 43/99] Change send_message signature, subject is not required now. Update imports in client and catch exception --- ejabberd_python3d/abc/methods.py | 11 ++++-- ejabberd_python3d/client.py | 62 +++++++++++++++++++------------- 2 files changed, 46 insertions(+), 27 deletions(-) diff --git a/ejabberd_python3d/abc/methods.py b/ejabberd_python3d/abc/methods.py index 59f155e..7952b49 100644 --- a/ejabberd_python3d/abc/methods.py +++ b/ejabberd_python3d/abc/methods.py @@ -394,8 +394,15 @@ def transform_response(self, api, arguments, response): class SendMessage(API): method = "send_message" - arguments = [StringArgument('type'), StringArgument('from'), StringArgument('to'), StringArgument('subject'), - StringArgument('body')] + arguments = [StringArgument('type'), StringArgument('from'), StringArgument('to'), + StringArgument('subject', required=False), StringArgument('body')] + + def transform_arguments(self, **kwargs): + from_jid = kwargs.pop('from_jid') + kwargs.update({ + 'from': from_jid + }) + return kwargs def transform_response(self, api, arguments, response): return response.get('res') == 0 diff --git a/ejabberd_python3d/client.py b/ejabberd_python3d/client.py index 3a37604..57a4b3f 100644 --- a/ejabberd_python3d/client.py +++ b/ejabberd_python3d/client.py @@ -2,29 +2,31 @@ import xmlrpc import copy +from xmlrpc import client as xmlrpc_client from urllib.parse import urlparse from ejabberd_python3d.core.errors import MissingArguments -from ejabberd_python3d.abc import api, methods -from ejabberd_python3d.defaults.constants import XMLRPC_API_PROTOCOL, XMLRPC_API_PORT +from ejabberd_python3d.abc import methods +from ejabberd_python3d.abc.api import API, APIArgument, EjabberdBaseAPI +from ejabberd_python3d.defaults.constants import XMLRPC_API_PROTOCOL, XMLRPC_API_PORT, XMLRPC_API_SERVER, \ + XMLRPC_API_PORT -class EjabberdAPIClient(api.EjabberdBaseAPI): +class EjabberdAPIClient(EjabberdBaseAPI): """ Python client for Ejabberd XML-RPC Administration API. """ - def __init__(self, host, username, password, server='127.0.0.1', port=4560, protocol='http', admin=True, + def __init__(self, host, username, password, server='localhost', port=4560, protocol='http', admin=True, verbose=False): """ Init XML-RPC server proxy. """ - super().__init__(self) self.host = host self.username = username self.password = password - self.server = server - self.port = port + self.server = server or XMLRPC_API_SERVER + self.port = port or XMLRPC_API_PORT self.admin = admin self.protocol = protocol or XMLRPC_API_PROTOCOL self.verbose = verbose @@ -76,7 +78,8 @@ def service_url(self): Returns the FQDN to the Ejabberd server's XML-RPC endpoint :return: """ - return "{}://{}:{}/".format(self.protocol, self.host, self.port) + # TODO: add endpoint parameter + return "{}://{}:{}".format(self.protocol, self.host, self.port) @property def server_proxy(self): @@ -84,7 +87,7 @@ def server_proxy(self): Returns the proxy object that is used to perform the calls to the XML-RPC endpoint """ if self._server_proxy is None: - self._server_proxy = xmlrpc.client.ServerProxy(self.service_url, verbose=(1 if self.verbose else 0)) + self._server_proxy = xmlrpc_client.ServerProxy(self.service_url, verbose=(1 if self.verbose else 0)) return self._server_proxy @property @@ -95,13 +98,14 @@ def auth(self): return { 'user': self.username, 'server': self.server, - 'password': self.password + 'password': self.password, + 'admin': self.admin } - def _validate_and_serialize_arguments(self, api, arguments): + def _validate_and_serialize_arguments(self, api_class, arguments): """ Internal method to validate and serialize arguments - :param api: An instance of an API class + :param api_class: An instance of an API class :param arguments: A dictionary of arguments that will be passed to the method :type arguments: dict :rtype: dict @@ -109,9 +113,9 @@ def _validate_and_serialize_arguments(self, api, arguments): """ ser_args = {} - for i in range(len(api.arguments)): - arg_desc = api.arguments[i] - assert isinstance(arg_desc, api.APIArgument) + for i in range(len(api_class.arguments)): + arg_desc = api_class.arguments[i] + assert isinstance(arg_desc, APIArgument) # validate argument presence arg_name = str(arg_desc.name) @@ -145,8 +149,9 @@ def _call_api(self, api_class, **kwargs): :rtype: object :return: Returns return value of the XMLRPC Method call """ + # validate api_class - assert issubclass(api_class, api.API) + assert issubclass(api_class, API) # create api instance api = api_class() @@ -158,16 +163,22 @@ def _call_api(self, api_class, **kwargs): # validate and serialize arguments args = self._validate_and_serialize_arguments(api, args) # retrieve method - method = getattr(self.server_proxy, str(api.method)) + try: + method = getattr(self.server_proxy, str(api.method)) + except xmlrpc_client.Fault as e: + raise Exception(f"Error: {e}") # print method call with arguments self._report_method_call(api.method, args) # perform call - if not api.authenticate: - response = method(args) - else: - response = method(self.auth, args) + try: + if not api.authenticate: + response = method(args) + else: + response = method(self.auth, args) + except xmlrpc_client.Fault as e: + raise Exception(f"Error: {e}") # validate response api.validate_response(api, args, response) @@ -573,7 +584,7 @@ def list_cluster(self): """ try: return self._call_api(methods.ListCluster) - except xmlrpc.client.Fault as e: + except xmlrpc_client.Fault as e: msg = 'list_cluster is NOT available in your version of ejabberd' raise Exception('{}\n{}\n'.format(msg, e.message)) @@ -724,12 +735,13 @@ def restart(self): # users): # Send a direct invitation to several destinations - def send_message(self, type, from_jid, to, subject, body): + def send_message(self, type, from_jid, to, body, subject=""): """ Send a message to a local or remote bare of full JID """ + # noinspection PyTypeChecker return self._call_api(methods.SendMessage, type=type, - from=from_jid, to=to, + from_jid=from_jid, to=to, subject=subject, body=body) @@ -769,7 +781,7 @@ def set_loglevel(self, loglevel): """ try: return self._call_api(methods.SetLogLevel, loglevel=loglevel) - except xmlrpc.Fault as e: + except xmlrpc_client.Fault as e: msg = 'set_loglevel is NOT available in your version of ejabberd' raise Exception('{}\n{}\n'.format(msg, e.message)) From a788b01328365f6b47fd5a75d4c1a6fee3eabaff Mon Sep 17 00:00:00 2001 From: Dedaldino3D Date: Mon, 16 Nov 2020 12:22:51 +0100 Subject: [PATCH 44/99] Add CheckPassword & DeleteExpiredMessages in methods --- ejabberd_python3d/abc/methods.py | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/ejabberd_python3d/abc/methods.py b/ejabberd_python3d/abc/methods.py index 7952b49..49766bf 100644 --- a/ejabberd_python3d/abc/methods.py +++ b/ejabberd_python3d/abc/methods.py @@ -61,9 +61,9 @@ class CheckPasswordHash(API): StringArgument('hashmethod')] def transform_arguments(self, **kwargs): - passwordhash = format_password_hash_sha(password=kwargs.pop('password')) + password_hash = format_password_hash_sha(password=kwargs.pop('password')) kwargs.update({ - 'passwordhash': passwordhash, + 'passwordhash': password_hash, 'hashmethod': 'sha' }) return kwargs @@ -433,3 +433,19 @@ class UnSubscribeRoom(API): def transform_response(self, api, arguments, response): return response.get('res') == 0 + + +class CheckPassword(API): + method = "check_password" + arguments = [StringArgument('user'), StringArgument('host'), StringArgument('password')] + + def transform_response(self, api, arguments, response): + return response.get('res') == 0 + + +class DeleteExpiredMessages(API): + method = "check_password" + arguments = [] + + def transform_response(self, api, arguments, response): + return response.get('res') == 0 From cb4509e1cfdd29602d3c9b04d02655b256a203dd Mon Sep 17 00:00:00 2001 From: Dedaldino3D Date: Mon, 16 Nov 2020 12:26:48 +0100 Subject: [PATCH 45/99] Add DeleteOldMessages & DeleteOldUsers in methods --- ejabberd_python3d/abc/methods.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/ejabberd_python3d/abc/methods.py b/ejabberd_python3d/abc/methods.py index 49766bf..78465cb 100644 --- a/ejabberd_python3d/abc/methods.py +++ b/ejabberd_python3d/abc/methods.py @@ -3,7 +3,7 @@ from ejabberd_python3d.abc.api import API from ejabberd_python3d.core.errors import UserAlreadyRegisteredError from ejabberd_python3d.core.utils import format_password_hash_sha -from ejabberd_python3d.defaults.arguments import StringArgument, IntegerArgument +from ejabberd_python3d.defaults.arguments import StringArgument, IntegerArgument, PositiveIntegerArgument from ejabberd_python3d.muc import muc_room_options_serializers from ejabberd_python3d.muc.arguments import MUCRoomArgument, AffiliationArgument from ejabberd_python3d.muc.enums import Affiliation, MUCRoomOption @@ -449,3 +449,19 @@ class DeleteExpiredMessages(API): def transform_response(self, api, arguments, response): return response.get('res') == 0 + + +class DeleteOldMessages(API): + method = "delete_old_messages" + arguments = [PositiveIntegerArgument('days')] + + def transform_response(self, api, arguments, response): + return response.get('res') == 0 + + +class DeleteOldUsers(API): + method = "delete_old_users" + arguments = [PositiveIntegerArgument('days')] + + def transform_response(self, api, arguments, response): + return response.get('res') From 1d3679e4303cf9b3ae640b20a2af5af4484ee686 Mon Sep 17 00:00:00 2001 From: Dedaldino3D Date: Mon, 16 Nov 2020 12:30:20 +0100 Subject: [PATCH 46/99] Add DeleteOldUsersVhost & GetCookie in methods --- ejabberd_python3d/abc/methods.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/ejabberd_python3d/abc/methods.py b/ejabberd_python3d/abc/methods.py index 78465cb..ade9403 100644 --- a/ejabberd_python3d/abc/methods.py +++ b/ejabberd_python3d/abc/methods.py @@ -465,3 +465,19 @@ class DeleteOldUsers(API): def transform_response(self, api, arguments, response): return response.get('res') + + +class DeleteOldUsersVhost(API): + method = "delete_old_users_vhost" + arguments = [StringArgument('host'), PositiveIntegerArgument('days')] + + def transform_response(self, api, arguments, response): + return response.get('res') + + +class GetCookie(API): + method = "get_cookie" + arguments = [] + + def transform_response(self, api, arguments, response): + return response.get('res') From f7c52358929fb1686b7f0bf77d44808860bedb10 Mon Sep 17 00:00:00 2001 From: Dedaldino3D Date: Mon, 16 Nov 2020 12:33:44 +0100 Subject: [PATCH 47/99] Add GetLogLevel & GetVcard in methods --- ejabberd_python3d/abc/methods.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/ejabberd_python3d/abc/methods.py b/ejabberd_python3d/abc/methods.py index ade9403..ec5cd21 100644 --- a/ejabberd_python3d/abc/methods.py +++ b/ejabberd_python3d/abc/methods.py @@ -480,4 +480,20 @@ class GetCookie(API): arguments = [] def transform_response(self, api, arguments, response): - return response.get('res') + return response.get('cookie') + + +class GetLogLevel(API): + method = "get_loglevel" + arguments = [] + + def transform_response(self, api, arguments, response): + return response.get('levelatom ') + + +class GetVcard(API): + method = "get_vcard" + arguments = [StringArgument('user'), StringArgument('host'), StringArgument('name')] + + def transform_response(self, api, arguments, response): + return response.get('content') From b2ea3bb4bf28dd03985f71bd121b91a7cdfc4e73 Mon Sep 17 00:00:00 2001 From: Dedaldino3D Date: Mon, 16 Nov 2020 12:37:17 +0100 Subject: [PATCH 48/99] Add GetVcard2Multi & GetVcard2 in methods --- ejabberd_python3d/abc/methods.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/ejabberd_python3d/abc/methods.py b/ejabberd_python3d/abc/methods.py index ec5cd21..1192473 100644 --- a/ejabberd_python3d/abc/methods.py +++ b/ejabberd_python3d/abc/methods.py @@ -497,3 +497,19 @@ class GetVcard(API): def transform_response(self, api, arguments, response): return response.get('content') + + +class GetVcard2(API): + method = "get_vcard2" + arguments = [StringArgument('user'), StringArgument('host'), StringArgument('name'), StringArgument('subname')] + + def transform_response(self, api, arguments, response): + return response.get("content") + + +class GetVcard2Multi(API): + method = "get_vcard2_multi" + arguments = [StringArgument('user'), StringArgument('host'), StringArgument('name'), StringArgument('subname')] + + def transform_response(self, api, arguments, response): + return response.get("contents") From 82ee002d8868b5719af466deffefedb6205210c1 Mon Sep 17 00:00:00 2001 From: Dedaldino3D Date: Mon, 16 Nov 2020 12:41:12 +0100 Subject: [PATCH 49/99] Add KickSession & IncomingS2SNumber in methods --- ejabberd_python3d/abc/methods.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/ejabberd_python3d/abc/methods.py b/ejabberd_python3d/abc/methods.py index 1192473..5f7a81c 100644 --- a/ejabberd_python3d/abc/methods.py +++ b/ejabberd_python3d/abc/methods.py @@ -513,3 +513,19 @@ class GetVcard2Multi(API): def transform_response(self, api, arguments, response): return response.get("contents") + + +class IncomingS2SNumber(API): + method = "incoming_s2s_number" + arguments = [] + + def transform_response(self, api, arguments, response): + return response.get("s2s_incoming") + + +class KickSession(API): + method = "kick_session" + arguments = [StringArgument('user'), StringArgument('host'), StringArgument('resource'), StringArgument('reason')] + + def transform_response(self, api, arguments, response): + return response.get("res") == 0 From a27e048d8c0763e05f17a5eedbdf44579a05843f Mon Sep 17 00:00:00 2001 From: Dedaldino3D Date: Mon, 16 Nov 2020 12:45:26 +0100 Subject: [PATCH 50/99] Add ListCluster, ModulesInstalled & ModulesAvailable in methods --- ejabberd_python3d/abc/methods.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/ejabberd_python3d/abc/methods.py b/ejabberd_python3d/abc/methods.py index 5f7a81c..a9cc53f 100644 --- a/ejabberd_python3d/abc/methods.py +++ b/ejabberd_python3d/abc/methods.py @@ -529,3 +529,27 @@ class KickSession(API): def transform_response(self, api, arguments, response): return response.get("res") == 0 + + +class ListCluster(API): + method = "list_cluster" + arguments = [] + + def transform_response(self, api, arguments, response): + return response.get("nodes") + + +class ModulesAvailable(API): + method = "modules_available" + arguments = [] + + def transform_response(self, api, arguments, response): + return response.get("modules") + + +class ModulesInstalled(API): + method = "modules_installed" + arguments = [] + + def transform_response(self, api, arguments, response): + return response.get("modules") From b3f9f1cc9f089af3e3fd1164b035bac6b63896bc Mon Sep 17 00:00:00 2001 From: Dedaldino3D Date: Mon, 16 Nov 2020 12:50:47 +0100 Subject: [PATCH 51/99] Remove num_cative_users from client and Add NumResources & OutgoingS2SNumber in methods --- ejabberd_python3d/abc/methods.py | 16 ++++++++++ ejabberd_python3d/client.py | 55 ++++++++++++-------------------- 2 files changed, 36 insertions(+), 35 deletions(-) diff --git a/ejabberd_python3d/abc/methods.py b/ejabberd_python3d/abc/methods.py index a9cc53f..2b1be84 100644 --- a/ejabberd_python3d/abc/methods.py +++ b/ejabberd_python3d/abc/methods.py @@ -553,3 +553,19 @@ class ModulesInstalled(API): def transform_response(self, api, arguments, response): return response.get("modules") + + +class NumResources(API): + method = "num_resources" + arguments = [StringArgument('user'), StringArgument('host')] + + def transform_response(self, api, arguments, response): + return response.get("resources") + + +class OutgoingS2SNumber(API): + method = "outgoing_s2s_number" + arguments = [] + + def transform_response(self, api, arguments, response): + return response.get('s2s_outgoing') diff --git a/ejabberd_python3d/client.py b/ejabberd_python3d/client.py index 57a4b3f..1baf517 100644 --- a/ejabberd_python3d/client.py +++ b/ejabberd_python3d/client.py @@ -1,14 +1,13 @@ from __future__ import print_function -import xmlrpc import copy -from xmlrpc import client as xmlrpc_client from urllib.parse import urlparse +from xmlrpc import client as xmlrpc_client -from ejabberd_python3d.core.errors import MissingArguments from ejabberd_python3d.abc import methods from ejabberd_python3d.abc.api import API, APIArgument, EjabberdBaseAPI -from ejabberd_python3d.defaults.constants import XMLRPC_API_PROTOCOL, XMLRPC_API_PORT, XMLRPC_API_SERVER, \ +from ejabberd_python3d.core.errors import MissingArguments +from ejabberd_python3d.defaults.constants import XMLRPC_API_PROTOCOL, XMLRPC_API_SERVER, \ XMLRPC_API_PORT @@ -17,18 +16,18 @@ class EjabberdAPIClient(EjabberdBaseAPI): Python client for Ejabberd XML-RPC Administration API. """ - def __init__(self, host, username, password, server='localhost', port=4560, protocol='http', admin=True, - verbose=False): + def __init__(self, host, username, password, server=XMLRPC_API_SERVER, port=XMLRPC_API_PORT, + protocol=XMLRPC_API_PROTOCOL, admin=True, verbose=False): """ Init XML-RPC server proxy. """ self.host = host self.username = username self.password = password - self.server = server or XMLRPC_API_SERVER - self.port = port or XMLRPC_API_PORT + self.server = server + self.port = port self.admin = admin - self.protocol = protocol or XMLRPC_API_PROTOCOL + self.protocol = protocol self.verbose = verbose self._server_proxy = None @@ -166,7 +165,8 @@ def _call_api(self, api_class, **kwargs): try: method = getattr(self.server_proxy, str(api.method)) except xmlrpc_client.Fault as e: - raise Exception(f"Error: {e}") + # TODO: add it to logger + raise Exception(f"{e.faultString} - code: {e.faultCode}") # print method call with arguments self._report_method_call(api.method, args) @@ -178,7 +178,7 @@ def _call_api(self, api_class, **kwargs): else: response = method(self.auth, args) except xmlrpc_client.Fault as e: - raise Exception(f"Error: {e}") + raise Exception(f"{e.faultString} - code: {e.faultCode}") # validate response api.validate_response(api, args, response) @@ -261,13 +261,13 @@ def add_rosteritem(self, """ Add an item to a user's roster (self,supports ODBC): """ - return self._call_api('add_rosteritem', {'localuser': localuser, - 'localserver': localserver, - 'user': user, - 'server': server, - 'nick': nick, - 'group': group, - 'subs': subs}) + return self._call_api(methods.AddRosterItem, localuser=localuser, + localserver=localserver, + user=user, + server=server, + nick=nick, + group=group, + subs=subs) # TODO def backup(self, file): Store the database to backup file @@ -296,15 +296,6 @@ def check_password(self, user, host, password): host=host, password=password) - def check_password_hash(self, user, host, passwordhash, hashmethod): - """ - Check if the password hash is correct - """ - return self._call_api(methods.CheckPasswordHash, user=user, - host=host, - passwordhash=passwordhash, - hashmethod=hashmethod) - # TODO def compile(self, file): # Recompile and reload Erlang source code file @@ -586,7 +577,7 @@ def list_cluster(self): return self._call_api(methods.ListCluster) except xmlrpc_client.Fault as e: msg = 'list_cluster is NOT available in your version of ejabberd' - raise Exception('{}\n{}\n'.format(msg, e.message)) + raise Exception('{}\n{}\n - code: {}'.format(msg, e.faultString, e.faultCode)) # TODO def load(self, file): # Restore the database from text file @@ -626,12 +617,6 @@ def modules_installed(self): # TODO def muc_unregister_nick(self, nick): # Unregister the nick in the MUC service - def num_active_users(self, host, days): - """ - Get number of users active in the last days - """ - return self._call_api(methods.NumActiveUsers, host=host, days=days) - def num_resources(self, user, host): """ Get the number of resources of a user @@ -783,7 +768,7 @@ def set_loglevel(self, loglevel): return self._call_api(methods.SetLogLevel, loglevel=loglevel) except xmlrpc_client.Fault as e: msg = 'set_loglevel is NOT available in your version of ejabberd' - raise Exception('{}\n{}\n'.format(msg, e.message)) + raise Exception('{}\n{} - code: {}\n '.format(msg, e.faultString, e.faultCode)) def set_master(self, nodename): """ From c09dfdb978410fe5b858a93e6b7f752a770d3a24 Mon Sep 17 00:00:00 2001 From: Dedaldino3D Date: Mon, 16 Nov 2020 12:55:30 +0100 Subject: [PATCH 52/99] Add PushAllToAll & ProcessRosterItems in methods --- ejabberd_python3d/abc/methods.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/ejabberd_python3d/abc/methods.py b/ejabberd_python3d/abc/methods.py index 2b1be84..4f4541a 100644 --- a/ejabberd_python3d/abc/methods.py +++ b/ejabberd_python3d/abc/methods.py @@ -569,3 +569,20 @@ class OutgoingS2SNumber(API): def transform_response(self, api, arguments, response): return response.get('s2s_outgoing') + + +class ProcessRosterItems(API): + method = "process_rosteritems" + arguments = [StringArgument('action'), StringArgument('subs'), StringArgument('asks'), StringArgument('users'), + StringArgument('contacts')] + + def transform_response(self, api, arguments, response): + return response.get("response") + + +class PushAllToAll(API): + method = "push_alltoall" + arguments = [StringArgument('host'), StringArgument('group')] + + def transform_response(self, api, arguments, response): + return response.get("res") == 0 From cc8357355c2e54e723831d87c8a1d65bc0d91d0a Mon Sep 17 00:00:00 2001 From: Dedaldino3D Date: Mon, 16 Nov 2020 12:57:57 +0100 Subject: [PATCH 53/99] Add RegisteredVhosts & ReloadConfig in methods --- ejabberd_python3d/abc/methods.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/ejabberd_python3d/abc/methods.py b/ejabberd_python3d/abc/methods.py index 4f4541a..75851c5 100644 --- a/ejabberd_python3d/abc/methods.py +++ b/ejabberd_python3d/abc/methods.py @@ -586,3 +586,19 @@ class PushAllToAll(API): def transform_response(self, api, arguments, response): return response.get("res") == 0 + + +class RegisteredVhosts(API): + method = "registered_vhosts" + arguments = [] + + def transform_response(self, api, arguments, response): + return response.get("vhosts") + + +class ReloadConfig(API): + method = "reload_config" + arguments = [] + + def transform_response(self, api, arguments, response): + return response.get("res") == 0 From 2b0d12990b1579c6154ffa3af0ad4078cd396730 Mon Sep 17 00:00:00 2001 From: Dedaldino3D Date: Mon, 16 Nov 2020 13:04:58 +0100 Subject: [PATCH 54/99] Delete remove_node from client and Add ResourceNum & ReopenLog in methods --- ejabberd_python3d/abc/methods.py | 16 ++++++++++++++++ ejabberd_python3d/client.py | 6 ------ 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/ejabberd_python3d/abc/methods.py b/ejabberd_python3d/abc/methods.py index 75851c5..7fc6c82 100644 --- a/ejabberd_python3d/abc/methods.py +++ b/ejabberd_python3d/abc/methods.py @@ -602,3 +602,19 @@ class ReloadConfig(API): def transform_response(self, api, arguments, response): return response.get("res") == 0 + + +class ReopenLog(API): + method = "reopen_log" + arguments = [] + + def transform_response(self, api, arguments, response): + return response.get("res") == 0 + + +class ResourceNum(API): + method = "resource_num" + arguments = [StringArgument('user'), StringArgument('host'), PositiveIntegerArgument('num')] + + def transform_response(self, api, arguments, response): + return response.get("resource") diff --git a/ejabberd_python3d/client.py b/ejabberd_python3d/client.py index 1baf517..3fd85a1 100644 --- a/ejabberd_python3d/client.py +++ b/ejabberd_python3d/client.py @@ -674,12 +674,6 @@ def reload_config(self): """ return self._call_api(methods.ReloadConfig) - def remove_node(self, node): - """ - Remove an ejabberd node from Mnesia clustering config - """ - return self._call_api(methods.RemoveNodes, node=node) - def reopen_log(self): """ Reopen the log files From 4890d548462934f8a590c19e2666bc8f6c6155fa Mon Sep 17 00:00:00 2001 From: Dedaldino3D Date: Mon, 16 Nov 2020 13:12:39 +0100 Subject: [PATCH 55/99] Add SendStanzaC2S & Restart in methods --- ejabberd_python3d/abc/methods.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/ejabberd_python3d/abc/methods.py b/ejabberd_python3d/abc/methods.py index 7fc6c82..fd6daaf 100644 --- a/ejabberd_python3d/abc/methods.py +++ b/ejabberd_python3d/abc/methods.py @@ -618,3 +618,19 @@ class ResourceNum(API): def transform_response(self, api, arguments, response): return response.get("resource") + + +class Restart(API): + method = "restart" + arguments = [] + + def transform_response(self, api, arguments, response): + return response.get("res") == 0 + + +class SendStanzaC2S(API): + method = "send_stanza_c2s" + arguments = [StringArgument('user'), StringArgument('host'), StringArgument('resource'), StringArgument('stanza')] + + def transform_response(self, api, arguments, response): + return response.get("res") == 0 From 2e9cb7d77efb5fb21d1ecb016996abffab02a2ef Mon Sep 17 00:00:00 2001 From: Dedaldino3D Date: Mon, 16 Nov 2020 13:44:32 +0100 Subject: [PATCH 56/99] Add new classes in methods.py, Add loglevel options, Create enums to contain generic enum options --- ejabberd_python3d/abc/methods.py | 27 ++++++++++++++++++++++++- ejabberd_python3d/defaults/__init__.py | 15 ++++++++++++++ ejabberd_python3d/defaults/arguments.py | 7 ++++++- ejabberd_python3d/defaults/constants.py | 4 ++-- ejabberd_python3d/defaults/enums.py | 13 ++++++++++++ ejabberd_python3d/muc/enums.py | 2 +- ejabberd_python3d/serializers.py | 5 +++++ 7 files changed, 68 insertions(+), 5 deletions(-) create mode 100644 ejabberd_python3d/defaults/enums.py diff --git a/ejabberd_python3d/abc/methods.py b/ejabberd_python3d/abc/methods.py index fd6daaf..22553cb 100644 --- a/ejabberd_python3d/abc/methods.py +++ b/ejabberd_python3d/abc/methods.py @@ -3,7 +3,9 @@ from ejabberd_python3d.abc.api import API from ejabberd_python3d.core.errors import UserAlreadyRegisteredError from ejabberd_python3d.core.utils import format_password_hash_sha -from ejabberd_python3d.defaults.arguments import StringArgument, IntegerArgument, PositiveIntegerArgument +from ejabberd_python3d.defaults import LogLevelOptions, loglevel_options_serializers +from ejabberd_python3d.defaults.arguments import StringArgument, IntegerArgument, PositiveIntegerArgument, \ + LogLevelArgument from ejabberd_python3d.muc import muc_room_options_serializers from ejabberd_python3d.muc.arguments import MUCRoomArgument, AffiliationArgument from ejabberd_python3d.muc.enums import Affiliation, MUCRoomOption @@ -634,3 +636,26 @@ class SendStanzaC2S(API): def transform_response(self, api, arguments, response): return response.get("res") == 0 + + +class SetLogLevel(API): + method = "set_loglevel" + arguments = [LogLevelArgument('loglevel')] + + def transform_arguments(self, **kwargs): + option = kwargs.pop('loglevel') + assert isinstance(option, LogLevelOptions) + serializer_class = loglevel_options_serializers.get(option, StringSerializer) + kwargs['value'] = serializer_class().to_api(kwargs['value']) + return kwargs + + def transform_response(self, api, arguments, response): + return response.get("res") == 0 + + +class SetMaster(API): + method = "set_master" + arguments = [StringArgument('nodename')] + + def transform_response(self, api, arguments, response): + return response.get("res") diff --git a/ejabberd_python3d/defaults/__init__.py b/ejabberd_python3d/defaults/__init__.py index e69de29..fbc1372 100644 --- a/ejabberd_python3d/defaults/__init__.py +++ b/ejabberd_python3d/defaults/__init__.py @@ -0,0 +1,15 @@ +from ejabberd_python3d.serializers import StringSerializer + +from .enums import LogLevelOptions + +loglevel_options_serializers = { + LogLevelOptions.none: StringSerializer, + LogLevelOptions.alert: StringSerializer, + LogLevelOptions.emergency: StringSerializer, + LogLevelOptions.error: StringSerializer, + LogLevelOptions.critical: StringSerializer, + LogLevelOptions.debug: StringSerializer, + LogLevelOptions.info: StringSerializer, + LogLevelOptions.notice: StringSerializer, + LogLevelOptions.warning: StringSerializer, +} diff --git a/ejabberd_python3d/defaults/arguments.py b/ejabberd_python3d/defaults/arguments.py index 9711420..fd55e53 100644 --- a/ejabberd_python3d/defaults/arguments.py +++ b/ejabberd_python3d/defaults/arguments.py @@ -1,7 +1,8 @@ from __future__ import unicode_literals from ..abc.api import APIArgument -from ..serializers import StringSerializer, IntegerSerializer, PositiveIntegerSerializer, BooleanSerializer +from ..serializers import StringSerializer, IntegerSerializer, PositiveIntegerSerializer, BooleanSerializer, \ + LogLevelSerializer class StringArgument(APIArgument): @@ -18,3 +19,7 @@ class PositiveIntegerArgument(APIArgument): class BooleanArgument(APIArgument): serializer_class = BooleanSerializer + + +class LogLevelArgument(APIArgument): + serializer_class = LogLevelSerializer diff --git a/ejabberd_python3d/defaults/constants.py b/ejabberd_python3d/defaults/constants.py index dccd439..299b2f5 100644 --- a/ejabberd_python3d/defaults/constants.py +++ b/ejabberd_python3d/defaults/constants.py @@ -1,3 +1,3 @@ -XMLRPC_API_PROTOCOL = 'https' +XMLRPC_API_PROTOCOL = 'http' XMLRPC_API_PORT = 4560 -XMLRPC_API_SERVER = '127.0.0.1' +XMLRPC_API_SERVER = 'localhost' diff --git a/ejabberd_python3d/defaults/enums.py b/ejabberd_python3d/defaults/enums.py new file mode 100644 index 0000000..fce9021 --- /dev/null +++ b/ejabberd_python3d/defaults/enums.py @@ -0,0 +1,13 @@ +from ejabberd_python3d.abc.api import Enum + + +class LogLevelOptions(Enum): + none = 1 + emergency = 2 + alert = 3 + critical = 4 + error = 5 + warning = 6 + notice = 7 + info = 8 + debug = 9 diff --git a/ejabberd_python3d/muc/enums.py b/ejabberd_python3d/muc/enums.py index 46fe95d..0f23b9d 100644 --- a/ejabberd_python3d/muc/enums.py +++ b/ejabberd_python3d/muc/enums.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals -from ..abc.api import Enum +from ejabberd_python3d.abc.api import Enum class MUCRoomOption(Enum): diff --git a/ejabberd_python3d/serializers.py b/ejabberd_python3d/serializers.py index 1371dec..a05b3ee 100644 --- a/ejabberd_python3d/serializers.py +++ b/ejabberd_python3d/serializers.py @@ -2,6 +2,7 @@ from six import string_types from ejabberd_python3d.abc.api import Enum, APIArgumentSerializer +from ejabberd_python3d.defaults.enums import LogLevelOptions class StringSerializer(APIArgumentSerializer): @@ -73,3 +74,7 @@ def to_builtin(self, value): res = self.enum_class.get_by_name(value) if res is None: raise ValueError("Expects enum value for {}, but got {}".format(self.enum_class, type(value))) + + +class LogLevelSerializer(EnumSerializer): + enum_class = LogLevelOptions From d005e0357141da04417ac97461574a3808c264ce Mon Sep 17 00:00:00 2001 From: Dedaldino3D Date: Mon, 16 Nov 2020 13:49:34 +0100 Subject: [PATCH 57/99] Add SetVcard & SetPresence in methods --- ejabberd_python3d/abc/methods.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/ejabberd_python3d/abc/methods.py b/ejabberd_python3d/abc/methods.py index 22553cb..acfd8d5 100644 --- a/ejabberd_python3d/abc/methods.py +++ b/ejabberd_python3d/abc/methods.py @@ -659,3 +659,21 @@ class SetMaster(API): def transform_response(self, api, arguments, response): return response.get("res") + + +class SetPresence(API): + method = "set_presence" + # TODO: some arguments is not required + arguments = [StringArgument('user'), StringArgument('host'), StringArgument('resource'), StringArgument('type'), + StringArgument('show'), StringArgument('status'), StringArgument('priority')] + + def transform_response(self, api, arguments, response): + return response.get("res") == 0 + + +class SetVcard(API): + method = "set_vcard" + arguments = [StringArgument('user'), StringArgument('host'), StringArgument('name'), StringArgument('content')] + + def transform_response(self, api, arguments, response): + return response.get("res") == 0 From 7a8e8d5a62d81fcb6d52ff48f1f5101701ac0106 Mon Sep 17 00:00:00 2001 From: Dedaldino3D Date: Mon, 16 Nov 2020 13:53:27 +0100 Subject: [PATCH 58/99] Add SetVcardMulti & SetVcard2 in methods --- ejabberd_python3d/abc/methods.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/ejabberd_python3d/abc/methods.py b/ejabberd_python3d/abc/methods.py index acfd8d5..c7724b0 100644 --- a/ejabberd_python3d/abc/methods.py +++ b/ejabberd_python3d/abc/methods.py @@ -677,3 +677,21 @@ class SetVcard(API): def transform_response(self, api, arguments, response): return response.get("res") == 0 + + +class SetVcard2(API): + method = "set_vcard2" + arguments = [StringArgument("user"), StringArgument("host"), StringArgument("name"), StringArgument("subname"), + StringArgument("content")] + + def transform_response(self, api, arguments, response): + return response.get("res") == 0 + + +class SetVcardMulti(API): + method = "set_vcard2_multi" + arguments = [StringArgument("user"), StringArgument("host"), StringArgument("name"), StringArgument("subname"), + StringArgument("contents")] + + def transform_response(self, api, arguments, response): + return response.get("res") == 0 From 39db0c7b1a29f9bb4fdabb7922bd7ee4691ed5b8 Mon Sep 17 00:00:00 2001 From: Dedaldino3D Date: Mon, 16 Nov 2020 13:56:44 +0100 Subject: [PATCH 59/99] Add SrgDelete & SrgCreate in methods --- ejabberd_python3d/abc/methods.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/ejabberd_python3d/abc/methods.py b/ejabberd_python3d/abc/methods.py index c7724b0..ec9cdb5 100644 --- a/ejabberd_python3d/abc/methods.py +++ b/ejabberd_python3d/abc/methods.py @@ -695,3 +695,20 @@ class SetVcardMulti(API): def transform_response(self, api, arguments, response): return response.get("res") == 0 + + +class SrgCreate(API): + method = "srg_create" + arguments = [StringArgument("group"), StringArgument("host"), StringArgument("name"), StringArgument("description"), + StringArgument("display")] + + def transform_response(self, api, arguments, response): + return response.get("res") == 0 + + +class SrgDelete(API): + method = "srg_delete" + arguments = [StringArgument("group"), StringArgument("host")] + + def transform_response(self, api, arguments, response): + return response.get("res") == 0 From c36d81362d04abe4973b7559a13a3f2e8fadfabe Mon Sep 17 00:00:00 2001 From: Dedaldino3D Date: Mon, 16 Nov 2020 13:59:08 +0100 Subject: [PATCH 60/99] Add SrgGetMembers & SrgGetInfo in methods --- ejabberd_python3d/abc/methods.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/ejabberd_python3d/abc/methods.py b/ejabberd_python3d/abc/methods.py index ec9cdb5..2f4e1a3 100644 --- a/ejabberd_python3d/abc/methods.py +++ b/ejabberd_python3d/abc/methods.py @@ -712,3 +712,19 @@ class SrgDelete(API): def transform_response(self, api, arguments, response): return response.get("res") == 0 + + +class SrgGetInfo(object): + method = "srg_get_info" + arguments = [StringArgument("group"), StringArgument("host")] + + def transform_response(self, api, arguments, response): + return response.get("informations") + + +class SrgGetMembers(object): + method = "srg_get_members" + arguments = [StringArgument("group"), StringArgument("host")] + + def transform_response(self, api, arguments, response): + return response.get("members") From 96c6d2c66f67b8172b9a63ca357db203e9fd4d57 Mon Sep 17 00:00:00 2001 From: Dedaldino3D Date: Mon, 16 Nov 2020 14:06:20 +0100 Subject: [PATCH 61/99] Add SrgUserDel, SrgUserAdd & SrgList in methods --- ejabberd_python3d/abc/methods.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/ejabberd_python3d/abc/methods.py b/ejabberd_python3d/abc/methods.py index 2f4e1a3..063ad7b 100644 --- a/ejabberd_python3d/abc/methods.py +++ b/ejabberd_python3d/abc/methods.py @@ -728,3 +728,27 @@ class SrgGetMembers(object): def transform_response(self, api, arguments, response): return response.get("members") + + +class SrgList(API): + method = "srg_list" + arguments = [StringArgument("host")] + + def transform_response(self, api, arguments, response): + return response.get("groups") + + +class SrgUserAdd(API): + method = "srg_user_add" + arguments = [StringArgument("user"), StringArgument("host"), StringArgument("group"), StringArgument("grouphost")] + + def transform_response(self, api, arguments, response): + return response.get("res") == 0 + + +class SrgUserDel(API): + method = "srg_user_del" + arguments = [StringArgument("user"), StringArgument("host"), StringArgument("group"), StringArgument("grouphost")] + + def transform_response(self, api, arguments, response): + return response.get("res") == 0 From 428069b4c1863f308eef3872578c03b984c27671 Mon Sep 17 00:00:00 2001 From: Dedaldino3D Date: Mon, 16 Nov 2020 14:14:22 +0100 Subject: [PATCH 62/99] Add Stats & StatsHost in methods --- ejabberd_python3d/abc/methods.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/ejabberd_python3d/abc/methods.py b/ejabberd_python3d/abc/methods.py index 063ad7b..3bb0c4f 100644 --- a/ejabberd_python3d/abc/methods.py +++ b/ejabberd_python3d/abc/methods.py @@ -752,3 +752,21 @@ class SrgUserDel(API): def transform_response(self, api, arguments, response): return response.get("res") == 0 + + +class Stats(API): + method = "stats" + # TODO: name is between: registeredusers onlineusers onlineusersnode uptimeseconds processes + arguments = [StringArgument('name')] + + def transform_response(self, api, arguments, response): + return response.get("stat") + + +class StatsHost(API): + method = "stats_host" + # TODO: name is between: registeredusers onlineusers + arguments = [StringArgument('name'), StringArgument('host')] + + def transform_response(self, api, arguments, response): + return response.get("stat") From 973d39a7a53dc212fac995845a9ecb78eb737d0f Mon Sep 17 00:00:00 2001 From: Dedaldino3D Date: Mon, 16 Nov 2020 14:19:35 +0100 Subject: [PATCH 63/99] Add statuses in methods --- ejabberd_python3d/abc/methods.py | 40 ++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/ejabberd_python3d/abc/methods.py b/ejabberd_python3d/abc/methods.py index 3bb0c4f..cde560d 100644 --- a/ejabberd_python3d/abc/methods.py +++ b/ejabberd_python3d/abc/methods.py @@ -770,3 +770,43 @@ class StatsHost(API): def transform_response(self, api, arguments, response): return response.get("stat") + + +class Status(API): + method = "status" + arguments = [] + + def transform_response(self, api, arguments, response): + return response.get("res") + + +class StatusList(API): + method = "status_list" + arguments = [StringArgument("status")] + + def transform_response(self, api, arguments, response): + return response.get("users") + + +class StatusListHost(API): + method = "status_list_host" + arguments = [StringArgument("host"), StringArgument("status")] + + def transform_response(self, api, arguments, response): + return response.get("users") + + +class StatusNum(API): + method = "status_num" + arguments = [StringArgument("status")] + + def transform_response(self, api, arguments, response): + return response.get("users") + + +class StatusNumHost(API): + method = "status_num_host" + arguments = [StringArgument("host"), StringArgument("status")] + + def transform_response(self, api, arguments, response): + return response.get("users") From 56c749812bc2222493c4be4ce9dd38da9ae137c9 Mon Sep 17 00:00:00 2001 From: Dedaldino3D Date: Mon, 16 Nov 2020 14:23:00 +0100 Subject: [PATCH 64/99] Add Stop & StopKindly in methods --- ejabberd_python3d/abc/methods.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/ejabberd_python3d/abc/methods.py b/ejabberd_python3d/abc/methods.py index cde560d..f6c1cdf 100644 --- a/ejabberd_python3d/abc/methods.py +++ b/ejabberd_python3d/abc/methods.py @@ -810,3 +810,19 @@ class StatusNumHost(API): def transform_response(self, api, arguments, response): return response.get("users") + + +class Stop(API): + method = "stop" + arguments = [] + + def transform_response(self, api, arguments, response): + return response.get("res") == 0 + + +class StopKindly(API): + method = "stop_kindly" + arguments = [PositiveIntegerArgument("delay"), StringArgument("announcement")] + + def transform_response(self, api, arguments, response): + return response.get("res") == 0 From c157e673e2fc70571a8ae2e6a02d30e35547c08a Mon Sep 17 00:00:00 2001 From: Dedaldino3D Date: Mon, 16 Nov 2020 14:29:05 +0100 Subject: [PATCH 65/99] Add UpdateList, Update & UpdateSql in methods, Add update_sql in client --- ejabberd_python3d/abc/methods.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/ejabberd_python3d/abc/methods.py b/ejabberd_python3d/abc/methods.py index f6c1cdf..afe4abf 100644 --- a/ejabberd_python3d/abc/methods.py +++ b/ejabberd_python3d/abc/methods.py @@ -826,3 +826,27 @@ class StopKindly(API): def transform_response(self, api, arguments, response): return response.get("res") == 0 + + +class Update(API): + method = "update" + arguments = [StringArgument("module")] + + def transform_response(self, api, arguments, response): + return response.get("res") + + +class UpdateList(API): + method = "update_list" + arguments = [] + + def transform_response(self, api, arguments, response): + return response.get("modules") + + +class UpdateSql(object): + method = "update_sql" + arguments = [] + + def transform_response(self, api, arguments, response): + return response.get("res") == 0 From 6ce0f2bfae2de29a4f82a952c0ed9621da1bf90d Mon Sep 17 00:00:00 2001 From: Dedaldino3D Date: Mon, 16 Nov 2020 14:31:25 +0100 Subject: [PATCH 66/99] Add UserResources in methods --- ejabberd_python3d/abc/methods.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/ejabberd_python3d/abc/methods.py b/ejabberd_python3d/abc/methods.py index afe4abf..571b61d 100644 --- a/ejabberd_python3d/abc/methods.py +++ b/ejabberd_python3d/abc/methods.py @@ -850,3 +850,11 @@ class UpdateSql(object): def transform_response(self, api, arguments, response): return response.get("res") == 0 + + +class UserResources(API): + method = "update_sql" + arguments = [StringArgument("user"), StringArgument("server")] + + def transform_response(self, api, arguments, response): + return response.get("resources") From 9290e522691cf6df1cb51934f77cdc15887c7ff8 Mon Sep 17 00:00:00 2001 From: Dedaldino3D Date: Mon, 16 Nov 2020 15:09:21 +0100 Subject: [PATCH 67/99] Finish implementation of some methods, finish add update_sql method in client --- ejabberd_python3d/abc/api.py | 13 ----------- ejabberd_python3d/abc/methods.py | 8 +++++++ ejabberd_python3d/client.py | 40 +++++++++++++------------------- 3 files changed, 24 insertions(+), 37 deletions(-) diff --git a/ejabberd_python3d/abc/api.py b/ejabberd_python3d/abc/api.py index 5be3d9e..b30b021 100644 --- a/ejabberd_python3d/abc/api.py +++ b/ejabberd_python3d/abc/api.py @@ -465,12 +465,6 @@ def modules_installed(self): # TODO def muc_unregister_nick(self, nick): # Unregister the nick in the MUC service - @abstractmethod - def num_active_users(self, host, days): - """ - Get number of users active in the last days - """ - pass @abstractmethod def num_resources(self, user, host): @@ -529,13 +523,6 @@ def reload_config(self): """ pass - @abstractmethod - def remove_node(self, node): - """ - Remove an ejabberd node from Mnesia clustering config - """ - pass - @abstractmethod def reopen_log(self): """ diff --git a/ejabberd_python3d/abc/methods.py b/ejabberd_python3d/abc/methods.py index 571b61d..87cfedc 100644 --- a/ejabberd_python3d/abc/methods.py +++ b/ejabberd_python3d/abc/methods.py @@ -858,3 +858,11 @@ class UserResources(API): def transform_response(self, api, arguments, response): return response.get("resources") + + +class KickUser(API): + method = "kick_user" + arguments = [StringArgument("user"), StringArgument("host")] + + def transform_response(self, api, arguments, response): + return response.get("num_resources") diff --git a/ejabberd_python3d/client.py b/ejabberd_python3d/client.py index 3fd85a1..4596450 100644 --- a/ejabberd_python3d/client.py +++ b/ejabberd_python3d/client.py @@ -407,31 +407,25 @@ def delete_rosteritem(self, localuser, localserver, user, server): # Generates markdown documentation for ejabberd_commands def muc_online_rooms(self, host=None): - pass + return self._call_api(methods.MucOnlineRooms, host=host) def create_room(self, name, service, host): - pass + return self._call_api(methods.CreateRoom, name=name, service=service, host=host) def destroy_room(self, name, service, host): - pass + return self._call_api(methods.DestroyRoom, name=name, service=service, host=host) def get_room_options(self, name, service): - pass + return self._call_api(methods.GetRoomOptions, name=name, service=service) def change_room_option(self, name, service, option, value): - pass + return self._call_api(methods.ChangeRoomOption, name=name, service=service, option=option, value=value) def set_room_affiliation(self, name, service, jid, affiliation): - pass + return self._call_api(methods.GetRoomAffiliation, name=name, service=service, jid=jid, affiliation=affiliation) def get_room_affiliations(self, name, service): - pass - - def add_roster_item(self, localuser, localserver, user, server, nick, group, subs): - pass - - def remove_rosteritem(self, localuser, localserver, user, server): - pass + return self._call_api(methods.GetRoomAffiliations, name=name, service=service) def get_cookie(self): """ @@ -559,7 +553,7 @@ def kick_user(self, user, host): """ Disconnect user's active sessions """ - return self._call_api(methods.KickUsers, user=user, host=host) + return self._call_api(methods.KickUser, user=user, host=host) # TODO def leave_cluster(self, node): # Remove node handled by Node from the cluster @@ -886,11 +880,9 @@ def stats(self, name): """ try: return self._call_api(methods.Stats, name=name) - except Exception as e: + except xmlrpc_client.Fault as e: msg = 'processes stats NOT available in this version of Ejabberd' - if e.message == self.errors['connect']: - raise Exception('{}\n{}\n'.format(msg, e.message)) - raise Exception(e) + raise Exception('{}\n{} - code: {}\n'.format(msg, e.faultString, e.faultCode)) def stats_host(self, name, host): """ @@ -947,12 +939,6 @@ def stop_kindly(self, delay, announcement): # TODO def subscribe_room(self, user, nick, room, nodes): # Subscribe to a MUC conference - def unregister(self, user, host): - """ - Unregister a user - """ - return self._call_api(methods.Unregister, user=user, host=host) - # TODO def unsubscribe_room(self, user, room): # Unsubscribe from a MUC conference @@ -968,6 +954,12 @@ def update_list(self): """ return self._call_api(methods.UpdateList) + def update_sql(self): + """ + List modified modules that can be updated + """ + return self._call_api(methods.UpdateSql) + def user_resources(self, user, server): """ List user's connected resources From e53f75abfc7670cde9693a5ccc8f110d9dd1ea61 Mon Sep 17 00:00:00 2001 From: Dedaldino3D Date: Mon, 16 Nov 2020 17:26:55 +0100 Subject: [PATCH 68/99] Create a script to extract TODOS --- scripts/TODOS.md | 0 scripts/extract_todos.py | 46 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+) create mode 100644 scripts/TODOS.md create mode 100644 scripts/extract_todos.py diff --git a/scripts/TODOS.md b/scripts/TODOS.md new file mode 100644 index 0000000..e69de29 diff --git a/scripts/extract_todos.py b/scripts/extract_todos.py new file mode 100644 index 0000000..c559420 --- /dev/null +++ b/scripts/extract_todos.py @@ -0,0 +1,46 @@ +import os +import time + +ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +MAIN_PACKAGE_DIRECTORY = os.path.join('ejabberd_python3d', ROOT_DIR) +WRITE_FILE_NAME = "TODOS.md" + + +def create_file(): + f = open(WRITE_FILE_NAME, 'w') + # f = open(os.path.join(WRITE_FILE_NAME, ROOT_DIR), 'w') + return f + + +def report_callee(filename, filewrite): + print("==> Writing todos in :{} from :{}".format(filewrite, filename)) + + +skip_dirs = ['build', 'dist', '__pycache__', '.git', '.idea', 'ejabberd_python3d.egg-info'] + + +def extract_todos(dir, debug=False): + if os.path.isdir(dir): + file = create_file() + for root, dirs, files in os.walk((dir if os.path.isabs(dir) else os.path.abspath(dir))): + for d in skip_dirs: + if d in dirs: + dirs.remove(d) + for f in files: + try: + with open(os.path.join(f, root), 'r') as fr: + line = fr.readline() + if line.startswith("# TODO") or line.find("TODO"): + file.writelines(line) + if debug: + report_callee(f, file.name) + time.sleep(1) + except PermissionError: + print("PermissionError. file: {:<25} dir: {}".format(f, root)) + time.sleep(0.5) + else: + raise ValueError("Enter a valid dir name") + + +if __name__ == '__main__': + extract_todos(MAIN_PACKAGE_DIRECTORY, debug=True) From 606ec24eb2454a48d9c74ff1f682bada96cbead1 Mon Sep 17 00:00:00 2001 From: Dedaldino3D Date: Mon, 16 Nov 2020 17:39:45 +0100 Subject: [PATCH 69/99] raise NotImplementedError in all methods and remove unused methods in api.py --- ejabberd_python3d/abc/api.py | 164 +++++++++++++++++------------------ 1 file changed, 78 insertions(+), 86 deletions(-) diff --git a/ejabberd_python3d/abc/api.py b/ejabberd_python3d/abc/api.py index b30b021..f96537e 100644 --- a/ejabberd_python3d/abc/api.py +++ b/ejabberd_python3d/abc/api.py @@ -88,79 +88,71 @@ def transform_response(self, api, arguments, response): class EjabberdBaseAPI(ABC): @abstractmethod def echo(self, sentence): - pass + raise NotImplementedError("superclass must implement this method") @abstractmethod def registered_users(self, host): - pass + raise NotImplementedError("superclass must implement this method") @abstractmethod def register(self, user, host, password): - pass + raise NotImplementedError("superclass must implement this method") @abstractmethod def unregister(self, user, host): - pass + raise NotImplementedError("superclass must implement this method") @abstractmethod def change_password(self, user, host, password, newpass): - pass + raise NotImplementedError("superclass must implement this method") @abstractmethod def check_password_hash(self, user, host, password): - pass + raise NotImplementedError("superclass must implement this method") @abstractmethod def connected_users(self): - pass + raise NotImplementedError("superclass must implement this method") @abstractmethod def connected_users_info(self): - pass + raise NotImplementedError("superclass must implement this method") @abstractmethod def connected_users_number(self): - pass + raise NotImplementedError("superclass must implement this method") @abstractmethod def user_sessions_info(self): - pass + raise NotImplementedError("superclass must implement this method") @abstractmethod def muc_online_rooms(self, host=None): - pass + raise NotImplementedError("superclass must implement this method") @abstractmethod def create_room(self, name, service, host): - pass + raise NotImplementedError("superclass must implement this method") @abstractmethod def destroy_room(self, name, service, host): - pass + raise NotImplementedError("superclass must implement this method") @abstractmethod def get_room_options(self, name, service): - pass + raise NotImplementedError("superclass must implement this method") @abstractmethod def change_room_option(self, name, service, option, value): - pass + raise NotImplementedError("superclass must implement this method") @abstractmethod def set_room_affiliation(self, name, service, jid, affiliation): - pass + raise NotImplementedError("superclass must implement this method") @abstractmethod def get_room_affiliations(self, name, service): - pass - - @abstractmethod - def add_roster_item(self, localuser, localserver, user, server, nick, group, subs): - pass - - @abstractmethod - def remove_rosteritem(self, localuser, localserver, user, server): - pass + raise NotImplementedError("superclass must implement this method") @abstractmethod def add_rosteritem(self, @@ -170,7 +162,7 @@ def add_rosteritem(self, """ Add an item to a user's roster (self,supports ODBC): """ - pass + raise NotImplementedError("superclass must implement this method") # TODO def backup(self, file): Store the database to backup file @@ -179,7 +171,7 @@ def ban_account(self, user, host, reason): """ Ban an account: kick sessions and set random password """ - pass + raise NotImplementedError("superclass must implement this method") # TODO def change_room_option(self, name, service, option, value) # Change an option in a MUC room @@ -189,14 +181,14 @@ def check_account(self, user, host): """ Check if an account exists or not """ - pass + raise NotImplementedError("superclass must implement this method") @abstractmethod def check_password(self, user, host, password): """ Check if a password is correct """ - pass + raise NotImplementedError("superclass must implement this method") # TODO def compile(self, file): # Recompile and reload Erlang source code file @@ -206,7 +198,7 @@ def connected_users_vhost(self, host): """ Get the list of established sessions in a vhost """ - pass + raise NotImplementedError("superclass must implement this method") # TODO def convert_to_scram(self, host): # Convert the passwords in ‘users’ SQL table to SCRAM @@ -228,7 +220,7 @@ def delete_expired_messages(self): """ Delete expired offline messages from database """ - pass + raise NotImplementedError("superclass must implement this method") # TODO def delete_mnesia(self, host): # Export all tables as SQL queries to a file @@ -241,14 +233,14 @@ def delete_old_messages(self, days): """ Delete offline messages older than DAYS """ - pass + raise NotImplementedError("superclass must implement this method") @abstractmethod def delete_old_users(self, days): """ Delete users that didn't log in last days, or that never logged """ - pass + raise NotImplementedError("superclass must implement this method") @abstractmethod def delete_old_users_vhost(self, host, days): @@ -256,14 +248,14 @@ def delete_old_users_vhost(self, host, days): Delete users that didn't log in last days in vhost, or that never logged """ - pass + raise NotImplementedError("superclass must implement this method") @abstractmethod def delete_rosteritem(self, localuser, localserver, user, server): """ Delete an item from a user's roster (self,supports ODBC): """ - pass + raise NotImplementedError("superclass must implement this method") # TODO def destroy_room(self, name, service): # Destroy a MUC room @@ -296,21 +288,21 @@ def get_cookie(self): """ Get the Erlang cookie of this node """ - pass + raise NotImplementedError("superclass must implement this method") @abstractmethod def get_last(self, user, host): """ Get last activity information (self,timestamp and status): """ - pass + raise NotImplementedError("superclass must implement this method") @abstractmethod def get_loglevel(self): """ Get the current loglevel """ - pass + raise NotImplementedError("superclass must implement this method") # TODO def get_offline_count(self): # Get the number of unread offline messages @@ -349,7 +341,7 @@ def get_roster(self, user, server): {group,string}]}}}} """ - pass + raise NotImplementedError("superclass must implement this method") # TODO get_subscribers(self, name, service): # List subscribers of a MUC conference @@ -361,21 +353,21 @@ def get_vcard(self, user, host, name): """ Get content from a vCard field """ - pass + raise NotImplementedError("superclass must implement this method") @abstractmethod def get_vcard2(self, user, host, name, subname): """ Get content from a vCard field """ - pass + raise NotImplementedError("superclass must implement this method") @abstractmethod def get_vcard2_multi(self, user, host, name, subname): """ Get multiple contents from a vCard field """ - pass + raise NotImplementedError("superclass must implement this method") # TODO def import_dir(self, file): # Import users data from jabberd14 spool dir @@ -392,7 +384,7 @@ def incoming_s2s_number(self): """ Number of incoming s2s connections on the node """ - pass + raise NotImplementedError("superclass must implement this method") # TODO def install_fallback(self, file): # Install the database from a fallback file @@ -404,14 +396,14 @@ def kick_session(self, user, host, resource, reason): """ Kick a user session """ - pass + raise NotImplementedError("superclass must implement this method") @abstractmethod def kick_user(self, user, host): """ Disconnect user's active sessions """ - pass + raise NotImplementedError("superclass must implement this method") # TODO def leave_cluster(self, node): # Remove node handled by Node from the cluster @@ -425,7 +417,7 @@ def list_cluster(self): {nodes,{list,{node,atom}}} """ - pass + raise NotImplementedError("superclass must implement this method") # TODO def load(self, file): # Restore the database from text file @@ -449,14 +441,14 @@ def modules_available(self): """ List available modules """ - pass + raise NotImplementedError("superclass must implement this method") @abstractmethod def modules_installed(self): """ List installed modules """ - pass + raise NotImplementedError("superclass must implement this method") # TODO def modules_update_specs(self): @@ -471,14 +463,14 @@ def num_resources(self, user, host): """ Get the number of resources of a user """ - pass + raise NotImplementedError("superclass must implement this method") @abstractmethod def outgoing_s2s_number(self): """ Number of outgoing s2s connections on the node """ - pass + raise NotImplementedError("superclass must implement this method") # TODO def privacy_set(self, user, host, xmlquery): # Send a IQ set privacy stanza for a local account @@ -493,14 +485,14 @@ def process_rosteritems(self, action, subs, asks, users, contacts): """ List or delete rosteritems that match filtering options """ - pass + raise NotImplementedError("superclass must implement this method") @abstractmethod def push_alltoall(self, host, group): """ Add all the users to all the users of Host in Group """ - pass + raise NotImplementedError("superclass must implement this method") # TODO def push_roster(self, file, user, host): # Push template roster from file to a user @@ -512,7 +504,7 @@ def registered_vhosts(self): """ List all registered vhosts in SERVER """ - pass + raise NotImplementedError("superclass must implement this method") @abstractmethod def reload_config(self): @@ -521,28 +513,28 @@ def reload_config(self): (only affects ACL and Access) """ - pass + raise NotImplementedError("superclass must implement this method") @abstractmethod def reopen_log(self): """ Reopen the log files """ - pass + raise NotImplementedError("superclass must implement this method") @abstractmethod def resource_num(self, user, host, num): """ Resource string of a session number """ - pass + raise NotImplementedError("superclass must implement this method") @abstractmethod def restart(self): """ Restart ejabberd """ - pass + raise NotImplementedError("superclass must implement this method") # TODO def restore(self, file): # Restore the database from backup file @@ -568,7 +560,7 @@ def send_message(self, type, from_jid, to, subject, body): """ Send a message to a local or remote bare of full JID """ - pass + raise NotImplementedError("superclass must implement this method") # TODO def send_stanza(self, from, to, stanza): # Send a stanza; provide From JID and valid To JID @@ -577,14 +569,14 @@ def send_stanza_c2s(self, user, host, resource, stanza): """ Send a stanza as if sent from a c2s session """ - pass + raise NotImplementedError("superclass must implement this method") @abstractmethod def set_last(self, user, host, timestamp, status): """ Set last activity information """ - pass + raise NotImplementedError("superclass must implement this method") @abstractmethod def set_loglevel(self, loglevel): @@ -600,28 +592,28 @@ def set_loglevel(self, loglevel): {logger,atom} """ - pass + raise NotImplementedError("superclass must implement this method") @abstractmethod def set_master(self, nodename): """ Set master node of the clustered Mnesia tables """ - pass + raise NotImplementedError("superclass must implement this method") @abstractmethod def set_nickname(self, user, host, nickname): """ Set nickname in a user's vCard """ - pass + raise NotImplementedError("superclass must implement this method") @abstractmethod def set_presence(self, user, host, resource, type, show, status, priority): """ Set presence of a session """ - pass + raise NotImplementedError("superclass must implement this method") # TODO def set_room_affiliation(self, name, service, jid, affiliation): # Change an affiliation in a MUC room @@ -631,70 +623,70 @@ def set_vcard(self, user, host, name, content): """ Set content in a vCard field """ - pass + raise NotImplementedError("superclass must implement this method") @abstractmethod def set_vcard2(self, user, host, name, subname, content): """ Set content in a vCard subfield """ - pass + raise NotImplementedError("superclass must implement this method") @abstractmethod def set_vcard2_multi(self, user, host, name, subname, contents): """ *Set multiple contents in a vCard subfield """ - pass + raise NotImplementedError("superclass must implement this method") @abstractmethod def srg_create(self, group, host, name, description, display): """ Create a Shared Roster Group """ - pass + raise NotImplementedError("superclass must implement this method") @abstractmethod def srg_delete(self, group, host): """ Delete a Shared Roster Group """ - pass + raise NotImplementedError("superclass must implement this method") @abstractmethod def srg_get_info(self, group, host): """ Get info of a Shared Roster Group """ - pass + raise NotImplementedError("superclass must implement this method") @abstractmethod def srg_get_members(self, group, host): """ Get members of a Shared Roster Group """ - pass + raise NotImplementedError("superclass must implement this method") @abstractmethod def srg_list(self, host): """ List the Shared Roster Groups in Host """ - pass + raise NotImplementedError("superclass must implement this method") @abstractmethod def srg_user_add(self, user, host, group, grouphost): """ Add the JID user@host to the Shared Roster Group """ - pass + raise NotImplementedError("superclass must implement this method") @abstractmethod def srg_user_del(self, user, host, group, grouphost): """ Delete this JID user@host from the Shared Roster Group """ - pass + raise NotImplementedError("superclass must implement this method") @abstractmethod def stats(self, name): @@ -707,7 +699,7 @@ def stats(self, name): * ``uptimeseconds`` * ``processes`` - Introduced sometime after Ejabberd 15.07 """ - pass + raise NotImplementedError("superclass must implement this method") @abstractmethod def stats_host(self, name, host): @@ -717,56 +709,56 @@ def stats_host(self, name, host): * ``registeredusers`` * ``onlineusers`` """ - pass + raise NotImplementedError("superclass must implement this method") @abstractmethod def status(self): """ Get ejabberd status """ - pass + raise NotImplementedError("superclass must implement this method") @abstractmethod def status_list(self, status): """ List of logged users with this status """ - pass + raise NotImplementedError("superclass must implement this method") @abstractmethod def status_list_host(self, host, status): """ List of users logged in host with their statuses """ - pass + raise NotImplementedError("superclass must implement this method") @abstractmethod def status_num(self, status): """ Number of logged users with this status """ - pass + raise NotImplementedError("superclass must implement this method") @abstractmethod def status_num_host(self, host, status): """ Number of logged users with this status in host """ - pass + raise NotImplementedError("superclass must implement this method") @abstractmethod def stop(self): """ Stop ejabberd """ - pass + raise NotImplementedError("superclass must implement this method") @abstractmethod def stop_kindly(self, delay, announcement): """ Inform users and rooms, wait, and stop the server """ - pass + raise NotImplementedError("superclass must implement this method") # TODO def subscribe_room(self, user, nick, room, nodes): # Subscribe to a MUC conference @@ -779,14 +771,14 @@ def update(self, module): """ Update the given module, or use the keyword: all """ - pass + raise NotImplementedError("superclass must implement this method") @abstractmethod def update_list(self): """ List modified modules that can be updated """ - pass + raise NotImplementedError("superclass must implement this method") @abstractmethod def user_resources(self, user, server): @@ -807,4 +799,4 @@ def user_resources(self, user, server): {resources,{list,{resource,string}}} """ - pass + raise NotImplementedError("superclass must implement this method") From b241dfbaeb07b0e93e0a4e2b819f7ecfb013ee22 Mon Sep 17 00:00:00 2001 From: Dedaldino3D Date: Mon, 16 Nov 2020 17:42:07 +0100 Subject: [PATCH 70/99] rename superclass ==> subclass in api.py --- ejabberd_python3d/abc/api.py | 156 +++++++++++++++++------------------ 1 file changed, 78 insertions(+), 78 deletions(-) diff --git a/ejabberd_python3d/abc/api.py b/ejabberd_python3d/abc/api.py index f96537e..0339eb6 100644 --- a/ejabberd_python3d/abc/api.py +++ b/ejabberd_python3d/abc/api.py @@ -88,71 +88,71 @@ def transform_response(self, api, arguments, response): class EjabberdBaseAPI(ABC): @abstractmethod def echo(self, sentence): - raise NotImplementedError("superclass must implement this method") + raise NotImplementedError("subclass must implement this method") @abstractmethod def registered_users(self, host): - raise NotImplementedError("superclass must implement this method") + raise NotImplementedError("subclass must implement this method") @abstractmethod def register(self, user, host, password): - raise NotImplementedError("superclass must implement this method") + raise NotImplementedError("subclass must implement this method") @abstractmethod def unregister(self, user, host): - raise NotImplementedError("superclass must implement this method") + raise NotImplementedError("subclass must implement this method") @abstractmethod def change_password(self, user, host, password, newpass): - raise NotImplementedError("superclass must implement this method") + raise NotImplementedError("subclass must implement this method") @abstractmethod def check_password_hash(self, user, host, password): - raise NotImplementedError("superclass must implement this method") + raise NotImplementedError("subclass must implement this method") @abstractmethod def connected_users(self): - raise NotImplementedError("superclass must implement this method") + raise NotImplementedError("subclass must implement this method") @abstractmethod def connected_users_info(self): - raise NotImplementedError("superclass must implement this method") + raise NotImplementedError("subclass must implement this method") @abstractmethod def connected_users_number(self): - raise NotImplementedError("superclass must implement this method") + raise NotImplementedError("subclass must implement this method") @abstractmethod def user_sessions_info(self): - raise NotImplementedError("superclass must implement this method") + raise NotImplementedError("subclass must implement this method") @abstractmethod def muc_online_rooms(self, host=None): - raise NotImplementedError("superclass must implement this method") + raise NotImplementedError("subclass must implement this method") @abstractmethod def create_room(self, name, service, host): - raise NotImplementedError("superclass must implement this method") + raise NotImplementedError("subclass must implement this method") @abstractmethod def destroy_room(self, name, service, host): - raise NotImplementedError("superclass must implement this method") + raise NotImplementedError("subclass must implement this method") @abstractmethod def get_room_options(self, name, service): - raise NotImplementedError("superclass must implement this method") + raise NotImplementedError("subclass must implement this method") @abstractmethod def change_room_option(self, name, service, option, value): - raise NotImplementedError("superclass must implement this method") + raise NotImplementedError("subclass must implement this method") @abstractmethod def set_room_affiliation(self, name, service, jid, affiliation): - raise NotImplementedError("superclass must implement this method") + raise NotImplementedError("subclass must implement this method") @abstractmethod def get_room_affiliations(self, name, service): - raise NotImplementedError("superclass must implement this method") + raise NotImplementedError("subclass must implement this method") @abstractmethod def add_rosteritem(self, @@ -162,7 +162,7 @@ def add_rosteritem(self, """ Add an item to a user's roster (self,supports ODBC): """ - raise NotImplementedError("superclass must implement this method") + raise NotImplementedError("subclass must implement this method") # TODO def backup(self, file): Store the database to backup file @@ -171,7 +171,7 @@ def ban_account(self, user, host, reason): """ Ban an account: kick sessions and set random password """ - raise NotImplementedError("superclass must implement this method") + raise NotImplementedError("subclass must implement this method") # TODO def change_room_option(self, name, service, option, value) # Change an option in a MUC room @@ -181,14 +181,14 @@ def check_account(self, user, host): """ Check if an account exists or not """ - raise NotImplementedError("superclass must implement this method") + raise NotImplementedError("subclass must implement this method") @abstractmethod def check_password(self, user, host, password): """ Check if a password is correct """ - raise NotImplementedError("superclass must implement this method") + raise NotImplementedError("subclass must implement this method") # TODO def compile(self, file): # Recompile and reload Erlang source code file @@ -198,7 +198,7 @@ def connected_users_vhost(self, host): """ Get the list of established sessions in a vhost """ - raise NotImplementedError("superclass must implement this method") + raise NotImplementedError("subclass must implement this method") # TODO def convert_to_scram(self, host): # Convert the passwords in ‘users’ SQL table to SCRAM @@ -220,7 +220,7 @@ def delete_expired_messages(self): """ Delete expired offline messages from database """ - raise NotImplementedError("superclass must implement this method") + raise NotImplementedError("subclass must implement this method") # TODO def delete_mnesia(self, host): # Export all tables as SQL queries to a file @@ -233,14 +233,14 @@ def delete_old_messages(self, days): """ Delete offline messages older than DAYS """ - raise NotImplementedError("superclass must implement this method") + raise NotImplementedError("subclass must implement this method") @abstractmethod def delete_old_users(self, days): """ Delete users that didn't log in last days, or that never logged """ - raise NotImplementedError("superclass must implement this method") + raise NotImplementedError("subclass must implement this method") @abstractmethod def delete_old_users_vhost(self, host, days): @@ -248,14 +248,14 @@ def delete_old_users_vhost(self, host, days): Delete users that didn't log in last days in vhost, or that never logged """ - raise NotImplementedError("superclass must implement this method") + raise NotImplementedError("subclass must implement this method") @abstractmethod def delete_rosteritem(self, localuser, localserver, user, server): """ Delete an item from a user's roster (self,supports ODBC): """ - raise NotImplementedError("superclass must implement this method") + raise NotImplementedError("subclass must implement this method") # TODO def destroy_room(self, name, service): # Destroy a MUC room @@ -288,21 +288,21 @@ def get_cookie(self): """ Get the Erlang cookie of this node """ - raise NotImplementedError("superclass must implement this method") + raise NotImplementedError("subclass must implement this method") @abstractmethod def get_last(self, user, host): """ Get last activity information (self,timestamp and status): """ - raise NotImplementedError("superclass must implement this method") + raise NotImplementedError("subclass must implement this method") @abstractmethod def get_loglevel(self): """ Get the current loglevel """ - raise NotImplementedError("superclass must implement this method") + raise NotImplementedError("subclass must implement this method") # TODO def get_offline_count(self): # Get the number of unread offline messages @@ -341,7 +341,7 @@ def get_roster(self, user, server): {group,string}]}}}} """ - raise NotImplementedError("superclass must implement this method") + raise NotImplementedError("subclass must implement this method") # TODO get_subscribers(self, name, service): # List subscribers of a MUC conference @@ -353,21 +353,21 @@ def get_vcard(self, user, host, name): """ Get content from a vCard field """ - raise NotImplementedError("superclass must implement this method") + raise NotImplementedError("subclass must implement this method") @abstractmethod def get_vcard2(self, user, host, name, subname): """ Get content from a vCard field """ - raise NotImplementedError("superclass must implement this method") + raise NotImplementedError("subclass must implement this method") @abstractmethod def get_vcard2_multi(self, user, host, name, subname): """ Get multiple contents from a vCard field """ - raise NotImplementedError("superclass must implement this method") + raise NotImplementedError("subclass must implement this method") # TODO def import_dir(self, file): # Import users data from jabberd14 spool dir @@ -384,7 +384,7 @@ def incoming_s2s_number(self): """ Number of incoming s2s connections on the node """ - raise NotImplementedError("superclass must implement this method") + raise NotImplementedError("subclass must implement this method") # TODO def install_fallback(self, file): # Install the database from a fallback file @@ -396,14 +396,14 @@ def kick_session(self, user, host, resource, reason): """ Kick a user session """ - raise NotImplementedError("superclass must implement this method") + raise NotImplementedError("subclass must implement this method") @abstractmethod def kick_user(self, user, host): """ Disconnect user's active sessions """ - raise NotImplementedError("superclass must implement this method") + raise NotImplementedError("subclass must implement this method") # TODO def leave_cluster(self, node): # Remove node handled by Node from the cluster @@ -417,7 +417,7 @@ def list_cluster(self): {nodes,{list,{node,atom}}} """ - raise NotImplementedError("superclass must implement this method") + raise NotImplementedError("subclass must implement this method") # TODO def load(self, file): # Restore the database from text file @@ -441,14 +441,14 @@ def modules_available(self): """ List available modules """ - raise NotImplementedError("superclass must implement this method") + raise NotImplementedError("subclass must implement this method") @abstractmethod def modules_installed(self): """ List installed modules """ - raise NotImplementedError("superclass must implement this method") + raise NotImplementedError("subclass must implement this method") # TODO def modules_update_specs(self): @@ -463,14 +463,14 @@ def num_resources(self, user, host): """ Get the number of resources of a user """ - raise NotImplementedError("superclass must implement this method") + raise NotImplementedError("subclass must implement this method") @abstractmethod def outgoing_s2s_number(self): """ Number of outgoing s2s connections on the node """ - raise NotImplementedError("superclass must implement this method") + raise NotImplementedError("subclass must implement this method") # TODO def privacy_set(self, user, host, xmlquery): # Send a IQ set privacy stanza for a local account @@ -485,14 +485,14 @@ def process_rosteritems(self, action, subs, asks, users, contacts): """ List or delete rosteritems that match filtering options """ - raise NotImplementedError("superclass must implement this method") + raise NotImplementedError("subclass must implement this method") @abstractmethod def push_alltoall(self, host, group): """ Add all the users to all the users of Host in Group """ - raise NotImplementedError("superclass must implement this method") + raise NotImplementedError("subclass must implement this method") # TODO def push_roster(self, file, user, host): # Push template roster from file to a user @@ -504,7 +504,7 @@ def registered_vhosts(self): """ List all registered vhosts in SERVER """ - raise NotImplementedError("superclass must implement this method") + raise NotImplementedError("subclass must implement this method") @abstractmethod def reload_config(self): @@ -513,28 +513,28 @@ def reload_config(self): (only affects ACL and Access) """ - raise NotImplementedError("superclass must implement this method") + raise NotImplementedError("subclass must implement this method") @abstractmethod def reopen_log(self): """ Reopen the log files """ - raise NotImplementedError("superclass must implement this method") + raise NotImplementedError("subclass must implement this method") @abstractmethod def resource_num(self, user, host, num): """ Resource string of a session number """ - raise NotImplementedError("superclass must implement this method") + raise NotImplementedError("subclass must implement this method") @abstractmethod def restart(self): """ Restart ejabberd """ - raise NotImplementedError("superclass must implement this method") + raise NotImplementedError("subclass must implement this method") # TODO def restore(self, file): # Restore the database from backup file @@ -560,7 +560,7 @@ def send_message(self, type, from_jid, to, subject, body): """ Send a message to a local or remote bare of full JID """ - raise NotImplementedError("superclass must implement this method") + raise NotImplementedError("subclass must implement this method") # TODO def send_stanza(self, from, to, stanza): # Send a stanza; provide From JID and valid To JID @@ -569,14 +569,14 @@ def send_stanza_c2s(self, user, host, resource, stanza): """ Send a stanza as if sent from a c2s session """ - raise NotImplementedError("superclass must implement this method") + raise NotImplementedError("subclass must implement this method") @abstractmethod def set_last(self, user, host, timestamp, status): """ Set last activity information """ - raise NotImplementedError("superclass must implement this method") + raise NotImplementedError("subclass must implement this method") @abstractmethod def set_loglevel(self, loglevel): @@ -592,28 +592,28 @@ def set_loglevel(self, loglevel): {logger,atom} """ - raise NotImplementedError("superclass must implement this method") + raise NotImplementedError("subclass must implement this method") @abstractmethod def set_master(self, nodename): """ Set master node of the clustered Mnesia tables """ - raise NotImplementedError("superclass must implement this method") + raise NotImplementedError("subclass must implement this method") @abstractmethod def set_nickname(self, user, host, nickname): """ Set nickname in a user's vCard """ - raise NotImplementedError("superclass must implement this method") + raise NotImplementedError("subclass must implement this method") @abstractmethod def set_presence(self, user, host, resource, type, show, status, priority): """ Set presence of a session """ - raise NotImplementedError("superclass must implement this method") + raise NotImplementedError("subclass must implement this method") # TODO def set_room_affiliation(self, name, service, jid, affiliation): # Change an affiliation in a MUC room @@ -623,70 +623,70 @@ def set_vcard(self, user, host, name, content): """ Set content in a vCard field """ - raise NotImplementedError("superclass must implement this method") + raise NotImplementedError("subclass must implement this method") @abstractmethod def set_vcard2(self, user, host, name, subname, content): """ Set content in a vCard subfield """ - raise NotImplementedError("superclass must implement this method") + raise NotImplementedError("subclass must implement this method") @abstractmethod def set_vcard2_multi(self, user, host, name, subname, contents): """ *Set multiple contents in a vCard subfield """ - raise NotImplementedError("superclass must implement this method") + raise NotImplementedError("subclass must implement this method") @abstractmethod def srg_create(self, group, host, name, description, display): """ Create a Shared Roster Group """ - raise NotImplementedError("superclass must implement this method") + raise NotImplementedError("subclass must implement this method") @abstractmethod def srg_delete(self, group, host): """ Delete a Shared Roster Group """ - raise NotImplementedError("superclass must implement this method") + raise NotImplementedError("subclass must implement this method") @abstractmethod def srg_get_info(self, group, host): """ Get info of a Shared Roster Group """ - raise NotImplementedError("superclass must implement this method") + raise NotImplementedError("subclass must implement this method") @abstractmethod def srg_get_members(self, group, host): """ Get members of a Shared Roster Group """ - raise NotImplementedError("superclass must implement this method") + raise NotImplementedError("subclass must implement this method") @abstractmethod def srg_list(self, host): """ List the Shared Roster Groups in Host """ - raise NotImplementedError("superclass must implement this method") + raise NotImplementedError("subclass must implement this method") @abstractmethod def srg_user_add(self, user, host, group, grouphost): """ Add the JID user@host to the Shared Roster Group """ - raise NotImplementedError("superclass must implement this method") + raise NotImplementedError("subclass must implement this method") @abstractmethod def srg_user_del(self, user, host, group, grouphost): """ Delete this JID user@host from the Shared Roster Group """ - raise NotImplementedError("superclass must implement this method") + raise NotImplementedError("subclass must implement this method") @abstractmethod def stats(self, name): @@ -699,7 +699,7 @@ def stats(self, name): * ``uptimeseconds`` * ``processes`` - Introduced sometime after Ejabberd 15.07 """ - raise NotImplementedError("superclass must implement this method") + raise NotImplementedError("subclass must implement this method") @abstractmethod def stats_host(self, name, host): @@ -709,56 +709,56 @@ def stats_host(self, name, host): * ``registeredusers`` * ``onlineusers`` """ - raise NotImplementedError("superclass must implement this method") + raise NotImplementedError("subclass must implement this method") @abstractmethod def status(self): """ Get ejabberd status """ - raise NotImplementedError("superclass must implement this method") + raise NotImplementedError("subclass must implement this method") @abstractmethod def status_list(self, status): """ List of logged users with this status """ - raise NotImplementedError("superclass must implement this method") + raise NotImplementedError("subclass must implement this method") @abstractmethod def status_list_host(self, host, status): """ List of users logged in host with their statuses """ - raise NotImplementedError("superclass must implement this method") + raise NotImplementedError("subclass must implement this method") @abstractmethod def status_num(self, status): """ Number of logged users with this status """ - raise NotImplementedError("superclass must implement this method") + raise NotImplementedError("subclass must implement this method") @abstractmethod def status_num_host(self, host, status): """ Number of logged users with this status in host """ - raise NotImplementedError("superclass must implement this method") + raise NotImplementedError("subclass must implement this method") @abstractmethod def stop(self): """ Stop ejabberd """ - raise NotImplementedError("superclass must implement this method") + raise NotImplementedError("subclass must implement this method") @abstractmethod def stop_kindly(self, delay, announcement): """ Inform users and rooms, wait, and stop the server """ - raise NotImplementedError("superclass must implement this method") + raise NotImplementedError("subclass must implement this method") # TODO def subscribe_room(self, user, nick, room, nodes): # Subscribe to a MUC conference @@ -771,14 +771,14 @@ def update(self, module): """ Update the given module, or use the keyword: all """ - raise NotImplementedError("superclass must implement this method") + raise NotImplementedError("subclass must implement this method") @abstractmethod def update_list(self): """ List modified modules that can be updated """ - raise NotImplementedError("superclass must implement this method") + raise NotImplementedError("subclass must implement this method") @abstractmethod def user_resources(self, user, server): @@ -799,4 +799,4 @@ def user_resources(self, user, server): {resources,{list,{resource,string}}} """ - raise NotImplementedError("superclass must implement this method") + raise NotImplementedError("subclass must implement this method") From f433c41baf0831e4d67ed5ee1c5fc8bc203fec57 Mon Sep 17 00:00:00 2001 From: Dedaldino3D Date: Mon, 16 Nov 2020 19:59:48 +0100 Subject: [PATCH 71/99] Move TODOS.md to root directory, update algorithm to extract TODOS --- TODOS.md | 122 +++++++++++++++++++++++++++++++++++++++ scripts/TODOS.md | 0 scripts/extract_todos.py | 51 +++++++++------- 3 files changed, 152 insertions(+), 21 deletions(-) create mode 100644 TODOS.md delete mode 100644 scripts/TODOS.md diff --git a/TODOS.md b/TODOS.md new file mode 100644 index 0000000..ced464d --- /dev/null +++ b/TODOS.md @@ -0,0 +1,122 @@ +# METHODS TO IMPLEMENT +### TODO def backup(self, file): Store the database to backup file +### TODO def change_room_option(self, name, service, option, value) +### TODO def compile(self, file): +### TODO def convert_to_scram(self, host): +### TODO def convert_to_yaml(self, in, out): +### TODO def create_room(self, name, service, host): +### TODO def create_room_with_opts(self, name, service, host, options): +### TODO def create_rooms_file(self, file): +### TODO def delete_mnesia(self, host): +### TODO def delete_old_mam_messages(self, type, days): +### TODO def destroy_room(self, name, service): +### TODO def destroy_rooms_file(self, file): +### TODO def dump(self, file): +### TODO def dump_table(self, file, table): +### TODO def export2sql(self, host, file): +### TODO def export_piefxis(self, dir): +### TODO def export_piefxis_host(self, dir, host): +### TODO def gen_html_doc_for_commands(self, file, regexp, examples): +### TODO def gen_markdown_doc_for_commands(self, file, regexp, examples): +### TODO def get_offline_count(self): +### TODO def get_room_affiliations(self, name, service): +### TODO def get_room_occupants(self, name, service): +### TODO def get_room_occupants_number(self, name, service): +### TODO def get_room_options(self, name, service): +### TODO get_subscribers(self, name, service): +### TODO get_user_rooms(self, user, host): +### TODO def import_dir(self, file): +### TODO def import_file(self, file): +### TODO def import_piefxis(self, file): +### TODO def import_prosody(self, dir) Import data from Prosody +### TODO def install_fallback(self, file): +### TODO def join_cluster(self, node): +### TODO def leave_cluster(self, node): +### TODO def load(self, file): +### TODO def mnesia_change_nodename(self, +### TODO def module_check(self, module): +### TODO def module_install(self, module): +### TODO def module_uninstall(self, module): +### TODO def module_upgrade(self, module): +### TODO def modules_update_specs(self): +### TODO def muc_online_rooms(self, host): +### TODO def muc_unregister_nick(self, nick): +### TODO def privacy_set(self, user, host, xmlquery): +### TODO def private_get(self, user, host, element, ns): +### TODO def private_set(self, user, host, element): +### TODO def push_roster(self, file, user, host): +### TODO def push_roster_all(self, file): +### TODO def restore(self, file): +### TODO def rooms_unused_destroy(self, host, days): +### TODO def rooms_unused_list(self, host, days): +### TODO def rotate_log(self): +### TODO def send_direct_invitation(self, +### TODO def send_stanza(self, from, to, stanza): +### TODO def set_room_affiliation(self, name, service, jid, affiliation): +### TODO def subscribe_room(self, user, nick, room, nodes): +### TODO def unsubscribe_room(self, user, room): +### TODO: add argument options: [{name::string,value::string}]: List of options +### TODO: nodes must be separated by commas, so therefore you can use an array and before send transform arguments +### TODO: some arguments is not required +### TODO: name is between: registeredusers onlineusers onlineusersnode uptimeseconds processes +### TODO: name is between: registeredusers onlineusers +### TODO def backup(self, file): Store the database to backup file +### TODO def change_room_option(self, name, service, option, value) +### TODO def compile(self, file): +### TODO def convert_to_scram(self, host): +### TODO def convert_to_yaml(self, in, out): +### TODO def create_room(self, name, service, host): +### TODO def create_room_with_opts(self, name, service, host, options): +### TODO def create_rooms_file(self, file): +### TODO def delete_mnesia(self, host): +### TODO def delete_old_mam_messages(self, type, days): +### TODO def destroy_room(self, name, service): +### TODO def destroy_rooms_file(self, file): +### TODO def dump(self, file): +### TODO def dump_table(self, file, table): +### TODO def export2sql(self, host, file): +### TODO def export_piefxis(self, dir): +### TODO def export_piefxis_host(self, dir, host): +### TODO def gen_html_doc_for_commands(self, file, regexp, examples): +### TODO def gen_markdown_doc_for_commands(self, file, regexp, examples): +### TODO def get_offline_count(self): +### TODO def get_room_affiliations(self, name, service): +### TODO def get_room_occupants(self, name, service): +### TODO def get_room_occupants_number(self, name, service): +### TODO def get_room_options(self, name, service): +### TODO get_subscribers(self, name, service): +### TODO get_user_rooms(self, user, host): +### TODO def import_dir(self, file): +### TODO def import_file(self, file): +### TODO def import_piefxis(self, file): +### TODO def import_prosody(self, dir) Import data from Prosody +### TODO def install_fallback(self, file): +### TODO def join_cluster(self, node): +### TODO def leave_cluster(self, node): +### TODO def load(self, file): +### TODO def mnesia_change_nodename(self, +### TODO def module_check(self, module): +### TODO def module_install(self, module): +### TODO def module_uninstall(self, module): +### TODO def module_upgrade(self, module): +### TODO def modules_update_specs(self): +### TODO def muc_online_rooms(self, host): +### TODO def muc_unregister_nick(self, nick): +### TODO def privacy_set(self, user, host, xmlquery): +### TODO def private_get(self, user, host, element, ns): +### TODO def private_set(self, user, host, element): +### TODO def push_roster(self, file, user, host): +### TODO def push_roster_all(self, file): +### TODO def restore(self, file): +### TODO def rooms_unused_destroy(self, host, days): +### TODO def rooms_unused_list(self, host, days): +### TODO def rotate_log(self): +### TODO def send_direct_invitation(self, +### TODO def send_stanza(self, from, to, stanza): +### TODO def set_room_affiliation(self, name, service, jid, affiliation): +### TODO def subscribe_room(self, user, nick, room, nodes): +### TODO def unsubscribe_room(self, user, room): + +## Generics TODOS +### TODO: add endpoint parameter +### TODO: add it to logger \ No newline at end of file diff --git a/scripts/TODOS.md b/scripts/TODOS.md deleted file mode 100644 index e69de29..0000000 diff --git a/scripts/extract_todos.py b/scripts/extract_todos.py index c559420..f2311ae 100644 --- a/scripts/extract_todos.py +++ b/scripts/extract_todos.py @@ -1,46 +1,55 @@ import os -import time ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) -MAIN_PACKAGE_DIRECTORY = os.path.join('ejabberd_python3d', ROOT_DIR) -WRITE_FILE_NAME = "TODOS.md" +MAIN_PACKAGE_DIRECTORY = os.path.join(ROOT_DIR, 'ejabberd_python3d') +WRITE_FILE_NAME = os.path.join(ROOT_DIR, "TODOS.md") +DEBUG = False def create_file(): f = open(WRITE_FILE_NAME, 'w') - # f = open(os.path.join(WRITE_FILE_NAME, ROOT_DIR), 'w') return f -def report_callee(filename, filewrite): - print("==> Writing todos in :{} from :{}".format(filewrite, filename)) +def report_callee(filename, filewrite, line): + print("==> Writing Todos in {} from: {}\nTODO ==> {}".format(filewrite, filename, line)) skip_dirs = ['build', 'dist', '__pycache__', '.git', '.idea', 'ejabberd_python3d.egg-info'] +file2write = create_file() -def extract_todos(dir, debug=False): + +def extract_todos(dir): if os.path.isdir(dir): - file = create_file() for root, dirs, files in os.walk((dir if os.path.isabs(dir) else os.path.abspath(dir))): for d in skip_dirs: if d in dirs: dirs.remove(d) - for f in files: - try: - with open(os.path.join(f, root), 'r') as fr: - line = fr.readline() - if line.startswith("# TODO") or line.find("TODO"): - file.writelines(line) - if debug: - report_callee(f, file.name) - time.sleep(1) - except PermissionError: - print("PermissionError. file: {:<25} dir: {}".format(f, root)) - time.sleep(0.5) + _extract_todos(files, root) else: raise ValueError("Enter a valid dir name") +def _extract_todos(files, root): + if len(files) == 0: + return + try: + file = files.pop() + _extract_todos2(file, root) + return _extract_todos(files, root) + except (IndexError, PermissionError,): + pass + + +def _extract_todos2(file, root, ): + with open(os.path.join(root, file), 'r') as fr: + for line in fr: + if "# TODO" in line: + file2write.writelines(line) + if DEBUG: + report_callee(file, file2write.name, line) + + if __name__ == '__main__': - extract_todos(MAIN_PACKAGE_DIRECTORY, debug=True) + extract_todos(MAIN_PACKAGE_DIRECTORY) From b9d878c29757cd3bd02c80671805879ec611bb0b Mon Sep 17 00:00:00 2001 From: Dedaldino3D Date: Sun, 22 Nov 2020 00:42:18 +0100 Subject: [PATCH 72/99] Rename auth ==> ejabberd_auth (avoid problems in django installed apps) --- ejabberd_python3d/{auth => ejabberd_auth}/__init__.py | 0 ejabberd_python3d/{auth => ejabberd_auth}/management/__init__.py | 0 .../{auth => ejabberd_auth}/management/commands/__init__.py | 0 .../{auth => ejabberd_auth}/management/commands/ejabberd_auth.py | 0 4 files changed, 0 insertions(+), 0 deletions(-) rename ejabberd_python3d/{auth => ejabberd_auth}/__init__.py (100%) rename ejabberd_python3d/{auth => ejabberd_auth}/management/__init__.py (100%) rename ejabberd_python3d/{auth => ejabberd_auth}/management/commands/__init__.py (100%) rename ejabberd_python3d/{auth => ejabberd_auth}/management/commands/ejabberd_auth.py (100%) diff --git a/ejabberd_python3d/auth/__init__.py b/ejabberd_python3d/ejabberd_auth/__init__.py similarity index 100% rename from ejabberd_python3d/auth/__init__.py rename to ejabberd_python3d/ejabberd_auth/__init__.py diff --git a/ejabberd_python3d/auth/management/__init__.py b/ejabberd_python3d/ejabberd_auth/management/__init__.py similarity index 100% rename from ejabberd_python3d/auth/management/__init__.py rename to ejabberd_python3d/ejabberd_auth/management/__init__.py diff --git a/ejabberd_python3d/auth/management/commands/__init__.py b/ejabberd_python3d/ejabberd_auth/management/commands/__init__.py similarity index 100% rename from ejabberd_python3d/auth/management/commands/__init__.py rename to ejabberd_python3d/ejabberd_auth/management/commands/__init__.py diff --git a/ejabberd_python3d/auth/management/commands/ejabberd_auth.py b/ejabberd_python3d/ejabberd_auth/management/commands/ejabberd_auth.py similarity index 100% rename from ejabberd_python3d/auth/management/commands/ejabberd_auth.py rename to ejabberd_python3d/ejabberd_auth/management/commands/ejabberd_auth.py From ac3ae21dd73acacdf1504d3d68b9936117f3bef6 Mon Sep 17 00:00:00 2001 From: Dedaldino3D Date: Sun, 22 Nov 2020 00:44:24 +0100 Subject: [PATCH 73/99] add ejabberd_auth to __init__ file --- ejabberd_python3d/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ejabberd_python3d/__init__.py b/ejabberd_python3d/__init__.py index fa53baf..e7bc29a 100644 --- a/ejabberd_python3d/__init__.py +++ b/ejabberd_python3d/__init__.py @@ -1,2 +1,2 @@ -from ejabberd_python3d import client, serializers +from ejabberd_python3d import client, serializers, ejabberd_auth from ejabberd_python3d import muc, abc, defaults From f2f97dfc2e4d2bae4dacff058096f3c15b2ba310 Mon Sep 17 00:00:00 2001 From: Dedaldino3D Date: Sun, 22 Nov 2020 01:16:48 +0100 Subject: [PATCH 74/99] Remove invalid classifier and wrong github link to repository --- setup.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/setup.py b/setup.py index d19deb7..ea593df 100644 --- a/setup.py +++ b/setup.py @@ -20,16 +20,15 @@ description="A library to make XML-RPC calls to ejabberd", long_description=long_description, long_description_content_type="text/markdown", - url="https://github.com/Dedaldino3D/ejabberd-python3D", + url="https://github.com/Dedaldino3D/ejabberd-python3d", license="MIT", - packages=setuptools.find_packages(), - py_modules=[splitext(basename(path))[0] for path in glob('src/*.py')], + packages=setuptools.find_packages(exclude=("scripts",)), + py_modules=[splitext(basename(path))[0] for path in glob('*.py')], include_package_data=True, zip_safe=False, classifiers=[ "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", - "Environment :: All Environment", "Intended Audience :: Developers", "Programming Language :: Python", "Programming Language :: Python :: 3", From 6144ed3767c89d2e88239325c4dacd2d6c53c85f Mon Sep 17 00:00:00 2001 From: Dedaldino3D Date: Sun, 22 Nov 2020 01:23:55 +0100 Subject: [PATCH 75/99] Change __version__ in setup file --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index ea593df..d3aab69 100644 --- a/setup.py +++ b/setup.py @@ -4,7 +4,7 @@ import setuptools from pip._internal.req import parse_requirements -__version__ = "0.0.1" +__version__ = "0.2.0" with open("README.md", "r") as fh: long_description = fh.read() From c84034c6c20af990d613194b1f616d075a145740 Mon Sep 17 00:00:00 2001 From: Dedaldino3D Date: Sun, 22 Nov 2020 15:10:47 +0100 Subject: [PATCH 76/99] Change argument in add_rosteritem(localserver => localhost, server => host) --- ejabberd_python3d/abc/api.py | 4 ++-- ejabberd_python3d/abc/methods.py | 7 ++++--- ejabberd_python3d/client.py | 10 +++++----- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/ejabberd_python3d/abc/api.py b/ejabberd_python3d/abc/api.py index 0339eb6..b812ad8 100644 --- a/ejabberd_python3d/abc/api.py +++ b/ejabberd_python3d/abc/api.py @@ -156,8 +156,8 @@ def get_room_affiliations(self, name, service): @abstractmethod def add_rosteritem(self, - localuser, localserver, - user, server, + localuser, localhost, + user, host, nick, group, subs): """ Add an item to a user's roster (self,supports ODBC): diff --git a/ejabberd_python3d/abc/methods.py b/ejabberd_python3d/abc/methods.py index 87cfedc..7622b34 100644 --- a/ejabberd_python3d/abc/methods.py +++ b/ejabberd_python3d/abc/methods.py @@ -201,9 +201,10 @@ def transform_response(self, api, arguments, response): class AddRosterItem(API): method = 'add_rosteritem' - arguments = [StringArgument('localuser'), StringArgument('localserver'), - StringArgument('user'), StringArgument('server'), - StringArgument('nick'), StringArgument('group'), StringArgument('subs')] + arguments = [StringArgument('localuser'), StringArgument('localhost'), + StringArgument('user'), StringArgument('host'), + StringArgument('nick', required=False), StringArgument('group', required=False), + StringArgument('subs', required=False)] def transform_response(self, api, arguments, response): return response.get('res') == 0 diff --git a/ejabberd_python3d/client.py b/ejabberd_python3d/client.py index 4596450..a4b123f 100644 --- a/ejabberd_python3d/client.py +++ b/ejabberd_python3d/client.py @@ -255,16 +255,16 @@ def check_password_hash(self, user, host, password): return self._call_api(methods.CheckPasswordHash, user=user, host=host, password=password) def add_rosteritem(self, - localuser, localserver, - user, server, - nick, group, subs): + localuser, localhost, + user, host, + nick="", group="", subs="to"): """ Add an item to a user's roster (self,supports ODBC): """ return self._call_api(methods.AddRosterItem, localuser=localuser, - localserver=localserver, + localhost=localhost, user=user, - server=server, + host=host, nick=nick, group=group, subs=subs) From c0854e42df7aa156428664d5727a8c62c170ed8a Mon Sep 17 00:00:00 2001 From: Dedaldino3D Date: Sun, 22 Nov 2020 15:21:27 +0100 Subject: [PATCH 77/99] Add method get_subscribers --- ejabberd_python3d/abc/api.py | 12 ++++++++++-- ejabberd_python3d/abc/methods.py | 2 +- ejabberd_python3d/client.py | 13 +++++++++++-- 3 files changed, 22 insertions(+), 5 deletions(-) diff --git a/ejabberd_python3d/abc/api.py b/ejabberd_python3d/abc/api.py index b812ad8..ba0d8f7 100644 --- a/ejabberd_python3d/abc/api.py +++ b/ejabberd_python3d/abc/api.py @@ -343,8 +343,16 @@ def get_roster(self, user, server): """ raise NotImplementedError("subclass must implement this method") - # TODO get_subscribers(self, name, service): - # List subscribers of a MUC conference + @abstractmethod + def get_subscribers(self, name, service): + """ + List subscribers of a MUC conference + :param name: + :param service: + :return: + """ + raise NotImplementedError("subclass must implement this method") + # TODO get_user_rooms(self, user, host): # Get the list of rooms where this user is occupant diff --git a/ejabberd_python3d/abc/methods.py b/ejabberd_python3d/abc/methods.py index 7622b34..5a9d818 100644 --- a/ejabberd_python3d/abc/methods.py +++ b/ejabberd_python3d/abc/methods.py @@ -347,7 +347,7 @@ def transform_response(self, api, arguments, response): return response.get('occupants') -class GetSubscrivers(API): +class GetSubscribers(API): method = 'get_subscribers' arguments = [StringArgument('name'), StringArgument('service')] diff --git a/ejabberd_python3d/client.py b/ejabberd_python3d/client.py index a4b123f..fe668bb 100644 --- a/ejabberd_python3d/client.py +++ b/ejabberd_python3d/client.py @@ -485,8 +485,17 @@ def get_roster(self, user, server): return self._call_api(methods.GetRoster, user=user, server=server) - # TODO get_subscribers(self, name, service): - # List subscribers of a MUC conference + def get_subscribers(self, name, service): + """ + List subscribers of a MUC conference + + :param name: Room name + :type name: str + :param service: MUC service + :type service: str + :return: + """ + return self._call_api(methods.GetSubscribers, name=name, service=service) # TODO get_user_rooms(self, user, host): # Get the list of rooms where this user is occupant From 940a53ab2e628f2aec411b420bc0d5ea51044662 Mon Sep 17 00:00:00 2001 From: Dedaldino3D Date: Sun, 22 Nov 2020 15:26:33 +0100 Subject: [PATCH 78/99] Add method get_user_rooms --- ejabberd_python3d/abc/api.py | 10 ++++++++-- ejabberd_python3d/client.py | 11 +++++++++-- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/ejabberd_python3d/abc/api.py b/ejabberd_python3d/abc/api.py index ba0d8f7..c37bcbb 100644 --- a/ejabberd_python3d/abc/api.py +++ b/ejabberd_python3d/abc/api.py @@ -353,9 +353,15 @@ def get_subscribers(self, name, service): """ raise NotImplementedError("subclass must implement this method") + def get_user_rooms(self, user, host): + """ + Get the list of rooms where this user is occupant + :param user: + :param host: + :return: + """ + raise NotImplementedError("subclass must implement this method") - # TODO get_user_rooms(self, user, host): - # Get the list of rooms where this user is occupant @abstractmethod def get_vcard(self, user, host, name): """ diff --git a/ejabberd_python3d/client.py b/ejabberd_python3d/client.py index fe668bb..36ec6a4 100644 --- a/ejabberd_python3d/client.py +++ b/ejabberd_python3d/client.py @@ -497,8 +497,15 @@ def get_subscribers(self, name, service): """ return self._call_api(methods.GetSubscribers, name=name, service=service) - # TODO get_user_rooms(self, user, host): - # Get the list of rooms where this user is occupant + def get_user_rooms(self, user, host): + """ + Get the list of rooms where this user is occupant + :param user: + :param host: + :return: + """ + return self._call_api(methods.GetUserRooms, user=user, host=host) + def get_vcard(self, user, host, name): """ From fd9db468dc1d5efcd83fcb27f27e957681f4ae26 Mon Sep 17 00:00:00 2001 From: Dedaldino3D Date: Sun, 22 Nov 2020 19:08:04 +0100 Subject: [PATCH 79/99] Finish subscription implimentation, added new serializers and arguments --- ejabberd_python3d/abc/api.py | 25 ++++++++---- ejabberd_python3d/abc/methods.py | 31 +++++++++++---- ejabberd_python3d/client.py | 25 +++++++++--- ejabberd_python3d/defaults/arguments.py | 6 ++- ejabberd_python3d/muc/__init__.py | 51 +++++++++++++++---------- ejabberd_python3d/muc/arguments.py | 6 ++- ejabberd_python3d/muc/enums.py | 10 +++++ ejabberd_python3d/muc/serializers.py | 25 +++++++++++- ejabberd_python3d/serializers.py | 22 ++++++++++- 9 files changed, 155 insertions(+), 46 deletions(-) diff --git a/ejabberd_python3d/abc/api.py b/ejabberd_python3d/abc/api.py index c37bcbb..b5a6430 100644 --- a/ejabberd_python3d/abc/api.py +++ b/ejabberd_python3d/abc/api.py @@ -206,9 +206,6 @@ def connected_users_vhost(self, host): # TODO def convert_to_yaml(self, in, out): # Convert the input file from Erlang to YAML format - # TODO def create_room(self, name, service, host): - # Create a MUC room name@service in host - # TODO def create_room_with_opts(self, name, service, host, options): # Create a MUC room name@service in host with given options @@ -774,11 +771,25 @@ def stop_kindly(self, delay, announcement): """ raise NotImplementedError("subclass must implement this method") - # TODO def subscribe_room(self, user, nick, room, nodes): - # Subscribe to a MUC conference + def subscribe_room(self, user, nick, room, nodes): + """ + Subscribe to a MUC conference + :param user: + :param nick: + :param room: + :param nodes: + :return: + """ + raise NotImplementedError("subclass must implement this method") - # TODO def unsubscribe_room(self, user, room): - # Unsubscribe from a MUC conference + def unsubscribe_room(self, user, room): + """ + Unsubscribe from a MUC conference + :param user: + :param room: + :return: + """ + raise NotImplementedError("subclass must implement this method") @abstractmethod def update(self, module): diff --git a/ejabberd_python3d/abc/methods.py b/ejabberd_python3d/abc/methods.py index 5a9d818..e3239d0 100644 --- a/ejabberd_python3d/abc/methods.py +++ b/ejabberd_python3d/abc/methods.py @@ -5,10 +5,11 @@ from ejabberd_python3d.core.utils import format_password_hash_sha from ejabberd_python3d.defaults import LogLevelOptions, loglevel_options_serializers from ejabberd_python3d.defaults.arguments import StringArgument, IntegerArgument, PositiveIntegerArgument, \ - LogLevelArgument -from ejabberd_python3d.muc import muc_room_options_serializers -from ejabberd_python3d.muc.arguments import MUCRoomArgument, AffiliationArgument -from ejabberd_python3d.muc.enums import Affiliation, MUCRoomOption + LogLevelArgument, ListArgument +from ejabberd_python3d.muc import muc_room_options_serializers, mucsub_room_nodes +from ejabberd_python3d.muc.arguments import MUCRoomArgument, AffiliationArgument, MUCNodesArgument +from ejabberd_python3d.muc.enums import Affiliation, MUCRoomOption, MUCNodes +from ejabberd_python3d.muc.serializers import MUCNodesSerializer from ejabberd_python3d.serializers import StringSerializer @@ -301,7 +302,10 @@ def transform_response(self, api, arguments, response): class CreateRoomWithOPTS(API): method = "create_room_with_opts" # TODO: add argument options: [{name::string,value::string}]: List of options - arguments = [StringArgument('name'), StringArgument('service'), StringArgument('host')] + arguments = [StringArgument('name'), StringArgument('service'), StringArgument('host'), MUCRoomOption()] + + def transform_arguments(self, **kwargs): + pass def transform_response(self, api, arguments, response): return response.get('res') == 0 @@ -422,9 +426,22 @@ def transform_response(self, api, arguments, response): class SubscribeRoom(API): method = "subscribe_room" - # TODO: nodes must be separated by commas, so therefore you can use an array and before send transform arguments arguments = [StringArgument('user'), StringArgument('nick'), StringArgument('room'), - StringArgument('nodes')] + ListArgument('nodes', required=False)] + + def transform_arguments(self, **kwargs): + nodes = kwargs.pop("nodes") + if nodes is None or not isinstance(nodes, list): + nodes = [MUCNodes.mucsub_system, MUCNodes.mucsub_subscribers, MUCNodes.mucsub_subject, + MUCNodes.mucsub_config, MUCNodes.mucsub_presence, MUCNodes.mucsub_messages, + MUCNodes.mucsub_affiliations] + nd = [] + for v in nodes: + serializer_class = MUCNodesSerializer + t = serializer_class().to_api(v) + nd.append(t) + kwargs['nodes'] = nd + return kwargs def transform_response(self, api, arguments, response): return response.get('nodes') diff --git a/ejabberd_python3d/client.py b/ejabberd_python3d/client.py index 36ec6a4..970c688 100644 --- a/ejabberd_python3d/client.py +++ b/ejabberd_python3d/client.py @@ -506,7 +506,6 @@ def get_user_rooms(self, user, host): """ return self._call_api(methods.GetUserRooms, user=user, host=host) - def get_vcard(self, user, host, name): """ Get content from a vCard field @@ -952,15 +951,31 @@ def stop_kindly(self, delay, announcement): return self._call_api(methods.StopKindly, delay=delay, announcement=announcement) - # TODO def subscribe_room(self, user, nick, room, nodes): - # Subscribe to a MUC conference + def subscribe_room(self, user, nick, room, nodes=None): + """ + Subscribe to a MUC conference + :param user: + :param nick: + :param room: + :param nodes: + :return: + """ + return self._call_api(methods.SubscribeRoom, user=user, nick=nick, room=room, nodes=nodes) - # TODO def unsubscribe_room(self, user, room): - # Unsubscribe from a MUC conference + def unsubscribe_room(self, user, room): + """ + Unsubscribe from a MUC conference + :param user: + :param room: + :return: + """ + return self._call_api(methods.UnSubscribeRoom, user=user, room=room) def update(self, module): """ Update the given module, or use the keyword: all + + :param module: """ return self._call_api(methods.Update, module=module) diff --git a/ejabberd_python3d/defaults/arguments.py b/ejabberd_python3d/defaults/arguments.py index fd55e53..9b092f6 100644 --- a/ejabberd_python3d/defaults/arguments.py +++ b/ejabberd_python3d/defaults/arguments.py @@ -2,7 +2,7 @@ from ..abc.api import APIArgument from ..serializers import StringSerializer, IntegerSerializer, PositiveIntegerSerializer, BooleanSerializer, \ - LogLevelSerializer + LogLevelSerializer, ListSerializer class StringArgument(APIArgument): @@ -23,3 +23,7 @@ class BooleanArgument(APIArgument): class LogLevelArgument(APIArgument): serializer_class = LogLevelSerializer + + +class ListArgument(APIArgument): + serializer_class = ListSerializer diff --git a/ejabberd_python3d/muc/__init__.py b/ejabberd_python3d/muc/__init__.py index 7584bdb..ad1e715 100644 --- a/ejabberd_python3d/muc/__init__.py +++ b/ejabberd_python3d/muc/__init__.py @@ -3,28 +3,37 @@ from ..serializers import BooleanSerializer, StringSerializer, PositiveIntegerSerializer from .serializers import AllowVisitorPrivateMessageSerializer -from .enums import MUCRoomOption - +from .enums import MUCRoomOption, MUCNodes muc_room_options_serializers = { - MUCRoomOption.allow_change_subj: BooleanSerializer, - MUCRoomOption.allow_private_messages: BooleanSerializer, + MUCRoomOption.allow_change_subj: BooleanSerializer, + MUCRoomOption.allow_private_messages: BooleanSerializer, MUCRoomOption.allow_private_messages_from_visitors: AllowVisitorPrivateMessageSerializer, - MUCRoomOption.allow_query_users: BooleanSerializer, - MUCRoomOption.allow_user_invites: BooleanSerializer, - MUCRoomOption.allow_visitor_nickchange: BooleanSerializer, - MUCRoomOption.allow_visitor_status: BooleanSerializer, - MUCRoomOption.anonymous: BooleanSerializer, - MUCRoomOption.captcha_protected: BooleanSerializer, - MUCRoomOption.logging: BooleanSerializer, - MUCRoomOption.max_users: PositiveIntegerSerializer, - MUCRoomOption.members_by_default: BooleanSerializer, - MUCRoomOption.members_only: BooleanSerializer, - MUCRoomOption.moderated: BooleanSerializer, - MUCRoomOption.password: StringSerializer, - MUCRoomOption.password_protected: BooleanSerializer, - MUCRoomOption.persistent: BooleanSerializer, - MUCRoomOption.public: BooleanSerializer, - MUCRoomOption.public_list: BooleanSerializer, - MUCRoomOption.title: StringSerializer + MUCRoomOption.allow_query_users: BooleanSerializer, + MUCRoomOption.allow_user_invites: BooleanSerializer, + MUCRoomOption.allow_visitor_nickchange: BooleanSerializer, + MUCRoomOption.allow_visitor_status: BooleanSerializer, + MUCRoomOption.anonymous: BooleanSerializer, + MUCRoomOption.captcha_protected: BooleanSerializer, + MUCRoomOption.logging: BooleanSerializer, + MUCRoomOption.max_users: PositiveIntegerSerializer, + MUCRoomOption.members_by_default: BooleanSerializer, + MUCRoomOption.members_only: BooleanSerializer, + MUCRoomOption.moderated: BooleanSerializer, + MUCRoomOption.password: StringSerializer, + MUCRoomOption.password_protected: BooleanSerializer, + MUCRoomOption.persistent: BooleanSerializer, + MUCRoomOption.public: BooleanSerializer, + MUCRoomOption.public_list: BooleanSerializer, + MUCRoomOption.title: StringSerializer +} + +mucsub_room_nodes = { + MUCNodes.mucsub_affiliations: StringSerializer, + MUCNodes.mucsub_messages: StringSerializer, + MUCNodes.mucsub_presence: StringSerializer, + MUCNodes.mucsub_config: StringSerializer, + MUCNodes.mucsub_subject: StringSerializer, + MUCNodes.mucsub_subscribers: StringSerializer, + MUCNodes.mucsub_system: StringSerializer } diff --git a/ejabberd_python3d/muc/arguments.py b/ejabberd_python3d/muc/arguments.py index 3166d8e..dabd1c5 100644 --- a/ejabberd_python3d/muc/arguments.py +++ b/ejabberd_python3d/muc/arguments.py @@ -2,7 +2,7 @@ from __future__ import unicode_literals from ..abc.api import APIArgument -from .serializers import MUCRoomOptionSerializer, AffiliationSerializer +from .serializers import MUCRoomOptionSerializer, AffiliationSerializer, MUCNodesSerializer class MUCRoomArgument(APIArgument): @@ -11,3 +11,7 @@ class MUCRoomArgument(APIArgument): class AffiliationArgument(APIArgument): serializer_class = AffiliationSerializer + + +class MUCNodesArgument(APIArgument): + serializer_class = MUCNodesSerializer diff --git a/ejabberd_python3d/muc/enums.py b/ejabberd_python3d/muc/enums.py index 0f23b9d..686e184 100644 --- a/ejabberd_python3d/muc/enums.py +++ b/ejabberd_python3d/muc/enums.py @@ -38,3 +38,13 @@ class Affiliation(Enum): member = 3 admin = 4 owner = 5 + + +class MUCNodes(Enum): + mucsub_presence = "urn:xmpp:mucsub:nodes:presence" + mucsub_messages = "urn:xmpp:mucsub:nodes:messages" + mucsub_affiliations = "urn:xmpp:mucsub:nodes:affiliations" + mucsub_subscribers = "urn:xmpp:mucsub:nodes:subscribers" + mucsub_config = "urn:xmpp:mucsub:nodes:config" + mucsub_subject = "urn:xmpp:mucsub:nodes:subject" + mucsub_system = "urn:xmpp:mucsub:nodes:system" diff --git a/ejabberd_python3d/muc/serializers.py b/ejabberd_python3d/muc/serializers.py index 1087cfa..e77175a 100644 --- a/ejabberd_python3d/muc/serializers.py +++ b/ejabberd_python3d/muc/serializers.py @@ -1,8 +1,9 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals +# from __future__ import unicode_literals +from six import string_types from ..serializers import EnumSerializer -from .enums import MUCRoomOption, AllowVisitorPrivateMessage, Affiliation +from .enums import MUCRoomOption, AllowVisitorPrivateMessage, Affiliation, MUCNodes class MUCRoomOptionSerializer(EnumSerializer): @@ -15,3 +16,23 @@ class AllowVisitorPrivateMessageSerializer(EnumSerializer): class AffiliationSerializer(EnumSerializer): enum_class = Affiliation + + +class MUCNodesSerializer(EnumSerializer): + enum_class = MUCNodes + + def to_api(self, value): + if isinstance(value, self.enum_class): + return value.value + if isinstance(value, string_types): + return value + elif isinstance(value, int): + return self.enum_class.get_by_value(value).name + raise ValueError("Invalid value for enum %s: %s" % (self.enum_class, value)) + + def to_builtin(self, value): + if not isinstance(value, string_types): + raise ValueError("Expects str or unicode , but got {}".format(type(value))) + res = self.enum_class.get_by_name(value) + if res is None: + raise ValueError("Expects enum value for {}, but got {}".format(self.enum_class, type(value))) diff --git a/ejabberd_python3d/serializers.py b/ejabberd_python3d/serializers.py index a05b3ee..c50d7f3 100644 --- a/ejabberd_python3d/serializers.py +++ b/ejabberd_python3d/serializers.py @@ -1,8 +1,8 @@ -from abc import ABCMeta, abstractmethod -from six import string_types +from abc import abstractmethod from ejabberd_python3d.abc.api import Enum, APIArgumentSerializer from ejabberd_python3d.defaults.enums import LogLevelOptions +from six import string_types class StringSerializer(APIArgumentSerializer): @@ -76,5 +76,23 @@ def to_builtin(self, value): raise ValueError("Expects enum value for {}, but got {}".format(self.enum_class, type(value))) +class ListSerializer(APIArgumentSerializer): + def to_api(self, value): + if not isinstance(value, list): + raise ValueError("Expects list, but got {}".format(type(value))) + vl = "" + for v in range(len(value)): + if v == 0: + vl += value[v] + else: + vl += "," + value[v] + return vl + + def to_builtin(self, value): + if not isinstance(value, list): + raise ValueError("Expects list, but got {}".format(type(value))) + return value + + class LogLevelSerializer(EnumSerializer): enum_class = LogLevelOptions From 4bf07e58bac79736b70191deea5b95732d3c8088 Mon Sep 17 00:00:00 2001 From: Dedaldino3D Date: Sun, 22 Nov 2020 21:35:44 +0100 Subject: [PATCH 80/99] Add create_room_with_opts(not finished) --- ejabberd_python3d/abc/api.py | 13 +++++++++++-- ejabberd_python3d/abc/methods.py | 24 +++++++++++++++++------- ejabberd_python3d/client.py | 16 ++++++++++++---- ejabberd_python3d/defaults/arguments.py | 6 +++++- ejabberd_python3d/serializers.py | 8 ++++++++ 5 files changed, 53 insertions(+), 14 deletions(-) diff --git a/ejabberd_python3d/abc/api.py b/ejabberd_python3d/abc/api.py index b5a6430..93bc912 100644 --- a/ejabberd_python3d/abc/api.py +++ b/ejabberd_python3d/abc/api.py @@ -206,8 +206,17 @@ def connected_users_vhost(self, host): # TODO def convert_to_yaml(self, in, out): # Convert the input file from Erlang to YAML format - # TODO def create_room_with_opts(self, name, service, host, options): - # Create a MUC room name@service in host with given options + @abstractmethod + def create_room_with_opts(self, name, service, host, options): + """ + Create a MUC room name@service in host with given options + :param name: + :param service: + :param host: + :param options: + :return: + """ + raise NotImplementedError("subclass must implement this method") # TODO def create_rooms_file(self, file): # Create the rooms indicated in file diff --git a/ejabberd_python3d/abc/methods.py b/ejabberd_python3d/abc/methods.py index e3239d0..fe1a690 100644 --- a/ejabberd_python3d/abc/methods.py +++ b/ejabberd_python3d/abc/methods.py @@ -5,12 +5,13 @@ from ejabberd_python3d.core.utils import format_password_hash_sha from ejabberd_python3d.defaults import LogLevelOptions, loglevel_options_serializers from ejabberd_python3d.defaults.arguments import StringArgument, IntegerArgument, PositiveIntegerArgument, \ - LogLevelArgument, ListArgument + LogLevelArgument, ListArgument, GenericArgument from ejabberd_python3d.muc import muc_room_options_serializers, mucsub_room_nodes from ejabberd_python3d.muc.arguments import MUCRoomArgument, AffiliationArgument, MUCNodesArgument from ejabberd_python3d.muc.enums import Affiliation, MUCRoomOption, MUCNodes from ejabberd_python3d.muc.serializers import MUCNodesSerializer -from ejabberd_python3d.serializers import StringSerializer +from ejabberd_python3d.serializers import StringSerializer, BooleanSerializer +from six import string_types class Echo(API): @@ -160,7 +161,6 @@ class ChangeRoomOption(API): def transform_arguments(self, **kwargs): option = kwargs.get('option') - assert isinstance(option, MUCRoomOption) serializer_class = muc_room_options_serializers.get(option, StringSerializer) kwargs['value'] = serializer_class().to_api(kwargs['value']) return kwargs @@ -299,13 +299,23 @@ def transform_response(self, api, arguments, response): return response.get('res') == 0 -class CreateRoomWithOPTS(API): +class CreateRoomWithOpts(API): method = "create_room_with_opts" - # TODO: add argument options: [{name::string,value::string}]: List of options - arguments = [StringArgument('name'), StringArgument('service'), StringArgument('host'), MUCRoomOption()] + arguments = [StringArgument('name'), StringArgument('service'), StringArgument('host'), GenericArgument('options')] def transform_arguments(self, **kwargs): - pass + options = kwargs.pop('options') + op = [] + for opt in options: + serializer_class = muc_room_options_serializers.get(opt['name'], StringSerializer) + if not isinstance(opt['name'], string_types): + opt['name'] = serializer_class().to_api(opt['name']) + opt['value'] = serializer_class().to_api(opt['value']) + op.append(opt) + kwargs.update({ + 'options': op + }) + return kwargs def transform_response(self, api, arguments, response): return response.get('res') == 0 diff --git a/ejabberd_python3d/client.py b/ejabberd_python3d/client.py index 970c688..f3bcccd 100644 --- a/ejabberd_python3d/client.py +++ b/ejabberd_python3d/client.py @@ -172,6 +172,7 @@ def _call_api(self, api_class, **kwargs): self._report_method_call(api.method, args) # perform call + print("auth args: %s, args: %s" % (self.auth, args)) try: if not api.authenticate: response = method(args) @@ -329,11 +330,18 @@ def connected_users_vhost(self, host): # TODO def convert_to_yaml(self, in, out): # Convert the input file from Erlang to YAML format - # TODO def create_room(self, name, service, host): - # Create a MUC room name@service in host + def create_room_with_opts(self, name, service, host, options): + """ + Create a MUC room name@service in host with given options + :param name: + :param service: + :param host: + :param options: Room options. Example: options = [{"name": "members_only","value": "False"}, + {"name": "moderated","value": "False"}] + :return: - # TODO def create_room_with_opts(self, name, service, host, options): - # Create a MUC room name@service in host with given options + """ + return self._call_api(methods.CreateRoomWithOpts, name=name, service=service, host=host, options=options) # TODO def create_rooms_file(self, file): # Create the rooms indicated in file diff --git a/ejabberd_python3d/defaults/arguments.py b/ejabberd_python3d/defaults/arguments.py index 9b092f6..4a1aef9 100644 --- a/ejabberd_python3d/defaults/arguments.py +++ b/ejabberd_python3d/defaults/arguments.py @@ -2,7 +2,11 @@ from ..abc.api import APIArgument from ..serializers import StringSerializer, IntegerSerializer, PositiveIntegerSerializer, BooleanSerializer, \ - LogLevelSerializer, ListSerializer + LogLevelSerializer, ListSerializer, GenericSerializer + + +class GenericArgument(APIArgument): + serializer_class = GenericSerializer class StringArgument(APIArgument): diff --git a/ejabberd_python3d/serializers.py b/ejabberd_python3d/serializers.py index c50d7f3..96c751b 100644 --- a/ejabberd_python3d/serializers.py +++ b/ejabberd_python3d/serializers.py @@ -5,6 +5,14 @@ from six import string_types +class GenericSerializer(APIArgumentSerializer): + def to_api(self, value): + return value + + def to_builtin(self, value): + return value + + class StringSerializer(APIArgumentSerializer): def to_api(self, value): if not isinstance(value, string_types): From 05848a46a70bb610b496bafbdb7dd02aab4b05d5 Mon Sep 17 00:00:00 2001 From: Dedaldino3D Date: Tue, 24 Nov 2020 01:40:55 +0100 Subject: [PATCH 81/99] Use django 3.0 or high instead 3.1 --- requirements.txt | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/requirements.txt b/requirements.txt index c4821f4..6fa36df 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,10 +1,4 @@ -asgiref==3.3.0 -certifi==2020.6.20 -chardet==3.0.4 -coverage==5.3 -Django==3.1.3 -idna==2.10 -pytz==2020.4 -requests==2.24.0 -sqlparse==0.4.1 -urllib3==1.25.11 +coverage>=5.3 +Django>=3.0 +requests>=2.24.0 +urllib3>=1.25.11 From 0ab924867624ec09dbcb6965c4b62089b8a9ac95 Mon Sep 17 00:00:00 2001 From: Dedaldino3D Date: Tue, 24 Nov 2020 01:47:38 +0100 Subject: [PATCH 82/99] Update version in setup --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index d3aab69..a450819 100644 --- a/setup.py +++ b/setup.py @@ -4,7 +4,7 @@ import setuptools from pip._internal.req import parse_requirements -__version__ = "0.2.0" +__version__ = "0.2.2" with open("README.md", "r") as fh: long_description = fh.read() From d0d9b2e4620fdb4718ec69ce61f786c01cf939e2 Mon Sep 17 00:00:00 2001 From: Dedaldino3D Date: Fri, 4 Dec 2020 22:47:10 +0100 Subject: [PATCH 83/99] Add docstrings in methods, Update arguments in some methods and abc --- ejabberd_python3d/abc/api.py | 83 +--- ejabberd_python3d/abc/methods.py | 12 +- ejabberd_python3d/client.py | 752 ++++++++++++++++++++++++++----- 3 files changed, 646 insertions(+), 201 deletions(-) diff --git a/ejabberd_python3d/abc/api.py b/ejabberd_python3d/abc/api.py index 93bc912..28c1a9c 100644 --- a/ejabberd_python3d/abc/api.py +++ b/ejabberd_python3d/abc/api.py @@ -123,11 +123,11 @@ def connected_users_number(self): raise NotImplementedError("subclass must implement this method") @abstractmethod - def user_sessions_info(self): + def user_sessions_info(self, user, host): raise NotImplementedError("subclass must implement this method") @abstractmethod - def muc_online_rooms(self, host=None): + def muc_online_rooms(self, service=None): raise NotImplementedError("subclass must implement this method") @abstractmethod @@ -135,7 +135,7 @@ def create_room(self, name, service, host): raise NotImplementedError("subclass must implement this method") @abstractmethod - def destroy_room(self, name, service, host): + def destroy_room(self, name, service): raise NotImplementedError("subclass must implement this method") @abstractmethod @@ -210,11 +210,6 @@ def connected_users_vhost(self, host): def create_room_with_opts(self, name, service, host, options): """ Create a MUC room name@service in host with given options - :param name: - :param service: - :param host: - :param options: - :return: """ raise NotImplementedError("subclass must implement this method") @@ -328,24 +323,6 @@ def get_loglevel(self): def get_roster(self, user, server): """ Get roster of a local user. - - Note, parameters changed in 15.09 - from ``user, host`` - to ``user, server``. - - Arguments: - - user :: binary - server :: binary - - Result: - - {contacts,{list,{contact,{tuple,[{jid,string}, - {nick,string}, - {subscription,string}, - {ask,string}, - {group,string}]}}}} - """ raise NotImplementedError("subclass must implement this method") @@ -353,18 +330,12 @@ def get_roster(self, user, server): def get_subscribers(self, name, service): """ List subscribers of a MUC conference - :param name: - :param service: - :return: """ raise NotImplementedError("subclass must implement this method") def get_user_rooms(self, user, host): """ Get the list of rooms where this user is occupant - :param user: - :param host: - :return: """ raise NotImplementedError("subclass must implement this method") @@ -431,11 +402,6 @@ def kick_user(self, user, host): def list_cluster(self): """ List nodes that are part of the cluster handled by Node - - Result: - - {nodes,{list,{node,atom}}} - """ raise NotImplementedError("subclass must implement this method") @@ -530,7 +496,6 @@ def registered_vhosts(self): def reload_config(self): """ Reload ejabberd configuration file into memory - (only affects ACL and Access) """ raise NotImplementedError("subclass must implement this method") @@ -602,15 +567,6 @@ def set_last(self, user, host, timestamp, status): def set_loglevel(self, loglevel): """ Set the loglevel (0 to 5) - - Arguments: - - loglevel :: integer - - Result: - - {logger,atom} - """ raise NotImplementedError("subclass must implement this method") @@ -712,12 +668,6 @@ def srg_user_del(self, user, host, group, grouphost): def stats(self, name): """ Get statistical value: - - * ``registeredusers`` - * ``onlineusers`` - * ``onlineusersnode`` - * ``uptimeseconds`` - * ``processes`` - Introduced sometime after Ejabberd 15.07 """ raise NotImplementedError("subclass must implement this method") @@ -725,9 +675,6 @@ def stats(self, name): def stats_host(self, name, host): """ Get statistical value for this host: - - * ``registeredusers`` - * ``onlineusers`` """ raise NotImplementedError("subclass must implement this method") @@ -783,20 +730,12 @@ def stop_kindly(self, delay, announcement): def subscribe_room(self, user, nick, room, nodes): """ Subscribe to a MUC conference - :param user: - :param nick: - :param room: - :param nodes: - :return: """ raise NotImplementedError("subclass must implement this method") def unsubscribe_room(self, user, room): """ Unsubscribe from a MUC conference - :param user: - :param room: - :return: """ raise NotImplementedError("subclass must implement this method") @@ -815,22 +754,8 @@ def update_list(self): raise NotImplementedError("subclass must implement this method") @abstractmethod - def user_resources(self, user, server): + def user_resources(self, user, host): """ List user's connected resources - - Note, parameters changed in 15.09 - from ``user, host`` - to ``user, server``. - - Arguments: - - user :: binary - server :: binary - - Result: - - {resources,{list,{resource,string}}} - """ raise NotImplementedError("subclass must implement this method") diff --git a/ejabberd_python3d/abc/methods.py b/ejabberd_python3d/abc/methods.py index fe1a690..6cce9d4 100644 --- a/ejabberd_python3d/abc/methods.py +++ b/ejabberd_python3d/abc/methods.py @@ -6,11 +6,11 @@ from ejabberd_python3d.defaults import LogLevelOptions, loglevel_options_serializers from ejabberd_python3d.defaults.arguments import StringArgument, IntegerArgument, PositiveIntegerArgument, \ LogLevelArgument, ListArgument, GenericArgument -from ejabberd_python3d.muc import muc_room_options_serializers, mucsub_room_nodes -from ejabberd_python3d.muc.arguments import MUCRoomArgument, AffiliationArgument, MUCNodesArgument -from ejabberd_python3d.muc.enums import Affiliation, MUCRoomOption, MUCNodes +from ejabberd_python3d.muc import muc_room_options_serializers +from ejabberd_python3d.muc.arguments import MUCRoomArgument, AffiliationArgument +from ejabberd_python3d.muc.enums import Affiliation, MUCNodes from ejabberd_python3d.muc.serializers import MUCNodesSerializer -from ejabberd_python3d.serializers import StringSerializer, BooleanSerializer +from ejabberd_python3d.serializers import StringSerializer from six import string_types @@ -134,7 +134,7 @@ def transform_response(self, api, arguments, response): class DestroyRoom(API): method = 'destroy_room' - arguments = [StringArgument('name'), StringArgument('service'), StringArgument('host')] + arguments = [StringArgument('name'), StringArgument('service')] def transform_response(self, api, arguments, response): return response.get('res') == 0 @@ -882,7 +882,7 @@ def transform_response(self, api, arguments, response): class UserResources(API): method = "update_sql" - arguments = [StringArgument("user"), StringArgument("server")] + arguments = [StringArgument("user"), StringArgument("host")] def transform_response(self, api, arguments, response): return response.get("resources") diff --git a/ejabberd_python3d/client.py b/ejabberd_python3d/client.py index f3bcccd..cfab7a7 100644 --- a/ejabberd_python3d/client.py +++ b/ejabberd_python3d/client.py @@ -11,6 +11,7 @@ XMLRPC_API_PORT +# noinspection PyTypeChecker class EjabberdAPIClient(EjabberdBaseAPI): """ Python client for Ejabberd XML-RPC Administration API. @@ -75,7 +76,6 @@ def get_instance(service_url, verbose=False): def service_url(self): """ Returns the FQDN to the Ejabberd server's XML-RPC endpoint - :return: """ # TODO: add endpoint parameter return "{}://{}:{}".format(self.protocol, self.host, self.port) @@ -105,10 +105,11 @@ def _validate_and_serialize_arguments(self, api_class, arguments): """ Internal method to validate and serialize arguments :param api_class: An instance of an API class + :type api_class: API :param arguments: A dictionary of arguments that will be passed to the method :type arguments: dict - :rtype: dict :return: The serialized arguments + :rtype: dict """ ser_args = {} @@ -129,11 +130,11 @@ def _validate_and_serialize_arguments(self, api_class, arguments): def _report_method_call(self, method, arguments): """ Internal method to print info about a method call - :param method: The name oft hem method to call + + :param method: The name of the method to call :type method: str|unicode :param arguments: A dictionary of arguments that will be passed to the method :type: arguments: dict - :return: """ if self.verbose: print("===> %s(%s)" % (method, ', '.join(['%s=%s' % (k, v) for k, v in arguments.items()]))) @@ -141,12 +142,13 @@ def _report_method_call(self, method, arguments): def _call_api(self, api_class, **kwargs): """ Internal method used to perform api calls + :param api_class: - :type api_class: py:class:API + :type api_class: API :param kwargs: :type kwargs: dict :rtype: object - :return: Returns return value of the XMLRPC Method call + :return: Return value of the XMLRPC Method call """ # validate api_class @@ -193,17 +195,19 @@ def echo(self, sentence): def registered_users(self, host): """ - List all registered users in the xmpp_host + List all registered users in the host + :param host: The XMPP_DOMAIN :type host: str|unicode :rtype: Iterable - :return: A list of registered users in the xmpp_host + :return: A List of registered accounts usernames """ return self._call_api(methods.RegisteredUsers, host=host) def register(self, user, host, password): """ - Registers a user to the ejabberd server + Register a user to the ejabberd server + :param user: The username for the new user :type user: str|unicode :param host: The XMPP_DOMAIN @@ -217,19 +221,21 @@ def register(self, user, host, password): def unregister(self, user, host): """ - UnRegisters a user from the ejabberd server + Unregister a user from the ejabberd server + :param user: The username for the new user :type user: str|unicode :param host: The XMPP_DOMAIN :type host: str|unicode :rtype: bool - :return: A boolean indicating if user unregistered has succeeded + :return: A boolean indicating if user unregistered """ return self._call_api(methods.Unregister, user=user, host=host) def change_password(self, user, host, newpass): """ Change the password for a given user + :param user: The username for the user we want to change the password for :type user: str|unicode :param host: The XMPP_DOMAIN @@ -244,14 +250,15 @@ def change_password(self, user, host, newpass): def check_password_hash(self, user, host, password): """ Checks whether a password is correct for a given user. The used hash-method is fixed to sha1. + :param user: The username for the user we want to check the password for :type user: str|unicode :param host: The XMPP_DOMAIN :type host: str|unicode :param password: The password we want to check for the user :type password: str|unicode - :rtype: bool :return: A boolean indicating if the given password matches the user's password + :rtype: bool """ return self._call_api(methods.CheckPasswordHash, user=user, host=host, password=password) @@ -260,7 +267,25 @@ def add_rosteritem(self, user, host, nick="", group="", subs="to"): """ - Add an item to a user's roster (self,supports ODBC): + Add an item to a user's roster + Group can be several groups separated by ; for example: "g1;g2;g3" + + :param localuser: User name + :type localuser: str + :param localhost:Server name + :type localhost: str + :param user: Contact user name + :type user: str + :param host: Contact server name + :type host: str + :param nick: Nickname, default: "" + :type nick: str + :param group: Subscription, default: "" + :type group: str + :param subs: Subscription, default: "" + :type subs: str + :return: Status code + :rtype: bool """ return self._call_api(methods.AddRosterItem, localuser=localuser, localhost=localhost, @@ -275,6 +300,15 @@ def add_rosteritem(self, def ban_account(self, user, host, reason): """ Ban an account: kick sessions and set random password + + :param user: User name to ban + :type user: str + :param host: Server name + :type host: str + :param reason: Reason for banning user + :type reason: str + :return: Status code + :rtype: True on success, False otherwise) """ return self._call_api(methods.BanAccount, user=user, host=host, @@ -286,12 +320,28 @@ def ban_account(self, user, host, reason): def check_account(self, user, host): """ Check if an account exists or not + + :param user: User name to check + :type user: str + :param host: Server to check + :type host: str + :return: Status code + :rtype: True on success, False otherwise) """ return self._call_api(methods.CheckAccount, user=user, host=host) def check_password(self, user, host, password): """ Check if a password is correct + + :param user: User name to check + :type user: str + :param host: Server to check + :type host: str + :param password: Password to check + :type password: str + :return: Status code + :rtype: True on success, False otherwise) """ return self._call_api(methods.CheckPassword, user=user, host=host, @@ -303,24 +353,37 @@ def check_password(self, user, host, password): def connected_users(self): """ List all established sessions + + :return: List of users sessions + :rtype: list """ return self._call_api(methods.ConnectedUsers) def connected_users_info(self): """ List all established sessions and their information + + :return: A dict with established connections + :rtype: list """ return self._call_api(methods.ConnectedUsersInfo) def connected_users_number(self): """ Get the number of established sessions + + :return: Number of established sessions + :rtype: int """ return self._call_api(methods.ConnectedUsersNumber) def connected_users_vhost(self, host): """ Get the list of established sessions in a vhost + + :param host: Server name + :return: List of established sessions + :rtype: list """ return self._call_api(methods.ConnectedUsersVhost, host=host) @@ -333,13 +396,17 @@ def connected_users_vhost(self, host): def create_room_with_opts(self, name, service, host, options): """ Create a MUC room name@service in host with given options - :param name: - :param service: - :param host: + + :param name: Room name + :type name: str + :param service: MUC service + :type service: str + :param host: Server host + :type host: str :param options: Room options. Example: options = [{"name": "members_only","value": "False"}, {"name": "moderated","value": "False"}] - :return: - + :return: Status code (True if success, False otherwise) + :rtype: bool """ return self._call_api(methods.CreateRoomWithOpts, name=name, service=service, host=host, options=options) @@ -349,6 +416,9 @@ def create_room_with_opts(self, name, service, host, options): def delete_expired_messages(self): """ Delete expired offline messages from database + + :return: Status code (True if success, False otherwise) + :rtype: bool """ return self._call_api(methods.DeleteExpiredMessages) @@ -361,31 +431,62 @@ def delete_expired_messages(self): def delete_old_messages(self, days): """ Delete offline messages older than DAYS + + :param days: Last login age in days of accounts that should be removed + :type days: int + :return: Status code (True if success, False otherwise) + :rtype: bool """ return self._call_api(methods.DeleteOldMessages, days=days) def delete_old_users(self, days): """ Delete users that didn't log in last days, or that never logged + + To protect admin accounts, configure this in your ejabberd.yml + example: access_rules: protect_old_users: - allow: admin - deny: all + :param days: Last login age in days of accounts that should be removed + :type days: int + :return: Status code (True if success, False otherwise) + :rtype: bool """ return self._call_api(methods.DeleteOldUsers, days=days) def delete_old_users_vhost(self, host, days): """ - Delete users that didn't log in last days in vhost, - or that never logged + Delete users that didn't log in last days in vhost, or that never logged + + To protect admin accounts, configure this in your ejabberd.yml + for example: access_rules: delete_old_users: - deny: admin - allow: all + :param host: + :type host: str + :param days: Last login age in days of accounts that should be removed + :type days: int + :return: Status code (True if success, False otherwise) + :rtype: bool """ return self._call_api(methods.DeleteOldUsersVhost, host=host, days=days) - def delete_rosteritem(self, localuser, localserver, user, server): + def delete_rosteritem(self, localuser, localhost, user, host): """ - Delete an item from a user's roster (self,supports ODBC): + Delete an item from a user's roster (supports ODBC) + + :param localuser: User name + :type localuser: str + :param localhost: Server name + :type localhost: str + :param user: Contact user name + :type user: str + :param host: Contact server name + :type host: str + :return: Status code (True if success, False otherwise) + :rtype: bool """ return self._call_api(methods.DeleteRosterItem, localuser=localuser, - localserver=localserver, + localserver=localhost, user=user, - server=server) + server=host) # TODO def destroy_room(self, name, service): # Destroy a MUC room @@ -414,42 +515,126 @@ def delete_rosteritem(self, localuser, localserver, user, server): # TODO def gen_markdown_doc_for_commands(self, file, regexp, examples): # Generates markdown documentation for ejabberd_commands - def muc_online_rooms(self, host=None): - return self._call_api(methods.MucOnlineRooms, host=host) + def muc_online_rooms(self, service="global"): + """ + List existing rooms ('global' to get all vhosts) + + :param service: MUC service, default: 'global' for all + :type service: str + :return: List of rooms + :rtype: list + """ + return self._call_api(methods.MucOnlineRooms, service=service) def create_room(self, name, service, host): + """ + Create a MUC room name@service in host + + :param name: Room name + :type name: str + :param service: MUC service + :type service: str + :param host: Server host + :type host: str + :return: Status code (True if success, False otherwise) + :rtype: bool + """ return self._call_api(methods.CreateRoom, name=name, service=service, host=host) - def destroy_room(self, name, service, host): - return self._call_api(methods.DestroyRoom, name=name, service=service, host=host) + def destroy_room(self, name, service): + """ + Destroy a MUC room + + :param name: Room name + :type name: str + :param service: MUC service + :type service: str + :return: Status code (True if success, False otherwise) + :rtype: bool + """ + return self._call_api(methods.DestroyRoom, name=name, service=service) def get_room_options(self, name, service): + """ + Get options from a MUC room + + :param name: Room name + :param service: MUC Service + :return: List of room options dict with name and value + :rtype: list + """ return self._call_api(methods.GetRoomOptions, name=name, service=service) def change_room_option(self, name, service, option, value): + """ + Change an option in a MUC room + + :param name: Room name + :param service: MUC Service + :param option: Option name + :param value: Value to assign + :return: Status code (True if success, False otherwise) + :rtype: bool + """ return self._call_api(methods.ChangeRoomOption, name=name, service=service, option=option, value=value) def set_room_affiliation(self, name, service, jid, affiliation): + """ + Change an affiliation in a MUC room + + :param name: Room name + :type name: str + :param service: MUC Service + :type service: str + :param jid: User JID + :type jid: str + :param affiliation: Affiliation to set + :type affiliation: str + :return: Status code (True if success, False otherwise) + :rtype: bool + """ return self._call_api(methods.GetRoomAffiliation, name=name, service=service, jid=jid, affiliation=affiliation) def get_room_affiliations(self, name, service): + """ + Get the list of affiliations of a MUC room + + :param name: Room name + :type name: str + :param service: MUC Service + :type service: str + :return: The list of affiliations with username, domain, affiliation and reason + :rtype: list + """ return self._call_api(methods.GetRoomAffiliations, name=name, service=service) def get_cookie(self): """ Get the Erlang cookie of this node + + :return: Erlang cookie used for authentication by ejabberd + :rtype: str """ return self._call_api(methods.GetCookie) def get_last(self, user, host): """ - Get last activity information (self,timestamp and status): + Get last activity information + + Timestamp is UTC and XEP-0082 format, for example: 2017-02-23T22:25:28.063062Z ONLINE + :param user: User name + :param host: Server name + :return: Last activity timestamp and status + :rtype: dict """ return self._call_api(methods.GetLast, user=user, host=host) def get_loglevel(self): """ Get the current loglevel + + :return: Tuple with the log level number, its keyword and description + :rtype: str """ return self._call_api(methods.GetLogLevel) @@ -470,53 +655,59 @@ def get_loglevel(self): def get_roster(self, user, server): """ - Get roster of a local user. - - Note, parameters changed in 15.09 - from ``user, host`` - to ``user, server``. - - Arguments: - - user :: binary - server :: binary - - Result: - - {contacts,{list,{contact,{tuple,[{jid,string}, - {nick,string}, - {subscription,string}, - {ask,string}, - {group,string}]}}}} + Get roster of a local user + :param user: User name + :param server: Server name + :return: List of subscriptions + :rtype: list """ - return self._call_api(methods.GetRoster, user=user, server=server) def get_subscribers(self, name, service): """ - List subscribers of a MUC conference + List subscribers of a MUC conference :param name: Room name :type name: str :param service: MUC service :type service: str - :return: + :return: The list of users that are subscribed to that room + :rtype: list """ return self._call_api(methods.GetSubscribers, name=name, service=service) def get_user_rooms(self, user, host): """ Get the list of rooms where this user is occupant - :param user: - :param host: - :return: + + :param user: Username + :param host: Server host + :return: List of user rooms + :rtype: list """ return self._call_api(methods.GetUserRooms, user=user, host=host) def get_vcard(self, user, host, name): """ Get content from a vCard field + + Some vcard field names in get/set_vcard are: + FN - Full Name + NICKNAME - Nickname + BDAY - Birthday + TITLE - Work: Position + ROLE - Work: Role + For a full list of vCard fields check XEP-0054: vcard-temp at http://www.xmpp.org/extensions/xep-0054.html + + :param user: User name + :type user: str + :param host: Server name + :type host: str + :param name: Field name + :type name: str + :return: Field content + :rtype: str """ return self._call_api(methods.GetVcard, user=user, host=host, @@ -524,7 +715,33 @@ def get_vcard(self, user, host, name): def get_vcard2(self, user, host, name, subname): """ - Get content from a vCard field + Get content from a vCard subfield + + Some vcard field names and subnames in get/set_vcard2 are: + N FAMILY - Family name + N GIVEN - Given name + N MIDDLE - Middle name + ADR CTRY - Address: Country + ADR LOCALITY - Address: City + TEL HOME - Telephone: Home + TEL CELL - Telephone: Cellphone + TEL WORK - Telephone: Work + TEL VOICE - Telephone: Voice + EMAIL USERID - E-Mail Address + ORG ORGNAME - Work: Company + ORG ORGUNIT - Work: Department + For a full list of vCard fields check XEP-0054: vcard-temp at http://www.xmpp.org/extensions/xep-0054.html + + :param user: User name + :type user: str + :param host: Server name + :type host: str + :param name: Field name + :type name: str + :param subname: Subfield name + :type subname: str + :return: Field content + :rtype: str """ return self._call_api(methods.GetVcard2, user=user, host=host, @@ -534,6 +751,32 @@ def get_vcard2(self, user, host, name, subname): def get_vcard2_multi(self, user, host, name, subname): """ Get multiple contents from a vCard field + + Some vcard field names and subnames in get/set_vcard2 are: + N FAMILY - Family name + N GIVEN - Given name + N MIDDLE - Middle name + ADR CTRY - Address: Country + ADR LOCALITY - Address: City + TEL HOME - Telephone: Home + TEL CELL - Telephone: Cellphone + TEL WORK - Telephone: Work + TEL VOICE - Telephone: Voice + EMAIL USERID - E-Mail Address + ORG ORGNAME - Work: Company + ORG ORGUNIT - Work: Department + For a full list of vCard fields check XEP-0054: vcard-temp at http://www.xmpp.org/extensions/xep-0054.html + + :param user: User name + :type user: str + :param host: Server name + :type host: str + :param name: Field name + :type name: str + :param subname: Subfield name + :type subname: str + :return: Field content + :rtype: str """ return self._call_api(methods.GetVcard2Multi, user=user, host=host, @@ -554,6 +797,9 @@ def get_vcard2_multi(self, user, host, name, subname): def incoming_s2s_number(self): """ Number of incoming s2s connections on the node + + :return: s2s number + :rtype: int """ return self._call_api(methods.IncomingS2SNumber) @@ -575,6 +821,13 @@ def kick_session(self, user, host, resource, reason): def kick_user(self, user, host): """ Disconnect user's active sessions + + :param user: User name + :type user: str + :param host: Server name + :type host: str + :return: Number of resources that were kicked + :rtype: int """ return self._call_api(methods.KickUser, user=user, host=host) @@ -585,16 +838,10 @@ def list_cluster(self): """ List nodes that are part of the cluster handled by Node - Result: - - {nodes,{list,{node,atom}}} - + :return: List of clusters + :rtype: list """ - try: - return self._call_api(methods.ListCluster) - except xmlrpc_client.Fault as e: - msg = 'list_cluster is NOT available in your version of ejabberd' - raise Exception('{}\n{}\n - code: {}'.format(msg, e.faultString, e.faultCode)) + return self._call_api(methods.ListCluster) # TODO def load(self, file): # Restore the database from text file @@ -616,13 +863,19 @@ def list_cluster(self): def modules_available(self): """ - List available modules + List the contributed modules available to install + + :return: List of dict with module name and description + :rtype: list """ return self._call_api(methods.ModulesAvailable) def modules_installed(self): """ - List installed modules + List the contributed modules already installed + + :return: List of dict with module name and description + :rtype: list """ return self._call_api(methods.ModulesInstalled) @@ -637,12 +890,22 @@ def modules_installed(self): def num_resources(self, user, host): """ Get the number of resources of a user + + :param user: User name + :type user: str + :param host: Server name + :type host: str + :return: Number of active resources for a user + :rtype: int """ return self._call_api(methods.NumResources, user=user, host=host) def outgoing_s2s_number(self): """ Number of outgoing s2s connections on the node + + :return: Number of outgoing s2s connections + :rtype: int """ return self._call_api(methods.OutgoingS2SNumber) @@ -657,7 +920,19 @@ def outgoing_s2s_number(self): def process_rosteritems(self, action, subs, asks, users, contacts): """ - List or delete rosteritems that match filtering options + List/delete rosteritems that match filter + + :param action: + :type action: str + :param subs: + :type subs: str + :param asks: + :type asks: str + :param users: + :type users: str + :param contacts: + :type contacts: str + :return: """ return self._call_api(methods.ProcessRosterItems, action=action, subs=subs, @@ -668,6 +943,11 @@ def process_rosteritems(self, action, subs, asks, users, contacts): def push_alltoall(self, host, group): """ Add all the users to all the users of Host in Group + + :param host: Server name + :param group: Group name + :return: Status code (True if success, False otherwise) + :rtype: bool """ return self._call_api(methods.PushAllToAll, host=host, group=group) @@ -680,26 +960,43 @@ def push_alltoall(self, host, group): def registered_vhosts(self): """ List all registered vhosts in SERVER + + :return: List of available vhosts + :rtype: list """ return self._call_api(methods.RegisteredVhosts) def reload_config(self): """ - Reload ejabberd configuration file into memory - + Reload config file in memory (only affects ACL and Access) + + :return: Status code (True if success, False otherwise) + :rtype: bool """ return self._call_api(methods.ReloadConfig) def reopen_log(self): """ Reopen the log files + + :return: Status code (True if success, False otherwise) + :rtype: bool """ return self._call_api(methods.ReopenLog) def resource_num(self, user, host, num): """ Resource string of a session number + + :param user: User name + :type user: str + :param host: Server name + :type host: str + :param num: ID of resource to return + :type num: int + :return: Name of user resource + :rtype: str """ return self._call_api(methods.ResourceNum, user=user, host=host, @@ -707,7 +1004,10 @@ def resource_num(self, user, host, num): def restart(self): """ - Restart ejabberd + Restart ejabberd gracefully + + :return: Status code (True if success, False otherwise) + :rtype: bool """ return self._call_api(methods.Restart) @@ -734,8 +1034,23 @@ def restart(self): def send_message(self, type, from_jid, to, body, subject=""): """ Send a message to a local or remote bare of full JID + + When sending a groupchat message to a MUC room, FROM must be the full JID of a room occupant, + or the bare JID of a MUC service admin, or the bare JID of a MUC/Sub subscribed user. + + :param type: Message type: normal, chat, headline, groupchat + :type type: str + :param from_jid: Sender JID + :type from_jid: str + :param to: Receiver JID + :type to: str + :param body: Body + :type body: str + :param subject: Subject, or empty string + :type subject: str + :return: Status code (True if success, False otherwise) + :rtype: bool """ - # noinspection PyTypeChecker return self._call_api(methods.SendMessage, type=type, from_jid=from_jid, to=to, subject=subject, @@ -747,6 +1062,13 @@ def send_message(self, type, from_jid, to, body, subject=""): def send_stanza_c2s(self, user, host, resource, stanza): """ Send a stanza as if sent from a c2s session + + :param user: Username + :param host: Server name + :param resource: Resource + :param stanza: Stanza + :return: Status code (True if success, False otherwise) + :rtype: bool """ return self._call_api(methods.SendStanzaC2S, user=user, host=host, @@ -756,6 +1078,14 @@ def send_stanza_c2s(self, user, host, resource, stanza): def set_last(self, user, host, timestamp, status): """ Set last activity information + Timestamp is the seconds since 1970-01-01 00:00:00 UTC, for example: date +%s + + :param user: User name + :param host: Server name + :param timestamp: Number of seconds since epoch + :param status: Status message + :return: Status code (True if success, False otherwise) + :rtype: bool """ return self._call_api(methods.SetLast, user=user, host=host, @@ -764,16 +1094,12 @@ def set_last(self, user, host, timestamp, status): def set_loglevel(self, loglevel): """ - Set the loglevel (0 to 5) - - Arguments: - - loglevel :: integer - - Result: - - {logger,atom} + Set the loglevel + :param loglevel: Desired logging level: none | emergency | alert | critical | error + | warning | notice | info | debug + :return: Status code (True if success, False otherwise) + :rtype: bool """ try: return self._call_api(methods.SetLogLevel, loglevel=loglevel) @@ -784,12 +1110,27 @@ def set_loglevel(self, loglevel): def set_master(self, nodename): """ Set master node of the clustered Mnesia tables + If you provide as nodename "self", this node will be set as its own master. + + :param nodename: Name of the erlang node that will be considered master of this node + :type nodename: str + :return: Raw result string + :rtype: bool """ return self._call_api(methods.SetMaster, nodename=nodename) def set_nickname(self, user, host, nickname): """ Set nickname in a user's vCard + + :param user: Username + :type user: str + :param host: Server name + :type host: str + :param nickname: Nickname + :type nickname: str + :return: Status code (True if success, False otherwise) + :rtype: bool """ return self._call_api(methods.SetNickname, user=user, host=host, @@ -798,6 +1139,23 @@ def set_nickname(self, user, host, nickname): def set_presence(self, user, host, resource, type, show, status, priority): """ Set presence of a session + + :param user: Username + :type user: str + :param host: Server name + :type host: str + :param resource: Resource + :type resource: str + :param type: Type: available, error, probe.. + :type type: str + :param show: Show: away, chat, dnd, xa + :type show: str + :param status: Status text + :type status: str + :param priority: Priority, provide this value as an integer + :type priority: str + :return: Status code (True if success, False otherwise) + :rtype: bool """ return self._call_api(methods.SetPresence, user=user, host=host, @@ -813,6 +1171,21 @@ def set_presence(self, user, host, resource, type, show, status, priority): def set_vcard(self, user, host, name, content): """ Set content in a vCard field + + Some vcard field names in get/set_vcard are: + FN - Full Name + NICKNAME - Nickname + BDAY - Birthday + TITLE - Work: Position + ROLE - Work: Role + For a full list of vCard fields check XEP-0054: vcard-temp at http://www.xmpp.org/extensions/xep-0054.html + + :param user: User name + :param host: Server name + :param name: Field name + :param content: Value + :return: Status code (True if success, False otherwise) + :rtype: bool """ return self._call_api(methods.SetVcard, user=user, host=host, @@ -822,6 +1195,14 @@ def set_vcard(self, user, host, name, content): def set_vcard2(self, user, host, name, subname, content): """ Set content in a vCard subfield + + :param user: User name + :param host: Server name + :param name: Field name + :param subname: Subfield name + :param content: Value + :return: Status code (True if success, False otherwise) + :rtype: bool """ return self._call_api(methods.SetVcard2, user=user, host=host, @@ -831,7 +1212,20 @@ def set_vcard2(self, user, host, name, subname, content): def set_vcard2_multi(self, user, host, name, subname, contents): """ - *Set multiple contents in a vCard subfield + Set multiple contents in a vCard subfield + + :param user: User name + :type user: str + :param host: Server name + :type host: str + :param name: Field name + :type name: str + :param subname: Subfield name + :type subname: str + :param contents: Contents + :type contents: dict + :return: Status code (True if success, False otherwise) + :rtype: bool """ return self._call_api(methods.SetVcardMulti, user=user, host=host, @@ -842,6 +1236,23 @@ def set_vcard2_multi(self, user, host, name, subname, contents): def srg_create(self, group, host, name, description, display): """ Create a Shared Roster Group + + If you want to specify several group identifiers in the Display argument, + put \ " around the argument and separate the identifiers with \ \ n + For example: ejabberdctl srg_create group3 myserver.com name desc \"group1\ngroup2\" + + :param group: Group identifier + :type group: str + :param host: Group server name + :type host: str + :param name: Group name + :type name: str + :param description: Group description + :type description: str + :param display: Groups to display + :type display: str + :return: Status code (True if success, False otherwise) + :rtype: bool """ return self._call_api(methods.SrgCreate, group=group, host=host, @@ -852,30 +1263,67 @@ def srg_create(self, group, host, name, description, display): def srg_delete(self, group, host): """ Delete a Shared Roster Group + + :param group: Group identifier + :type group: str + :param host: Group server name + :type host: str + :return: Status code (True if success, False otherwise) + :rtype: bool """ return self._call_api(methods.SrgDelete, group=group, host=host) def srg_get_info(self, group, host): """ Get info of a Shared Roster Group + + :param group: Group identifier + :type group: str + :param host: Group server name + :type host: str + :return: List of group informations, as key and value + :rtype: list """ return self._call_api(methods.SrgGetInfo, group=group, host=host) def srg_get_members(self, group, host): """ Get members of a Shared Roster Group + + :param group: Group identifier + :type group: str + :param host: Group server name + :type host: str + :return: List of group identifiers + :rtype: list """ return self._call_api(methods.SrgGetMembers, group=group, host=host) def srg_list(self, host): """ List the Shared Roster Groups in Host + + :param host: Server name + :type host: str + :return: List of group identifiers + :rtype: list """ return self._call_api(methods.SrgList, host=host) def srg_user_add(self, user, host, group, grouphost): """ Add the JID user@host to the Shared Roster Group + + :param user: User name + :type user: str + :param host: User server name + :type host: str + :param group: Group identifier + :type group: str + :param grouphost: Group server name + :type grouphost: str + :return: Status code (True if success, False otherwise) + :rtype: bool """ return self._call_api(methods.SrgUserAdd, user=user, host=host, @@ -885,6 +1333,17 @@ def srg_user_add(self, user, host, group, grouphost): def srg_user_del(self, user, host, group, grouphost): """ Delete this JID user@host from the Shared Roster Group + + :param user: User name + :type user: str + :param host: User server name + :type host: str + :param group: Group identifier + :type group: str + :param grouphost: Group server name + :type grouphost: str + :return: Status code (True if success, False otherwise) + :rtype: bool """ return self._call_api(methods.SrgUserDel, user=user, host=host, @@ -893,68 +1352,111 @@ def srg_user_del(self, user, host, group, grouphost): def stats(self, name): """ - Get statistical value: + Get statistical value + :param name: Statistic name: * ``registeredusers`` * ``onlineusers`` * ``onlineusersnode`` * ``uptimeseconds`` - * ``processes`` - Introduced sometime after Ejabberd 15.07 + * ``processes`` + :type name: str + :return: Integer statistic value """ - try: - return self._call_api(methods.Stats, name=name) - except xmlrpc_client.Fault as e: - msg = 'processes stats NOT available in this version of Ejabberd' - raise Exception('{}\n{} - code: {}\n'.format(msg, e.faultString, e.faultCode)) + return self._call_api(methods.Stats, name=name) def stats_host(self, name, host): """ - Get statistical value for this host: + Get statistical value + :param name: Statistic name: * ``registeredusers`` * ``onlineusers`` + + :type name: str + :param host: Server JID + :type host: str + :return: Integer statistic value + :rtype: int """ return self._call_api(methods.StatsHost, name=name, host=host) def status(self): """ - Get ejabberd status + Get status of the ejabberd server + + :return: Raw result string + :rtype: str """ return self._call_api(methods.Status) def status_list(self, status): """ List of logged users with this status + + :param status: Status type to check + :type status: str + :return: List of users with this `status` + :rtype: list """ return self._call_api(methods.StatusList, status=status) def status_list_host(self, host, status): """ List of users logged in host with their statuses + + :param host: Server name + :type host: str + :param status: Status type to check + :type status: str + :return: List of users with this `status` in `host` + :rtype: list """ return self._call_api(methods.StatusListHost, host=host, status=status) def status_num(self, status): """ - Number of logged users with this status + Number of logged users with this status + + :param status: Status type to check + :type status: str + :return: Number of connected sessions with given status type + :rtype: list """ return self._call_api(methods.StatusNum, status=status) def status_num_host(self, host, status): """ Number of logged users with this status in host + + :param host: Server name + :type host: str + :param status: Status type to check + :type status: str + :return: Number of connected sessions with given status type + :rtype: list """ return self._call_api(methods.StatusNumHost, host=host, status=status) def stop(self): """ - Stop ejabberd + Stop ejabberd gracefully + + :return: Status code (True if success, False otherwise) + :rtype: bool """ return self._call_api(methods.Stop) def stop_kindly(self, delay, announcement): """ Inform users and rooms, wait, and stop the server + Provide the delay in seconds, and the announcement quoted, + for example: ejabberdctl stop_kindly 60 \"The server will stop in one minute.\" + + :param delay: Seconds to wait + :param announcement: Announcement to send, with quotes + :return: Status code (True if success, False otherwise) + :rtype: bool """ return self._call_api(methods.StopKindly, delay=delay, announcement=announcement) @@ -962,20 +1464,30 @@ def stop_kindly(self, delay, announcement): def subscribe_room(self, user, nick, room, nodes=None): """ Subscribe to a MUC conference - :param user: - :param nick: - :param room: - :param nodes: - :return: + + :param user: User JID + :type user: str + :param nick: A user's nick + :type nick: str + :param room: The room to subscribe + :type room: str + :param nodes: List of nodes + :type nodes: list + :return: The list of nodes that has subscribed + :rtype: list """ return self._call_api(methods.SubscribeRoom, user=user, nick=nick, room=room, nodes=nodes) def unsubscribe_room(self, user, room): """ Unsubscribe from a MUC conference - :param user: - :param room: - :return: + + :param user: User JID + :type user: str + :param room: The room to subscribe + :type room: str + :return: Status code (True if success, False otherwise) + :rtype: bool """ return self._call_api(methods.UnSubscribeRoom, user=user, room=room) @@ -983,44 +1495,52 @@ def update(self, module): """ Update the given module, or use the keyword: all - :param module: + :param module: Module to update + :return: Raw result string + :rtype: str """ return self._call_api(methods.Update, module=module) def update_list(self): """ List modified modules that can be updated + + :return: List of modules + :rtype: list """ return self._call_api(methods.UpdateList) def update_sql(self): """ - List modified modules that can be updated + Convert SQL DB to the new format + + :return: Status code (True if success, False otherwise) + :rtype: bool """ return self._call_api(methods.UpdateSql) - def user_resources(self, user, server): + def user_resources(self, user, host): """ List user's connected resources - Note, parameters changed in 15.09 - from ``user, host`` - to ``user, server``. - - Arguments: - - user :: binary - server :: binary - - Result: - - {resources,{list,{resource,string}}} - + :param user: User name + :type user: str + :param host: Server name + :type host: str + :return: List of resources + :rtype: list """ - return self._call_api(methods.UserResources, user=user, server=server) + return self._call_api(methods.UserResources, user=user, host=host) def user_sessions_info(self, user, host): """ Get information about all sessions of a user + + :param user: User name + :type user: str + :param host: Server name + :type host: str + :return: A List with user sessions + :rtype: list """ return self._call_api(methods.UserSessionInfo, user=user, host=host) From 58546027a2e7793b184876cb8bd97cf6a2556e6f Mon Sep 17 00:00:00 2001 From: Dedaldino3D Date: Sat, 5 Dec 2020 00:11:50 +0100 Subject: [PATCH 84/99] Change version in setup.py --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index a450819..5b0d68b 100644 --- a/setup.py +++ b/setup.py @@ -4,7 +4,7 @@ import setuptools from pip._internal.req import parse_requirements -__version__ = "0.2.2" +__version__ = "0.2.3" with open("README.md", "r") as fh: long_description = fh.read() From 98c90fef69a5197424afe1a958227a282b5c00e0 Mon Sep 17 00:00:00 2001 From: Dedaldino3D Date: Mon, 4 Jan 2021 17:07:45 +0100 Subject: [PATCH 85/99] Add get_room_occupants --- ejabberd_python3d/abc/api.py | 7 +++++-- ejabberd_python3d/client.py | 6 +++--- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/ejabberd_python3d/abc/api.py b/ejabberd_python3d/abc/api.py index 28c1a9c..735109b 100644 --- a/ejabberd_python3d/abc/api.py +++ b/ejabberd_python3d/abc/api.py @@ -311,8 +311,11 @@ def get_loglevel(self): # TODO def get_room_affiliations(self, name, service): # Get the list of affiliations of a MUC room - # TODO def get_room_occupants(self, name, service): - # Get the list of occupants of a MUC room + def get_room_occupants(self, name, service): + """ + Get the list of occupants of a MUC room + """ + raise NotImplementedError("subclass must implement this method") # TODO def get_room_occupants_number(self, name, service): # Get the number of occupants of a MUC room diff --git a/ejabberd_python3d/client.py b/ejabberd_python3d/client.py index cfab7a7..447ca13 100644 --- a/ejabberd_python3d/client.py +++ b/ejabberd_python3d/client.py @@ -174,7 +174,6 @@ def _call_api(self, api_class, **kwargs): self._report_method_call(api.method, args) # perform call - print("auth args: %s, args: %s" % (self.auth, args)) try: if not api.authenticate: response = method(args) @@ -644,8 +643,9 @@ def get_loglevel(self): # TODO def get_room_affiliations(self, name, service): # Get the list of affiliations of a MUC room - # TODO def get_room_occupants(self, name, service): - # Get the list of occupants of a MUC room + def get_room_occupants(self, name, service): + # Get the list of occupants of a MUC room + return self._call_api(methods.GetRoomOccupants, name=name, service=service) # TODO def get_room_occupants_number(self, name, service): # Get the number of occupants of a MUC room From f6c3ec7dfc6c1949ac70b6f359d361989c63f24d Mon Sep 17 00:00:00 2001 From: Dedaldino3D Date: Mon, 4 Jan 2021 17:13:25 +0100 Subject: [PATCH 86/99] Alter change_password method signature --- ejabberd_python3d/abc/api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ejabberd_python3d/abc/api.py b/ejabberd_python3d/abc/api.py index 735109b..d2716e8 100644 --- a/ejabberd_python3d/abc/api.py +++ b/ejabberd_python3d/abc/api.py @@ -103,7 +103,7 @@ def unregister(self, user, host): raise NotImplementedError("subclass must implement this method") @abstractmethod - def change_password(self, user, host, password, newpass): + def change_password(self, user, host, newpass): raise NotImplementedError("subclass must implement this method") @abstractmethod From 5461980f9a74a8e19618b64603dd87be625b7919 Mon Sep 17 00:00:00 2001 From: Dedaldino3D Date: Mon, 4 Jan 2021 17:19:24 +0100 Subject: [PATCH 87/99] Add backup method --- ejabberd_python3d/abc/api.py | 4 +++- ejabberd_python3d/client.py | 10 +++++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/ejabberd_python3d/abc/api.py b/ejabberd_python3d/abc/api.py index d2716e8..b134a63 100644 --- a/ejabberd_python3d/abc/api.py +++ b/ejabberd_python3d/abc/api.py @@ -164,7 +164,9 @@ def add_rosteritem(self, """ raise NotImplementedError("subclass must implement this method") - # TODO def backup(self, file): Store the database to backup file + def backup(self, file): + """Store the database to backup file""" + raise NotImplementedError("subclass must implement this method") @abstractmethod def ban_account(self, user, host, reason): diff --git a/ejabberd_python3d/client.py b/ejabberd_python3d/client.py index 447ca13..6afcf76 100644 --- a/ejabberd_python3d/client.py +++ b/ejabberd_python3d/client.py @@ -294,7 +294,15 @@ def add_rosteritem(self, group=group, subs=subs) - # TODO def backup(self, file): Store the database to backup file + def backup(self, file): + """ + Store the database to backup file + :param file: Full path for the destination backup file + :type file: str + :return: raw string result + :rtype: str + """ + return self._call_api(methods.Backup, file=file) def ban_account(self, user, host, reason): """ From acf3ecefa4571ce4539783056597c841ae9d72c9 Mon Sep 17 00:00:00 2001 From: Dedaldino3D Date: Mon, 4 Jan 2021 17:22:20 +0100 Subject: [PATCH 88/99] Add change_room_option & remove change_room_option TODO --- ejabberd_python3d/abc/api.py | 4 +--- ejabberd_python3d/client.py | 3 --- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/ejabberd_python3d/abc/api.py b/ejabberd_python3d/abc/api.py index b134a63..c4d136b 100644 --- a/ejabberd_python3d/abc/api.py +++ b/ejabberd_python3d/abc/api.py @@ -144,6 +144,7 @@ def get_room_options(self, name, service): @abstractmethod def change_room_option(self, name, service, option, value): + """Change an option in a MUC room""" raise NotImplementedError("subclass must implement this method") @abstractmethod @@ -175,9 +176,6 @@ def ban_account(self, user, host, reason): """ raise NotImplementedError("subclass must implement this method") - # TODO def change_room_option(self, name, service, option, value) - # Change an option in a MUC room - @abstractmethod def check_account(self, user, host): """ diff --git a/ejabberd_python3d/client.py b/ejabberd_python3d/client.py index 6afcf76..7edaa08 100644 --- a/ejabberd_python3d/client.py +++ b/ejabberd_python3d/client.py @@ -321,9 +321,6 @@ def ban_account(self, user, host, reason): host=host, reason=reason) - # TODO def change_room_option(self, name, service, option, value) - # Change an option in a MUC room - def check_account(self, user, host): """ Check if an account exists or not From 2e6fb0d048b03ea114f0a3774bcc9aaa3cd1ca67 Mon Sep 17 00:00:00 2001 From: Dedaldino3D Date: Mon, 4 Jan 2021 17:25:06 +0100 Subject: [PATCH 89/99] Add compile & remove compile TODO --- ejabberd_python3d/abc/api.py | 5 +++-- ejabberd_python3d/client.py | 9 +++++++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/ejabberd_python3d/abc/api.py b/ejabberd_python3d/abc/api.py index c4d136b..4189ed7 100644 --- a/ejabberd_python3d/abc/api.py +++ b/ejabberd_python3d/abc/api.py @@ -190,8 +190,9 @@ def check_password(self, user, host, password): """ raise NotImplementedError("subclass must implement this method") - # TODO def compile(self, file): - # Recompile and reload Erlang source code file + def compile(self, file): + """Recompile and reload Erlang source code file""" + raise NotImplementedError("subclass must implement this method") @abstractmethod def connected_users_vhost(self, host): diff --git a/ejabberd_python3d/client.py b/ejabberd_python3d/client.py index 7edaa08..59cd87e 100644 --- a/ejabberd_python3d/client.py +++ b/ejabberd_python3d/client.py @@ -351,8 +351,13 @@ def check_password(self, user, host, password): host=host, password=password) - # TODO def compile(self, file): - # Recompile and reload Erlang source code file + def compile(self, file): + """ + Recompile and reload Erlang source code file + :param file: Filename of erlang source file to compile + :return: True on success, False otherwise + """ + return self._call_api(methods.Compile, file=file) def connected_users(self): """ From 93cee6e1f913a45986d080f4e32860653851160e Mon Sep 17 00:00:00 2001 From: Dedaldino3D Date: Mon, 4 Jan 2021 17:28:03 +0100 Subject: [PATCH 90/99] Add convert_to_scram & remove convert_to_scram TODO --- ejabberd_python3d/abc/api.py | 5 +++-- ejabberd_python3d/client.py | 9 +++++++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/ejabberd_python3d/abc/api.py b/ejabberd_python3d/abc/api.py index 4189ed7..f6da21b 100644 --- a/ejabberd_python3d/abc/api.py +++ b/ejabberd_python3d/abc/api.py @@ -201,8 +201,9 @@ def connected_users_vhost(self, host): """ raise NotImplementedError("subclass must implement this method") - # TODO def convert_to_scram(self, host): - # Convert the passwords in ‘users’ SQL table to SCRAM + def convert_to_scram(self, host): + """Convert the passwords in ‘users’ SQL table to SCRAM """ + raise NotImplementedError("subclass must implement this method") # TODO def convert_to_yaml(self, in, out): # Convert the input file from Erlang to YAML format diff --git a/ejabberd_python3d/client.py b/ejabberd_python3d/client.py index 59cd87e..f635e4a 100644 --- a/ejabberd_python3d/client.py +++ b/ejabberd_python3d/client.py @@ -396,8 +396,13 @@ def connected_users_vhost(self, host): """ return self._call_api(methods.ConnectedUsersVhost, host=host) - # TODO def convert_to_scram(self, host): - # Convert the passwords in ‘users’ SQL table to SCRAM + def convert_to_scram(self, host): + """ + Convert the passwords of users to SCRAM + :param host: Vhost which users' passwords will be scrammed + :return: + """ + return self._call_api(methods.ConvertToSCRAM, host=host) # TODO def convert_to_yaml(self, in, out): # Convert the input file from Erlang to YAML format From 4901e9885e5dfe0ee814caa68a52dc1a77bd707c Mon Sep 17 00:00:00 2001 From: Dedaldino3D Date: Mon, 4 Jan 2021 17:38:59 +0100 Subject: [PATCH 91/99] Add convert_to_yaml method --- ejabberd_python3d/abc/api.py | 5 +++-- ejabberd_python3d/abc/methods.py | 17 +++++++++++++++++ ejabberd_python3d/client.py | 10 ++++++++-- 3 files changed, 28 insertions(+), 4 deletions(-) diff --git a/ejabberd_python3d/abc/api.py b/ejabberd_python3d/abc/api.py index f6da21b..86ededd 100644 --- a/ejabberd_python3d/abc/api.py +++ b/ejabberd_python3d/abc/api.py @@ -205,8 +205,9 @@ def convert_to_scram(self, host): """Convert the passwords in ‘users’ SQL table to SCRAM """ raise NotImplementedError("subclass must implement this method") - # TODO def convert_to_yaml(self, in, out): - # Convert the input file from Erlang to YAML format + def convert_to_yaml(self, in_file, out_file): + """Convert the input file from Erlang to YAML format""" + raise NotImplementedError("subclass must implement this method") @abstractmethod def create_room_with_opts(self, name, service, host, options): diff --git a/ejabberd_python3d/abc/methods.py b/ejabberd_python3d/abc/methods.py index 6cce9d4..2ffc578 100644 --- a/ejabberd_python3d/abc/methods.py +++ b/ejabberd_python3d/abc/methods.py @@ -894,3 +894,20 @@ class KickUser(API): def transform_response(self, api, arguments, response): return response.get("num_resources") + + +class ConvertToYAML(API): + method = 'convert_to_yaml' + arguments = [StringArgument('in_file'), StringArgument('out_file')] + + def transform_arguments(self, **kwargs): + in_file = kwargs.pop('in_file') + out_file = kwargs.pop('out_file') + kwargs.update({ + 'in': in_file, + 'out': out_file, + }) + return kwargs + + def transform_response(self, api, arguments, response): + return response.get("res") == 0 diff --git a/ejabberd_python3d/client.py b/ejabberd_python3d/client.py index f635e4a..10c5c91 100644 --- a/ejabberd_python3d/client.py +++ b/ejabberd_python3d/client.py @@ -404,8 +404,14 @@ def convert_to_scram(self, host): """ return self._call_api(methods.ConvertToSCRAM, host=host) - # TODO def convert_to_yaml(self, in, out): - # Convert the input file from Erlang to YAML format + def convert_to_yaml(self, in_file, out_file): + """ + Convert the input file from Erlang to YAML format + :param in_file: Full path to the original configuration file + :param out_file: Full path to final file + :return: + """ + return self._call_api(methods.ConvertToYAML, in_file=in_file, out_file=out_file) def create_room_with_opts(self, name, service, host, options): """ From d6867a89ac5e484c8ae9851c432d827ec27e5db9 Mon Sep 17 00:00:00 2001 From: Dedaldino3D Date: Mon, 4 Jan 2021 17:44:24 +0100 Subject: [PATCH 92/99] Add create_rooms_file method --- ejabberd_python3d/abc/api.py | 5 +++-- ejabberd_python3d/abc/methods.py | 8 ++++++++ ejabberd_python3d/client.py | 9 +++++++-- 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/ejabberd_python3d/abc/api.py b/ejabberd_python3d/abc/api.py index 86ededd..3c8476b 100644 --- a/ejabberd_python3d/abc/api.py +++ b/ejabberd_python3d/abc/api.py @@ -216,8 +216,9 @@ def create_room_with_opts(self, name, service, host, options): """ raise NotImplementedError("subclass must implement this method") - # TODO def create_rooms_file(self, file): - # Create the rooms indicated in file + def create_rooms_file(self, file): + """Create the rooms indicated in file""" + raise NotImplementedError("subclass must implement this method") @abstractmethod def delete_expired_messages(self): diff --git a/ejabberd_python3d/abc/methods.py b/ejabberd_python3d/abc/methods.py index 2ffc578..0b901eb 100644 --- a/ejabberd_python3d/abc/methods.py +++ b/ejabberd_python3d/abc/methods.py @@ -911,3 +911,11 @@ def transform_arguments(self, **kwargs): def transform_response(self, api, arguments, response): return response.get("res") == 0 + + +class CreateRoomsFile(API): + method = "create_rooms_file" + arguments = [StringArgument('file')] + + def transform_response(self, api, arguments, response): + return response.get('res') == 0 diff --git a/ejabberd_python3d/client.py b/ejabberd_python3d/client.py index 10c5c91..b0a3408 100644 --- a/ejabberd_python3d/client.py +++ b/ejabberd_python3d/client.py @@ -430,8 +430,13 @@ def create_room_with_opts(self, name, service, host, options): """ return self._call_api(methods.CreateRoomWithOpts, name=name, service=service, host=host, options=options) - # TODO def create_rooms_file(self, file): - # Create the rooms indicated in file + def create_rooms_file(self, file): + """ + Create the rooms indicated in file + :param file: Path to the text file with one room JID per line + :return: True on success, False otherwise + """ + return self._call_api(methods.CreateRoomsFile, file=file) def delete_expired_messages(self): """ From f434718cb7e1a5a13b6635a4bde71ee72b3030a3 Mon Sep 17 00:00:00 2001 From: Dedaldino3D Date: Mon, 4 Jan 2021 17:48:14 +0100 Subject: [PATCH 93/99] Add delete_mnesia method --- ejabberd_python3d/abc/api.py | 5 +++-- ejabberd_python3d/abc/methods.py | 8 ++++++++ ejabberd_python3d/client.py | 9 +++++++-- 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/ejabberd_python3d/abc/api.py b/ejabberd_python3d/abc/api.py index 3c8476b..365f627 100644 --- a/ejabberd_python3d/abc/api.py +++ b/ejabberd_python3d/abc/api.py @@ -227,8 +227,9 @@ def delete_expired_messages(self): """ raise NotImplementedError("subclass must implement this method") - # TODO def delete_mnesia(self, host): - # Export all tables as SQL queries to a file + def delete_mnesia(self, host): + """Delete elements in Mnesia database for a given vhost""" + raise NotImplementedError("subclass must implement this method") # TODO def delete_old_mam_messages(self, type, days): # Delete MAM messages older than DAYS diff --git a/ejabberd_python3d/abc/methods.py b/ejabberd_python3d/abc/methods.py index 0b901eb..8fc89df 100644 --- a/ejabberd_python3d/abc/methods.py +++ b/ejabberd_python3d/abc/methods.py @@ -919,3 +919,11 @@ class CreateRoomsFile(API): def transform_response(self, api, arguments, response): return response.get('res') == 0 + + +class DeleteMnesia(API): + method = "delete_mnesia" + arguments = [StringArgument('host')] + + def transform_response(self, api, arguments, response): + return response.get("res") == 0 diff --git a/ejabberd_python3d/client.py b/ejabberd_python3d/client.py index b0a3408..16046e9 100644 --- a/ejabberd_python3d/client.py +++ b/ejabberd_python3d/client.py @@ -447,8 +447,13 @@ def delete_expired_messages(self): """ return self._call_api(methods.DeleteExpiredMessages) - # TODO def delete_mnesia(self, host): - # Export all tables as SQL queries to a file + def delete_mnesia(self, host): + """ + Delete elements in Mnesia database for a given vhost + :param host: Vhost which content will be deleted in Mnesia database + :return: + """ + return self._call_api(methods.DeleteMnesia, host=host) # TODO def delete_old_mam_messages(self, type, days): # Delete MAM messages older than DAYS From c35a9180eb812c8a8890625870330b2a7b0ab4b2 Mon Sep 17 00:00:00 2001 From: Dedaldino3D Date: Mon, 4 Jan 2021 17:52:27 +0100 Subject: [PATCH 94/99] Add delete_old_mam_messages method --- ejabberd_python3d/abc/api.py | 5 +++-- ejabberd_python3d/abc/methods.py | 8 ++++++++ ejabberd_python3d/client.py | 12 +++++++++--- 3 files changed, 20 insertions(+), 5 deletions(-) diff --git a/ejabberd_python3d/abc/api.py b/ejabberd_python3d/abc/api.py index 365f627..fee6e9a 100644 --- a/ejabberd_python3d/abc/api.py +++ b/ejabberd_python3d/abc/api.py @@ -231,8 +231,9 @@ def delete_mnesia(self, host): """Delete elements in Mnesia database for a given vhost""" raise NotImplementedError("subclass must implement this method") - # TODO def delete_old_mam_messages(self, type, days): - # Delete MAM messages older than DAYS + def delete_old_mam_messages(self, type, days): + """Delete MAM messages older than DAYS""" + raise NotImplementedError("subclass must implement this method") @abstractmethod def delete_old_messages(self, days): diff --git a/ejabberd_python3d/abc/methods.py b/ejabberd_python3d/abc/methods.py index 8fc89df..cbc543a 100644 --- a/ejabberd_python3d/abc/methods.py +++ b/ejabberd_python3d/abc/methods.py @@ -927,3 +927,11 @@ class DeleteMnesia(API): def transform_response(self, api, arguments, response): return response.get("res") == 0 + + +class DeleteOldMAMMessages(API): + method = "delete_old_mam_messages" + arguments = [StringArgument('type'), StringArgument('days')] + + def transform_response(self, api, arguments, response): + return response.get('res') == 0 diff --git a/ejabberd_python3d/client.py b/ejabberd_python3d/client.py index 16046e9..f3d2940 100644 --- a/ejabberd_python3d/client.py +++ b/ejabberd_python3d/client.py @@ -451,12 +451,18 @@ def delete_mnesia(self, host): """ Delete elements in Mnesia database for a given vhost :param host: Vhost which content will be deleted in Mnesia database - :return: + :return: True on success, False otherwise """ return self._call_api(methods.DeleteMnesia, host=host) - # TODO def delete_old_mam_messages(self, type, days): - # Delete MAM messages older than DAYS + def delete_old_mam_messages(self, type, days): + """ + Delete MAM messages older than DAYS + :param type: Type of messages to delete (chat, groupchat, all) + :param days: Days to keep messages + :return: True on success, False otherwise + """ + return self._call_api(methods.DeleteOldMAMMessages, type=type, days=days) def delete_old_messages(self, days): """ From e98dfb1f17d6e660dadd938a0d77c91ccf062b21 Mon Sep 17 00:00:00 2001 From: Dedaldino3D Date: Mon, 4 Jan 2021 17:55:08 +0100 Subject: [PATCH 95/99] Remove delete_room TODO --- ejabberd_python3d/abc/api.py | 3 +-- ejabberd_python3d/client.py | 3 --- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/ejabberd_python3d/abc/api.py b/ejabberd_python3d/abc/api.py index fee6e9a..16e96b4 100644 --- a/ejabberd_python3d/abc/api.py +++ b/ejabberd_python3d/abc/api.py @@ -136,6 +136,7 @@ def create_room(self, name, service, host): @abstractmethod def destroy_room(self, name, service): + """Destroy a MUC room""" raise NotImplementedError("subclass must implement this method") @abstractmethod @@ -264,8 +265,6 @@ def delete_rosteritem(self, localuser, localserver, user, server): """ raise NotImplementedError("subclass must implement this method") - # TODO def destroy_room(self, name, service): - # Destroy a MUC room # TODO def destroy_rooms_file(self, file): # Destroy the rooms indicated in file. Provide one room JID per line. diff --git a/ejabberd_python3d/client.py b/ejabberd_python3d/client.py index f3d2940..dc6c490 100644 --- a/ejabberd_python3d/client.py +++ b/ejabberd_python3d/client.py @@ -524,9 +524,6 @@ def delete_rosteritem(self, localuser, localhost, user, host): user=user, server=host) - # TODO def destroy_room(self, name, service): - # Destroy a MUC room - # TODO def destroy_rooms_file(self, file): # Destroy the rooms indicated in file. Provide one room JID per line. From 7a387d9feafed4c225b12f7897635c47dccd9f1f Mon Sep 17 00:00:00 2001 From: Dedaldino3D Date: Mon, 4 Jan 2021 17:58:29 +0100 Subject: [PATCH 96/99] Add destroy_rooms_file method --- ejabberd_python3d/abc/api.py | 6 +++--- ejabberd_python3d/abc/methods.py | 8 ++++++++ ejabberd_python3d/client.py | 9 +++++++-- 3 files changed, 18 insertions(+), 5 deletions(-) diff --git a/ejabberd_python3d/abc/api.py b/ejabberd_python3d/abc/api.py index 16e96b4..2f98d95 100644 --- a/ejabberd_python3d/abc/api.py +++ b/ejabberd_python3d/abc/api.py @@ -265,9 +265,9 @@ def delete_rosteritem(self, localuser, localserver, user, server): """ raise NotImplementedError("subclass must implement this method") - - # TODO def destroy_rooms_file(self, file): - # Destroy the rooms indicated in file. Provide one room JID per line. + def destroy_rooms_file(self, file): + """Destroy the rooms indicated in file. Provide one room JID per line.""" + raise NotImplementedError("subclass must implement this method") # TODO def dump(self, file): # Dump the database to text file diff --git a/ejabberd_python3d/abc/methods.py b/ejabberd_python3d/abc/methods.py index cbc543a..041f9bc 100644 --- a/ejabberd_python3d/abc/methods.py +++ b/ejabberd_python3d/abc/methods.py @@ -935,3 +935,11 @@ class DeleteOldMAMMessages(API): def transform_response(self, api, arguments, response): return response.get('res') == 0 + + +class DestroyRoomsFile(API): + method = "destroy_rooms_file" + arguments = [StringArgument('file')] + + def transform_response(self, api, arguments, response): + return response.get('res') == 0 diff --git a/ejabberd_python3d/client.py b/ejabberd_python3d/client.py index dc6c490..501f3d3 100644 --- a/ejabberd_python3d/client.py +++ b/ejabberd_python3d/client.py @@ -524,8 +524,13 @@ def delete_rosteritem(self, localuser, localhost, user, host): user=user, server=host) - # TODO def destroy_rooms_file(self, file): - # Destroy the rooms indicated in file. Provide one room JID per line. + def destroy_rooms_file(self, file): + """ + Destroy the rooms indicated in file. Provide one room JID per line. + :param file: Path to the text file with one room JID per line + :return: + """ + return self._call_api(methods.DestroyRoomsFile, file=file) # TODO def dump(self, file): # Dump the database to text file From 8379785539e161c70c3983861ab80666d7d07e4d Mon Sep 17 00:00:00 2001 From: Dedaldino3D Date: Mon, 4 Jan 2021 18:01:53 +0100 Subject: [PATCH 97/99] Add dump method --- ejabberd_python3d/abc/api.py | 5 +++-- ejabberd_python3d/abc/methods.py | 8 ++++++++ ejabberd_python3d/client.py | 9 +++++++-- 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/ejabberd_python3d/abc/api.py b/ejabberd_python3d/abc/api.py index 2f98d95..f16669f 100644 --- a/ejabberd_python3d/abc/api.py +++ b/ejabberd_python3d/abc/api.py @@ -269,8 +269,9 @@ def destroy_rooms_file(self, file): """Destroy the rooms indicated in file. Provide one room JID per line.""" raise NotImplementedError("subclass must implement this method") - # TODO def dump(self, file): - # Dump the database to text file + def dump(self, file): + """Dump the database to text file""" + raise NotImplementedError("subclass must implement this method") # TODO def dump_table(self, file, table): # Dump a table to text file diff --git a/ejabberd_python3d/abc/methods.py b/ejabberd_python3d/abc/methods.py index 041f9bc..512f165 100644 --- a/ejabberd_python3d/abc/methods.py +++ b/ejabberd_python3d/abc/methods.py @@ -943,3 +943,11 @@ class DestroyRoomsFile(API): def transform_response(self, api, arguments, response): return response.get('res') == 0 + + +class Dump(API): + method = "dump" + arguments = [StringArgument('file')] + + def transform_response(self, api, arguments, response): + return response.get('res') == 0 diff --git a/ejabberd_python3d/client.py b/ejabberd_python3d/client.py index 501f3d3..4d18914 100644 --- a/ejabberd_python3d/client.py +++ b/ejabberd_python3d/client.py @@ -532,8 +532,13 @@ def destroy_rooms_file(self, file): """ return self._call_api(methods.DestroyRoomsFile, file=file) - # TODO def dump(self, file): - # Dump the database to text file + def dump(self, file): + """ + Dump the database to text file + :param file: Full path for the text file + :return: + """ + return self._call_api(methods.Dump, file=file) # TODO def dump_table(self, file, table): # Dump a table to text file From b61092f50aead16db2489330631493dd0330b1ff Mon Sep 17 00:00:00 2001 From: Dedaldino3D Date: Mon, 4 Jan 2021 18:32:29 +0100 Subject: [PATCH 98/99] README updated --- README.md | 54 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 53 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 5d252b7..1f14436 100644 --- a/README.md +++ b/README.md @@ -1 +1,53 @@ -A python lib to run ejabberd XML-RPC commands +**A python lib to run ejabberd XML-RPC commands** + +**Features** +
    +
  • Execute XML-RPC commands
  • +
  • External authentication
  • +
  • Register Users
  • +
  • Delete Users
  • +
  • Create Rooms
  • +
  • Subscribe to Rooms
  • +
  • Send Messages
  • +
  • and many others features, ... .
  • +
+ +#### How to install +When working with python its a common approach to use a virtualenv to encapsulate all dependencies. +First create a virtual environment: +__if you have virtualenv installed run this code__ +```python +virtualenv ejabberd_python3d_venv +``` +__if not, so install with this code:__ +````python +pip install virtualenv +```` +and then install **ejabberd_python3d** lib: +```python +pip install ejabberd_python3d +``` + +To get the most updated version, you'll need to clone this repository: +````git +git clone http://github.com/Dedaldino3D/ejabberd_python3d.git +```` +Run +````python +python setup.py +```` + +After installation is completed, create a client instance: + +````python +from ejabberd_python3d.client import EjabberdAPIClient + +client = EjabberdAPIClient('localhost','dedaldino','123456') +users = client.registered_users('localhost') +# assuming that you have an user registered (the admin) +print(users) # [dedaldino] +client.register('dedaldino3d','localhost','nopassword') +users = client.registered_users('localhost') +print(users) # ['dedaldino3d'] +```` + From 4ff4474347b89ecfda48ff05b76d3a8b3c983046 Mon Sep 17 00:00:00 2001 From: Dedaldino3D Date: Thu, 3 Jun 2021 18:19:44 +0100 Subject: [PATCH 99/99] Start implementation of tests & update muc/enums --- ejabberd_python3d/muc/enums.py | 4 ++-- ejabberd_python3d/testing.py | 14 ++++++++++++++ tests/__init__.py | 0 3 files changed, 16 insertions(+), 2 deletions(-) create mode 100644 ejabberd_python3d/testing.py create mode 100644 tests/__init__.py diff --git a/ejabberd_python3d/muc/enums.py b/ejabberd_python3d/muc/enums.py index 686e184..e5e27b9 100644 --- a/ejabberd_python3d/muc/enums.py +++ b/ejabberd_python3d/muc/enums.py @@ -4,8 +4,8 @@ class MUCRoomOption(Enum): - allow_change_subj = 1 - allow_private_messages = 2 + allow_change_subj = 'allow_change_subj' + allow_private_messages = 'allow_private_messages' allow_private_messages_from_visitors = 3 allow_query_users = 4 allow_user_invites = 5 diff --git a/ejabberd_python3d/testing.py b/ejabberd_python3d/testing.py new file mode 100644 index 0000000..afd52b3 --- /dev/null +++ b/ejabberd_python3d/testing.py @@ -0,0 +1,14 @@ +from ejabberd_python3d import client + +conn = client.EjabberdAPIClient("localhost", "dedaldino", "Dedaldino18", "localhost") +groups = conn.muc_online_rooms() + +for g in groups: + # TODO: add get_room_occupants in ejabberd_python3d lib + room_name = g['room'].split('@')[0] + subs = [u.split('@')[0] for u in + conn.get_subscribers(room_name, 'groupchat.localhost')] + print("subs: ", subs) + options = conn.get_room_options(room_name, 'groupchat.localhost') + print("muc options: ", options) + diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29