Merge branch 'dev'

This commit is contained in:
bursa-pastoris 2023-03-24 10:11:12 +01:00
commit d5c0a608de
20 changed files with 1627 additions and 575 deletions

View File

@ -1,9 +0,0 @@
## v0.2
* Added admins allowed to stop the bot from Telegram
* Completely anonymized logs
* Added privacy info
* Improved logs
* License compliance and update
* General cleanup
* Documentation fixes

View File

@ -1,20 +0,0 @@
# Contributing
I develop Albatrobot as a bot for personal purposes and therefore it is not
supposed to receive external contributes. However, if you have a proposal I'd
be glad to look at it. I will consider contributes received through pull
requests on the [Disroot
repository](https://git.disroot.org/bursa-pastoris/albatrobot/pulls) or by
email at `bursapastoris at disroot dot org`, as either patches or pull-requests
from other repositories. If you want to send contributes by email, see
[here](https://git.disroot.org/bursapastoris/info/contact.md) for more
information about how to contact me by email and
[here](https://git-scm.com/book/en/v2/Distributed-Git-Contributing-to-a-Project)
for how to use `git format-patch`.
Please note that I may take some time to answer.
Also, consider that I will merge only contributes that are useful to me, as I
use and develop Albatrobot for my own needs only. However, you can take
advantage of the rights granted by [AGPL-3.0-only](./LICENSE) license and
fork the project as you prefer.

131
README.md
View File

@ -1,8 +1,13 @@
# Albatrobot
---
gitea: none
include_toc: true
---
Albatrobot is a personal project born as a simple bot to roll dice, but has
since then evolved with the addition of more functions, most of which is
useless. It thus evolved to the current bloat hodgepodge - but I like it the
same.
Albatrobot is a personal project born as a bot to dice rolling only, but has
since then evolved with the addition of more functions (most of which is
useless). But I like it even if it is substantially bloat.
## Setup
@ -17,28 +22,46 @@ To install Albatrobot, follow those steps.
source .venv/bin/activate
python3 -m pip install -r requirements.txt
```
3. Configure the bot as you like (see [Configuration](#configuration)
paragraph).
### Configuration
All the required configuration is in `settings.ini` file, that can must edited
manually.
All the required configuration is in `settings.ini` file, that must edited
manually. Some keys already contain a value: those are suggested values, that
can be tuned to user's needs.
| Option | Meaning |
| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `Token` | Token to connect the bot script to the bot user on Telegram server.[^token] |
| `Users` | Comma-separated user IDs of the users allowed to use the bot. |
| `Admins` | Comma-separated user IDs of the users with admin powers. Admins are always considered Users as well. |
| `MsgCooldown` | Minimum time in seconds between two following uses of commands `/Easter` and `/Christmas` by the same user. The time is the same but is considered separately for each command. |
| `CitePath` | Path of the document containing the citations for `/Easter` command. |
| `ImgPath` | Path of the document containing the images for `Christmas` command. |
- `Bot`
- `Token`: the token to connect the bot script to the bot user on Telegram
server.[^token]
- `Users`
- `Users`: a comma-separated list of the user IDs of the users allowed to use
the bot
- `Admins`: a comma-separated user IDs of the users with admin powers. Admins
are always considered Users as well.
- `Settings`
- `Language`: the language of the bot interface
- `MsgCooldown`: the minimum time, in seconds, between two following uses of
commands `/Easter` and `/Christmas` by the same user. The time is the same
but is considered separately for each command.
- `Proxy`: proxy settings to connect to Telegram's bot API. The string must
follow the syntax
`<protocol>://[<username[:<password>]@]<hostname>[:<port>]` (e.g.
`socks5://127.0.0.1:9050`)
- `YellLang`: language for `/yell` voice messages
- `YellSpeed`: speed for `/yell` voice messages
- `Resources`
- `CitePath`: path of the document containing the citations for `/Easter`
command.
- `ImgPath`: path of the document containing the images for `Christmas`
command.
### Usage
Albatrobot can be automatically run at system boot by adding the following command in `crontab`'s queue the following command:
Albatrobot can be automatically run at system boot by adding the following
command in `crontab`'s queue the following command:
@reboot sleep 10 && cd <installation path> && ./main.py > /tmp/albatrobot.txt 2>&1
```
@reboot sleep 10 && cd <installation path> && ./main.py > /tmp/albatrobot.txt 2>&1
```
To do so, run `crontab -e` and add the above line to the file that will be
opened, then save and close.
@ -54,30 +77,58 @@ If you run Albatrobot this way - the recommended one - consider the following:
If the bot is stopped, it can be started again with the following commands:
cd <installation path>
source .venv/bin/activate
nohup ./main.py &
```
cd <installation path>
source .venv/bin/activate
nohup ./main.py &
```
The bot can be stopped by killing its process on the machine it runs on or by
any admin with the `/stop` command.
For testing purposes, Albatrobot can be run with the following command:
cd <installation path> && source .venv/bin/activate && ./main.py
```
cd <installation path> && source .venv/bin/activate && ./main.py
```
It can then be killed with `Ctrl + C`.
## Commands
Commands available for all users:
- `/christmas`: get an image
- `/easter (<author>)`: get a citation from given author, or a random one if no
author is specified.
- `/end_of_year_dinner`: send all citations in the archive to a random user.
- `/epiphony (<text>)`: get a voice message from Albatrobot reading given text
- `/help`: get help about bot commands.
- `/legal`: get legal info about privacy and copyright.
- `/roll <dice>(<modifiers>)`: roll dice, with or without modifiers. Launch
without arguments to get help on the modifiers.
- `/version`: get Albatrobot's version
Additional commands for bot admins:
- `/init`: reinitialize the bot. This updates inline suggestions and purges
/epiphony cached files.
- `/stop`: stop Albatrobot. To be used only in case of emergency: restarting
the bot after this command is used requires direct access to the server.
## Common problems
### When I try to start the bot Python exits with an error
Check:
1. that the configuration file content is in the correct format and contains
the correct information (see paragraph [Configuration](#configuration));
2. that you have installed the required libraries (see paragraph
[Installation](#installation));
3. that you have activate the virtual environment (see paragraph
3. that you are using the virtual environment (see paragraph
[Installation](#installation));
4. that the virtual environment has not been changed after the installation.
@ -85,27 +136,28 @@ In the first three cases, the solution is self-explaining. In the fourth, the
simplest way is to just delete the virtual environment and recreate it. Just
run the following commands in the installation path:
rm -rf .venv
python3 -m venv .venv
source .venv/bin/activate
python3 -m pip install -r requirements.txt
```
rm -rf .venv
python3 -m venv .venv
source .venv/bin/activate
python3 -m pip install -r requirements.txt
```
This does not cause any data loss.
### The Albatrobot seems correctly booted but on Telegram it doesn't work or behaves oddly
### The bot seems correctly booted but it doesn't work or behaves oddly
Check:
1. that you have used the correct token (see paragraph
[Configuration](#configuration));
2. that only one instance of the albatrobot is active for the same Telegram bot
user
If none of the above solution has effect, try the ones of those suggested for
[Python errors](#when-i-try-to-start-the-bot-python-exits-with-an-error).
### Albatrobot says that there are no avilable citations or images
**Albatrobot says that there are no avilable citations or images**
Check:
1. that there are available citations and images;
2. that citations and images are in supported formats;
3. that in the configuration the correct paths are set.
@ -119,23 +171,16 @@ repository](https://git.disroot.org/bursa-pastoris/albatrobot) or send an email
to `bursapastoris at disroot dot org` taking the precautions described
[here](https://git.disroot.org/bursa-pastoris/info/src/branch/master/contact.md).
## Some notes
### About language
Albatrobot was not originally developed nor used in English and the translation
may contain mistakes. Sorry for that. If you want so, you can help improving
it.
### About the repository
## A note about the repository
This git repository does not contain all the project's development history
because of two causes.
1. At the beginning, the project was not tracked with git.
2. I chose to redistribute the software after much time of development, so the
original archive contained some data that I didn't want nor could publish.
But at that time it was way easier to create a new archive than purging
those data from the original one. And the original archive contained many
those data from the original one. Also, the original archive contained many
completely unuseful files.
## Legal
@ -151,4 +196,4 @@ For all legal info see [LEGAL.md](./LEGAL.md).
bot?](https://core.telegram.org/bots#3-how-do-i-create-a-bot) paragraph on
*Bots: An introduction for developers* and [Authorizing your
bot](https://core.telegram.org/bots/api#authorizing-your-bot) paragraph of
*Telegram Bot API* on [Telegram's API portal](https://core.telegram.org/)
*Telegram Bot API* on [Telegram's API portal](https://core.telegram.org/)

View File

@ -1,6 +1,6 @@
"""
Albatrobot - Telegram bot for RPG groups and similar
Copyright (C) 2019, 2020, 2021, 2021 bursa-pastoris
Copyright (C) 2019, 2020, 2021, 2022 bursa-pastoris
This file is part of Albatrobot.
@ -17,79 +17,88 @@
along with Albatrobot. If not, see <https://www.gnu.org/licenses/>.
"""
import os
import logging
import random
import glob
import time
import os
import psutil
import signal
import csv
from datetime import datetime
import glob
import hashlib
import logging
import os
import pyttsx3
import random
from re import findall as find
import signal
import time
import Levenshtein
from telegram import ParseMode, ChatAction
import psutil
from rolldice import roll_dice, rolldice
from telegram import BotCommand
from telegram.constants import ChatAction, ChatType, ParseMode
from constants import *
# Prepare paths
required_paths = ['log',TMPVOICEDIR]
for i in required_paths:
if not os.path.exists(i):
os.mkdir(i)
# Logging
if not os.path.exists('log'):
os.mkdir('log')
formatter = logging.Formatter('(%(asctime)s) %(levelname)s: %(message)s')
def set_logger(name, log_file, level=logging.INFO):
"""Create logger more easily."""
handler = logging.FileHandler(log_file)
handler.setFormatter(formatter)
logger = logging.getLogger(name)
logger.setLevel(level)
logger.addHandler(handler)
return logger
action_logger = set_logger("action_logger", "log/albatrobot.log")
system_logger = set_logger("system_logger", "log/python.log", logging.DEBUG)
# Settings
users = []
admins = []
cite_path = ""
img_path = ""
logger = logging.getLogger('albatrobot_logger')
logger.setLevel(logging.DEBUG)
logformat = logging.Formatter('%(asctime)s %(levelname)s: %(message)s')
loghandler_toconsole = logging.StreamHandler()
loghandler_toconsole.setLevel(logging.DEBUG)
loghandler_toconsole.setFormatter(logformat)
loghandler_tofile = logging.FileHandler('log/albatrobot.log')
loghandler_tofile.setLevel(logging.DEBUG)
loghandler_tofile.setFormatter(logformat)
logger.addHandler(loghandler_toconsole)
logger.addHandler(loghandler_tofile)
# Internal functions
def closest(good_list, item):
distances={i:Levenshtein.distance(i,item) for i in good_list}
lowest_distance=distances[min(list(distances.keys()),
key=distances.__getitem__)]
closest_keys=[i for i, distance in distances.items() if distance == lowest_distance]
return closest_keys
"""Return the item(s) of good_list closest to item.
"Closest" means "with the shortest Levenshtein distance.
"""
distances = {}
for i in good_list:
i_dist = Levenshtein.distance(i,item)
if i_dist not in distances:
distances[i_dist] = list()
distances[i_dist].append(i)
closest_items = distances[min(distances.keys())]
return closest_items
def unauthorized_user(update, context):
context.bot.send_message(
async def unauthorized_user(update, context):
"""Inform the user that he is not authorized to used the bot."""
await context.bot.send_message(
chat_id=update.effective_chat.id,
text=_("User not authorized.")
text=_('User not authorized.')
)
def unknown_command(update, context):
context.bot.send_message(
async def unknown_command(update, context):
"""Inform the user that the command does not exist."""
await context.bot.send_message(
chat_id=update.effective_chat.id,
text=_("Unknown command.")
text=_('Unknown command.')
)
def error_handler(update, context):
system_logger.error(context.error)
print(context.error)
#def error_handler(update, context): # This should be deeply reworked. Sooner or later.
# system_logger.error(context.error)
# print(context.error)
# Albatrobot base functions
def start(update, context):
# Basic functions
async def start(update, context):
"""Show a message when a user contacts the bot for the first time."""
context.bot.send_message(
await context.bot.send_message(
chat_id=update.effective_chat.id,
text=_("Hello, I'm Albatrobot\! Type \/help for help\!\n"
"\n"
@ -103,212 +112,363 @@ def start(update, context):
)
def stop(update, context):
async def stop(update, context):
"""Stop the bot."""
pid = os.getpid()
parent = psutil.Process(pid)
action_logger.info("Albatrobot was terminated.")
await context.bot.send_message(
chat_id=update.effective_chat.id,
text=_('Albatrobot will be stopped in a few moments.')
)
logger.info('Albatrobot was stopped.')
parent.send_signal(signal.SIGTERM)
def help(update, context):
"""
Show a help message.
async def help(update, context):
"""Show a help message.
The message doesn't correspond to the inline suggestion set in
@BotFather, that must be set manually through a Telegram client.
"""
instructions = (
_("*Officially supported commands*\n"
"/help: show officialy supported commands\n"
"/legal: send legal info about privacy and copyright\n"
"/roll: roll dice following DnD notation\n"
"\n"
"Admins can also use /stop to stop the bot from Telegram in case of"
" emergency\.")
# Common commands
ccom_header = _('*Albatrobot commands*')
ccom_help = {
_('christmas'):_('get an image'),
_('easter')+_(' `\(\<author\>\)`'):_('get a citation from given author,'
' or a random one if no author is specified\.'),
_('end\_of\_year\_dinner'):_('send all citations in the archive to a'
' random user\.'),
_('epiphony')+_(' `\(\<text\>\)`'):_('get a voice message from'
' Albatrobot reading given text'),
_('help'):_('get help about bot commands\.'),
_('legal'):_('get legal info about privacy and copyright\.'),
_('roll')+_(' `\<dice\>\(\<modifiers\>\)`'):_('roll dice, with or'
' without modifiers\. Launch without arguments to get help on the'
' modifiers\.'),
_('version'):_("get Albatrobot's version")
}
ccom_help = [f'\- \/{i}: {ccom_help[i]}'
for i in ccom_help]
ccom_help.sort()
ccom_help = '\n'.join(ccom_help)
ccom_help = f'{ccom_header}\n{ccom_help}'
# Admin commands
acom_header = _('*Additional commands for bot admins*')
acom_help = {
_('init'):_('reinitialize the bot\. This updates inline suggestions'
' and purges /epiphony cached files\.'),
_('stop'):_('stop Albatrobot\. To be used _only_ in case of emergency:'
' restarting the bot after this command is used requires direct'
' access to the server\.')
}
acom_help = [f'\- \/{i}: {acom_help[i]}'
for i in acom_help]
acom_help.sort()
acom_help = '\n'.join(acom_help)
acom_help = f'{acom_header}\n{acom_help}'
# Addendum
non_admin_addendum = _(
'Bot admins have access to additional commands and can get help on'
' them using /help in a private chat\.'
)
context.bot.send_message(
# Actually sending stuff
if (update.effective_chat.type == ChatType.PRIVATE
and update.effective_user.id in ADMINS):
await context.bot.send_message(
chat_id=update.effective_user.id,
text=_(f'{ccom_help}\n\n{acom_help}'),
parse_mode=ParseMode.MARKDOWN_V2
)
else:
await context.bot.send_message(
chat_id=update.effective_chat.id,
text=_(f'{ccom_help}\n\n{non_admin_addendum}'),
parse_mode=ParseMode.MARKDOWN_V2
)
async def init(update, context):
"""Initialize the bot."""
# Set commands suggestions
commands=[BotCommand(_('roll'),_('Roll dice'))]
await context.bot.send_message(
chat_id=update.effective_chat.id,
text=instructions,
parse_mode=ParseMode.MARKDOWN_V2)
text=_('Initializing...'))
await context.bot.set_my_commands(commands)
await context.bot.send_message(
chat_id=update.effective_chat.id,
text=_('Done'))
# Delete /epiphony files
for i in os.listdir(TMPVOICEDIR):
os.remove(f'{TMPVOICEDIR}/{i}')
def legal(update, context):
async def legal(update, context):
"""
Send legal information (privacy and copyright).
"""
context.bot.send_chat_action(
await context.bot.send_chat_action(
chat_id=update.effective_chat.id,
action=ChatAction.UPLOAD_DOCUMENT)
with open("LEGAL.md","rb") as legalnotes:
context.bot.send_document(
with open('doc/LEGAL.md','rb') as legalnotes:
await context.bot.send_document(
chat_id=update.effective_chat.id,
document=legalnotes,
filename="albatrobot-legal-notes.txt")
action_logger.info("A user asked for legal info.")
filename='albatrobot-legal-notes.txt')
logger.info('A user asked for legal info.')
def version(update, context):
async def version(update, context):
"""Send Albatrobot's current version."""
context.bot.send_message(
await context.bot.send_message(
chat_id=update.effective_chat.id,
text=_("*Albatrobot's version:* {}'").format('0\.2'),
text=_("*Albatrobot's version:* {}").format(VERSION.replace('.','\.')),
parse_mode=ParseMode.MARKDOWN_V2
)
# Albatrobot commands
def roll(update, context):
"""
Roll a dice following DnD notation: <dice number>d<die faces>.
The function supports any dice number and any faces number, but bot
the modifiers.
"""
to_roll = str(context.args[0]) # Gets the argument
dnum = int(to_roll.split('d')[0])
dfac = int(to_roll.split('d')[1])
rolls = [] # Single rolls list
if dnum > 1000 or dfac > 1000:
context.bot.send_message(
chat_id=update.effective_chat.id,
text=_("{}, you can roll at most 1000 dice with at most 1000 faces"
" at once!").format(update.message.from_user.first_name))
else:
for n in range(dnum):
rolls.append(random.randint(1,dfac))
if len(rolls) == 1:
result = str(rolls[0])
elif len(rolls) > 1:
result = [str(roll) for roll in rolls]
result = '+'.join(result)
result = result + ' = ' + str(sum(rolls))
context.bot.send_message(
chat_id=update.effective_chat.id,
text=_("{user_name} rolled {result}").format(
user_name=update.message.from_user.first_name,
result=result
)
)
# Albatrobot easter eggs
def christmas(update, context):
# Commands
async def christmas(update, context):
"""Send a (hopefully funny) image from the archive."""
imgs = glob.glob(img_path)
imgs = glob.glob(str(IMGPATH+'/*'))
try:
last_msg = context.user_data["last_christmas"]
if datetime.now()-last_msg < msg_cooldown:
context.bot.send_message(
last_msg = context.user_data['last_christmas']
if datetime.now()-last_msg < MSGCOOLDOWN:
await context.bot.send_message(
chat_id=update.effective_user.id,
text=_("Christmas is good, but you can't invoke it so often!"
" Try again in some seconds...'")
text=_("Christmas is good, but you can't invoke it so often! "
"Try again in some seconds...")
)
action_logger.info("A user wants too much Christmas.")
logger.info('A user wants too much Christmas.')
return
# If last_christmas doesn't exist, it just means the user never used
# /christmas since last bot start.
except KeyError:
pass
context.bot.send_chat_action(
await context.bot.send_chat_action(
chat_id=update.effective_chat.id,
action=ChatAction.UPLOAD_PHOTO
)
context.bot.send_photo(
await context.bot.send_photo(
chat_id=update.effective_chat.id,
photo=open(random.choice(imgs), "rb")
photo=open(random.choice(imgs), 'rb')
)
context.user_data["last_christmas"] = datetime.now()
context.user_data['last_christmas'] = datetime.now()
def easter(update, context):
"""
Send a citation from the archive.
async def easter(update, context):
"""Send a citation from the archive.
User can either choose the source or let the bot pick a random one.
"""
try:
last_msg = context.user_data['last_easter']
if datetime.now()-last_msg < msg_cooldown:
context.bot.send_message(
if datetime.now()-last_msg < MSGCOOLDOWN:
await context.bot.send_message(
chat_id=update.effective_user.id,
text=_("Easter is good, but you can't invoke it so often!"
" Retry in some seconds...")
)
action_logger.info("A user wants too much Easter.")
logger.info('A user wants too much Easter.')
return
# If last_easter doesn't exist, it just means the user never used /easter
# since last bot start.
except KeyError:
pass
from resources.citations import citations
cits_archive = citations.citations
with open(CITARCHIVE,'r') as cits_file:
cits = csv.DictReader(cits_file)
authors = tuple({row['author'] for row in cits})
# If the user asked for an author...
if len(context.args) > 0:
if context.args[0] in list(cits_archive.keys()):
# ...and it exists
if context.args[0] in authors:
author = str(context.args[0])
# ...and it does not
else:
closest_authors = closest(list(cits_archive.keys()),context.args[0])
closest_authors = closest(authors,context.args[0])
if len(closest_authors) == 1:
suggestion = _("The most similar is {}.").format(closest_authors[0])
suggestion = _('The most similar is {}.').format(closest_authors[0])
else:
suggestion = _("The most similar are {}.").format(
str(", ".join(i for i in closest_authors[:-1])
+ " e "
+ closest_authors[-1]
)
)
context.bot.send_message(
last_author = closest_authors[-1]
other_authors = ', '.join(closest_authors[:-1])
suggestion = _('The most similar are {} and {}.').format(
other_authors, last_author)
await context.bot.send_message(
chat_id=update.effective_chat.id,
text=_("Author {required_author} is not present in the archive!"
" {suggestion}\n"
"\n"
"N.b.: CaSe Is ImPoRtAnT!").format(
text=_('Author {required_author} is not present in the archive!'
' {suggestion}\n'
'\n'
'N.b.: CaSe Is ImPoRtAnT!').format(
required_author=context.args[0],
suggestion=suggestion)
)
action_logger.info("A user wants easter from non registered"
" author {author}.".format(author=context.args[0]))
logger.info('A user wants easter from non registered'
' author {author}.'.format(author=context.args[0]))
return
# If he did not
else:
author = random.choice(list(cits_archive.keys()))
context.bot.send_message(
author = random.choice(authors)
with open(CITARCHIVE,'r') as cits_file:
cits = csv.DictReader(cits_file)
cits_pool = [cit['text'] for cit in cits if cit['author'] == author]
await context.bot.send_message(
chat_id=update.effective_chat.id,
text="{cit}\n\n~{author}".format(
cit=random.choice(cits_archive[author]),
text=_('{cit}\n\n~{author}').format(
cit=random.choice(cits_pool),
author=author
)
)
context.user_data["last_easter"] = datetime.now()
context.user_data['last_easter'] = datetime.now()
def end_of_year_dinner(update, context):
"""Sends a user each citation from the archive."""
async def end_of_year_dinner(update, context):
"""Send a user each citation from the archive."""
# Importing here is so that the citations list can be updated without
# restarting the bot.
from resources.citations import citations
cits_archive = citations.citations
with open(CITARCHIVE,'r') as cits_file:
cits_archive = list(csv.DictReader(cits_file))
cits_number = 0
dest = random.choice(users)
context.bot.send_message(
chat_id=dest,
text=_("Hello there, here an end of year dinner offered by {} !").format(
dest_user_id = random.choice(USERS)
dest_user = await context.bot.getChatMember(chat_id=dest_user_id,user_id=dest_user_id)
dest_user_firstname = dest_user.user.first_name
await context.bot.send_message(
chat_id=dest_user_id,
text=_('Hello there, here an end of year dinner offered by {}!').format(
update.message.from_user.first_name)
)
for author in list(cits_archive.keys()):
for cit in cits_archive[author]:
context.bot.send_message(
chat_id=dest,
text='{cit}\n\n~{author}'.format(cit=cit,author=author)
)
cits_number += 1
time.sleep(1.1) # To avoid hitting Telegram's message rate limit
context.bot.send_message(
for cit in cits_archive:
await context.bot.send_message(
chat_id=dest_user_id,
text=_('{cit}\n\n~{author}'.format(cit=cit['text'],author=cit['author']))
)
cits_number +=1
time.sleep(1.1) # To avoid hitting Telegram's message rate limit
await context.bot.send_message(
chat_id=update.effective_chat.id,
text=_("End of year dinner of {cits_number} plates delvered to {dest}!").format(
text=_('End of year dinner of {cits_number} plates delvered to {dest}!').format(
cits_number=cits_number,
dest=context.bot.getChatMember(chat_id=dest,user_id=dest).user.first_name
dest=dest_user_firstname
)
)
action_logger.info("A user sent an end of year dinner.")
logger.info('A user sent an end of year dinner.')
async def epiphony(update, context):
sentence = ' '.join(context.args).upper()
if len(sentence) == 0:
await context.bot.send_message(
chat_id=update.effective_chat.id,
text=_('What should I say?')
)
else:
sentence_hash = hashlib.md5(sentence.encode(encoding='UTF-8')).hexdigest()
voice_file = f'{TMPVOICEDIR}/{sentence_hash}.ogg'
await context.bot.send_chat_action(
chat_id=update.effective_chat.id,
action=ChatAction.RECORD_VOICE)
if not os.path.exists(voice_file):
engine = pyttsx3.init()
engine.setProperty('voice',YELLLANG)
engine.setProperty('rate',YELLSPEED)
engine.save_to_file(sentence,voice_file)
engine.runAndWait()
time.sleep(1) # Workaround to prevent ReferenceError due to using
# pyttsx3 with threads
await context.bot.send_voice(
chat_id=update.effective_chat.id,
voice=open(voice_file,'rb'))
async def roll(update, context):
"""Roll a dice following DnD notation: <dice number>d<die faces>.
Many modifiers are supported. Dice number and faces are limited to 500
each, to prevent DoS effects.
"""
to_roll = ''.join(context.args)
if len(to_roll) == 0:
roll_modifiers = {
'\+n':_('add `n` to the roll'),
'\-n':_('subtract `n` from the roll'),
'\*n':_('multiply the roll by `n`'),
'/n':_('divide the roll by `n`'),
'//n':_('floor divide the roll by `n`'),
'\*\*n':_('exponentiate the roll to `n`'),
'K\(n\)':_('keep highest `n` rolls \(defaults to `1`\)'),
'k\(n\)':_('keep lowest `n` rolls \(defaults to `1`\)'),
'X\(n\)':_('drop highest `n` rolls \(defaults to `1`\)'),
'x\(n\)':_('drop lowest `n` rolls \(defaults to `1`\)'),
'\!\(n\)':_('explode rolls equal to `n` \(defaults to die size\)'),
'\!\<n':_('explode rolls lower than `n`'),
'\!\>n':_('explode rolls higher than `n`'),
'f\<n':_('count failures as rolls lower than `n`'),
'f\>n':_('count failures as rolls higher than `n`'),
'\<n':_('count successes as rolls lower than `n`'),
'\>n':_('count successes as rolls higher than `n`'),
'\!p':_('penetrate rolls equal to dice size'),
'\!p\<n':_('penetrate rolls lower than `n`'),
'\!p\>n':_('penetrate rolls higher than `n`'),
'an':_('add `n` to each roll'),
'sn':_('subtract `n` from each roll'),
'mn':_('multiply by `n` each roll'),
'R\(n\)':_('reroll each dice that rolled `n` until there are no'
' `n` left\(defaults to `1`\)'),
'r\(n\)':_('reroll once each dice that rolled `n` \(defaults to'
' 1\)'),
'R\<n':_('reroll dice that rolled lower than `n` until there are'
' no results lower than `n` left'),
'R\>n':_('reroll dice that rolled higher than `n` until there'
' no results higher than `n` left'),
'r\<n':_('reroll once dice that roll lower than `n`'),
'r\>n':_('reroll once dice that roll higher than `n`')
}
roll_modifiers = [f'\- `{i}`: {roll_modifiers[i]}' for i in roll_modifiers]
roll_modifiers = '\n'.join(roll_modifiers)
roll_help = _(
"To roll, use the syntax `\<number of dice\>d\<dice size\>`\. You"
" can also use the following modifiers\. Note that:\n"
"\- `\(n\)` means that an integer number may be specified, but if"
" it isn't the specified default value is assumed\n"
"\- `n` means that an integer number is required\n"
"\- any other symbol means exactly that symbol\n"
"\n"
"\n"
)+roll_modifiers # "+" is a workaround to incompatibility between
# f-strings and parse_mode
await context.bot.send_message(
chat_id=update.effective_chat.id,
text=roll_help,
parse_mode=ParseMode.MARKDOWN_V2,
disable_web_page_preview=True
)
elif max([int(i) for i in find('\d+',to_roll)]) > 500: # Prevent DoS
await context.bot.send_message(
chat_id=update.effective_chat.id,
text=_('You can roll up to 500 dice with up to 500 faces at once!')
)
else:
try:
the_roll = roll_dice(to_roll)
await context.bot.send_message(
chat_id=update.effective_chat.id,
text=_('{user_name} rolled {rolls} = {result}').format(
user_name=update.message.from_user.first_name,
rolls=the_roll[1],result=the_roll[0]
)
)
except rolldice.DiceGroupException:
await context.bot.send_message(
chat_id=update.effective_chat.id,
text=_('You used a wrong syntax!')
)

27
constants.py Normal file
View File

@ -0,0 +1,27 @@
import configparser
from datetime import timedelta
config = configparser.ConfigParser()
config.read("settings.ini")
# Bot
TOKEN = config["Bot"]["Token"]
# Users
ADMINS = [int(admin) for admin in config["Users"]["Admins"].split(",")]
USERS = [int(user) for user in config["Users"]["Users"].split(",")]
# Settings
LANG = config["Settings"]["Language"]
MSGCOOLDOWN = timedelta(seconds=int(config["Settings"]["MsgCooldown"]))
PROXY = config["Settings"]["Proxy"]
YELLLANG = config['Settings']['YellLang']
YELLSPEED = config['Settings']['YellSpeed']
# Resources
CITARCHIVE = config["Resources"]["CitArchive"]
IMGPATH = config["Resources"]["ImgPath"]
# Private
TMPVOICEDIR = 'tmp'
VERSION = "0.3"

39
doc/CHANGELOG.md Normal file
View File

@ -0,0 +1,39 @@
---
gitea: none
include_toc: true
---
## v0.3
### New features
- Inline suggestions
- New commands:
- `/epiphony`, to let Albatrobot speak
- `/init`, to set again commands suggestions and clean `/epiphony` "cache"
- Support for proxy
- Support for roll modifiers
### Enhancements
- Better help texts
- Enhance "no images available" image
### Code
- Constants moved to own module
### Misc
- Update dependencies, including `python-telegram-bot` to v20
- Misc cleanup and fixes
## v0.2
* Added admins allowed to stop the bot from Telegram
* Completely anonymized logs
* Added privacy info
* Improved logs
* License compliance and update
* General cleanup
* Documentation fixes

21
doc/CONTRIBUTING.md Normal file
View File

@ -0,0 +1,21 @@
I develop ALbatrobot for my own use.
Everyone is welcome to propose his contribution:
1. through pull requests on the [Disroot
repository](https://git.disroot.org/bursa-pastoris/albatrobot/pulls), or
2. by email at `bursapastoris at disroot dot org`, as either patches or pull
requests from other repositories.[^email]
But note that, as Albatrobot is a personal project, I will merge only the
contributions that I find useful or interesting *for me*. However, everyone
can enjoy the rights granted by [AGPL-3.0-only license](./LICENSE) to fork the
project and do whatever they want on their copies (as long as the license is
honoured).
[^email]: If you want to send contributes by email, see
[here](https://git.disroot.org/bursapastoris/info/contact.md) for more
information about how to contact me by email and
[here](https://git-scm.com/book/en/v2/Distributed-Git-Contributing-to-a-Project)
for how to use `git format-patch`.

View File

@ -1,6 +1,7 @@
## Privacy
Albatrobot's developer does not process any personal data under the definition of the
Albatrobot's developer does not process any personal data under the definition
of the
[GDPR](https://eur-lex.europa.eu/legal-content/EN/TXT/?uri=CELEX:32016R0679)
and therefore is not a data controller.
@ -25,14 +26,14 @@ Also, anonymous logs are stored:
* `/easter`, if the user hits the time limit or asks for an unknown author
* whenever an error is raised
The logs are intended for debug and issue resolution only and never contain
The logs are intended for debug and issue resolution only and *never* contain
personal data.
## Copyright
Albatrobot is proudly [free
software](https://www.gnu.org/philosophy/free-sw.html#four-freedoms) (as both
in "free speech" and "free beer") and always will be, as it is released under
in "free speech" and "free beer"), and always will be, as it is released under
[AGPL-3.0-only](./LICENSE.md) license. Its source code is publicly available
at [Disroot](https://git.disroot.org/bursapastoris/albatrobot).

30
doc/RELEASE_PROCESS.md Normal file
View File

@ -0,0 +1,30 @@
To release a new version:
1. Checkout a new branch named `release-v{release}`
2. Complete the checklist *in given order*. If anything changes, restart from
the first step.
- [ ] Check if dependencies are up to date
- [ ] Reorder stuff in source code (imports, requirements, function
definitions, command handlers...)
- [ ] Update docstrings and comments
- [ ] Update /help messages
- [ ] Update translation
- [ ] the template
- [ ] each known language
- [ ] Update version number
- [ ] in `constants.py`
- [ ] in the updated translations
- [ ] Update documentation not listed below
- [ ] Update `doc/README.md`
- [ ] Update `doc/CHANGELOG.md`
- [ ] _At least now_, test _each command_, both as admin and simple user, to
identify new bugs; testing English is mandatory, all other languages are
recommended
- [ ] If the items were checked, *uncheck them*!
Using at least one commit per item is recommended. They can be squashed
afterwards.
3. Merge the branch in `master`
4. Tag the merge commit as `v{release}` and push

View File

@ -1,43 +1,51 @@
# Translate
---
gitea: none
include_toc: true
---
This document explains how to translate the Albatrobot.
## Prerequisites
To translate the Albatrobot, you will need to have the [`gettext`
To translate the Albatrobot, you need to have the [`gettext`
package](https://www.gnu.org/software/gettext/) from the GNU project installed
in your system. In Debian, you can install with `sudo apt install gettext`.
## Translate to a new language
To translate to a new language, create a path named
`locales/<lang>/LC_MESSAGES` (where `<lang>` is the two-letters code of the
language you are translasting to), then run `xgettext main.py albatrobot.py -o
`locales/<lang>/LC_MESSAGES`, where `<lang>` is the two-letters code of the
language you are translasting to, then run `xgettext main.py albatrobot.py -o
locales/<lang>/LC_MESSAGES/template.to`.
Edit `locales/<lang>/LC_MESSAGES/template.to`. You will find several lines of
metadata and after a serie of groups of rows like the following:
metadata and after a serie of groups of rows like the following:
#: main.py:59
msgid "help"
msgstr ""
```
#: main.py:59
msgid "help"
msgstr ""
```
The meaning is the following.
* `main.py` is the source file the string comes from.
* `59 ` is the number of the line in the source file where the string begins.
* In `msgid "help"`, `msgid` tells that what follows is the string as it is
written in the source code, `help` is the string itself. The string is always
written between quotation marks ( `"..."`).
* In `msgstr` tells that what follow is the string translation, `aide` is the
translation itself.
* In `msgstr ""` tells that what follow is the string translation. It will be
empty because the string was never translated.
To translate you must edit the content of the quotation marks after `msgstr`,
adding the translation. For example, if you were translating to French the
lines above would become
#: main.py:59
msgid "help"
msgstr "aide"
```
#: main.py:59
msgid "help"
msgstr "aide"
```
Any not translates string will be printed as it is written in the source code.
For example, if you didn't translate to French the example above the Albatrobot
@ -52,11 +60,10 @@ commit it.
### Existing strings
If you just want to update the translation of already translated strings, an
existing translation, just edit `locales/<lang>/LC_MESSAGES/template.po`, where
`<lang>` is the two-letter code of the language you are updating. For an
explanation of the content of that file, see [Translate to a new
language](#Translate to a new language).
If you just want to update the translation of already translated strings, just
edit `locales/<lang>/LC_MESSAGES/template.po`, where `<lang>` is the two-letter
code of the language you are updating. For an explanation of the content of
that file, see [Translate to a new language](#Translate to a new language).
When you are happy with your work, run `msgfmt template.to`. This will produce
`messages.mo`: it is a binary file with your translation. You need it to test
@ -75,13 +82,25 @@ If you want to translate strings that are present in the source code but not in
You can now go on as desribed in [Existing strings](#Existing strings).
Running `msgmerge` may mark some translations with `#, fuzzy`, such as in
```
#: main.py:59
#, fuzzy
msgid "help"
msgstr "aide"
```
This means that something in that these translation changed. You can check them
and if necessary fix them and then remove the `#, fuzzy` line.
### String removal
If a string is removed from the main source code, the next run of `msgmerge` on
each translation will move the translation of such string to the bottom of the
`.po` file and prepend each of its lines with `#~`. You should remove them
before merging the updated translation file. However, if you forget to do so it
will be done before the next release.
before committing the updated translation file. However, if you forget to do so
it will be done before the next release.
## Metadata
@ -91,17 +110,17 @@ changed to the following.
| metadata | value |
| ------------------------------------- | -------------------------------------------------------------------------------------------------------- |
| `SOME DESCRIPTIVE TITLE` | `Albatrabot translation` |
| `SOME DESCRIPTIVE TITLE` | `Albatrabot translation.` |
| | |
| `YEAR THE PACKAGE'S COPYRIGHT HOLDER` | `2021 bursa-pastoris` |
| `YEAR THE PACKAGE'S COPYRIGHT HOLDER` | `2021, 2023 bursa-pastoris` |
| | |
| `PACKAGE` | `Albatrobot` |
| | |
| `FIRST AUTHOR <EMAIL@ADDRESS>, YEAR` | `bursa-pastoris <bursapastoris at disroot dot org>, 2021` |
| `FIRST AUTHOR <EMAIL@ADDRESS>, YEAR` | `bursa-pastoris <bursapastoris at disroot dot org>, 2021, 2023.` |
| | |
| `PACKAGE VERSION` | `1.2` |
| `PACKAGE VERSION` | `0.3` |
| | |
| `YEAR-MO-DA HO:MI+ZONE` | `<year>-<month>-<day> <hour>:<minute>+<timezone>` the translation was created at and in.[^creation-time] |
| `YEAR-MO-DA HO:MI+ZONE` | `<year>-<month>-<day> <hour>:<minute>+<timezone>` the translation was created at and in.[^creation-time] |
| | |
| `FULL NAME <EMAIL@ADDRESS>` | `<translator's name or nickname> <translator's email address>` |
| | |

View File

@ -1,63 +1,71 @@
# Albatrobot translation.
# Copyright (C) 2021 bursa-pastoris
# Translation template for Albatrobot.
# Copyright (C) 2021, 2023 bursa-pastoris
# This file is distributed under the same license as the Albatrobot package.
# bursa-pastoris <bursapastoris at disroot dot org>, 2021.
# bursa-pastoris <bursapastoris at disroot dot org>, 2021, 2023.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: 0.2\n"
"Project-Id-Version: Albatrobot 0.3\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2022-01-01 19:49+0100\n"
"PO-Revision-Date: 2022-01-01 19:53+0100\n"
"Last-Translator: bursa-pastoris <bursapastoris at disroot dot org>\n"
"Language-Team: Englsih <bursapastoris at disroot dot org>\n"
"POT-Creation-Date: 2023-03-23 00:00+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
#: main.py:68 main.py:72 main.py:76
#: main.py:48 main.py:60 albatrobot.py:161
msgid "stop"
msgstr ""
#: main.py:79
#: main.py:48 main.py:69 albatrobot.py:159
msgid "init"
msgstr ""
#: main.py:65 albatrobot.py:143
msgid "help"
msgstr ""
#: main.py:82
#: main.py:73 albatrobot.py:144
msgid "legal"
msgstr ""
#: main.py:85
#: main.py:77 albatrobot.py:148
msgid "version"
msgstr ""
#: main.py:90
msgid "roll"
msgstr ""
#: main.py:95
#: main.py:84 albatrobot.py:136
msgid "christmas"
msgstr ""
#: main.py:98
#: main.py:88 albatrobot.py:137
msgid "easter"
msgstr ""
#: main.py:101
#: main.py:92
msgid "end_of_year_dinner"
msgstr ""
#: albatrobot.py:73
#: main.py:96 albatrobot.py:141
msgid "epiphony"
msgstr ""
#: main.py:100 albatrobot.py:145 albatrobot.py:196
msgid "roll"
msgstr ""
#: albatrobot.py:81
msgid "User not authorized."
msgstr ""
#: albatrobot.py:80
#: albatrobot.py:89
msgid "Unknown command."
msgstr ""
#: albatrobot.py:94
#: albatrobot.py:103
msgid ""
"Hello, I'm Albatrobot\\! Type \\/help for help\\!\n"
"\n"
@ -69,49 +77,139 @@ msgid ""
msgstr ""
#: albatrobot.py:121
msgid "Albatrobot will be stopped in a few moments."
msgstr ""
#: albatrobot.py:134
msgid "*Albatrobot commands*"
msgstr ""
#: albatrobot.py:136
msgid "get an image"
msgstr ""
#: albatrobot.py:137
msgid " `\\(\\<author\\>\\)`"
msgstr ""
#: albatrobot.py:137
msgid ""
"*Officially supported commands*\n"
"/help: show officialy supported commands\n"
"/legal: send legal info about privacy and copyright\n"
"/roll: roll dice following DnD notation\n"
"\n"
"Admins can also use /stop to stop the bot from Telegram in case of emergency"
"\\."
"get a citation from given author, or a random one if no author is "
"specified\\."
msgstr ""
#: albatrobot.py:154
msgid "*Albatrobot's version:* {}'"
#: albatrobot.py:139
msgid "end\\_of\\_year\\_dinner"
msgstr ""
#: albatrobot.py:174
msgid "{}, you can roll at most 1000 dice with at most 1000 faces at once!"
#: albatrobot.py:139
msgid "send all citations in the archive to a random user\\."
msgstr ""
#: albatrobot.py:187
#: albatrobot.py:141
msgid " `\\(\\<text\\>\\)`"
msgstr ""
#: albatrobot.py:141
msgid "get a voice message from Albatrobot reading given text"
msgstr ""
#: albatrobot.py:143
msgid "get help about bot commands\\."
msgstr ""
#: albatrobot.py:144
msgid "get legal info about privacy and copyright\\."
msgstr ""
#: albatrobot.py:145
msgid " `\\<dice\\>\\(\\<modifiers\\>\\)`"
msgstr ""
#: albatrobot.py:145
msgid ""
"roll dice, with or without modifiers\\. Launch without arguments to get help "
"on the modifiers\\."
msgstr ""
#: albatrobot.py:148
msgid "get Albatrobot's version"
msgstr ""
#: albatrobot.py:157
msgid "*Additional commands for bot admins*"
msgstr ""
#: albatrobot.py:159
msgid ""
"reinitialize the bot\\. This updates inline suggestions and purges /epiphony "
"cached files\\."
msgstr ""
#: albatrobot.py:161
msgid ""
"stop Albatrobot\\. To be used _only_ in case of emergency: restarting the "
"bot after this command is used requires direct access to the server\\."
msgstr ""
#: albatrobot.py:173
msgid ""
"Bot admins have access to additional commands and can get help on them "
"using /help in a private chat\\."
msgstr ""
#: albatrobot.py:182
#, python-brace-format
msgid "{user_name} rolled {result}"
msgid ""
"{ccom_help}\n"
"\n"
"{acom_help}"
msgstr ""
#: albatrobot.py:188
#, python-brace-format
msgid ""
"{ccom_help}\n"
"\n"
"{non_admin_addendum}"
msgstr ""
#: albatrobot.py:196
msgid "Roll dice"
msgstr ""
#: albatrobot.py:199
msgid "Initializing..."
msgstr ""
#: albatrobot.py:203
msgid ""
"Christmas is good, but you can't invoke it so often! Try again in some "
"seconds...'"
msgid "Done"
msgstr ""
#: albatrobot.py:233
#: albatrobot.py:228
msgid "*Albatrobot's version:* {}"
msgstr ""
#: albatrobot.py:242
msgid ""
"Christmas is good, but you can't invoke it so often! Try again in some "
"seconds..."
msgstr ""
#: albatrobot.py:273
msgid ""
"Easter is good, but you can't invoke it so often! Retry in some seconds..."
msgstr ""
#: albatrobot.py:250
#: albatrobot.py:296
msgid "The most similar is {}."
msgstr ""
#: albatrobot.py:252
msgid "The most similar are {}."
#: albatrobot.py:300
msgid "The most similar are {} and {}."
msgstr ""
#: albatrobot.py:260
#: albatrobot.py:304
#, python-brace-format
msgid ""
"Author {required_author} is not present in the archive! {suggestion}\n"
@ -119,11 +217,170 @@ msgid ""
"N.b.: CaSe Is ImPoRtAnT!"
msgstr ""
#: albatrobot.py:295
msgid "Hello there, here an end of year dinner offered by {} !"
#: albatrobot.py:324 albatrobot.py:352
#, python-brace-format
msgid ""
"{cit}\n"
"\n"
"~{author}"
msgstr ""
#: albatrobot.py:309
#: albatrobot.py:345
msgid "Hello there, here an end of year dinner offered by {}!"
msgstr ""
#: albatrobot.py:359
#, python-brace-format
msgid "End of year dinner of {cits_number} plates delvered to {dest}!"
msgstr ""
#: albatrobot.py:372
msgid "What should I say?"
msgstr ""
#: albatrobot.py:402
msgid "add `n` to the roll"
msgstr ""
#: albatrobot.py:403
msgid "subtract `n` from the roll"
msgstr ""
#: albatrobot.py:404
msgid "multiply the roll by `n`"
msgstr ""
#: albatrobot.py:405
msgid "divide the roll by `n`"
msgstr ""
#: albatrobot.py:406
msgid "floor divide the roll by `n`"
msgstr ""
#: albatrobot.py:407
msgid "exponentiate the roll to `n`"
msgstr ""
#: albatrobot.py:408
msgid "keep highest `n` rolls \\(defaults to `1`\\)"
msgstr ""
#: albatrobot.py:409
msgid "keep lowest `n` rolls \\(defaults to `1`\\)"
msgstr ""
#: albatrobot.py:410
msgid "drop highest `n` rolls \\(defaults to `1`\\)"
msgstr ""
#: albatrobot.py:411
msgid "drop lowest `n` rolls \\(defaults to `1`\\)"
msgstr ""
#: albatrobot.py:412
msgid "explode rolls equal to `n` \\(defaults to die size\\)"
msgstr ""
#: albatrobot.py:413
msgid "explode rolls lower than `n`"
msgstr ""
#: albatrobot.py:414
msgid "explode rolls higher than `n`"
msgstr ""
#: albatrobot.py:415
msgid "count failures as rolls lower than `n`"
msgstr ""
#: albatrobot.py:416
msgid "count failures as rolls higher than `n`"
msgstr ""
#: albatrobot.py:417
msgid "count successes as rolls lower than `n`"
msgstr ""
#: albatrobot.py:418
msgid "count successes as rolls higher than `n`"
msgstr ""
#: albatrobot.py:419
msgid "penetrate rolls equal to dice size"
msgstr ""
#: albatrobot.py:420
msgid "penetrate rolls lower than `n`"
msgstr ""
#: albatrobot.py:421
msgid "penetrate rolls higher than `n`"
msgstr ""
#: albatrobot.py:422
msgid "add `n` to each roll"
msgstr ""
#: albatrobot.py:423
msgid "subtract `n` from each roll"
msgstr ""
#: albatrobot.py:424
msgid "multiply by `n` each roll"
msgstr ""
#: albatrobot.py:425
msgid ""
"reroll each dice that rolled `n` until there are no `n` left\\(defaults to "
"`1`\\)"
msgstr ""
#: albatrobot.py:427
msgid "reroll once each dice that rolled `n` \\(defaults to 1\\)"
msgstr ""
#: albatrobot.py:429
msgid ""
"reroll dice that rolled lower than `n` until there are no results lower than "
"`n` left"
msgstr ""
#: albatrobot.py:431
msgid ""
"reroll dice that rolled higher than `n` until there no results higher than "
"`n` left"
msgstr ""
#: albatrobot.py:433
msgid "reroll once dice that roll lower than `n`"
msgstr ""
#: albatrobot.py:434
msgid "reroll once dice that roll higher than `n`"
msgstr ""
#: albatrobot.py:439
msgid ""
"To roll, use the syntax `\\<number of dice\\>d\\<dice size\\>`\\. You can "
"also use the following modifiers\\. Note that:\n"
"\\- `\\(n\\)` means that an integer number may be specified, but if it isn't "
"the specified default value is assumed\n"
"\\- `n` means that an integer number is required\n"
"\\- any other symbol means exactly that symbol\n"
"\n"
"\n"
msgstr ""
#: albatrobot.py:458
msgid "You can roll up to 500 dice with up to 500 faces at once!"
msgstr ""
#: albatrobot.py:465
#, python-brace-format
msgid "{user_name} rolled {rolls} = {result}"
msgstr ""
#: albatrobot.py:473
msgid "You used a wrong syntax!"
msgstr ""

View File

@ -1,63 +1,70 @@
# Albatrobot translation.
# Copyright (C) 2021 bursa-pastoris
# This file is distributed under the same license as the Albatrobot package.
# bursa-pastoris <bursapastoris at disroot dot org>, 2021.
# bursa-pastoris <bursapastoris at disroot dot org>, 2021, 2023.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: 0.2\n"
"Project-Id-Version: 0.3\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2022-01-01 19:49+0100\n"
"PO-Revision-Date: 2022-01-01 19:55+0100\n"
"POT-Creation-Date: 2023-03-23 00:00+0000\n"
"PO-Revision-Date: 2023-03-0 00:00+0000\n"
"Last-Translator: bursa-pastoris <bursapastoris at disroot dot org>\n"
"Language-Team: Italian <bursapastoris at disroot dot org>\n"
"Language: \n"
"Language: it\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
#: main.py:68 main.py:72 main.py:76
#: main.py:48 main.py:60 albatrobot.py:161
msgid "stop"
msgstr "stop"
#: main.py:79
#: main.py:48 main.py:69 albatrobot.py:159
msgid "init"
msgstr "imposta"
#: main.py:65 albatrobot.py:143
msgid "help"
msgstr "aiuto"
#: main.py:82
#: main.py:73 albatrobot.py:144
msgid "legal"
msgstr "legge"
msgstr "burocrazia"
#: main.py:85
#: main.py:77 albatrobot.py:148
msgid "version"
msgstr "versione"
#: main.py:90
msgid "roll"
msgstr "tira"
#: main.py:95
#: main.py:84 albatrobot.py:136
msgid "christmas"
msgstr "natale"
#: main.py:98
#: main.py:88 albatrobot.py:137
msgid "easter"
msgstr "pasqua"
#: main.py:101
#: main.py:92
msgid "end_of_year_dinner"
msgstr "cenone_di_fine_anno"
#: albatrobot.py:73
#: main.py:96 albatrobot.py:141
msgid "epiphony"
msgstr "epifonia"
#: main.py:100 albatrobot.py:145 albatrobot.py:196
msgid "roll"
msgstr "tira"
#: albatrobot.py:81
msgid "User not authorized."
msgstr "Utente non autorizzato."
#: albatrobot.py:80
#: albatrobot.py:89
msgid "Unknown command."
msgstr "Comando sconosciuto."
#: albatrobot.py:94
#: albatrobot.py:103
msgid ""
"Hello, I'm Albatrobot\\! Type \\/help for help\\!\n"
"\n"
@ -76,77 +83,353 @@ msgstr ""
"bursapastoris\\/albatrobot)\\._"
#: albatrobot.py:121
#, fuzzy
msgid "Albatrobot will be stopped in a few moments."
msgstr "L'Albatrobot sarà arrestato tra un istante."
#: albatrobot.py:134
msgid "*Albatrobot commands*"
msgstr "*Comandi dell'Albatrobot*"
#: albatrobot.py:136
msgid "get an image"
msgstr "ricevi un'immagine"
#: albatrobot.py:137
msgid " `\\(\\<author\\>\\)`"
msgstr " `\\(\\<autore\\>\\)`"
#: albatrobot.py:137
msgid ""
"*Officially supported commands*\n"
"/help: show officialy supported commands\n"
"/legal: send legal info about privacy and copyright\n"
"/roll: roll dice following DnD notation\n"
"\n"
"Admins can also use /stop to stop the bot from Telegram in case of emergency"
"\\."
"get a citation from given author, or a random one if no author is "
"specified\\."
msgstr ""
"*Comandi ufficialmente supportati*\n"
"/aiuto: mostra i comandi ufficialmente supportati\n"
"/legge: invia le informazioni legali su privacy e diritto d'autore\n"
"/tira: tira un dado secondo la notazione di DnD"
"\n"
"Gli amministratori possono anche usare /stop per arrestre il bot da Telegram "
"in caso di emergenza\\."
"ricevi una citazione dall'autore indicato, o da uno casuale se non viene "
"specificato nessun autore\\."
#: albatrobot.py:154
msgid "*Albatrobot's version:* {}'"
msgstr "*Versione dell'Albatrobot:* {}"
#: albatrobot.py:139
msgid "end\\_of\\_year\\_dinner"
msgstr "cenone\\_di\\_fine\\_anno"
#: albatrobot.py:174
msgid "{}, you can roll at most 1000 dice with at most 1000 faces at once!"
#: albatrobot.py:139
msgid "send all citations in the archive to a random user\\."
msgstr "invia tutte le citazioni nell'archivio a un utente casuale\\."
#: albatrobot.py:141
msgid " `\\(\\<text\\>\\)`"
msgstr " `\\(\\testo\\>\\)`"
#: albatrobot.py:141
msgid "get a voice message from Albatrobot reading given text"
msgstr ""
"{}, puoi tirare al massimo 1000 dadi con al massimo 1000 facce per volta!"
"ricevi un messaggio vocale dall'Albatrobot che legge il testo specificato"
#: albatrobot.py:187
#: albatrobot.py:143
msgid "get help about bot commands\\."
msgstr "ricevi aiuto sui comandi del bot\\."
#: albatrobot.py:144
msgid "get legal info about privacy and copyright\\."
msgstr "ricevi informazioni legali su riservatezza e diritto d'autore\\."
#: albatrobot.py:145
msgid " `\\<dice\\>\\(\\<modifiers\\>\\)`"
msgstr " `\\dadi\\>\\(\\<modificatori\\>\\)`"
#: albatrobot.py:145
msgid ""
"roll dice, with or without modifiers\\. Launch without arguments to get help "
"on the modifiers\\."
msgstr ""
"tira dei dadi, con o senza modificatori\\. Lancia senza argomenti per "
"ricevere aiuto sui modificatori\\."
#: albatrobot.py:148
msgid "get Albatrobot's version"
msgstr "ricevi la versione dell'Albatrobot"
#: albatrobot.py:157
msgid "*Additional commands for bot admins*"
msgstr "*Comandi aggiuntivi per gli amministratori del bot*"
#: albatrobot.py:159
msgid ""
"reinitialize the bot\\. This updates inline suggestions and purges /epiphony "
"cached files\\."
msgstr ""
"reimposta il bot\\. Questo aggiorna i suggerimenti in linea e rimuove i "
"documenti conservati per /epifonia\\."
#: albatrobot.py:161
msgid ""
"stop Albatrobot\\. To be used _only_ in case of emergency: restarting the "
"bot after this command is used requires direct access to the server\\."
msgstr ""
"arresta l'ALbatrobot\\. Da usare _solo_ in caso di emergenza: riavviare il "
"bot dopo che questo comando è stato utilizzato richiede accesso diretto al "
"server\\."
#: albatrobot.py:173
msgid ""
"Bot admins have access to additional commands and can get help on them "
"using /help in a private chat\\."
msgstr ""
"Gli amministratori del bot hanno accesso a comandi aggiuntivi e possono "
"ricevere aiuto su di essi usando /aiuto in una conversazione privata\\."
#: albatrobot.py:182
#, python-brace-format
msgid "{user_name} rolled {result}"
msgstr "{user_name} ha tirato {result}"
msgid ""
"{ccom_help}\n"
"\n"
"{acom_help}"
msgstr ""
"{ccom_help}\n"
"\n"
"{acom_help}"
#: albatrobot.py:188
#, python-brace-format
msgid ""
"{ccom_help}\n"
"\n"
"{non_admin_addendum}"
msgstr ""
"{ccom_help}\n"
"\n"
"{non_admin_addendum}"
#: albatrobot.py:196
msgid "Roll dice"
msgstr "Tira dei dadi"
#: albatrobot.py:199
msgid "Initializing..."
msgstr "Reimpostazione in corso..."
#: albatrobot.py:203
msgid "Done"
msgstr "Fatto"
#: albatrobot.py:228
msgid "*Albatrobot's version:* {}"
msgstr "*Versione dell'Albatrobot:* {}"
#: albatrobot.py:242
msgid ""
"Christmas is good, but you can't invoke it so often! Try again in some "
"seconds...'"
"seconds..."
msgstr ""
"Il Natale è bello, ma non puoi invocarlo così spesso! RIprova tra qualche "
"secondo..."
#: albatrobot.py:233
#: albatrobot.py:273
msgid ""
"Easter is good, but you can't invoke it so often! Retry in some seconds..."
msgstr ""
"La Pasqua è bella, ma non puoi invocarla così spesso! Riprova tra qualche "
"secondo..."
#: albatrobot.py:250
#: albatrobot.py:296
msgid "The most similar is {}."
msgstr "Il più simile è {}."
#: albatrobot.py:252
msgid "The most similar are {}."
msgstr "I più simili sono {}."
#: albatrobot.py:300
msgid "The most similar are {} and {}."
msgstr "I più simili sono {} e {}."
#: albatrobot.py:260
#: albatrobot.py:304
#, python-brace-format
msgid ""
"Author {required_author} is not present in the archive! {suggestion}\n"
"\n"
"N.b.: CaSe Is ImPoRtAnT!"
msgstr ""
"L'autore {required_authr} non è presente nell'archivio! {suggestion}\n"
"L'autore {required_author} non è presente nell'archivio! {suggestion}\n"
"\n"
"N.b.: Le MaIuScOlE sOnO iMpOrTaNtI!"
#: albatrobot.py:295
msgid "Hello there, here an end of year dinner offered by {} !"
#: albatrobot.py:324 albatrobot.py:352
#, python-brace-format
msgid ""
"{cit}\n"
"\n"
"~{author}"
msgstr ""
"{cit}\n"
"\n"
"~{author}"
#: albatrobot.py:345
msgid "Hello there, here an end of year dinner offered by {}!"
msgstr "Ciao, ecco un cenone di fine anno offerta da {}!"
#: albatrobot.py:309
#: albatrobot.py:359
#, python-brace-format
msgid "End of year dinner of {cits_number} plates delvered to {dest}!"
msgstr "Cenone di fine anno di {cits_number} portate consegnato a {dest}!"
#: albatrobot.py:372
msgid "What should I say?"
msgstr "Cosa dovrei dire?"
#: albatrobot.py:402
msgid "add `n` to the roll"
msgstr "aggiungi `n` al tiro"
#: albatrobot.py:403
msgid "subtract `n` from the roll"
msgstr "sottrati `n` dal tiro"
#: albatrobot.py:404
msgid "multiply the roll by `n`"
msgstr "moltiplica il tiro per `n`"
#: albatrobot.py:405
msgid "divide the roll by `n`"
msgstr "dividi il tiro per `n`"
#: albatrobot.py:406
msgid "floor divide the roll by `n`"
msgstr "ottieni il quoto del tiro diviso per `n`"
#: albatrobot.py:407
msgid "exponentiate the roll to `n`"
msgstr "eleva il tiro alla potenza `n`"
#: albatrobot.py:408
msgid "keep highest `n` rolls \\(defaults to `1`\\)"
msgstr "tieni gli `n` tiri più alti \\(valore predefinito: `1`\\)"
#: albatrobot.py:409
msgid "keep lowest `n` rolls \\(defaults to `1`\\)"
msgstr "tieni gli `n` tiri più bassi \\(valore predefinito: `1`\\)"
#: albatrobot.py:410
msgid "drop highest `n` rolls \\(defaults to `1`\\)"
msgstr "scarta gli `n` tiri più alti \\(valore predefinito: `1`\\)"
#: albatrobot.py:411
msgid "drop lowest `n` rolls \\(defaults to `1`\\)"
msgstr "scarta gli `n` tiri più bassi \\(valore predefinito: `1`\\)"
#: albatrobot.py:412
msgid "explode rolls equal to `n` \\(defaults to die size\\)"
msgstr ""
"fai esplodere i tiri pari a `n` \\(valore predefinito: dimensione del dado\\)"
#: albatrobot.py:413
msgid "explode rolls lower than `n`"
msgstr "fai esplodere i tiri minori di `n`"
#: albatrobot.py:414
msgid "explode rolls higher than `n`"
msgstr "fai esplodere i tiri maggiori di `n`"
#: albatrobot.py:415
msgid "count failures as rolls lower than `n`"
msgstr "conta i fallimenti come i tiri minori di `n`"
#: albatrobot.py:416
msgid "count failures as rolls higher than `n`"
msgstr "conta i fallimenti come i tiri maggiori di `n`"
#: albatrobot.py:417
msgid "count successes as rolls lower than `n`"
msgstr "conta i successi come i tiri minori di `n`"
#: albatrobot.py:418
msgid "count successes as rolls higher than `n`"
msgstr "conta i successi come i tiri maggiori di `n`"
#: albatrobot.py:419
msgid "penetrate rolls equal to dice size"
msgstr "penetra i tiri pari alla dimensione del dado"
#: albatrobot.py:420
msgid "penetrate rolls lower than `n`"
msgstr "penetra i tiri minori di `n`"
#: albatrobot.py:421
msgid "penetrate rolls higher than `n`"
msgstr "penetra i tiri maggiori di `n`"
#: albatrobot.py:422
msgid "add `n` to each roll"
msgstr "aggiungi `n` a ogni tiro"
#: albatrobot.py:423
msgid "subtract `n` from each roll"
msgstr "sottrai `n` a ogni tiro"
#: albatrobot.py:424
msgid "multiply by `n` each roll"
msgstr "moltiplica ogni tiro per `n`"
#: albatrobot.py:425
msgid ""
"reroll each dice that rolled `n` until there are no `n` left\\(defaults to "
"`1`\\)"
msgstr ""
"ritira ogni dado con risultato `n` finché non ci sono `n` rimanenti "
"\\(valore predefinito: `1`\\)"
#: albatrobot.py:427
msgid "reroll once each dice that rolled `n` \\(defaults to 1\\)"
msgstr ""
"ritira una volta ogni dado che con risultato `n` \\(valore predefinito: 1\\)"
#: albatrobot.py:429
msgid ""
"reroll dice that rolled lower than `n` until there are no results lower than "
"`n` left"
msgstr ""
"ritira i dadi con risultato minore di `n` finché non ci sono risultati "
"minori di `n` rimanenti"
#: albatrobot.py:431
msgid ""
"reroll dice that rolled higher than `n` until there no results higher than "
"`n` left"
msgstr ""
"ritira i dadi con risultato maggiore di `n` finché non ci sono risultati "
"maggiori di `n` rimanenti"
#: albatrobot.py:433
msgid "reroll once dice that roll lower than `n`"
msgstr "ritira una volta i dadi con risultati minore di `n`"
#: albatrobot.py:434
msgid "reroll once dice that roll higher than `n`"
msgstr "ritira una volta i dadi con risultato maggiore di `n`"
#: albatrobot.py:439
msgid ""
"To roll, use the syntax `\\<number of dice\\>d\\<dice size\\>`\\. You can "
"also use the following modifiers\\. Note that:\n"
"\\- `\\(n\\)` means that an integer number may be specified, but if it isn't "
"the specified default value is assumed\n"
"\\- `n` means that an integer number is required\n"
"\\- any other symbol means exactly that symbol\n"
"\n"
"\n"
msgstr ""
"Per tirare, usa la sintassi `\\<numero di dadi\\>d\\<dimensione dei "
"dadi\\>`\\. Puoi anche usare i modificatori seguenti\\. Tieni presente che:\n"
"\\- `\\(n\\)` significa che può essere specificato un numero intero, ma se "
"non è specificato viene usato il valore predefinito\n"
"\\- qualunque altro simbolo indica esattamente quel simbolo\n"
"\n"
"\n"
#: albatrobot.py:458
msgid "You can roll up to 500 dice with up to 500 faces at once!"
msgstr "Puoi tirare al massimo 1000 dadi con al massimo 1000 facce per volta!"
#: albatrobot.py:465
#, python-brace-format
msgid "{user_name} rolled {rolls} = {result}"
msgstr "{user_name} ha tirato {rolls} = {result}"
#: albatrobot.py:473
msgid "You used a wrong syntax!"
msgstr "Hai usato una sintassi errata!"

View File

@ -1,14 +1,14 @@
# Albatrobot translation.
# Copyright (C) 2021 bursa-pastoris
# Translation template for Albatrobot.
# Copyright (C) 2021, 2023 bursa-pastoris
# This file is distributed under the same license as the Albatrobot package.
# bursa-pastoris <bursapastoris at disroot dot org>, 2021.
# bursa-pastoris <bursapastoris at disroot dot org>, 2021, 2023.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: 0.2\n"
"Project-Id-Version: Albatrobot 0.3\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2022-01-01 19:49+0100\n"
"POT-Creation-Date: 2023-03-23 00:00+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@ -17,47 +17,55 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
#: main.py:68 main.py:72 main.py:76
#: main.py:48 main.py:60 albatrobot.py:161
msgid "stop"
msgstr ""
#: main.py:79
#: main.py:48 main.py:69 albatrobot.py:159
msgid "init"
msgstr ""
#: main.py:65 albatrobot.py:143
msgid "help"
msgstr ""
#: main.py:82
#: main.py:73 albatrobot.py:144
msgid "legal"
msgstr ""
#: main.py:85
#: main.py:77 albatrobot.py:148
msgid "version"
msgstr ""
#: main.py:90
msgid "roll"
msgstr ""
#: main.py:95
#: main.py:84 albatrobot.py:136
msgid "christmas"
msgstr ""
#: main.py:98
#: main.py:88 albatrobot.py:137
msgid "easter"
msgstr ""
#: main.py:101
#: main.py:92
msgid "end_of_year_dinner"
msgstr ""
#: albatrobot.py:73
#: main.py:96 albatrobot.py:141
msgid "epiphony"
msgstr ""
#: main.py:100 albatrobot.py:145 albatrobot.py:196
msgid "roll"
msgstr ""
#: albatrobot.py:81
msgid "User not authorized."
msgstr ""
#: albatrobot.py:80
#: albatrobot.py:89
msgid "Unknown command."
msgstr ""
#: albatrobot.py:94
#: albatrobot.py:103
msgid ""
"Hello, I'm Albatrobot\\! Type \\/help for help\\!\n"
"\n"
@ -69,49 +77,139 @@ msgid ""
msgstr ""
#: albatrobot.py:121
msgid "Albatrobot will be stopped in a few moments."
msgstr ""
#: albatrobot.py:134
msgid "*Albatrobot commands*"
msgstr ""
#: albatrobot.py:136
msgid "get an image"
msgstr ""
#: albatrobot.py:137
msgid " `\\(\\<author\\>\\)`"
msgstr ""
#: albatrobot.py:137
msgid ""
"*Officially supported commands*\n"
"/help: show officialy supported commands\n"
"/legal: send legal info about privacy and copyright\n"
"/roll: roll dice following DnD notation\n"
"\n"
"Admins can also use /stop to stop the bot from Telegram in case of emergency"
"\\."
"get a citation from given author, or a random one if no author is "
"specified\\."
msgstr ""
#: albatrobot.py:154
msgid "*Albatrobot's version:* {}'"
#: albatrobot.py:139
msgid "end\\_of\\_year\\_dinner"
msgstr ""
#: albatrobot.py:174
msgid "{}, you can roll at most 1000 dice with at most 1000 faces at once!"
#: albatrobot.py:139
msgid "send all citations in the archive to a random user\\."
msgstr ""
#: albatrobot.py:187
#: albatrobot.py:141
msgid " `\\(\\<text\\>\\)`"
msgstr ""
#: albatrobot.py:141
msgid "get a voice message from Albatrobot reading given text"
msgstr ""
#: albatrobot.py:143
msgid "get help about bot commands\\."
msgstr ""
#: albatrobot.py:144
msgid "get legal info about privacy and copyright\\."
msgstr ""
#: albatrobot.py:145
msgid " `\\<dice\\>\\(\\<modifiers\\>\\)`"
msgstr ""
#: albatrobot.py:145
msgid ""
"roll dice, with or without modifiers\\. Launch without arguments to get help "
"on the modifiers\\."
msgstr ""
#: albatrobot.py:148
msgid "get Albatrobot's version"
msgstr ""
#: albatrobot.py:157
msgid "*Additional commands for bot admins*"
msgstr ""
#: albatrobot.py:159
msgid ""
"reinitialize the bot\\. This updates inline suggestions and purges /epiphony "
"cached files\\."
msgstr ""
#: albatrobot.py:161
msgid ""
"stop Albatrobot\\. To be used _only_ in case of emergency: restarting the "
"bot after this command is used requires direct access to the server\\."
msgstr ""
#: albatrobot.py:173
msgid ""
"Bot admins have access to additional commands and can get help on them "
"using /help in a private chat\\."
msgstr ""
#: albatrobot.py:182
#, python-brace-format
msgid "{user_name} rolled {result}"
msgid ""
"{ccom_help}\n"
"\n"
"{acom_help}"
msgstr ""
#: albatrobot.py:188
#, python-brace-format
msgid ""
"{ccom_help}\n"
"\n"
"{non_admin_addendum}"
msgstr ""
#: albatrobot.py:196
msgid "Roll dice"
msgstr ""
#: albatrobot.py:199
msgid "Initializing..."
msgstr ""
#: albatrobot.py:203
msgid ""
"Christmas is good, but you can't invoke it so often! Try again in some "
"seconds...'"
msgid "Done"
msgstr ""
#: albatrobot.py:233
#: albatrobot.py:228
msgid "*Albatrobot's version:* {}"
msgstr ""
#: albatrobot.py:242
msgid ""
"Christmas is good, but you can't invoke it so often! Try again in some "
"seconds..."
msgstr ""
#: albatrobot.py:273
msgid ""
"Easter is good, but you can't invoke it so often! Retry in some seconds..."
msgstr ""
#: albatrobot.py:250
#: albatrobot.py:296
msgid "The most similar is {}."
msgstr ""
#: albatrobot.py:252
msgid "The most similar are {}."
#: albatrobot.py:300
msgid "The most similar are {} and {}."
msgstr ""
#: albatrobot.py:260
#: albatrobot.py:304
#, python-brace-format
msgid ""
"Author {required_author} is not present in the archive! {suggestion}\n"
@ -119,11 +217,170 @@ msgid ""
"N.b.: CaSe Is ImPoRtAnT!"
msgstr ""
#: albatrobot.py:295
msgid "Hello there, here an end of year dinner offered by {} !"
#: albatrobot.py:324 albatrobot.py:352
#, python-brace-format
msgid ""
"{cit}\n"
"\n"
"~{author}"
msgstr ""
#: albatrobot.py:309
#: albatrobot.py:345
msgid "Hello there, here an end of year dinner offered by {}!"
msgstr ""
#: albatrobot.py:359
#, python-brace-format
msgid "End of year dinner of {cits_number} plates delvered to {dest}!"
msgstr ""
#: albatrobot.py:372
msgid "What should I say?"
msgstr ""
#: albatrobot.py:402
msgid "add `n` to the roll"
msgstr ""
#: albatrobot.py:403
msgid "subtract `n` from the roll"
msgstr ""
#: albatrobot.py:404
msgid "multiply the roll by `n`"
msgstr ""
#: albatrobot.py:405
msgid "divide the roll by `n`"
msgstr ""
#: albatrobot.py:406
msgid "floor divide the roll by `n`"
msgstr ""
#: albatrobot.py:407
msgid "exponentiate the roll to `n`"
msgstr ""
#: albatrobot.py:408
msgid "keep highest `n` rolls \\(defaults to `1`\\)"
msgstr ""
#: albatrobot.py:409
msgid "keep lowest `n` rolls \\(defaults to `1`\\)"
msgstr ""
#: albatrobot.py:410
msgid "drop highest `n` rolls \\(defaults to `1`\\)"
msgstr ""
#: albatrobot.py:411
msgid "drop lowest `n` rolls \\(defaults to `1`\\)"
msgstr ""
#: albatrobot.py:412
msgid "explode rolls equal to `n` \\(defaults to die size\\)"
msgstr ""
#: albatrobot.py:413
msgid "explode rolls lower than `n`"
msgstr ""
#: albatrobot.py:414
msgid "explode rolls higher than `n`"
msgstr ""
#: albatrobot.py:415
msgid "count failures as rolls lower than `n`"
msgstr ""
#: albatrobot.py:416
msgid "count failures as rolls higher than `n`"
msgstr ""
#: albatrobot.py:417
msgid "count successes as rolls lower than `n`"
msgstr ""
#: albatrobot.py:418
msgid "count successes as rolls higher than `n`"
msgstr ""
#: albatrobot.py:419
msgid "penetrate rolls equal to dice size"
msgstr ""
#: albatrobot.py:420
msgid "penetrate rolls lower than `n`"
msgstr ""
#: albatrobot.py:421
msgid "penetrate rolls higher than `n`"
msgstr ""
#: albatrobot.py:422
msgid "add `n` to each roll"
msgstr ""
#: albatrobot.py:423
msgid "subtract `n` from each roll"
msgstr ""
#: albatrobot.py:424
msgid "multiply by `n` each roll"
msgstr ""
#: albatrobot.py:425
msgid ""
"reroll each dice that rolled `n` until there are no `n` left\\(defaults to "
"`1`\\)"
msgstr ""
#: albatrobot.py:427
msgid "reroll once each dice that rolled `n` \\(defaults to 1\\)"
msgstr ""
#: albatrobot.py:429
msgid ""
"reroll dice that rolled lower than `n` until there are no results lower than "
"`n` left"
msgstr ""
#: albatrobot.py:431
msgid ""
"reroll dice that rolled higher than `n` until there no results higher than "
"`n` left"
msgstr ""
#: albatrobot.py:433
msgid "reroll once dice that roll lower than `n`"
msgstr ""
#: albatrobot.py:434
msgid "reroll once dice that roll higher than `n`"
msgstr ""
#: albatrobot.py:439
msgid ""
"To roll, use the syntax `\\<number of dice\\>d\\<dice size\\>`\\. You can "
"also use the following modifiers\\. Note that:\n"
"\\- `\\(n\\)` means that an integer number may be specified, but if it isn't "
"the specified default value is assumed\n"
"\\- `n` means that an integer number is required\n"
"\\- any other symbol means exactly that symbol\n"
"\n"
"\n"
msgstr ""
#: albatrobot.py:458
msgid "You can roll up to 500 dice with up to 500 faces at once!"
msgstr ""
#: albatrobot.py:465
#, python-brace-format
msgid "{user_name} rolled {rolls} = {result}"
msgstr ""
#: albatrobot.py:473
msgid "You used a wrong syntax!"
msgstr ""

149
main.py
View File

@ -19,90 +19,93 @@
along with Albatrobot. If not, see <https://www.gnu.org/licenses/>.
"""
import configparser
from datetime import timedelta
from telegram.ext import Updater, CommandHandler, MessageHandler, Filters
import gettext
from telegram import Update
from telegram.ext import ApplicationBuilder, ContextTypes, CommandHandler, MessageHandler, filters
import albatrobot
# Settings
config = configparser.ConfigParser()
config.read("settings.ini")
token = config["Bot"]["Token"]
for var in config["Settings"]["Users"].split(","):
albatrobot.users.append(int(var))
for var in config["Settings"]["Admins"].split(","):
albatrobot.admins.append(int(var))
albatrobot.users.append(int(var))
albatrobot.msg_cooldown = timedelta(
seconds=int(config["Settings"]["MsgCooldown"]))
albatrobot.cite_path = config["Resources"]["CitePath"]+"/*"
albatrobot.img_path = config["Resources"]["ImgPath"]+"/*"
lang = config["Settings"]["Language"]
from constants import *
# Localization
import gettext
transl = gettext.translation("messages", localedir="locales",languages=[lang])
transl = gettext.translation('messages', localedir='locales',languages=[LANG])
transl.install()
_ = transl.gettext
# Link to bot platform
updater = Updater(token=token, use_context=True)
dispatcher = updater.dispatcher
if PROXY != '':
application = ApplicationBuilder().token(TOKEN).get_updates_proxy_url(PROXY).build()
else:
application = ApplicationBuilder().token(TOKEN).build()
# Bot building - Unauthorized users filtering
dispatcher.add_handler(
MessageHandler(
Filters.command and (~Filters.user(albatrobot.users)),
albatrobot.unauthorized_user))
# Bot building - Basic functions
dispatcher.add_handler(
CommandHandler("start",
albatrobot.start))
dispatcher.add_handler(
CommandHandler(["stop",_("stop")],
albatrobot.unauthorized_user,
(~Filters.user(albatrobot.admins))))
dispatcher.add_handler(
CommandHandler(["stop",_("stop")],
albatrobot.stop,
Filters.user(albatrobot.admins)))
dispatcher.add_handler(
CommandHandler(["stop",_("stop")],
albatrobot.unauthorized_user))
dispatcher.add_handler(
CommandHandler(["help",_("help")],
albatrobot.help))
dispatcher.add_handler(
CommandHandler(["legal",_("legal")],
albatrobot.legal))
dispatcher.add_handler(
CommandHandler(["version",_("version")],
albatrobot.version))
# Filter unauthorized users
application.add_handler(
MessageHandler((~filters.User(USERS)),
albatrobot.unauthorized_user)
)
application.add_handler(
CommandHandler(['stop',_('stop'),'init',_('init')],
albatrobot.unauthorized_user,
(~filters.User(ADMINS)))
)
# Bot building - Commands
dispatcher.add_handler(
CommandHandler(["roll",_("roll")],
albatrobot.roll))
# Bot building - Easter eggs
dispatcher.add_handler(
CommandHandler(["christmas",_("christmas")],
albatrobot.christmas))
dispatcher.add_handler(
CommandHandler(["easter",_("easter")],
albatrobot.easter))
dispatcher.add_handler(
CommandHandler(["end_of_year_dinner",_("end_of_year_dinner")],
albatrobot.end_of_year_dinner))
dispatcher.add_handler(
MessageHandler(Filters.command,
albatrobot.unknown_command))
# Basic functions
application.add_handler(
CommandHandler('start',
albatrobot.start)
)
application.add_handler(
CommandHandler(['stop',_('stop')],
albatrobot.stop,
filters.User(ADMINS))
)
application.add_handler(
CommandHandler(['help',_('help')],
albatrobot.help)
)
application.add_handler(
CommandHandler(['init',_('init')],
albatrobot.init)
)
application.add_handler(
CommandHandler(['legal',_('legal')],
albatrobot.legal)
)
application.add_handler(
CommandHandler(['version',_('version')],
albatrobot.version)
)
# Commands
application.add_handler(
CommandHandler(['christmas',_('christmas')],
albatrobot.christmas)
)
application.add_handler(
CommandHandler(['easter',_('easter')],
albatrobot.easter)
)
application.add_handler(
CommandHandler(['end_of_year_dinner',_('end_of_year_dinner')],
albatrobot.end_of_year_dinner)
)
application.add_handler(
CommandHandler(['epiphony',_('epiphony')],
albatrobot.epiphony)
)
application.add_handler(
CommandHandler(['roll',_('roll')],
albatrobot.roll)
)
# Unknown commands
application.add_handler(
MessageHandler(filters.COMMAND,
albatrobot.unknown_command)
)
# Start bot
updater.start_polling()
application.run_polling()

View File

@ -1,3 +1,6 @@
python-telegram-bot==13.0
python-Levenshtein==0.12.2
psutil==5.9.0
python-telegram-bot[socks]~=20.0
psutil~=5.9
py-rolldice~=0.4
python-Levenshtein~=0.12
pyttsx3~=2.90

View File

@ -0,0 +1,3 @@
ID,author,text,year,place
ABT-0001,Albatrobot,No citations available,2022,
ABT-0002,Albatrobot,"No citations available, I said!",,Brescia
1 ID author text year place
2 ABT-0001 Albatrobot No citations available 2022
3 ABT-0002 Albatrobot No citations available, I said! Brescia

View File

@ -1 +0,0 @@
citations = {'Albatrobot':['no citations available']}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 262 KiB

After

Width:  |  Height:  |  Size: 242 KiB

View File

@ -1,71 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="227.25415mm"
height="136.32494mm"
viewBox="0 0 227.25415 136.32494"
version="1.1"
id="svg964"
inkscape:version="1.1.1 (3bf5ae0d25, 2021-09-20)"
sodipodi:docname="no-imgs.svg"
inkscape:export-filename="/home/kzt//.tmp/no-available-images.png"
inkscape:export-xdpi="599.961"
inkscape:export-ydpi="599.961"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview966"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageshadow="2"
inkscape:pageopacity="1"
inkscape:pagecheckerboard="0"
inkscape:document-units="mm"
showgrid="false"
inkscape:zoom="0.61557941"
inkscape:cx="448.35808"
inkscape:cy="325.7094"
inkscape:window-width="1588"
inkscape:window-height="856"
inkscape:window-x="3"
inkscape:window-y="35"
inkscape:window-maximized="1"
inkscape:current-layer="layer1"
fit-margin-top="20"
fit-margin-left="20"
fit-margin-right="20"
fit-margin-bottom="20" />
<defs
id="defs961" />
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(-159.2345,-88.183751)">
<text
xml:space="preserve"
style="font-size:35.2778px;line-height:1;font-family:FreeM;-inkscape-font-specification:FreeM;letter-spacing:0px;word-spacing:0px;stroke-width:0.264583"
x="273.00266"
y="124.05876"
id="text2457"><tspan
sodipodi:role="line"
id="tspan2455"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:35.2778px;font-family:FreeMono;-inkscape-font-specification:FreeMono;text-align:center;text-anchor:middle;stroke-width:0.264583"
x="273.00266"
y="124.05876">no</tspan><tspan
sodipodi:role="line"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:35.2778px;font-family:FreeMono;-inkscape-font-specification:FreeMono;text-align:center;text-anchor:middle;stroke-width:0.264583"
x="273.00266"
y="160.75595"
id="tspan2461">available</tspan><tspan
sodipodi:role="line"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:35.2778px;font-family:FreeMono;-inkscape-font-specification:FreeMono;text-align:center;text-anchor:middle;stroke-width:0.264583"
x="273.00266"
y="197.45312"
id="tspan5257">images</tspan></text>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 2.8 KiB

View File

@ -1,13 +1,18 @@
[Bot]
Token:
[Settings]
[Users]
Users:
Admins:
[Settings]
Language:
MsgCooldown: 5
Proxy:
YellLang:
YellSpeed: 120
[Resources]
CitePath: resources/citations
CitArchive: resources/citations/citations.csv
ImgPath: resources/imgs