Skip to content

Commit 5ebf846

Browse files
authored
Version 0.1.2 (#4)
* Add documentation * Fix minor bugs
1 parent d5f0231 commit 5ebf846

File tree

10 files changed

+361
-80
lines changed

10 files changed

+361
-80
lines changed

.devcontainer/Dockerfile

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# See here for image contents: https://github.com/microsoft/vscode-dev-containers/tree/v0.145.1/containers/python-3/.devcontainer/base.Dockerfile
2+
3+
# [Choice] Python version: 3, 3.9, 3.8, 3.7, 3.6
4+
ARG VARIANT="3"
5+
FROM mcr.microsoft.com/vscode/devcontainers/python:0-${VARIANT}
6+
7+
# [Option] Install Node.js
8+
ARG INSTALL_NODE="true"
9+
ARG NODE_VERSION="lts/*"
10+
RUN if [ "${INSTALL_NODE}" = "true" ]; then su vscode -c "source /usr/local/share/nvm/nvm.sh && nvm install ${NODE_VERSION} 2>&1"; fi
11+
12+
# [Optional] If your pip requirements rarely change, uncomment this section to add them to the image.
13+
# COPY requirements.txt /tmp/pip-tmp/
14+
# RUN pip3 --disable-pip-version-check --no-cache-dir install -r /tmp/pip-tmp/requirements.txt \
15+
# && rm -rf /tmp/pip-tmp
16+
17+
# [Optional] Uncomment this section to install additional OS packages.
18+
# RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
19+
# && apt-get -y install --no-install-recommends <your-package-list-here>
20+
21+
# [Optional] Uncomment this line to install global node packages.
22+
# RUN su vscode -c "source /usr/local/share/nvm/nvm.sh && npm install -g <your-package-here>" 2>&1

.devcontainer/devcontainer.json

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
{
2+
"name": "Python 3",
3+
"build": {
4+
"dockerfile": "Dockerfile",
5+
"context": "..",
6+
"args": {
7+
// Update 'VARIANT' to pick a Python version: 3, 3.6, 3.7, 3.8, 3.9
8+
"VARIANT": "3",
9+
// Options
10+
"INSTALL_NODE": "false",
11+
"NODE_VERSION": "lts/*"
12+
}
13+
},
14+
15+
// Set *default* container specific settings.json values on container create.
16+
"settings": {
17+
"terminal.integrated.shell.linux": "/bin/bash",
18+
"python.pythonPath": "/usr/local/bin/python",
19+
"python.linting.enabled": true,
20+
"python.linting.pylintEnabled": true,
21+
"python.formatting.autopep8Path": "/usr/local/py-utils/bin/autopep8",
22+
"python.formatting.blackPath": "/usr/local/py-utils/bin/black",
23+
"python.formatting.yapfPath": "/usr/local/py-utils/bin/yapf",
24+
"python.linting.banditPath": "/usr/local/py-utils/bin/bandit",
25+
"python.linting.flake8Path": "/usr/local/py-utils/bin/flake8",
26+
"python.linting.mypyPath": "/usr/local/py-utils/bin/mypy",
27+
"python.linting.pycodestylePath": "/usr/local/py-utils/bin/pycodestyle",
28+
"python.linting.pydocstylePath": "/usr/local/py-utils/bin/pydocstyle",
29+
"python.linting.pylintPath": "/usr/local/py-utils/bin/pylint"
30+
},
31+
32+
// Add the IDs of extensions you want installed when the container is created.
33+
"extensions": [
34+
"ms-python.python"
35+
]
36+
37+
// Use 'forwardPorts' to make a list of ports inside the container available locally.
38+
// "forwardPorts": [],
39+
40+
// Use 'postCreateCommand' to run commands after the container is created.
41+
// "postCreateCommand": "pip3 install --user -r requirements.txt",
42+
43+
// Uncomment to connect as a non-root user. See https://aka.ms/vscode-remote/containers/non-root.
44+
// "remoteUser": "vscode"
45+
}

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ The callback method ( `callback(self, context: telegram.ext.CallbackContext)`) m
3131

3232
### UrlTask
3333
Extension of the `GenericTask` to simplify the usage for jobs calling a URL and returning the link/ response to the subscribers.
34-
It tries until it gets a response every 2 seconds.
34+
It retries every 2 seconds until it gets a response.
3535

3636
This class adds a `url` field where the information lies.
3737

requirements.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
python-telegram-bot==12.2.0
2-
python-dotenv==0.10.3
2+
python-dotenv==0.10.3
3+
requests==2.22.0

setup.cfg

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
[metadata]
2-
description-file = README.md
2+
description-file = README.md
3+
license_files = LICENSE

setup.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,20 @@
22

33
setup(
44
name='telegram-task-bot',
5-
version='0.1.1',
6-
license='LGPLv3',
5+
version='0.1.2',
76
description='Library for writing task based telegram bots',
87
long_description=open('README.md').read(),
98
long_description_content_type='text/markdown',
109
author='bb4L',
1110
author_email='39266013+bb4L@users.noreply.github.com',
12-
project_urls={"Source Code": "https://github.com/bb4L/telegram-task-bot-pip"},
11+
project_urls={
12+
"Source Code": "https://github.com/bb4L/telegram-task-bot-pip"},
1313
packages=['telegramtaskbot', 'telegramtaskbot.Tasks'],
1414
keywords=['Telegram', 'Bot'],
1515
install_requires=[
1616
'python-telegram-bot==12.2.0',
1717
'python-dotenv==0.10.3',
18+
'requests==2.22.0',
1819
],
1920
classifiers=[
2021
'Development Status :: 4 - Beta',
@@ -23,6 +24,8 @@
2324
"License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)",
2425
'Programming Language :: Python :: 3',
2526
'Programming Language :: Python :: 3.6',
27+
'Programming Language :: Python :: 3.7',
28+
'Programming Language :: Python :: 3.8',
2629
],
2730
include_package_data=True,
2831
)

telegramtaskbot/Bot.py

Lines changed: 58 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -6,25 +6,60 @@
66
import telegram
77
from dotenv import load_dotenv
88
from telegram import InlineKeyboardButton, InlineKeyboardMarkup
9-
from telegram.ext import Updater, CommandHandler, Filters, CallbackQueryHandler
9+
from telegram.ext import CallbackQueryHandler, CommandHandler, Filters, Updater
1010

11+
from telegramtaskbot.Tasks.Task import Task
1112
from telegramtaskbot.Tasks.UrlTask import UrlTask
1213

1314

1415
class TelegramTaskBot(object):
16+
"""
17+
A bot that can handle implementations of the different tasks.
18+
19+
Attributes
20+
----------
21+
jobs : List[telegram.ext.Job]
22+
list of the jobs registered
23+
24+
default_button_list : List[InlineKeyboardButton]
25+
list of the default buttons
26+
27+
cmd_fun : dict
28+
dictionary to map a command to a function
29+
30+
logger : Logger
31+
the logger for the button
32+
33+
Methods
34+
----------
35+
36+
get_default_filter() -> telegram.ext.Filters.user
37+
returns a user filter
38+
39+
start(self, update, context) -> None
40+
gives the buttons to start or get tasks
41+
42+
run(self) -> None
43+
Start the bot
44+
45+
handle_button(self, update, context) -> None
46+
Function to handle button clicks
47+
"""
48+
1549
jobs: List[telegram.ext.Job] = []
1650
default_button_list: List[InlineKeyboardButton] = []
1751
cmd_fun = {}
18-
job_names = {}
1952
logger = logging.getLogger(__name__)
2053

21-
def __init__(self, tasks: []):
54+
def __init__(self, tasks: List[Task]) -> None:
2255
load_dotenv()
2356
self.updater = Updater(token=os.getenv('BOT_TOKEN'), use_context=True)
2457
self.dispatcher = self.updater.dispatcher
2558
default_filter = self.get_default_filter()
26-
self.dispatcher.add_handler(CommandHandler('start', self.start, default_filter))
27-
self.dispatcher.add_handler(CallbackQueryHandler(self.handle_button, default_filter))
59+
self.dispatcher.add_handler(CommandHandler(
60+
'start', self.start, default_filter))
61+
self.dispatcher.add_handler(CallbackQueryHandler(
62+
self.handle_button, default_filter))
2863

2964
self.TASKS = [task(self.updater.job_queue) for task in tasks]
3065

@@ -38,13 +73,15 @@ def __init__(self, tasks: []):
3873
self.dispatcher.add_handler(
3974
CommandHandler(task.job_actual_value, task.get_actual_value_cmd, default_filter))
4075

41-
self.dispatcher.add_handler(CommandHandler(task.job_start_name, task.start_command, default_filter))
42-
self.dispatcher.add_handler(CommandHandler(task.job_stop_name, task.stop_command, default_filter))
76+
self.dispatcher.add_handler(CommandHandler(
77+
task.job_start_name, task.start_command, default_filter))
78+
self.dispatcher.add_handler(CommandHandler(
79+
task.job_stop_name, task.stop_command, default_filter))
4380

44-
self.load_from_json()
81+
self._load_from_json()
4582

4683
@staticmethod
47-
def get_default_filter():
84+
def get_default_filter() -> telegram.ext.Filters.user:
4885
str_value = os.getenv('ALLOWED_USERS')
4986
if 'any' in str_value or 'ANY' in str_value:
5087
default_filter = None
@@ -53,44 +90,46 @@ def get_default_filter():
5390
default_filter = Filters.user(user_id=allowed_users)
5491
return default_filter
5592

56-
def start(self, update, context):
57-
reply_markup = InlineKeyboardMarkup(self.build_menu(self.default_button_list, n_cols=1))
93+
def start(self, update, context) -> None:
94+
reply_markup = InlineKeyboardMarkup(
95+
self._build_menu(self.default_button_list, n_cols=1))
5896
context.bot.send_message(chat_id=update.effective_chat.id, text=os.getenv('START_MESSAGE'),
5997
reply_markup=reply_markup)
6098

61-
def run(self):
99+
def run(self) -> None:
62100
self.updater.start_polling(clean=True)
63101

64-
def handle_button(self, update, context):
102+
def handle_button(self, update, context) -> None:
65103
query = update.callback_query
66104
self.cmd_fun.get(query.data)(self.jobs, update, context)
67-
self.save_to_json()
105+
self._save_to_json()
68106
self.logger.info('after save')
69107

70-
def load_from_json(self):
108+
def _load_from_json(self) -> None:
71109
try:
72110
with open('saved_jobs.json') as json_file:
73111
data = json.load(json_file)
74112
for job in data['jobs']:
75113
for task in self.TASKS:
76114
if task.job_name == job['name']:
77-
task._start(self.jobs, self.updater.job_queue, job['context'])
115+
task._start(
116+
self.jobs, self.updater.job_queue, job['context'])
78117
self.logger.info(f'Loaded {len(data["jobs"])} from JSON')
79118
except IOError:
80119
self.logger.info("File not accessible")
81120

82-
def save_to_json(self):
121+
def _save_to_json(self) -> None:
83122
data = {'jobs': []}
84123
for job in self.jobs:
85124
data['jobs'].append({
86125
'context': job.context,
87126
'name': job.name,
88127
})
89-
with open('saved_jobs.json', 'w') as outfile:
128+
with open('saved_jobs.json', 'w+') as outfile:
90129
json.dump(data, outfile)
91130

92131
@staticmethod
93-
def build_menu(buttons, n_cols, header_buttons=None, footer_buttons=None):
132+
def _build_menu(buttons, n_cols, header_buttons=None, footer_buttons=None) -> List[InlineKeyboardButton]:
94133
menu = [buttons[i:i + n_cols] for i in range(0, len(buttons), n_cols)]
95134
if header_buttons:
96135
menu.insert(0, header_buttons)

telegramtaskbot/Tasks/GenericTask.py

Lines changed: 62 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -4,64 +4,106 @@
44

55
import telegram
66
from telegram.ext import JobQueue
7-
87
from telegramtaskbot.Tasks.Task import Task
98

109

1110
class GenericTask(Task):
11+
"""
12+
Basic task users can subscribe to.
13+
14+
Attributes
15+
----------
16+
job_name : str
17+
the name of the task
18+
19+
job_actual_value : str
20+
the string to get the actual value fo the task
21+
22+
generic : bool
23+
defines if the task looks the same for each user
24+
25+
show_subscribe_buttons : bool
26+
if the subscribe and unsubscribe buttons should be shown or not
27+
28+
Methods
29+
----------
30+
callback(self, context: telegram.ext.CallbackContext) -> None
31+
Execute the task and use the context to send the message.
32+
33+
start(self, jobs: List[telegram.ext.Job], update: telegram.Update, context: telegram.ext.CallbackContext) -> None
34+
Starts the task by button
35+
36+
start_command(self, update: telegram.Update, context: telegram.ext.CallbackContext) -> None
37+
Starts the task by command
38+
39+
stop_command(self, update: telegram.Update, context: telegram.ext.CallbackContext) -> None
40+
Stops the task by command
41+
42+
stop(self, jobs: List[telegram.ext.Job], update: telegram.Update, context: telegram.ext.CallbackContext) -> None
43+
Stops the task by button
44+
45+
save_user(self, user: str) -> None
46+
Add the user to the subscriber list
47+
48+
load_users(self) -> List[str]
49+
Get all the subscribed users
50+
"""
51+
52+
job_name: str
1253
job_actual_value: str
1354
generic = True
1455
show_subscribe_buttons = False
1556

16-
def __init__(self, job_queue: JobQueue = None):
57+
def __init__(self, job_queue: JobQueue = None) -> None:
1758
super().__init__()
1859
self.job_actual_value = 'actual_' + self.job_name
1960
self._start([], job_queue, self.job_name)
2061

2162
@abstractmethod
22-
def callback(self, context: telegram.ext.CallbackContext):
63+
def callback(self, context: telegram.ext.CallbackContext) -> None:
2364
pass
2465

25-
def start(self, jobs: List[telegram.ext.Job], update: telegram.Update, context: telegram.ext.CallbackContext):
26-
self.handle_start(context, update.callback_query.message.chat_id)
66+
def start(self, jobs: List[telegram.ext.Job], update: telegram.Update, context: telegram.ext.CallbackContext) -> None:
67+
self._handle_start(context, update.callback_query.message.chat_id)
2768

28-
def start_command(self, update: telegram.Update, context: telegram.ext.CallbackContext):
29-
self.handle_start(context, update.message.chat_id)
69+
def start_command(self, update: telegram.Update, context: telegram.ext.CallbackContext) -> None:
70+
self._handle_start(context, update.message.chat_id)
3071

31-
def handle_start(self, context: telegram.ext.CallbackContext, chat_id: str, with_message=True):
72+
def _handle_start(self, context: telegram.ext.CallbackContext, chat_id: str, with_message: bool = True) -> None:
3273
if with_message:
3374
context.bot.send_message(chat_id=chat_id,
3475
text=f'Thank you for subscribing')
3576
self.save_user(chat_id)
3677
self.logger.debug(f'User {chat_id} subscribed')
3778

38-
def stop(self, jobs: List[telegram.ext.Job], update: telegram.Update, context: telegram.ext.CallbackContext):
39-
self.handle_stop(context, update.callback_query.message.chat_id)
79+
def stop(self, jobs: List[telegram.ext.Job], update: telegram.Update, context: telegram.ext.CallbackContext) -> None:
80+
self._handle_stop(context, update.callback_query.message.chat_id)
4081

41-
def stop_command(self, update: telegram.Update, context: telegram.ext.CallbackContext):
42-
self.handle_stop(context, update.message.chat_id)
82+
def stop_command(self, update: telegram.Update, context: telegram.ext.CallbackContext) -> None:
83+
self._handle_stop(context, update.message.chat_id)
4384

44-
def handle_stop(self, context: telegram.ext.CallbackContext, chat_id: str, with_message=True):
85+
def _handle_stop(self, context: telegram.ext.CallbackContext, chat_id: str, with_message: bool = True) -> None:
4586
users = self.load_users()
4687
users.remove(chat_id)
47-
self.save_to_json(users)
88+
self._save_to_json(users)
4889
self.logger.debug(f'User {chat_id} unsubscribed')
4990
if with_message:
50-
context.bot.send_message(chat_id=chat_id, text=f'You succesfully unsubscribed')
91+
context.bot.send_message(
92+
chat_id=chat_id, text=f'You succesfully unsubscribed')
5193

52-
def save_user(self, user: str):
94+
def save_user(self, user: str) -> None:
5395
users = self.load_users()
5496
users.append(user)
5597
final_users = list(set(users))
56-
self.save_to_json(final_users)
98+
self._save_to_json(final_users)
5799

58-
def save_to_json(self, users):
100+
def _save_to_json(self, users: List[str]) -> None:
59101
data = {'users': users}
60-
with open(self.filename + '.json', 'w') as outfile:
102+
with open(self.filename + '.json', 'w+') as outfile:
61103
json.dump(data, outfile)
62104
self.logger.debug('Saved User')
63105

64-
def load_users(self):
106+
def load_users(self) -> List[str]:
65107
users = []
66108
try:
67109
with open(self.filename + '.json') as json_file:

0 commit comments

Comments
 (0)