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
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
diff --git a/LICENSE b/LICENSE
index 0e259d4..bfd5c15 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) 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
+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.
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
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..1f14436
--- /dev/null
+++ b/README.md
@@ -0,0 +1,53 @@
+**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']
+````
+
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/ejabberd_python3d/__init__.py b/ejabberd_python3d/__init__.py
new file mode 100644
index 0000000..e7bc29a
--- /dev/null
+++ b/ejabberd_python3d/__init__.py
@@ -0,0 +1,2 @@
+from ejabberd_python3d import client, serializers, ejabberd_auth
+from ejabberd_python3d import muc, abc, defaults
diff --git a/ejabberd_python3d/abc/__init__.py b/ejabberd_python3d/abc/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/ejabberd_python3d/abc/api.py b/ejabberd_python3d/abc/api.py
new file mode 100644
index 0000000..f16669f
--- /dev/null
+++ b/ejabberd_python3d/abc/api.py
@@ -0,0 +1,770 @@
+from __future__ import unicode_literals
+
+from abc import ABC, abstractmethod
+from enum import Enum as BaseClassEnum
+
+
+class APIArgumentSerializer(ABC):
+ @abstractmethod
+ def to_api(self, value):
+ pass
+
+ @abstractmethod
+ def to_builtin(self, value):
+ pass
+
+
+class APIArgument(ABC):
+ def __init__(self, name, description=None, required=True, **kwargs):
+ self.name = name
+ self.description = description
+ self.required = required
+
+ @abstractmethod
+ 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(ABC):
+ @abstractmethod
+ def method(self):
+ """
+ Return the exact name of the XML-RPC API method to call
+ """
+ pass
+
+ @abstractmethod
+ def arguments(self):
+ """
+ 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 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 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 return as the output of the API
+ :param response:
+ :param api: The api object that has been used fot the call
+ :param arguments: The dictionary containing the arguments
+ """
+ return response
+
+
+class EjabberdBaseAPI(ABC):
+ @abstractmethod
+ def echo(self, sentence):
+ raise NotImplementedError("subclass must implement this method")
+
+ @abstractmethod
+ def registered_users(self, host):
+ raise NotImplementedError("subclass must implement this method")
+
+ @abstractmethod
+ def register(self, user, host, password):
+ raise NotImplementedError("subclass must implement this method")
+
+ @abstractmethod
+ def unregister(self, user, host):
+ raise NotImplementedError("subclass must implement this method")
+
+ @abstractmethod
+ def change_password(self, user, host, newpass):
+ raise NotImplementedError("subclass must implement this method")
+
+ @abstractmethod
+ def check_password_hash(self, user, host, password):
+ raise NotImplementedError("subclass must implement this method")
+
+ @abstractmethod
+ def connected_users(self):
+ raise NotImplementedError("subclass must implement this method")
+
+ @abstractmethod
+ def connected_users_info(self):
+ raise NotImplementedError("subclass must implement this method")
+
+ @abstractmethod
+ def connected_users_number(self):
+ raise NotImplementedError("subclass must implement this method")
+
+ @abstractmethod
+ def user_sessions_info(self, user, host):
+ raise NotImplementedError("subclass must implement this method")
+
+ @abstractmethod
+ def muc_online_rooms(self, service=None):
+ raise NotImplementedError("subclass must implement this method")
+
+ @abstractmethod
+ def create_room(self, name, service, host):
+ raise NotImplementedError("subclass must implement this method")
+
+ @abstractmethod
+ def destroy_room(self, name, service):
+ """Destroy a MUC room"""
+ raise NotImplementedError("subclass must implement this method")
+
+ @abstractmethod
+ def get_room_options(self, name, service):
+ raise NotImplementedError("subclass must implement this method")
+
+ @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
+ def set_room_affiliation(self, name, service, jid, affiliation):
+ raise NotImplementedError("subclass must implement this method")
+
+ @abstractmethod
+ def get_room_affiliations(self, name, service):
+ raise NotImplementedError("subclass must implement this method")
+
+ @abstractmethod
+ def add_rosteritem(self,
+ localuser, localhost,
+ user, host,
+ nick, group, subs):
+ """
+ Add an item to a user's roster (self,supports ODBC):
+ """
+ raise NotImplementedError("subclass must implement this method")
+
+ 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):
+ """
+ Ban an account: kick sessions and set random password
+ """
+ raise NotImplementedError("subclass must implement this method")
+
+ @abstractmethod
+ def check_account(self, user, host):
+ """
+ Check if an account exists or not
+ """
+ raise NotImplementedError("subclass must implement this method")
+
+ @abstractmethod
+ def check_password(self, user, host, password):
+ """
+ Check if a password is correct
+ """
+ raise NotImplementedError("subclass must implement this method")
+
+ 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):
+ """
+ Get the list of established sessions in a vhost
+ """
+ raise NotImplementedError("subclass must implement this method")
+
+ def convert_to_scram(self, host):
+ """Convert the passwords in ‘users’ SQL table to SCRAM """
+ raise NotImplementedError("subclass must implement this method")
+
+ 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):
+ """
+ Create a MUC room name@service in host with given options
+ """
+ raise NotImplementedError("subclass must implement this method")
+
+ 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):
+ """
+ Delete expired offline messages from database
+ """
+ raise NotImplementedError("subclass must implement this method")
+
+ def delete_mnesia(self, host):
+ """Delete elements in Mnesia database for a given vhost"""
+ raise NotImplementedError("subclass must implement this method")
+
+ 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):
+ """
+ Delete offline messages older than DAYS
+ """
+ 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("subclass must implement this method")
+
+ @abstractmethod
+ 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("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("subclass must implement this method")
+
+ 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")
+
+ 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
+
+ # 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
+ """
+ raise NotImplementedError("subclass must implement this method")
+
+ @abstractmethod
+ def get_last(self, user, host):
+ """
+ Get last activity information (self,timestamp and status):
+ """
+ raise NotImplementedError("subclass must implement this method")
+
+ @abstractmethod
+ def get_loglevel(self):
+ """
+ Get the current loglevel
+ """
+ raise NotImplementedError("subclass must implement this method")
+
+ # 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
+
+ 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
+
+ # 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.
+ """
+ raise NotImplementedError("subclass must implement this method")
+
+ @abstractmethod
+ def get_subscribers(self, name, service):
+ """
+ List subscribers of a MUC conference
+ """
+ raise NotImplementedError("subclass must implement this method")
+
+ def get_user_rooms(self, user, host):
+ """
+ Get the list of rooms where this user is occupant
+ """
+ raise NotImplementedError("subclass must implement this method")
+
+ @abstractmethod
+ def get_vcard(self, user, host, name):
+ """
+ Get content from a vCard field
+ """
+ raise NotImplementedError("subclass must implement this method")
+
+ @abstractmethod
+ def get_vcard2(self, user, host, name, subname):
+ """
+ Get content from a vCard field
+ """
+ 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("subclass must implement this method")
+
+ # 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
+ """
+ raise NotImplementedError("subclass must implement this method")
+
+ # 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
+ """
+ raise NotImplementedError("subclass must implement this method")
+
+ @abstractmethod
+ def kick_user(self, user, host):
+ """
+ Disconnect user's active sessions
+ """
+ raise NotImplementedError("subclass must implement this method")
+
+ # 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
+ """
+ raise NotImplementedError("subclass must implement this method")
+
+ # 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
+ """
+ raise NotImplementedError("subclass must implement this method")
+
+ @abstractmethod
+ def modules_installed(self):
+ """
+ List installed modules
+ """
+ raise NotImplementedError("subclass must implement this method")
+
+ # 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_resources(self, user, host):
+ """
+ Get the number of resources of a user
+ """
+ raise NotImplementedError("subclass must implement this method")
+
+ @abstractmethod
+ def outgoing_s2s_number(self):
+ """
+ Number of outgoing s2s connections on the node
+ """
+ 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
+
+ # 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 process_rosteritems(self, action, subs, asks, users, contacts):
+ """
+ List or delete rosteritems that match filtering options
+ """
+ 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("subclass must implement this method")
+
+ # 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
+ """
+ raise NotImplementedError("subclass must implement this method")
+
+ @abstractmethod
+ def reload_config(self):
+ """
+ Reload ejabberd configuration file into memory
+ (only affects ACL and Access)
+ """
+ raise NotImplementedError("subclass must implement this method")
+
+ @abstractmethod
+ def reopen_log(self):
+ """
+ Reopen the log files
+ """
+ raise NotImplementedError("subclass must implement this method")
+
+ @abstractmethod
+ def resource_num(self, user, host, num):
+ """
+ Resource string of a session number
+ """
+ raise NotImplementedError("subclass must implement this method")
+
+ @abstractmethod
+ def restart(self):
+ """
+ Restart ejabberd
+ """
+ raise NotImplementedError("subclass must implement this method")
+
+ # 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
+ """
+ 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
+ @abstractmethod
+ def send_stanza_c2s(self, user, host, resource, stanza):
+ """
+ Send a stanza as if sent from a c2s session
+ """
+ raise NotImplementedError("subclass must implement this method")
+
+ @abstractmethod
+ def set_last(self, user, host, timestamp, status):
+ """
+ Set last activity information
+ """
+ raise NotImplementedError("subclass must implement this method")
+
+ @abstractmethod
+ def set_loglevel(self, loglevel):
+ """
+ Set the loglevel (0 to 5)
+ """
+ raise NotImplementedError("subclass must implement this method")
+
+ @abstractmethod
+ def set_master(self, nodename):
+ """
+ Set master node of the clustered Mnesia tables
+ """
+ raise NotImplementedError("subclass must implement this method")
+
+ @abstractmethod
+ def set_nickname(self, user, host, nickname):
+ """
+ Set nickname in a user's vCard
+ """
+ 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("subclass must implement this method")
+
+ # 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
+ """
+ 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("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("subclass must implement this method")
+
+ @abstractmethod
+ def srg_create(self, group, host, name, description, display):
+ """
+ Create a Shared Roster Group
+ """
+ raise NotImplementedError("subclass must implement this method")
+
+ @abstractmethod
+ def srg_delete(self, group, host):
+ """
+ Delete a Shared Roster Group
+ """
+ raise NotImplementedError("subclass must implement this method")
+
+ @abstractmethod
+ def srg_get_info(self, group, host):
+ """
+ Get info of a Shared Roster Group
+ """
+ raise NotImplementedError("subclass must implement this method")
+
+ @abstractmethod
+ def srg_get_members(self, group, host):
+ """
+ Get members of a Shared Roster Group
+ """
+ raise NotImplementedError("subclass must implement this method")
+
+ @abstractmethod
+ def srg_list(self, host):
+ """
+ List the Shared Roster Groups in Host
+ """
+ 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("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("subclass must implement this method")
+
+ @abstractmethod
+ def stats(self, name):
+ """
+ Get statistical value:
+ """
+ raise NotImplementedError("subclass must implement this method")
+
+ @abstractmethod
+ def stats_host(self, name, host):
+ """
+ Get statistical value for this host:
+ """
+ raise NotImplementedError("subclass must implement this method")
+
+ @abstractmethod
+ def status(self):
+ """
+ Get ejabberd status
+ """
+ raise NotImplementedError("subclass must implement this method")
+
+ @abstractmethod
+ def status_list(self, status):
+ """
+ List of logged users with this status
+ """
+ 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("subclass must implement this method")
+
+ @abstractmethod
+ def status_num(self, status):
+ """
+ Number of logged users with this status
+ """
+ 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("subclass must implement this method")
+
+ @abstractmethod
+ def stop(self):
+ """
+ Stop ejabberd
+ """
+ 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("subclass must implement this method")
+
+ def subscribe_room(self, user, nick, room, nodes):
+ """
+ Subscribe to a MUC conference
+ """
+ raise NotImplementedError("subclass must implement this method")
+
+ def unsubscribe_room(self, user, room):
+ """
+ Unsubscribe from a MUC conference
+ """
+ raise NotImplementedError("subclass must implement this method")
+
+ @abstractmethod
+ def update(self, module):
+ """
+ Update the given module, or use the keyword: all
+ """
+ raise NotImplementedError("subclass must implement this method")
+
+ @abstractmethod
+ def update_list(self):
+ """
+ List modified modules that can be updated
+ """
+ raise NotImplementedError("subclass must implement this method")
+
+ @abstractmethod
+ def user_resources(self, user, host):
+ """
+ List user's connected resources
+ """
+ raise NotImplementedError("subclass must implement this method")
diff --git a/ejabberd_python3d/abc/methods.py b/ejabberd_python3d/abc/methods.py
new file mode 100644
index 0000000..512f165
--- /dev/null
+++ b/ejabberd_python3d/abc/methods.py
@@ -0,0 +1,953 @@
+from __future__ import unicode_literals
+
+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 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
+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
+from six import string_types
+
+
+class Echo(API):
+ method = 'dedaldino_denis_3D'
+ 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('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'
+ 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):
+ password_hash = format_password_hash_sha(password=kwargs.pop('password'))
+ kwargs.update({
+ 'passwordhash': password_hash,
+ '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 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')]
+
+ 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: {}'.format(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')
+ 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 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'),
+ 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('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
+
+
+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('server')]
+
+ 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
+
+
+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"
+ arguments = [StringArgument('name'), StringArgument('service'), StringArgument('host'), GenericArgument('options')]
+
+ def transform_arguments(self, **kwargs):
+ 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
+
+
+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 GetSubscribers(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', 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
+
+
+class SetLast(API):
+ method = "set_last"
+ arguments = [StringArgument('user'), StringArgument('host'), IntegerArgument('timestamp'),
+ StringArgument('status')]
+
+ def transform_response(self, api, arguments, response):
+ return response.get('res') == 0
+
+
+class SubscribeRoom(API):
+ method = "subscribe_room"
+ arguments = [StringArgument('user'), StringArgument('nick'), StringArgument('room'),
+ 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')
+
+
+class UnSubscribeRoom(API):
+ method = "unsubscribe_room"
+ arguments = [StringArgument('user'), StringArgument('room')]
+
+ 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
+
+
+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')
+
+
+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('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')
+
+
+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")
+
+
+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
+
+
+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")
+
+
+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')
+
+
+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
+
+
+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
+
+
+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")
+
+
+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
+
+
+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")
+
+
+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
+
+
+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
+
+
+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
+
+
+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")
+
+
+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
+
+
+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")
+
+
+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")
+
+
+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
+
+
+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
+
+
+class UserResources(API):
+ method = "update_sql"
+ arguments = [StringArgument("user"), StringArgument("host")]
+
+ 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")
+
+
+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
+
+
+class CreateRoomsFile(API):
+ method = "create_rooms_file"
+ arguments = [StringArgument('file')]
+
+ 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
+
+
+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
+
+
+class DestroyRoomsFile(API):
+ method = "destroy_rooms_file"
+ arguments = [StringArgument('file')]
+
+ 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
new file mode 100644
index 0000000..4d18914
--- /dev/null
+++ b/ejabberd_python3d/client.py
@@ -0,0 +1,1590 @@
+from __future__ import print_function
+
+import copy
+from urllib.parse import urlparse
+from xmlrpc import client as xmlrpc_client
+
+from ejabberd_python3d.abc import methods
+from ejabberd_python3d.abc.api import API, APIArgument, EjabberdBaseAPI
+from ejabberd_python3d.core.errors import MissingArguments
+from ejabberd_python3d.defaults.constants import XMLRPC_API_PROTOCOL, XMLRPC_API_SERVER, \
+ XMLRPC_API_PORT
+
+
+# noinspection PyTypeChecker
+class EjabberdAPIClient(EjabberdBaseAPI):
+ """
+ Python client for Ejabberd XML-RPC Administration API.
+ """
+
+ 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
+ self.port = port
+ self.admin = admin
+ self.protocol = protocol
+ self.verbose = verbose
+ self._server_proxy = None
+
+ @staticmethod
+ def get_instance(service_url, verbose=False):
+ """
+ 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
+ """
+ # TODO: add endpoint parameter
+ 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,
+ 'admin': self.admin
+ }
+
+ 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
+ :return: The serialized arguments
+ :rtype: dict
+ """
+ ser_args = {}
+
+ 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)
+ 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 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
+ """
+ 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: API
+ :param kwargs:
+ :type kwargs: dict
+ :rtype: object
+ :return: 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 serialize arguments
+ args = self._validate_and_serialize_arguments(api, args)
+ # retrieve method
+ try:
+ method = getattr(self.server_proxy, str(api.method))
+ except xmlrpc_client.Fault as 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)
+
+ # perform call
+ try:
+ if not api.authenticate:
+ response = method(args)
+ else:
+ response = method(self.auth, args)
+ except xmlrpc_client.Fault as e:
+ raise Exception(f"{e.faultString} - code: {e.faultCode}")
+
+ # validate response
+ api.validate_response(api, args, response)
+ # transform 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 registered_users(self, host):
+ """
+ List all registered users in the host
+
+ :param host: The XMPP_DOMAIN
+ :type host: str|unicode
+ :rtype: Iterable
+ :return: A List of registered accounts usernames
+ """
+ return self._call_api(methods.RegisteredUsers, host=host)
+
+ def register(self, user, host, password):
+ """
+ Register 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):
+ """
+ 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
+ """
+ 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
+ :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)
+
+ def add_rosteritem(self,
+ localuser, localhost,
+ user, host,
+ nick="", group="", subs="to"):
+ """
+ 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,
+ user=user,
+ host=host,
+ nick=nick,
+ group=group,
+ subs=subs)
+
+ 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):
+ """
+ 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,
+ reason=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,
+ password=password)
+
+ 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):
+ """
+ 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)
+
+ 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)
+
+ 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):
+ """
+ Create a MUC room name@service in host with given options
+
+ :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: Status code (True if success, False otherwise)
+ :rtype: bool
+ """
+ return self._call_api(methods.CreateRoomWithOpts, name=name, service=service, host=host, options=options)
+
+ 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):
+ """
+ Delete expired offline messages from database
+
+ :return: Status code (True if success, False otherwise)
+ :rtype: bool
+ """
+ return self._call_api(methods.DeleteExpiredMessages)
+
+ 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: True on success, False otherwise
+ """
+ return self._call_api(methods.DeleteMnesia, host=host)
+
+ 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):
+ """
+ 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
+
+ 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, localhost, user, host):
+ """
+ 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=localhost,
+ user=user,
+ server=host)
+
+ 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)
+
+ 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
+
+ # 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 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):
+ """
+ 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
+
+ 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)
+
+ # 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
+
+ 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
+
+ # 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
+
+ :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
+
+ :param name: Room name
+ :type name: str
+ :param service: MUC service
+ :type service: str
+ :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: 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,
+ name=name)
+
+ def get_vcard2(self, user, host, name, subname):
+ """
+ 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,
+ name=name,
+ subname=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,
+ 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: s2s number
+ :rtype: int
+ """
+ return self._call_api(methods.IncomingS2SNumber)
+
+ # 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(methods.KickSession, user=user,
+ host=host,
+ resource=resource,
+ reason=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)
+
+ # 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
+
+ :return: List of clusters
+ :rtype: list
+ """
+ return self._call_api(methods.ListCluster)
+
+ # 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 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 the contributed modules already installed
+
+ :return: List of dict with module name and description
+ :rtype: list
+ """
+ return self._call_api(methods.ModulesInstalled)
+
+ # 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_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)
+
+ # 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/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,
+ asks=asks,
+ users=users,
+ contacts=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)
+
+ # 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 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 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,
+ num=num)
+
+ def restart(self):
+ """
+ Restart ejabberd gracefully
+
+ :return: Status code (True if success, False otherwise)
+ :rtype: bool
+ """
+ return self._call_api(methods.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, 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
+ """
+ return self._call_api(methods.SendMessage, type=type,
+ from_jid=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
+
+ :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,
+ resource=resource,
+ stanza=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,
+ timestamp=timestamp,
+ status=status)
+
+ def set_loglevel(self, loglevel):
+ """
+ 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)
+ except xmlrpc_client.Fault as e:
+ msg = 'set_loglevel is NOT available in your version of ejabberd'
+ raise Exception('{}\n{} - code: {}\n '.format(msg, e.faultString, e.faultCode))
+
+ 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,
+ nickname=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,
+ 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
+
+ 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,
+ name=name,
+ content=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,
+ name=name,
+ subname=subname,
+ content=content)
+
+ def set_vcard2_multi(self, user, host, name, subname, contents):
+ """
+ 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,
+ name=name,
+ subname=subname,
+ contents=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,
+ name=name,
+ description=description,
+ display=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,
+ group=group,
+ grouphost=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,
+ group=group,
+ grouphost=grouphost)
+
+ def stats(self, name):
+ """
+ Get statistical value
+
+ :param name: Statistic name:
+ * ``registeredusers``
+ * ``onlineusers``
+ * ``onlineusersnode``
+ * ``uptimeseconds``
+ * ``processes``
+ :type name: str
+ :return: Integer statistic value
+ """
+ return self._call_api(methods.Stats, name=name)
+
+ def stats_host(self, name, 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 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
+
+ :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 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)
+
+ def subscribe_room(self, user, nick, room, nodes=None):
+ """
+ Subscribe to a MUC conference
+
+ :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: 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)
+
+ def update(self, module):
+ """
+ Update the given module, or use the keyword: all
+
+ :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):
+ """
+ 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, host):
+ """
+ List user's connected resources
+
+ :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, 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)
diff --git a/ejabberd_python3d/core/__init__.py b/ejabberd_python3d/core/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/ejabberd_python3d/core/errors.py b/ejabberd_python3d/core/errors.py
new file mode 100644
index 0000000..07d379e
--- /dev/null
+++ b/ejabberd_python3d/core/errors.py
@@ -0,0 +1,23 @@
+class ConnectionError(Exception):
+ """Error when connecting to API"""
+ pass
+
+
+class AccessDeniedError(Exception):
+ """Access denied, account unprivileged"""
+ pass
+
+
+class MissingArguments(ValueError):
+ """Missing arguments in call"""
+ pass
+
+
+class BadArgument(ValueError):
+ """Wrong Argument"""
+ pass
+
+
+class UserAlreadyRegisteredError(Exception):
+ """User already registered"""
+ pass
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())
diff --git a/ejabberd_python3d/defaults/__init__.py b/ejabberd_python3d/defaults/__init__.py
new file mode 100644
index 0000000..fbc1372
--- /dev/null
+++ 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
new file mode 100644
index 0000000..4a1aef9
--- /dev/null
+++ b/ejabberd_python3d/defaults/arguments.py
@@ -0,0 +1,33 @@
+from __future__ import unicode_literals
+
+from ..abc.api import APIArgument
+from ..serializers import StringSerializer, IntegerSerializer, PositiveIntegerSerializer, BooleanSerializer, \
+ LogLevelSerializer, ListSerializer, GenericSerializer
+
+
+class GenericArgument(APIArgument):
+ serializer_class = GenericSerializer
+
+
+class StringArgument(APIArgument):
+ serializer_class = StringSerializer
+
+
+class IntegerArgument(APIArgument):
+ serializer_class = IntegerSerializer
+
+
+class PositiveIntegerArgument(APIArgument):
+ serializer_class = PositiveIntegerSerializer
+
+
+class BooleanArgument(APIArgument):
+ serializer_class = BooleanSerializer
+
+
+class LogLevelArgument(APIArgument):
+ serializer_class = LogLevelSerializer
+
+
+class ListArgument(APIArgument):
+ serializer_class = ListSerializer
diff --git a/ejabberd_python3d/defaults/constants.py b/ejabberd_python3d/defaults/constants.py
new file mode 100644
index 0000000..299b2f5
--- /dev/null
+++ b/ejabberd_python3d/defaults/constants.py
@@ -0,0 +1,3 @@
+XMLRPC_API_PROTOCOL = 'http'
+XMLRPC_API_PORT = 4560
+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/ejabberd_auth/__init__.py b/ejabberd_python3d/ejabberd_auth/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/ejabberd_python3d/ejabberd_auth/management/__init__.py b/ejabberd_python3d/ejabberd_auth/management/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/ejabberd_python3d/ejabberd_auth/management/commands/__init__.py b/ejabberd_python3d/ejabberd_auth/management/commands/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/ejabberd_python3d/ejabberd_auth/management/commands/ejabberd_auth.py b/ejabberd_python3d/ejabberd_auth/management/commands/ejabberd_auth.py
new file mode 100644
index 0000000..cf63c31
--- /dev/null
+++ b/ejabberd_python3d/ejabberd_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)
diff --git a/ejabberd_python3d/muc/__init__.py b/ejabberd_python3d/muc/__init__.py
new file mode 100644
index 0000000..ad1e715
--- /dev/null
+++ b/ejabberd_python3d/muc/__init__.py
@@ -0,0 +1,39 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from ..serializers import BooleanSerializer, StringSerializer, PositiveIntegerSerializer
+from .serializers import AllowVisitorPrivateMessageSerializer
+from .enums import MUCRoomOption, MUCNodes
+
+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
+}
+
+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
new file mode 100644
index 0000000..dabd1c5
--- /dev/null
+++ b/ejabberd_python3d/muc/arguments.py
@@ -0,0 +1,17 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from ..abc.api import APIArgument
+from .serializers import MUCRoomOptionSerializer, AffiliationSerializer, MUCNodesSerializer
+
+
+class MUCRoomArgument(APIArgument):
+ serializer_class = MUCRoomOptionSerializer
+
+
+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
new file mode 100644
index 0000000..e5e27b9
--- /dev/null
+++ b/ejabberd_python3d/muc/enums.py
@@ -0,0 +1,50 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+from ejabberd_python3d.abc.api import Enum
+
+
+class MUCRoomOption(Enum):
+ 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
+ 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
+
+
+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
new file mode 100644
index 0000000..e77175a
--- /dev/null
+++ b/ejabberd_python3d/muc/serializers.py
@@ -0,0 +1,38 @@
+# -*- coding: utf-8 -*-
+# from __future__ import unicode_literals
+from six import string_types
+
+from ..serializers import EnumSerializer
+from .enums import MUCRoomOption, AllowVisitorPrivateMessage, Affiliation, MUCNodes
+
+
+class MUCRoomOptionSerializer(EnumSerializer):
+ enum_class = MUCRoomOption
+
+
+class AllowVisitorPrivateMessageSerializer(EnumSerializer):
+ enum_class = AllowVisitorPrivateMessage
+
+
+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
new file mode 100644
index 0000000..96c751b
--- /dev/null
+++ b/ejabberd_python3d/serializers.py
@@ -0,0 +1,106 @@
+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 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):
+ 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(StringSerializer):
+ @abstractmethod
+ 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)))
+
+
+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
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/requirements.txt b/requirements.txt
new file mode 100644
index 0000000..6fa36df
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1,4 @@
+coverage>=5.3
+Django>=3.0
+requests>=2.24.0
+urllib3>=1.25.11
diff --git a/scripts/extract_todos.py b/scripts/extract_todos.py
new file mode 100644
index 0000000..f2311ae
--- /dev/null
+++ b/scripts/extract_todos.py
@@ -0,0 +1,55 @@
+import os
+
+ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
+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')
+ return f
+
+
+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):
+ if os.path.isdir(dir):
+ 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)
+ _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)
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..5b0d68b
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,49 @@
+from glob import glob
+from os.path import basename, splitext
+
+import setuptools
+from pip._internal.req import parse_requirements
+
+__version__ = "0.2.3"
+
+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(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",
+ "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={
+ }
+)
diff --git a/tests/__init__.py b/tests/__init__.py
new file mode 100644
index 0000000..e69de29