diff --git a/requirements.txt b/requirements.txt
index 9c96191..3d6982a 100755
--- a/requirements.txt
+++ b/requirements.txt
@@ -6,10 +6,11 @@ python-telegram-bot==13.1
psycopg2-binary
feedparser
toml
-googletrans
+googletrans==4.0.0-rc1
pillow
alphabet-detector
flask
flask-restplus
-Werkzeug==0.16.1
-flask-httpauth
\ No newline at end of file
+Werkzeug==1.0.1
+flask-httpauth
+schedule
\ No newline at end of file
diff --git a/tg_bot/__init__.py b/tg_bot/__init__.py
index b82f650..9577eb8 100755
--- a/tg_bot/__init__.py
+++ b/tg_bot/__init__.py
@@ -22,12 +22,12 @@
import telegram.ext as tg
from tg_bot.strings.string_helper import get_string
+VERSION = 2.1
+
# Module name
module = "init"
# enable logging
-
-
LOGGER = logging.getLogger(__name__)
ENV = bool(os.environ.get('ENV', False))
@@ -49,7 +49,11 @@
format=LOGFORMAT,
level=logging.INFO)
-
+LOGGER.info(f"Nemesis v{VERSION}\n"
+ f"This program is free software: you can redistribute it and/or modify\n"
+ f"it under the terms of the GNU General Public License as published by\n"
+ f"the Free Software Foundation, either version 3 of the License, or\n"
+ f"(at your option) any later version.")
# if version < 3.6, stop bot.
if sys.version_info[0] < 3 or sys.version_info[1] < 6:
LOGGER.error(get_string(module, "ERR_INVALID_PYTHON_VERSION", DEFAULT_LANG)) # ERR_INVALID_PYTHON_VERSION
@@ -152,7 +156,6 @@
SUDO_USERS.add(OWNER_ID)
SUDO_USERS.add(CO_OWNER_ID)
LOGGER.info("Owner: %s", OWNER_ID )
-LOGGER.info("Co-Owner: %s", CO_OWNER_ID )
updater = tg.Updater(TOKEN, workers=WORKERS, use_context=True)
diff --git a/tg_bot/__main__.py b/tg_bot/__main__.py
index c5aaf4b..803b055 100644
--- a/tg_bot/__main__.py
+++ b/tg_bot/__main__.py
@@ -30,7 +30,7 @@
from telegram.utils.helpers import escape_markdown
from tg_bot import dispatcher, updater, TOKEN, WEBHOOK, OWNER_ID, DONATION_LINK, CERT_PATH, PORT, URL, LOGGER, \
- ALLOW_EXCL, DEFAULT_LANG
+ ALLOW_EXCL, DEFAULT_LANG, VERSION
from tg_bot.strings.string_helper import get_string
@@ -368,6 +368,7 @@ def get_settings(update: Update, context: CallbackContext):
else:
send_settings(chat.id, user.id, True)
+
def migrate_chats(update: Update, context: CallbackContext):
msg = update.effective_message # type: Optional[Message]
if msg.migrate_to_chat_id:
@@ -412,7 +413,7 @@ def about(update: Update, context: CallbackContext):
" - [Severus Snape](https://t.me/GenosseSeverus) - Co-Owner\n" \
" - [Luna Loony](https://t.me/Luna_loony) - Admin\n"
- update.effective_message.reply_text("*Nemesis - Powerful open-source group manager*\n"
+ update.effective_message.reply_text("*Nemesis v{} - Powerful open-source group manager*\n"
"Copyright (C) 2017 - 2019 Paul Larsen\n"
"Copyright (C) 2019 - 2020 KaratekHD\n\n"
"This program is free software: you can redistribute it and/or modify "
@@ -428,7 +429,7 @@ def about(update: Update, context: CallbackContext):
"*Translation*\n"
"{}"
"*Production*\n"
- "{}".format(DEVELOPMENT, TRANSLATION, PRODUCTION), parse_mode=ParseMode.MARKDOWN)
+ "{}".format(VERSION, DEVELOPMENT, TRANSLATION, PRODUCTION), parse_mode=ParseMode.MARKDOWN)
def load_api():
diff --git a/tg_bot/modules/admin.py b/tg_bot/modules/admin.py
index 2c5f159..3008ae9 100755
--- a/tg_bot/modules/admin.py
+++ b/tg_bot/modules/admin.py
@@ -53,7 +53,6 @@ def toggle_mute(update: Update, context: CallbackContext) -> str:
@user_not_admin
def on_message(update: Update, context: CallbackContext):
- LOGGER.debug("Yeet!")
if mute_sql.get_muted(update.effective_chat.id):
update.effective_message.delete()
diff --git a/tg_bot/modules/reputations.py b/tg_bot/modules/reputations.py
index e8ea046..652e6fb 100644
--- a/tg_bot/modules/reputations.py
+++ b/tg_bot/modules/reputations.py
@@ -133,10 +133,18 @@ def __user_settings__(user_id):
INCREASE_MESSAGE_HANDLER = DisableAbleMessageHandler(Filters.regex(r"^\+$"), increase, friendly="increase",
run_async=True)
+
dispatcher.add_handler(INCREASE_MESSAGE_HANDLER)
-INCREASE_MESSAGE_HANDLER = DisableAbleMessageHandler(Filters.regex(r"^\-$"), decrease, friendly="decrease",
+DECREASE_MESSAGE_HANDLER = DisableAbleMessageHandler(Filters.regex(r"^\-$"), decrease, friendly="decrease",
run_async=True)
-dispatcher.add_handler(INCREASE_MESSAGE_HANDLER)
+dispatcher.add_handler(DECREASE_MESSAGE_HANDLER)
+INCREASE_MESSAGE_HANDLER2 = DisableAbleMessageHandler(Filters.regex(r"^\👍$"), increase, friendly="increase",
+ run_async=True)
+
+dispatcher.add_handler(INCREASE_MESSAGE_HANDLER2)
+DECREASE_MESSAGE_HANDLER2 = DisableAbleMessageHandler(Filters.regex(r"^\👎$"), decrease, friendly="decrease",
+ run_async=True)
+dispatcher.add_handler(DECREASE_MESSAGE_HANDLER2)
SETTINGS_HANDLER = CommandHandler("reputation", reputation, run_async=True)
dispatcher.add_handler(SETTINGS_HANDLER)
diff --git a/tg_bot/modules/sql/api_sql.py b/tg_bot/modules/sql/api_sql.py
index 54812df..c870f37 100644
--- a/tg_bot/modules/sql/api_sql.py
+++ b/tg_bot/modules/sql/api_sql.py
@@ -15,10 +15,8 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
-import threading
from sqlalchemy import Column, String, UnicodeText, func, distinct, Integer
-from werkzeug.security import generate_password_hash, check_password_hash
from tg_bot.modules.sql import SESSION, BASE
@@ -37,6 +35,7 @@ def __repr__(self):
Api.__table__.create(checkfirst=True)
+
def set_key(user_id, key):
user = SESSION.query(Api).get(user_id)
if not user:
@@ -49,10 +48,10 @@ def set_key(user_id, key):
def verify_key(key):
- hash = SESSION.query(Api).get(Api.key == str(key))
- ret = "null"
- if hash:
- ret = hash.user_id
+ result = SESSION.query(Api).filter(Api.key == str(key)).scalar()
+ ret = None
+ if result:
+ ret = result.user_id
SESSION.close()
return ret
diff --git a/tg_bot/modules/sql/users_sql.py b/tg_bot/modules/sql/users_sql.py
index b664996..d98c582 100755
--- a/tg_bot/modules/sql/users_sql.py
+++ b/tg_bot/modules/sql/users_sql.py
@@ -88,6 +88,13 @@ def ensure_bot_in_db():
SESSION.commit()
+def get_chatname(chat_id):
+ try:
+ return SESSION.query(Chats).get(str(chat_id)).chat_name
+ finally:
+ SESSION.close()
+
+
def update_user(user_id, username, chat_id=None, chat_name=None):
with INSERTION_LOCK:
user = SESSION.query(Users).get(user_id)
@@ -141,6 +148,13 @@ def get_chat_members(chat_id):
SESSION.close()
+def get_chats_by_member(user_id):
+ try:
+ return SESSION.query(ChatMembers).filter(ChatMembers.user == str(user_id)).all()
+ finally:
+ SESSION.close()
+
+
def get_all_chats():
try:
return SESSION.query(Chats).all()
@@ -148,6 +162,13 @@ def get_all_chats():
SESSION.close()
+def get_all_users():
+ try:
+ return SESSION.query(Users).all()
+ finally:
+ SESSION.close()
+
+
def get_user_num_chats(user_id):
try:
return SESSION.query(ChatMembers).filter(ChatMembers.user == int(user_id)).count()
diff --git a/tg_bot/modules/stickers.py b/tg_bot/modules/stickers.py
index 43951fc..c5b8df7 100644
--- a/tg_bot/modules/stickers.py
+++ b/tg_bot/modules/stickers.py
@@ -170,6 +170,9 @@ def kang(update, context):
print(e)
else:
+ update.effective_message.reply_text("Kanging animated stickers is not supported, see"
+ " [#57](https://github.com/KaratekHD/Nemesis/issues/57)", parse_mode=ParseMode.MARKDOWN)
+ return
packname = "animated" + str(user.id) + "_by_" + context.bot.username
packname_found = 0
max_stickers = 50
@@ -304,7 +307,7 @@ def kang(update, context):
elif e.message == "Invalid sticker emojis":
msg.reply_text("Invalid emoji(s).")
elif e.message == "Stickers_too_much":
- msg.reply_text("Max packsize reached. Press F to pay respecc.")
+ msg.reply_text("Max packsize reached. Press F to pay respect.")
elif e.message == "Internal Server Error: sticker set not found (500)":
msg.reply_text(
"Sticker successfully added to [pack](t.me/addstickers/%s)"
@@ -466,6 +469,8 @@ def __help__(update: Update) -> str:
"""
__mod_name__ = "Stickers"
+
+
KANG_HANDLER = DisableAbleCommandHandler("kang", kang, pass_args=True, admin_ok=True, run_async=True)
STICKERID_HANDLER = DisableAbleCommandHandler("stickerid", stickerid, run_async=True)
GETSTICKER_HANDLER = DisableAbleCommandHandler("getsticker", getsticker, run_async=True)
diff --git a/tg_bot/restapi/__init__.py b/tg_bot/restapi/__init__.py
index e0b773a..2ee6196 100644
--- a/tg_bot/restapi/__init__.py
+++ b/tg_bot/restapi/__init__.py
@@ -19,9 +19,13 @@
from flask_restplus import Api
from tg_bot.restapi.resources.basic import basic_api
from tg_bot.restapi.resources.chats import chats_api
+import tg_bot.restapi.resources.management as management
+import tg_bot.restapi.resources.modules.admin as admin
app = Flask("Nemesis Telegram Bot")
api = Api(app, version="2.0 Development Preview 1", title="Nemesis Telegram Bot")
api.add_namespace(basic_api)
api.add_namespace(chats_api)
+api.add_namespace(management.api)
+api.add_namespace(admin.api)
\ No newline at end of file
diff --git a/tg_bot/restapi/auth.py b/tg_bot/restapi/auth.py
index be1c477..9287a2e 100644
--- a/tg_bot/restapi/auth.py
+++ b/tg_bot/restapi/auth.py
@@ -14,32 +14,9 @@
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
-from functools import wraps
-
-from flask import request, abort
import tg_bot.modules.sql.api_sql as sql
-def token_required(view_function):
- @wraps(view_function)
- # the new, post-decoration function. Note *args and **kwargs here.
- def decorated_function(*args, **kwargs):
- if request.args.get('key') and request.args.get('key') == "test":
- return view_function(*args, **kwargs)
- else:
- abort(401)
- return decorated_function
-
-def authenticate(username, password):
- if username and password:
- key = sql.get_key(username)
- if key != "null":
- if password is key:
- return True
- else:
- return False
- else:
- return False
- else:
- return False
\ No newline at end of file
+def verify_auth_token(token):
+ sql.verify_key(token)
diff --git a/tg_bot/restapi/models/chats.py b/tg_bot/restapi/models/chats.py
new file mode 100644
index 0000000..c1e3f93
--- /dev/null
+++ b/tg_bot/restapi/models/chats.py
@@ -0,0 +1,25 @@
+# Nemesis - Powerful Telegram group managment bot
+# Copyright (C) 2017 - 2019 Paul Larsen
+# Copyright (C) 2019 - 2020 KaratekHD
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+from flask_restplus import fields
+
+
+def create_chat_model(api):
+ model = api.model('Chats', {
+ 'id': fields.String(description="unique group identifier", required=True),
+ 'name': fields.String(description="group name", required=True)
+ })
+ return model
diff --git a/tg_bot/restapi/models/management.py b/tg_bot/restapi/models/management.py
new file mode 100644
index 0000000..4b56ec3
--- /dev/null
+++ b/tg_bot/restapi/models/management.py
@@ -0,0 +1,64 @@
+# Nemesis - Powerful Telegram group managment bot
+# Copyright (C) 2017 - 2019 Paul Larsen
+# Copyright (C) 2019 - 2020 KaratekHD
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+from flask_restplus import fields
+
+from tg_bot.restapi.models.users import create_user_model
+
+
+def create_chatnumber_model(api):
+ model = api.model('Total Chats', {
+ 'number': fields.Integer(description="Number of group the bot has registered.", required=True)
+ })
+ return model
+
+
+def create_countusers_model(api):
+ model = api.model('Total Users', {
+ 'number': fields.Integer(description="Number of users the bot has registered.", required=True)
+ })
+ return model
+
+
+def create_chat_model(api):
+ model = api.model('Chat', {
+ 'id': fields.Integer(description="Telegram ID of chat", required=True),
+ 'name': fields.String(description="Name of the group", required=True)
+ })
+ return model
+
+
+def create_chatlist_model(api):
+ model = api.model('Chats', {
+ 'chats': fields.Nested(create_chat_model(api), description="list of chats", required=True)
+ })
+ return model
+
+
+def create_broadcast_model(api):
+ model = api.model("Broadcast", {
+ 'message': fields.String(description="The message that was broadcasted.", required=True),
+ "failed": fields.Integer(description="Number of chats that failed receiving the broadcast.", required=True),
+ "failed_chats": fields.Nested(create_chat_model(api), description="Chats that failed receiving the broadcast.", required=False)
+ })
+ return model
+
+
+def create_userlist_model(api):
+ model = api.model('Users', {
+ 'users': fields.Nested(create_user_model(api), description="list of users", required=True)
+ })
+ return model
\ No newline at end of file
diff --git a/tg_bot/restapi/models/modules/__init__.py b/tg_bot/restapi/models/modules/__init__.py
new file mode 100644
index 0000000..1e15628
--- /dev/null
+++ b/tg_bot/restapi/models/modules/__init__.py
@@ -0,0 +1,17 @@
+# Nemesis - Powerful Telegram group managment bot
+# Copyright (C) 2017 - 2019 Paul Larsen
+# Copyright (C) 2019 - 2020 KaratekHD
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+
diff --git a/tg_bot/restapi/models/modules/admin.py b/tg_bot/restapi/models/modules/admin.py
new file mode 100644
index 0000000..1420b75
--- /dev/null
+++ b/tg_bot/restapi/models/modules/admin.py
@@ -0,0 +1,31 @@
+# Nemesis - Powerful Telegram group managment bot
+# Copyright (C) 2017 - 2019 Paul Larsen
+# Copyright (C) 2019 - 2020 KaratekHD
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+
+from flask_restplus import fields
+
+from tg_bot.restapi.models import users
+
+
+def create_chat_model(api):
+ model = api.model("Chat", {
+ "id": fields.Integer(description="Telegram ID of group", required=True),
+ "name": fields.String(description="Name of group", required=True),
+ "is_muted": fields.Boolean(description="Global mute status", required=True),
+ # "link": fields.String(description="Convenience property. If username is available, returns a t.me link of the user.", required=False),
+ "admins": fields.Nested(users.create_user_model(api), description="Administrators", required=False)
+ })
+ return model
\ No newline at end of file
diff --git a/tg_bot/restapi/models/users.py b/tg_bot/restapi/models/users.py
new file mode 100644
index 0000000..49f09f3
--- /dev/null
+++ b/tg_bot/restapi/models/users.py
@@ -0,0 +1,31 @@
+# Nemesis - Powerful Telegram group managment bot
+# Copyright (C) 2017 - 2019 Paul Larsen
+# Copyright (C) 2019 - 2020 KaratekHD
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+from flask_restplus import fields
+
+from tg_bot.restapi.models.chats import create_chat_model
+
+
+def create_user_model(api):
+ model = api.model("User", {
+ "id": fields.Integer(description="Telegram ID of user", required=True),
+ "first_name": fields.String(description="First name of user", required=True),
+ "last_name": fields.String(description="Last name of user", required=False),
+ "username": fields.String(description="Username", required=False),
+ "link": fields.String(description="Convenience property. If username is available, returns a t.me link of the user.", required=False),
+ "groups": fields.Nested(create_chat_model(api), description="All chats the user is part of.", required=False)
+ })
+ return model
\ No newline at end of file
diff --git a/tg_bot/restapi/resources/chats.py b/tg_bot/restapi/resources/chats.py
index 1622293..596d268 100644
--- a/tg_bot/restapi/resources/chats.py
+++ b/tg_bot/restapi/resources/chats.py
@@ -14,22 +14,45 @@
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
+import json
from http import HTTPStatus
-from flask import request, jsonify
-from flask_httpauth import HTTPBasicAuth
+from flask import request, jsonify, abort
from flask_restplus import Namespace, Resource
+from telegram import TelegramError
+
import tg_bot.modules.sql.api_sql as sql
-from tg_bot import LOGGER
+import tg_bot.modules.sql.users_sql as user_sql
+from tg_bot import dispatcher
+from tg_bot.restapi.models.chats import create_chat_model
chats_api = Namespace("chats", description="Gather information about chats you have access to.")
+chat = create_chat_model(chats_api)
+
-@chats_api.route("")
-class List(Resource):
- def get(self):
+@chats_api.route("/")
+@chats_api.param('id', 'The group identifier')
+@chats_api.response(404, 'Chat not found.')
+@chats_api.response(401, "Unauthorized")
+@chats_api.response(410, "Bot is not a member of the chat (anymore).")
+class Chats(Resource):
+ @chats_api.marshal_with(chat)
+ def get(self, id):
+ '''Gets a chat by id'''
key = request.args.get('api_key')
- test = sql.verify_key(key)
- LOGGER.debug(test)
- '''Get All chats you have access to.'''
- return "Nemesis Telegram Bot v2.0 Development Preview 1", HTTPStatus.OK
+ if not key:
+ abort(HTTPStatus.UNAUTHORIZED, "Unauthorized")
+ user_id = sql.verify_key(key)
+ chats = []
+ for element in user_sql.get_chats_by_member(user_id):
+ chats.append(element.chat)
+ if id not in chats:
+ abort(HTTPStatus.NOT_FOUND, "Chat not found.")
+ else:
+ try:
+ name = dispatcher.bot.get_chat(id).title
+ except TelegramError as excp:
+ if excp.message == 'Chat not found':
+ abort(HTTPStatus.GONE, "Bot is not a member of the chat (anymore).")
+ return {"id": id, "name": name}
diff --git a/tg_bot/restapi/resources/management.py b/tg_bot/restapi/resources/management.py
new file mode 100644
index 0000000..c601d93
--- /dev/null
+++ b/tg_bot/restapi/resources/management.py
@@ -0,0 +1,266 @@
+# Nemesis - Powerful Telegram group management bot
+# Copyright (C) 2017 - 2019 Paul Larsen
+# Copyright (C) 2019 - 2020 KaratekHD
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+
+from time import sleep
+
+from flask import request, abort
+from flask_restplus import Namespace, Resource
+from flask_restplus._http import HTTPStatus
+from telegram import TelegramError
+
+from tg_bot import OWNER_ID, CO_OWNER_ID, SUDO_USERS, dispatcher, LOGGER
+import tg_bot.modules.sql.api_sql as sql
+from tg_bot.restapi.models.management import *
+from tg_bot.restapi.models.users import *
+from tg_bot.modules.sql.users_sql import num_chats, get_all_chats, get_all_users, num_users, get_chats_by_member, get_chatname, update_user
+
+api = Namespace("management", description="Administrative Access")
+
+chatnumber = create_chatnumber_model(api)
+
+
+@api.route("/countchats")
+@api.response(200, "Number attached")
+@api.response(403, "User is not bot admin.")
+@api.response(401, "Unauthorized")
+class NumChats(Resource):
+ @api.marshal_with(chatnumber)
+ def get(self):
+ '''Count all chats from database'''
+ key = request.args.get('api_key')
+ if not key:
+ abort(HTTPStatus.UNAUTHORIZED, "Unauthorized")
+ return
+ user_id = sql.verify_key(key)
+ isowner = user_id is OWNER_ID
+ iscowoner = user_id is CO_OWNER_ID
+ isudo = user_id in SUDO_USERS
+ isadmin = isowner or iscowoner or isudo
+ if not isadmin:
+ abort(HTTPStatus.FORBIDDEN, "User is not bot admin.")
+ else:
+ data = {'number': num_chats()}
+ return data, HTTPStatus.OK
+
+
+listchats = create_chatlist_model(api)
+
+
+@api.route("/listchats")
+@api.response(200, "List attached")
+@api.response(403, "User is not bot admin.")
+@api.response(401, "Unauthorized")
+class ListChats(Resource):
+ @api.marshal_with(listchats)
+ def get(self):
+ '''List all chats from database'''
+ key = request.args.get('api_key')
+ if not key:
+ abort(HTTPStatus.UNAUTHORIZED, "Unauthorized")
+ return
+ user_id = sql.verify_key(key)
+ if user_id is None:
+ abort(HTTPStatus.UNAUTHORIZED, "Invalid Key")
+ isowner = user_id is OWNER_ID
+ iscowoner = user_id is CO_OWNER_ID
+ isudo = user_id in SUDO_USERS
+ isadmin = isowner or iscowoner or isudo
+ if not isadmin:
+ abort(HTTPStatus.FORBIDDEN, "User is not bot admin.")
+ else:
+ chats_sql = get_all_chats()
+ chats = []
+ for element in chats_sql:
+ chats.append([{"id": element.chat_id, "name": element.chat_name}])
+ return {"chats": chats}
+
+
+broadcast = create_broadcast_model(api)
+
+
+@api.route("/broadcast")
+@api.response(200, "Broadcast completed")
+@api.response(403, "User is not bot admin.")
+@api.response(401, "Unauthorized")
+@api.response(400, "Bad Request")
+class Broadcast(Resource):
+ @api.marshal_with(broadcast)
+ def post(self):
+ '''Send a message to all chats'''
+ key = request.args.get('api_key')
+ if not key:
+ abort(HTTPStatus.UNAUTHORIZED, "Unauthorized")
+ return
+ user_id = sql.verify_key(key)
+ if user_id is None:
+ abort(HTTPStatus.UNAUTHORIZED, "Invalid Key")
+ isowner = user_id is OWNER_ID
+ iscowoner = user_id is CO_OWNER_ID
+ isudo = user_id in SUDO_USERS
+ isadmin = isowner or iscowoner or isudo
+ if not isadmin:
+ abort(HTTPStatus.FORBIDDEN, "User is not bot admin.")
+ else:
+ data = request.get_json()
+ try:
+ to_send = data["message"]
+ except KeyError:
+ abort(HTTPStatus.BAD_REQUEST, "Bad Request")
+ bot = dispatcher.bot
+ failed_chats = []
+ if len(to_send) >= 2:
+ chats = get_all_chats() or []
+ failed = 0
+ for chat in chats:
+ try:
+ bot.sendMessage(int(chat.chat_id), to_send)
+ sleep(0.1)
+ except TelegramError:
+ failed += 1
+ failed_chats.append([{"id": chat.chat_id, "name": chat.chat_name}])
+ LOGGER.warning("Couldn't send broadcast to %s, group name %s", str(chat.chat_id),
+ str(chat.chat_name))
+ if failed_chats == 0:
+ return {"message": to_send, "failed": failed}
+ else:
+ return {"message": to_send, "failed": failed, "failed_chats": failed_chats}
+
+
+@api.route("/listusers")
+@api.response(403, "User is not bot admin.")
+@api.response(401, "Unauthorized")
+@api.response(400, "Bad Request")
+class ListUsers(Resource):
+ @api.marshal_with(create_userlist_model(api))
+ def get(self):
+ '''Get a list of all users known to the bot. Note that this does only include people who messaged the bot at least once.'''
+ key = request.args.get('api_key')
+ if not key:
+ abort(HTTPStatus.UNAUTHORIZED, "Unauthorized")
+ return
+ user_id = sql.verify_key(key)
+ if user_id is None:
+ abort(HTTPStatus.UNAUTHORIZED, "Invalid Key")
+ isowner = user_id is OWNER_ID
+ iscowoner = user_id is CO_OWNER_ID
+ isudo = user_id in SUDO_USERS
+ isadmin = isowner or iscowoner or isudo
+ if not isadmin:
+ abort(HTTPStatus.FORBIDDEN, "User is not bot admin.")
+ else:
+ users = get_all_users()
+ result = []
+ list_groups_str = request.args.get('list_groups')
+ if list_groups_str:
+ if list_groups_str == "True" or list_groups_str == "true":
+ list_groups = True
+ if list_groups_str == "False" or list_groups_str == "false":
+ list_groups = False
+ if list_groups_str.lower() != "false" and list_groups_str.lower() != "true":
+ abort(HTTPStatus.BAD_REQUEST, "'list_groups' is invalid.")
+ else:
+ list_groups = False
+ for user in users:
+ data = {}
+ try:
+ data.update({"id": user.user_id})
+ chat = dispatcher.bot.get_chat(user.user_id)
+ if chat.username:
+ data.update({"username": chat.username})
+ data.update({"link": chat.link})
+ data.update({"first_name": chat.first_name})
+ if chat.last_name:
+ data.update({"last_name": chat.last_name})
+
+ if list_groups:
+ groups = []
+ for group in get_chats_by_member(chat.id):
+ groups.append([{"id": group.chat, "name": get_chatname(group.chat)}])
+ data.update({"groups": groups})
+
+ result.append(data)
+ except TelegramError as excp:
+ if excp.message == 'Chat not found':
+ pass
+
+
+ return {"users": result}, HTTPStatus.OK
+
+
+@api.route("/countusers")
+@api.response(403, "User is not bot admin.")
+@api.response(401, "Unauthorized")
+@api.response(400, "Bad Request")
+class CountUsers(Resource):
+ @api.marshal_with(create_countusers_model(api))
+ def get(self):
+ '''Count all users from database'''
+ key = request.args.get('api_key')
+ if not key:
+ abort(HTTPStatus.UNAUTHORIZED, "Unauthorized")
+ return
+ user_id = sql.verify_key(key)
+ isowner = user_id is OWNER_ID
+ iscowoner = user_id is CO_OWNER_ID
+ isudo = user_id in SUDO_USERS
+ isadmin = isowner or iscowoner or isudo
+ if not isadmin:
+ abort(HTTPStatus.FORBIDDEN, "User is not bot admin.")
+ else:
+ data = {'number': num_users()}
+ return data, HTTPStatus.OK
+
+
+@api.route("/updateuser")
+@api.response(403, "User is not bot admin.")
+@api.response(401, "Unauthorized")
+@api.response(400, "Bad Request")
+class UpdateUser(Resource):
+ def post(self):
+ '''Update a user inside the db. Use at your own risk!'''
+ key = request.args.get('api_key')
+ if not key:
+ abort(HTTPStatus.UNAUTHORIZED, "Unauthorized")
+ return
+ user_id = sql.verify_key(key)
+ if user_id is None:
+ abort(HTTPStatus.UNAUTHORIZED, "Invalid Key")
+ isowner = user_id is OWNER_ID
+ iscowoner = user_id is CO_OWNER_ID
+ isudo = user_id in SUDO_USERS
+ isadmin = isowner or iscowoner or isudo
+ if not isadmin:
+ abort(HTTPStatus.FORBIDDEN, "User is not bot admin.")
+ else:
+ data = request.get_json()
+ try:
+ tochange = int(data["user_id"])
+ except KeyError:
+ abort(HTTPStatus.BAD_REQUEST, "Bad Request")
+ try:
+ chat_id = str(data["chat_id"])
+ except KeyError:
+ chat_id = None
+ pass
+ try:
+ username = str(data["username"])
+ except KeyError:
+ username = None
+ pass
+
+ update_user(tochange, username, chat_id)
+
diff --git a/tg_bot/restapi/resources/modules/__init__.py b/tg_bot/restapi/resources/modules/__init__.py
new file mode 100644
index 0000000..1e15628
--- /dev/null
+++ b/tg_bot/restapi/resources/modules/__init__.py
@@ -0,0 +1,17 @@
+# Nemesis - Powerful Telegram group managment bot
+# Copyright (C) 2017 - 2019 Paul Larsen
+# Copyright (C) 2019 - 2020 KaratekHD
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+
diff --git a/tg_bot/restapi/resources/modules/admin.py b/tg_bot/restapi/resources/modules/admin.py
new file mode 100644
index 0000000..f1be8aa
--- /dev/null
+++ b/tg_bot/restapi/resources/modules/admin.py
@@ -0,0 +1,96 @@
+# Nemesis - Powerful Telegram group managment bot
+# Copyright (C) 2017 - 2019 Paul Larsen
+# Copyright (C) 2019 - 2020 KaratekHD
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+
+from http import HTTPStatus
+
+from flask import request, abort
+from flask_restplus import Namespace, Resource
+from telegram import TelegramError
+
+from tg_bot import dispatcher
+from tg_bot.modules.sql import api_sql
+import tg_bot.modules.sql.users_sql as user_sql
+import tg_bot.modules.sql.mute as mute_sql
+import tg_bot.restapi.models.modules.admin as admin_model
+
+api = Namespace("admin", description="Functions designed to manage basic things inside a group.")
+
+
+@api.route("/")
+@api.param('id', 'The group identifier')
+@api.response(400, "Bad Request")
+@api.response(404, 'Chat not found.')
+@api.response(403, "User is not permitted to view this chat.")
+@api.response(401, "Unauthorized")
+@api.response(410, "Bot is not a member of the chat (anymore).")
+class Admins(Resource):
+ @api.marshal_with(admin_model.create_chat_model(api))
+ def get(self, id):
+ key = request.args.get('api_key')
+ if not key:
+ abort(HTTPStatus.UNAUTHORIZED, "Unauthorized")
+ user_id = api_sql.verify_key(key)
+ chats = []
+ for element in user_sql.get_chats_by_member(user_id):
+ chats.append(element.chat)
+ if id not in chats:
+ abort(HTTPStatus.NOT_FOUND, "Chat not found.")
+ user_chats = []
+ for element in user_sql.get_chats_by_member(user_id):
+ user_chats.append(element.chat)
+ if id not in user_chats:
+ abort(HTTPStatus.FORBIDDEN, "User is not permitted to view this chat.")
+ is_muted = mute_sql.get_muted(id)
+ list_groups_str = request.args.get('list_groups')
+ if list_groups_str:
+ if list_groups_str == "True" or list_groups_str == "true":
+ list_groups = True
+ if list_groups_str == "False" or list_groups_str == "false":
+ list_groups = False
+ if list_groups_str.lower() != "false" and list_groups_str.lower() != "true":
+ abort(HTTPStatus.BAD_REQUEST, "'list_groups' is invalid.")
+ else:
+ list_groups = False
+ try:
+ groupchat = dispatcher.bot.get_chat(id)
+ except TelegramError as excp:
+ if excp.message == 'Chat not found':
+ abort(HTTPStatus.GONE, "Bot is not a member of the chat (anymore).")
+ admins = []
+ for admin in groupchat.get_administrators():
+ data = {}
+ try:
+ data.update({"id": admin.user.id})
+ chat = admin.user
+ if chat.username:
+ data.update({"username": chat.username})
+ data.update({"link": chat.link})
+ data.update({"first_name": chat.first_name})
+ if chat.last_name:
+ data.update({"last_name": chat.last_name})
+
+ if list_groups:
+ groups = []
+ for group in user_sql.get_chats_by_member(chat.id):
+ groups.append([{"id": group.chat, "name": user_sql.get_chatname(group.chat)}])
+ data.update({"groups": groups})
+
+ admins.append(data)
+ except TelegramError as excp:
+ if excp.message == 'Chat not found':
+ pass
+ return {"id": id, "name" : groupchat.title, "is_muted" : is_muted, "admins" : admins}, HTTPStatus.OK