Initial commit
This commit is contained in:
commit
7427007520
7 changed files with 837 additions and 0 deletions
246
.gitignore
vendored
Normal file
246
.gitignore
vendored
Normal file
|
@ -0,0 +1,246 @@
|
||||||
|
|
||||||
|
# Created by https://www.gitignore.io/api/vim,macos,emacs,python,sublimetext
|
||||||
|
|
||||||
|
### Emacs ###
|
||||||
|
# -*- mode: gitignore; -*-
|
||||||
|
*~
|
||||||
|
\#*\#
|
||||||
|
/.emacs.desktop
|
||||||
|
/.emacs.desktop.lock
|
||||||
|
*.elc
|
||||||
|
auto-save-list
|
||||||
|
tramp
|
||||||
|
.\#*
|
||||||
|
|
||||||
|
# Org-mode
|
||||||
|
.org-id-locations
|
||||||
|
*_archive
|
||||||
|
|
||||||
|
# flymake-mode
|
||||||
|
*_flymake.*
|
||||||
|
|
||||||
|
# eshell files
|
||||||
|
/eshell/history
|
||||||
|
/eshell/lastdir
|
||||||
|
|
||||||
|
# elpa packages
|
||||||
|
/elpa/
|
||||||
|
|
||||||
|
# reftex files
|
||||||
|
*.rel
|
||||||
|
|
||||||
|
# AUCTeX auto folder
|
||||||
|
/auto/
|
||||||
|
|
||||||
|
# cask packages
|
||||||
|
.cask/
|
||||||
|
dist/
|
||||||
|
|
||||||
|
# Flycheck
|
||||||
|
flycheck_*.el
|
||||||
|
|
||||||
|
# server auth directory
|
||||||
|
/server/
|
||||||
|
|
||||||
|
# projectiles files
|
||||||
|
.projectile
|
||||||
|
projectile-bookmarks.eld
|
||||||
|
|
||||||
|
# directory configuration
|
||||||
|
.dir-locals.el
|
||||||
|
|
||||||
|
# saveplace
|
||||||
|
places
|
||||||
|
|
||||||
|
# url cache
|
||||||
|
url/cache/
|
||||||
|
|
||||||
|
# cedet
|
||||||
|
ede-projects.el
|
||||||
|
|
||||||
|
# smex
|
||||||
|
smex-items
|
||||||
|
|
||||||
|
# company-statistics
|
||||||
|
company-statistics-cache.el
|
||||||
|
|
||||||
|
# anaconda-mode
|
||||||
|
anaconda-mode/
|
||||||
|
|
||||||
|
### macOS ###
|
||||||
|
*.DS_Store
|
||||||
|
.AppleDouble
|
||||||
|
.LSOverride
|
||||||
|
|
||||||
|
# Icon must end with two \r
|
||||||
|
Icon
|
||||||
|
|
||||||
|
# Thumbnails
|
||||||
|
._*
|
||||||
|
|
||||||
|
# Files that might appear in the root of a volume
|
||||||
|
.DocumentRevisions-V100
|
||||||
|
.fseventsd
|
||||||
|
.Spotlight-V100
|
||||||
|
.TemporaryItems
|
||||||
|
.Trashes
|
||||||
|
.VolumeIcon.icns
|
||||||
|
.com.apple.timemachine.donotpresent
|
||||||
|
|
||||||
|
# Directories potentially created on remote AFP share
|
||||||
|
.AppleDB
|
||||||
|
.AppleDesktop
|
||||||
|
Network Trash Folder
|
||||||
|
Temporary Items
|
||||||
|
.apdisk
|
||||||
|
|
||||||
|
### Python ###
|
||||||
|
# Byte-compiled / optimized / DLL files
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*$py.class
|
||||||
|
|
||||||
|
# C extensions
|
||||||
|
*.so
|
||||||
|
|
||||||
|
# Distribution / packaging
|
||||||
|
.Python
|
||||||
|
build/
|
||||||
|
develop-eggs/
|
||||||
|
downloads/
|
||||||
|
eggs/
|
||||||
|
.eggs/
|
||||||
|
lib/
|
||||||
|
lib64/
|
||||||
|
parts/
|
||||||
|
sdist/
|
||||||
|
var/
|
||||||
|
wheels/
|
||||||
|
*.egg-info/
|
||||||
|
.installed.cfg
|
||||||
|
*.egg
|
||||||
|
|
||||||
|
# PyInstaller
|
||||||
|
# Usually these files are written by a python script from a template
|
||||||
|
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||||
|
*.manifest
|
||||||
|
*.spec
|
||||||
|
|
||||||
|
# Installer logs
|
||||||
|
pip-log.txt
|
||||||
|
pip-delete-this-directory.txt
|
||||||
|
|
||||||
|
# Unit test / coverage reports
|
||||||
|
htmlcov/
|
||||||
|
.tox/
|
||||||
|
.coverage
|
||||||
|
.coverage.*
|
||||||
|
.cache
|
||||||
|
nosetests.xml
|
||||||
|
coverage.xml
|
||||||
|
*.cover
|
||||||
|
.hypothesis/
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
*.mo
|
||||||
|
*.pot
|
||||||
|
|
||||||
|
# Django stuff:
|
||||||
|
*.log
|
||||||
|
local_settings.py
|
||||||
|
|
||||||
|
# Flask stuff:
|
||||||
|
instance/
|
||||||
|
.webassets-cache
|
||||||
|
|
||||||
|
# Scrapy stuff:
|
||||||
|
.scrapy
|
||||||
|
|
||||||
|
# Sphinx documentation
|
||||||
|
docs/_build/
|
||||||
|
|
||||||
|
# PyBuilder
|
||||||
|
target/
|
||||||
|
|
||||||
|
# Jupyter Notebook
|
||||||
|
.ipynb_checkpoints
|
||||||
|
|
||||||
|
# pyenv
|
||||||
|
.python-version
|
||||||
|
|
||||||
|
# celery beat schedule file
|
||||||
|
celerybeat-schedule
|
||||||
|
|
||||||
|
# SageMath parsed files
|
||||||
|
*.sage.py
|
||||||
|
|
||||||
|
# Environments
|
||||||
|
.env
|
||||||
|
.venv
|
||||||
|
env/
|
||||||
|
venv/
|
||||||
|
ENV/
|
||||||
|
env.bak/
|
||||||
|
venv.bak/
|
||||||
|
|
||||||
|
# Spyder project settings
|
||||||
|
.spyderproject
|
||||||
|
.spyproject
|
||||||
|
|
||||||
|
# Rope project settings
|
||||||
|
.ropeproject
|
||||||
|
|
||||||
|
# mkdocs documentation
|
||||||
|
/site
|
||||||
|
|
||||||
|
# mypy
|
||||||
|
.mypy_cache/
|
||||||
|
|
||||||
|
### SublimeText ###
|
||||||
|
# cache files for sublime text
|
||||||
|
*.tmlanguage.cache
|
||||||
|
*.tmPreferences.cache
|
||||||
|
*.stTheme.cache
|
||||||
|
|
||||||
|
# workspace files are user-specific
|
||||||
|
*.sublime-workspace
|
||||||
|
|
||||||
|
# project files should be checked into the repository, unless a significant
|
||||||
|
# proportion of contributors will probably not be using SublimeText
|
||||||
|
# *.sublime-project
|
||||||
|
|
||||||
|
# sftp configuration file
|
||||||
|
sftp-config.json
|
||||||
|
|
||||||
|
# Package control specific files
|
||||||
|
Package Control.last-run
|
||||||
|
Package Control.ca-list
|
||||||
|
Package Control.ca-bundle
|
||||||
|
Package Control.system-ca-bundle
|
||||||
|
Package Control.cache/
|
||||||
|
Package Control.ca-certs/
|
||||||
|
Package Control.merged-ca-bundle
|
||||||
|
Package Control.user-ca-bundle
|
||||||
|
oscrypto-ca-bundle.crt
|
||||||
|
bh_unicode_properties.cache
|
||||||
|
|
||||||
|
# Sublime-github package stores a github token in this file
|
||||||
|
# https://packagecontrol.io/packages/sublime-github
|
||||||
|
GitHub.sublime-settings
|
||||||
|
|
||||||
|
### Vim ###
|
||||||
|
# swap
|
||||||
|
[._]*.s[a-v][a-z]
|
||||||
|
[._]*.sw[a-p]
|
||||||
|
[._]s[a-v][a-z]
|
||||||
|
[._]sw[a-p]
|
||||||
|
# session
|
||||||
|
Session.vim
|
||||||
|
# temporary
|
||||||
|
.netrwhist
|
||||||
|
# auto-generated tag files
|
||||||
|
tags
|
||||||
|
|
||||||
|
config.py
|
||||||
|
|
||||||
|
# End of https://www.gitignore.io/api/vim,macos,emacs,python,sublimetext
|
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
[submodule "telewalrus"]
|
||||||
|
path = telewalrus
|
||||||
|
url = git@github.com:SijmenSchoon/telewalrus.git
|
102
api.py
Normal file
102
api.py
Normal file
|
@ -0,0 +1,102 @@
|
||||||
|
import urllib.parse
|
||||||
|
import aiohttp
|
||||||
|
|
||||||
|
SCHEME = 'http'
|
||||||
|
NETLOC = 'localhost:5000'
|
||||||
|
|
||||||
|
|
||||||
|
class ApiError(Exception): pass
|
||||||
|
|
||||||
|
class BadRequestError(ApiError): pass
|
||||||
|
class PermissionDeniedError(ApiError): pass
|
||||||
|
class NotFoundError(ApiError): pass
|
||||||
|
class InternalServerError(ApiError): pass
|
||||||
|
|
||||||
|
|
||||||
|
def build_url(path, query_args=None):
|
||||||
|
query = urllib.parse.urlencode(query_args if query_args else {})
|
||||||
|
parse_result = urllib.parse.ParseResult(
|
||||||
|
scheme=SCHEME, netloc=NETLOC, path=path,
|
||||||
|
params='', query=query, fragment='')
|
||||||
|
return urllib.parse.urlunparse(parse_result)
|
||||||
|
|
||||||
|
def check_status(status):
|
||||||
|
if status == 400:
|
||||||
|
raise BadRequestError
|
||||||
|
elif status == 403:
|
||||||
|
raise PermissionDeniedError
|
||||||
|
elif status == 404:
|
||||||
|
raise NotFoundError
|
||||||
|
elif status == 500:
|
||||||
|
raise InternalServerError
|
||||||
|
|
||||||
|
|
||||||
|
async def get_json(url, token=None):
|
||||||
|
headers = {'Authorization': f'Bearer {token}'} if token else {}
|
||||||
|
async with aiohttp.ClientSession() as session:
|
||||||
|
async with session.get(url, headers=headers) as resp:
|
||||||
|
check_status(resp.status)
|
||||||
|
return await resp.json()
|
||||||
|
|
||||||
|
|
||||||
|
async def post_json(url, obj, token=None):
|
||||||
|
headers = {'Authorization': f'Bearer {token}'} if token else {}
|
||||||
|
async with aiohttp.ClientSession() as session:
|
||||||
|
async with session.post(url, json=obj, headers=headers) as resp:
|
||||||
|
check_status(resp.status)
|
||||||
|
return await resp.json()
|
||||||
|
|
||||||
|
|
||||||
|
async def put_json(url, obj, token=None):
|
||||||
|
headers = {'Authorization': f'Bearer {token}'} if token else {}
|
||||||
|
async with aiohttp.ClientSession() as session:
|
||||||
|
async with session.put(url, json=obj, headers=headers) as resp:
|
||||||
|
check_status(resp.status)
|
||||||
|
return await resp.json()
|
||||||
|
|
||||||
|
|
||||||
|
async def get_tasks(token, group_id=None):
|
||||||
|
args = {'group_id': group_id} if group_id else {}
|
||||||
|
url = build_url('/pimpy/api/tasks/', args)
|
||||||
|
return await get_json(url, token=token)
|
||||||
|
|
||||||
|
|
||||||
|
async def get_group_tasks(token, group_id):
|
||||||
|
url = build_url(f'/pimpy/api/groups/{group_id:d}/tasks/')
|
||||||
|
return await get_json(url, token=token)
|
||||||
|
|
||||||
|
|
||||||
|
async def get_group_user_tasks(token, group_id, user_id='me'):
|
||||||
|
url = build_url(f'/pimpy/api/groups/{group_id:d}/users/{user_id}/tasks/')
|
||||||
|
return await get_json(url, token=token)
|
||||||
|
|
||||||
|
|
||||||
|
async def get_group_task(token, group_id, task_id):
|
||||||
|
url = build_url(f'/pimpy/api/groups/{group_id:d}/tasks/{task_id:d}/')
|
||||||
|
return await get_json(url, token=token)
|
||||||
|
|
||||||
|
|
||||||
|
async def add_group_task(token, group_id, owners, title):
|
||||||
|
url = build_url(f'/pimpy/api/groups/{group_id:d}/tasks/')
|
||||||
|
obj = {'owners': owners, 'title': title}
|
||||||
|
return await post_json(url, obj, token=token)
|
||||||
|
|
||||||
|
|
||||||
|
async def get_group_users(token, group_id):
|
||||||
|
url = build_url(f'/pimpy/api/groups/{group_id:d}/users/')
|
||||||
|
return await get_json(url, token=token)
|
||||||
|
|
||||||
|
|
||||||
|
async def get_task(token, task_id):
|
||||||
|
url = build_url(f'/pimpy/api/tasks/{task_id:d}/')
|
||||||
|
return await get_json(url, token=token)
|
||||||
|
|
||||||
|
|
||||||
|
async def set_group_task_status(token, group_id, task_id, status):
|
||||||
|
url = build_url(f'/pimpy/api/groups/{group_id:d}/tasks/{task_id:d}/status/')
|
||||||
|
return await put_json(url, {'status': status}, token=token)
|
||||||
|
|
||||||
|
|
||||||
|
async def set_task_status(token, task_id, status):
|
||||||
|
url = build_url(f'/pimpy/api/tasks/{task_id:d}/status/')
|
||||||
|
return await put_json(url, {'status': status}, token=token)
|
326
app.py
Normal file
326
app.py
Normal file
|
@ -0,0 +1,326 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
import json
|
||||||
|
import re
|
||||||
|
from collections import defaultdict
|
||||||
|
from math import ceil
|
||||||
|
|
||||||
|
import baas32
|
||||||
|
import telewalrus.bot
|
||||||
|
|
||||||
|
import api
|
||||||
|
import messages
|
||||||
|
|
||||||
|
|
||||||
|
from config import VIA_USERS, VIA_GROUPS, TG_TOKEN, USER_TOKENS
|
||||||
|
BOT = telewalrus.bot.Bot(TG_TOKEN)
|
||||||
|
|
||||||
|
|
||||||
|
@BOT.command('start')
|
||||||
|
async def cmd_start(message):
|
||||||
|
name = message.from_user.first_name
|
||||||
|
|
||||||
|
if message.from_user.id not in VIA_USERS:
|
||||||
|
await message.chat.message(messages.stranger_message(name))
|
||||||
|
return
|
||||||
|
|
||||||
|
await message.chat.message(
|
||||||
|
f'Heya, {name}! Zie /tasks om te zien welke taken je open hebt staan.')
|
||||||
|
|
||||||
|
|
||||||
|
@BOT.command('chatinfo')
|
||||||
|
async def cmd_chatinfo(message):
|
||||||
|
chat = message.chat
|
||||||
|
|
||||||
|
if message.chat.type == 'private':
|
||||||
|
admins = []
|
||||||
|
else:
|
||||||
|
admins = [admin.user for admin in await chat.administrators()]
|
||||||
|
|
||||||
|
msg = f'''
|
||||||
|
id: {chat.id}
|
||||||
|
type: {chat.type}
|
||||||
|
title: {chat.title}
|
||||||
|
username: {chat.username}
|
||||||
|
first_name: {chat.first_name}
|
||||||
|
last_name: {chat.last_name}
|
||||||
|
admins: {admins}
|
||||||
|
'''
|
||||||
|
await message.chat.message(msg)
|
||||||
|
|
||||||
|
|
||||||
|
@BOT.command('tasks')
|
||||||
|
async def cmd_tasks(message):
|
||||||
|
token = USER_TOKENS.get(message.from_user.id)
|
||||||
|
if not token:
|
||||||
|
msg = messages.stranger_message(message.from_user.first_name)
|
||||||
|
await message.chat.message(msg)
|
||||||
|
return
|
||||||
|
|
||||||
|
is_group = message.chat.type != 'private'
|
||||||
|
if not is_group:
|
||||||
|
tasks = await api.get_tasks(token)
|
||||||
|
else:
|
||||||
|
group_id = VIA_GROUPS.get(message.chat.id)
|
||||||
|
if group_id is None:
|
||||||
|
await message.chat.message(
|
||||||
|
'pimpy is nog niet ingeschakeld voor deze groep :/')
|
||||||
|
return
|
||||||
|
|
||||||
|
tasks = await api.get_group_user_tasks(token, group_id)
|
||||||
|
print(json.dumps(tasks, indent=4))
|
||||||
|
|
||||||
|
msg = messages.tasks_message(tasks, is_group)
|
||||||
|
await message.chat.message(msg, parse_mode='HTML')
|
||||||
|
|
||||||
|
|
||||||
|
@BOT.command('grouptasks')
|
||||||
|
async def cmd_grouptasks(message):
|
||||||
|
token = USER_TOKENS.get(message.from_user.id)
|
||||||
|
if not token:
|
||||||
|
msg = messages.stranger_message(message.from_user.first_name)
|
||||||
|
await message.chat.message(msg)
|
||||||
|
return
|
||||||
|
|
||||||
|
if message.chat.type == 'private':
|
||||||
|
await message.chat.message(
|
||||||
|
'Dit commando werkt alleen in commissiechats.')
|
||||||
|
return
|
||||||
|
|
||||||
|
group_id = VIA_GROUPS.get(message.chat.id)
|
||||||
|
if not group_id:
|
||||||
|
await message.chat.message(
|
||||||
|
'pimpy is nog niet ingeschakeld voor deze groep :/')
|
||||||
|
return
|
||||||
|
|
||||||
|
user_tasks = await api.get_group_tasks(token, group_id)
|
||||||
|
|
||||||
|
msg = ''
|
||||||
|
for i, (name, tasks) in enumerate(user_tasks.items()):
|
||||||
|
status = defaultdict(int)
|
||||||
|
for task in tasks:
|
||||||
|
status[task['status']] += 1
|
||||||
|
|
||||||
|
msg += f'<b>{name}</b>:\n' \
|
||||||
|
f' ⏸ {status["Niet begonnen"]}, ▶️ {status["Begonnen"]}, ' \
|
||||||
|
f'✅ {status["Done"]}, ❌ {status["Niet Done"]}\n'
|
||||||
|
|
||||||
|
users = await api.get_group_users(token, group_id)
|
||||||
|
keyboard = []
|
||||||
|
for i, user in enumerate(users):
|
||||||
|
if i % 3 == 0:
|
||||||
|
keyboard.append([])
|
||||||
|
|
||||||
|
keyboard[-1].append({
|
||||||
|
'text': user['name'],
|
||||||
|
'callback_data': f'tasks {user["id"]} {user["name"]}'
|
||||||
|
})
|
||||||
|
|
||||||
|
msg += '\nKlik op een naam hieronder om zijn/haar taken weer te geven.'
|
||||||
|
reply_markup = {'inline_keyboard': keyboard}
|
||||||
|
|
||||||
|
await message.chat.message(msg, parse_mode='HTML',
|
||||||
|
disable_web_page_preview=True,
|
||||||
|
reply_markup=json.dumps(reply_markup))
|
||||||
|
|
||||||
|
|
||||||
|
async def get_task_from_args(token, message, group_id=None):
|
||||||
|
task_code = baas32.normalize(message.args)
|
||||||
|
if not task_code:
|
||||||
|
await message.chat.message(
|
||||||
|
'Welke taak? Protip: zet de taakcode achter het commando.')
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
task_id = baas32.decode(task_code)
|
||||||
|
except ValueError:
|
||||||
|
await message.chat.message(f'{task_code} is geen geldige taakcode.')
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
if group_id:
|
||||||
|
task = await api.get_group_task(token, group_id, task_id)
|
||||||
|
else:
|
||||||
|
task = await api.get_task(token, task_id)
|
||||||
|
except api.NotFoundError:
|
||||||
|
await message.chat.message(f'Kan taak {task_code} niet vinden :(')
|
||||||
|
return
|
||||||
|
except api.PermissionDeniedError:
|
||||||
|
await message.chat.message(
|
||||||
|
f'Je hebt geen rechten voor taak {task_code}.')
|
||||||
|
return
|
||||||
|
|
||||||
|
return task, task_code
|
||||||
|
|
||||||
|
|
||||||
|
@BOT.command('task')
|
||||||
|
async def cmd_task(message):
|
||||||
|
token = USER_TOKENS.get(message.from_user.id)
|
||||||
|
if not token:
|
||||||
|
msg = messages.stranger_message(message.from_user.first_name)
|
||||||
|
await message.chat.message(msg)
|
||||||
|
return
|
||||||
|
|
||||||
|
group_id = None
|
||||||
|
if message.chat.type != 'private':
|
||||||
|
group_id = VIA_GROUPS.get(message.chat.id)
|
||||||
|
if not group_id:
|
||||||
|
await message.chat.message(
|
||||||
|
'pimpy is nog niet ingeschakeld voor deze groep :/')
|
||||||
|
return
|
||||||
|
|
||||||
|
task, _ = await get_task_from_args(token, message, group_id)
|
||||||
|
if not task:
|
||||||
|
return
|
||||||
|
|
||||||
|
msg, reply_markup = messages.task_message(task, group_id is not None)
|
||||||
|
|
||||||
|
await message.chat.message(msg, parse_mode='HTML',
|
||||||
|
reply_markup=json.dumps(reply_markup),
|
||||||
|
disable_web_page_preview=True)
|
||||||
|
|
||||||
|
|
||||||
|
@BOT.command('done')
|
||||||
|
async def cmd_done(message):
|
||||||
|
user_id = VIA_USERS.get(message.from_user.id)
|
||||||
|
if not user_id:
|
||||||
|
msg = messages.stranger_message(message.from_user.first_name)
|
||||||
|
await message.chat.message(msg)
|
||||||
|
return
|
||||||
|
|
||||||
|
task, task_code = await get_task_from_args(message)
|
||||||
|
if not task:
|
||||||
|
return
|
||||||
|
|
||||||
|
if not user_id in (user['id'] for user in task['users']):
|
||||||
|
msg = f'Je bent geen eigenaar van de taak {task_code}!'
|
||||||
|
await message.chat.message(msg, parse_mode='HTML')
|
||||||
|
return
|
||||||
|
|
||||||
|
await api.set_task_status(task['id'], 'done')
|
||||||
|
|
||||||
|
msg = f'Taak {task_code} staat nu op done!'
|
||||||
|
await message.chat.message(msg)
|
||||||
|
|
||||||
|
|
||||||
|
@BOT.command('addtask')
|
||||||
|
async def cmd_addtask(message):
|
||||||
|
user_id = VIA_USERS.get(message.from_user.id)
|
||||||
|
if not user_id:
|
||||||
|
msg = messages.stranger_message(message.from_user.first_name)
|
||||||
|
await message.chat.message(msg)
|
||||||
|
return
|
||||||
|
|
||||||
|
match = re.match(r'^([^"\' ]+|["\'][^"\']+["\']) (.*)$', message.args)
|
||||||
|
group = match.group(1).strip('\'"')
|
||||||
|
title = match.group(2).strip('\'"')
|
||||||
|
|
||||||
|
await message.chat.message(f'Groep: {group}\nTitel: {title}')
|
||||||
|
|
||||||
|
|
||||||
|
@BOT.command('actie')
|
||||||
|
async def cmd_actie(message):
|
||||||
|
if message.chat.type == 'private':
|
||||||
|
await message.chat.message(
|
||||||
|
'Deze functie werkt alleen in commissiechats.')
|
||||||
|
return
|
||||||
|
|
||||||
|
group_id = VIA_GROUPS.get(message.chat.id)
|
||||||
|
if not group_id:
|
||||||
|
await message.chat.message(
|
||||||
|
'pimpy is nog niet ingeschakeld voor deze groep :/')
|
||||||
|
return
|
||||||
|
|
||||||
|
match = re.match(r'^([^:]+): (.*)$', message.args)
|
||||||
|
if not match:
|
||||||
|
match = re.match(r'^([^ ]+) (.*)$', message.args)
|
||||||
|
if match:
|
||||||
|
owner = match.group(1)
|
||||||
|
title = match.group(2)
|
||||||
|
await message.chat.message(
|
||||||
|
f'Incorrecte syntax. Misschien bedoelde je /actie {owner}: {title}?')
|
||||||
|
else:
|
||||||
|
await message.chat.message(
|
||||||
|
f'Incorrecte syntax. Probeer eens /actie [naam]: [titel].')
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
task = await api.add_group_task(group_id, match.group(1), match.group(2))
|
||||||
|
msg, reply_markup = messages.task_message(task, None, True)
|
||||||
|
task_code = baas32.encode(task['id'])
|
||||||
|
msg = f'Taak <code>[{task_code}]</code> aangemaakt!\n\n' + msg
|
||||||
|
|
||||||
|
await message.chat.message(msg, parse_mode='HTML',
|
||||||
|
reply_markup=json.dumps(reply_markup),
|
||||||
|
disable_web_page_preview=True)
|
||||||
|
|
||||||
|
|
||||||
|
async def callback_status(query, _, args):
|
||||||
|
token = USER_TOKENS.get(query.from_user.id)
|
||||||
|
if not token:
|
||||||
|
msg = messages.stranger_message(message.from_user.first_name)
|
||||||
|
await message.chat.message(msg)
|
||||||
|
return
|
||||||
|
|
||||||
|
status, task_id = args
|
||||||
|
task_id = int(task_id)
|
||||||
|
|
||||||
|
if query.message.chat.type == 'private':
|
||||||
|
await api.set_task_status(token, task_id, status)
|
||||||
|
task = await api.get_task(token, task_id)
|
||||||
|
else:
|
||||||
|
group_id = VIA_GROUPS.get(query.message.chat.id)
|
||||||
|
if not group_id:
|
||||||
|
return
|
||||||
|
|
||||||
|
await api.set_group_task_status(token, group_id, task_id, status)
|
||||||
|
task = await api.get_group_task(token, group_id, task_id)
|
||||||
|
|
||||||
|
|
||||||
|
msg, reply_markup = messages.task_message(task, False)
|
||||||
|
await query.message.edit(msg, parse_mode='HTML',
|
||||||
|
reply_markup=json.dumps(reply_markup),
|
||||||
|
disable_web_page_preview=True)
|
||||||
|
await query.answer()
|
||||||
|
|
||||||
|
|
||||||
|
async def callback_tasks(query, _, args):
|
||||||
|
token = USER_TOKENS.get(query.from_user.id)
|
||||||
|
if not token:
|
||||||
|
msg = messages.stranger_message(message.from_user.first_name)
|
||||||
|
await message.chat.message(msg)
|
||||||
|
return
|
||||||
|
|
||||||
|
group_id = VIA_GROUPS.get(query.message.chat.id)
|
||||||
|
if not group_id:
|
||||||
|
await query.answer()
|
||||||
|
return
|
||||||
|
|
||||||
|
user_id = int(args[0])
|
||||||
|
user_name = ' '.join(args[1:])
|
||||||
|
|
||||||
|
tasks = await api.get_group_user_tasks(token, group_id, user_id)
|
||||||
|
print(json.dumps(tasks, indent=4))
|
||||||
|
msg = messages.tasks_message(tasks, True, user_name)
|
||||||
|
await query.message.chat.message(msg, parse_mode='HTML')
|
||||||
|
|
||||||
|
|
||||||
|
CALLBACK_HANDLERS = {
|
||||||
|
'status': callback_status,
|
||||||
|
'tasks': callback_tasks
|
||||||
|
}
|
||||||
|
|
||||||
|
@BOT.callback
|
||||||
|
async def callback(query):
|
||||||
|
command, *args = query.data.split(' ')
|
||||||
|
handler = CALLBACK_HANDLERS.get(command)
|
||||||
|
if handler:
|
||||||
|
await handler(query, command, args)
|
||||||
|
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
BOT.run()
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
print('\nhee doei hè')
|
||||||
|
break
|
||||||
|
except:
|
||||||
|
pass
|
153
messages.py
Normal file
153
messages.py
Normal file
|
@ -0,0 +1,153 @@
|
||||||
|
from datetime import datetime
|
||||||
|
from collections import defaultdict
|
||||||
|
|
||||||
|
import random
|
||||||
|
import locale
|
||||||
|
import baas32
|
||||||
|
|
||||||
|
locale.setlocale(locale.LC_TIME, 'nl_NL')
|
||||||
|
|
||||||
|
STATUS_EMOJI = {
|
||||||
|
'Niet begonnen': '⏸',
|
||||||
|
'Begonnen': '▶️',
|
||||||
|
'Done': '✅',
|
||||||
|
'Niet Done': '❌'
|
||||||
|
}
|
||||||
|
|
||||||
|
def stranger_message(name):
|
||||||
|
return f'''
|
||||||
|
Heya, {name}! Cool dat je even komt kijken!
|
||||||
|
|
||||||
|
Voor nu is deze bot nog even afgesloten voor het publiek,
|
||||||
|
maar kom later vooral een keertje terug.
|
||||||
|
|
||||||
|
Joe!'''
|
||||||
|
|
||||||
|
|
||||||
|
def me_message(user_id):
|
||||||
|
return f'''
|
||||||
|
Dit weet ik over je:
|
||||||
|
|
||||||
|
svia.nl user_id: {user_id}
|
||||||
|
'''
|
||||||
|
|
||||||
|
|
||||||
|
def tasks_message(tasks, is_group=False, user_name=None):
|
||||||
|
groups = defaultdict(list)
|
||||||
|
for task in tasks:
|
||||||
|
groups[task['group']['id']].append(task)
|
||||||
|
|
||||||
|
if not user_name:
|
||||||
|
user_name = 'Je'
|
||||||
|
else:
|
||||||
|
user_name += '\'s'
|
||||||
|
|
||||||
|
if is_group:
|
||||||
|
msg = f'<strong>{user_name} taken voor deze groep:</strong>\n\n'
|
||||||
|
else:
|
||||||
|
msg = f'<strong>{user_name} taken:</strong>\n\n'
|
||||||
|
|
||||||
|
for _, group_tasks in groups.items():
|
||||||
|
if not group_tasks:
|
||||||
|
continue
|
||||||
|
|
||||||
|
group_name = group_tasks[0]['group']['name'] if not is_group else None
|
||||||
|
msg += taskset_message(group_name, group_tasks)
|
||||||
|
|
||||||
|
random_task = baas32.encode(random.choice(tasks)['id'])
|
||||||
|
msg += f'Gebruik /task <task_id> voor meer informatie. ' \
|
||||||
|
f'Bijvoorbeeld: /task {random_task}'
|
||||||
|
|
||||||
|
return msg
|
||||||
|
|
||||||
|
|
||||||
|
def taskset_message(name, tasks):
|
||||||
|
msg = f'<strong>{name}:</strong>\n' if name else ''
|
||||||
|
for task in tasks:
|
||||||
|
task_code = baas32.encode(task['id'])
|
||||||
|
emoji = STATUS_EMOJI[task['status']]
|
||||||
|
if len(task['users']) == 2:
|
||||||
|
emoji += ' 👨👦'
|
||||||
|
elif len(task['users']) == 3:
|
||||||
|
emoji += ' 👨👧👦'
|
||||||
|
elif len(task['users']) > 3:
|
||||||
|
emoji += ' 👨👩👧👧'
|
||||||
|
|
||||||
|
msg += f'• <code>[{task_code}]</code> ' \
|
||||||
|
f'{emoji} {task["title"].strip()}'
|
||||||
|
|
||||||
|
msg += '\n'
|
||||||
|
|
||||||
|
msg += '\n'
|
||||||
|
|
||||||
|
return msg
|
||||||
|
|
||||||
|
|
||||||
|
def task_message(task, is_group):
|
||||||
|
task_code = baas32.encode(task['id'])
|
||||||
|
msg = f'<code>[{task_code}]</code> <strong>{task["title"]}</strong>\n'
|
||||||
|
|
||||||
|
timestamp = datetime.strptime(task["timestamp"], "%Y-%m-%dT%H:%M:%S")
|
||||||
|
msg += f'<em>{timestamp.strftime("%d %B %Y, %H:%M")}</em>\n\n'
|
||||||
|
|
||||||
|
# Print the task group
|
||||||
|
msg += f'<strong>Groep:</strong> {task["group"]["name"]}\n'
|
||||||
|
|
||||||
|
# Print the task state
|
||||||
|
msg += f'<strong>Status:</strong> {task["status"]}\n'
|
||||||
|
|
||||||
|
# Print task owner(s)
|
||||||
|
users = task['users']
|
||||||
|
if not users:
|
||||||
|
msg += f'<em>Geen eigenaren</em>\n'
|
||||||
|
elif len(users) == 1:
|
||||||
|
msg += f'<strong>Eigenaar:</strong> {users[0]["name"]}\n'
|
||||||
|
elif 1 < len(users) <= 2:
|
||||||
|
msg += f'<strong>Eigenaren:</strong> ' \
|
||||||
|
f'{users[0]["name"]} en {users[1]["name"]}\n'
|
||||||
|
else:
|
||||||
|
msg += '\n<strong>Eigenaren:</strong>\n'
|
||||||
|
for user in task['users']:
|
||||||
|
msg += f'• {user["name"]}\n'
|
||||||
|
msg += '\n'
|
||||||
|
|
||||||
|
# Print the description, if available
|
||||||
|
try:
|
||||||
|
msg += f'<strong>Beschrijving:</strong>\n{task["content"]}\n\n'
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Print the minute URL, if available
|
||||||
|
try:
|
||||||
|
minute = task['minute']
|
||||||
|
minute_url = f'http://svia.nl/pimpy/minutes/single/{minute["id"]}/'
|
||||||
|
minute_url += str(minute['line']) if 'line' in minute else ''
|
||||||
|
|
||||||
|
msg += f'<a href="{minute_url}">Bijbehorende notulen</a>\n'
|
||||||
|
except KeyError:
|
||||||
|
msg += f'<em>Geen bijbehorende notulen</em>\n'
|
||||||
|
|
||||||
|
keyboard = []
|
||||||
|
if task['status'] != 'Niet begonnen':
|
||||||
|
keyboard.append({
|
||||||
|
'text': '⏸ Niet begonnen',
|
||||||
|
'callback_data': f'status unstarted {task["id"]}'
|
||||||
|
})
|
||||||
|
if task['status'] != 'Begonnen':
|
||||||
|
keyboard.append({
|
||||||
|
'text': '▶️ Begonnen',
|
||||||
|
'callback_data': f'status started {task["id"]}'
|
||||||
|
})
|
||||||
|
if task['status'] != 'Done':
|
||||||
|
keyboard.append({
|
||||||
|
'text': '✅ Done',
|
||||||
|
'callback_data': f'status done {task["id"]}'
|
||||||
|
})
|
||||||
|
if task['status'] != 'Niet Done':
|
||||||
|
keyboard.append({
|
||||||
|
'text': '❌ Niet Done',
|
||||||
|
'callback_data': f'status notdone {task["id"]}'
|
||||||
|
})
|
||||||
|
|
||||||
|
reply_markup = {'inline_keyboard': [keyboard]}
|
||||||
|
return msg, reply_markup
|
6
requirements.txt
Normal file
6
requirements.txt
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
aiohttp==2.3.2
|
||||||
|
async-timeout==2.0.0
|
||||||
|
baas32==0.3.2
|
||||||
|
chardet==3.0.4
|
||||||
|
multidict==3.3.2
|
||||||
|
yarl==0.13.0
|
1
telewalrus
Submodule
1
telewalrus
Submodule
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit 244c7ed6e6658dfb09dbae45446e132f9bc144c5
|
Loading…
Reference in a new issue