Skip to content

Commit

Permalink
Added settings (#14)
Browse files Browse the repository at this point in the history
Changes:
Added localizations and languages
Added "fast spin" feature
Added reply when user is not admin
Added /about command
Now you are able to respin in specified chat
Code fixes and improvements
  • Loading branch information
evgfilim1 committed Jul 6, 2017
1 parent 9a8414e commit 869bb52
Show file tree
Hide file tree
Showing 5 changed files with 346 additions and 136 deletions.
66 changes: 8 additions & 58 deletions config_example.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@

# You can find out necessary IDs from @ShowJsonBot

# Version string for bot
__version__ = '1.5.1-dev'

# Repository URL to be shown in /about
REPO_URL = "https://github.com/evgfilim1/spin_everyday_bot"

# Your bot's token, which you can get from @BotFather
BOT_TOKEN = "123456789:ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghi"

Expand All @@ -16,65 +22,9 @@
# Your Telegram User ID
BOT_CREATOR = 123456987

# Default name of spin
DEFAULT_SPIN_NAME = "ноунейм"

# Spin texts
# {s} is replaced with name of spin and {n} -- with user name
TEXTS = [["Итак, кто же сегодня *{s} дня*?", "_Хмм, интересно..._", "*АГА!*",
"Сегодня ты *{s} дня,* {n}"],
["*Колесо Сансары запущено!*", "_Что за дичь?!_", "Ну ок...",
"Поздравляю, ты *{s} дня,* {n}"],
["Кручу-верчу, *наебать* хочу", "Сегодня ты *{s} дня*, @spin\_everyday\_bot",
"_(нет)_", "На самом деле, это {n}"],
["Эмм... Ты уверен?", "Ты *точно* уверен?", "Хотя ладно, процесс уже необратим",
"Сегодня я назначаю тебе должность *{s} дня*, {n}!"],
["_Ищем рандомного кота на улице..._", "_Ищем палку..._", "_Ищем шапку..._", "_Рисуем ASCII-арт..._",
"*Готово!*", """```
.∧_∧
( ・ω・。)つ━☆・*。
⊂  ノ    ・゜+.
しーJ   °。+ *´¨)
         .· ´¸.·*´¨) ¸.·*¨)
          (¸.·´ (¸.·'* ☆ ВЖУХ, И ТЫ {s} ДНЯ,```{n}
"""]]

# Text that shows if spin was done already
TEXT_ALREADY = "Согласно сегодняшнему розыгрышу, *{s} дня* — `{n}`"

# How much users will be shown on one page of top
TOP_PAGE_SIZE = 10

# Help text
HELP_TEXT = {"main": ("*Привет!* Я бот, который делает ежедневные розыгрыши. Чтобы узнать о моих возможностях, "
"выберите пункт меню", ("Команды%commands", "О боте%about")),
"commands": ("Выберите нужную команду, чтобы узнать подробнее о ней",
("/setname%name", "/admgroup%admin", "/count%count", "/spin%spin", "/auto%auto",
"/stat%stat", "Назад%main")),
"about": ("*SpinEverydayBot v.1.5.1*\nПо всем вопросам обращайтесь к <username>\n"
"Бот распространяется по лицензии [GNU AGPLv3](https://www.gnu.org/licenses/#AGPL)\n"
"[Репозиторий бота](URL)",
("Назад%main",)),
"name": ("`/setname` — устанавливает название розыгрыша.\n_Использование:_\n"
"/setname — покажет текущее название розыгрыша\n"
"/setname TEXT — установит названием розыгрыша `TEXT`", ("Назад%commands",)),
"admin": ("`/admgroup` — управляет правами.\n_Использование:_\n"
"/admgroup add — разрешает пользователю выполнять административные операции\n"
"/admgroup del — запрещает пользователю выполнять административные операции\n"
"/admgroup list — показывает список тех, кто может выполнять административные операции",
("Назад%commands",)),
"count": ("`/count` — считает кол-во пользователей, принимающих участие в розыгрыше",
("Назад%commands",)),
"spin": ("`/spin` — запускает розыгрыш. Если розыгрыш уже был запущен в течение текущего дня, "
"показывает результат.", ("Назад%commands",)),
"auto": ("`/auto` - управление автоматическими розыгрышами.\n_Использование:_\n"
"/auto set TIME — устанавливает розыгрыш на `TIME` GMT+0 (MSK-3). Формат времени: `hh:mm`\n"
"/auto del — отключает автоматический розыгрыш\n"
"/auto status — просмотр состояния и времени автоматического розыгрыша", ("Назад%commands",)),
"stat": ("`/stat` — просмотр статистики в чате.\n_Использование:_\n"
"/stat — просмотр статистики всего чата или конкретного пользователя\n"
"/stat me — просмотр собственной статистики", ("Назад%commands",))}

# Time of resetting spin results (in GMT)
RESET_TIME = "21:00"

Expand All @@ -93,5 +43,5 @@
# Logging format (message format that will be written in `LOG_FILE` or console)
LOG_FORMAT = '{levelname:<8} [{asctime}]: {name}: {message}'

# Message that will be shown in group chats if the bot can't write in PM
PM_ONLY_MESSAGE = "Для начала, запусти или разбань меня в ЛС"
# If specified key is missing from language file, translation will fallback to this language
FALLBACK_LANG = "ru"
58 changes: 46 additions & 12 deletions core.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@
import pickle
from datetime import time

from telegram import (ChatMember, ParseMode, TelegramError,
User, Update, Bot)
from telegram import (ChatMember, ParseMode, TelegramError, Update, Bot)
from telegram.ext import JobQueue
from telegram.ext.dispatcher import run_async
import logging
Expand All @@ -16,7 +15,7 @@

class TelegramHandler(logging.Handler):
def __init__(self, bot: Bot):
logging.Handler.__init__(self)
super(TelegramHandler, self).__init__()
self.bot = bot

def emit(self, record):
Expand All @@ -34,6 +33,8 @@ def emit(self, record):
results_total = {}
auto_spins = {}
auto_spin_jobs = {}
chat_config = {}
languages = {}

announcement_chats = []
log = None
Expand Down Expand Up @@ -75,34 +76,49 @@ def not_pm(f: callable):
def wrapper(bot: Bot, update: Update, *args, **kwargs):
msg = update.effective_message
if is_private(msg.chat_id):
msg.reply_text("Эта команда недоступна в ЛС")
msg.reply_text(get_lang(msg.chat_id, 'not_in_pm'))
return
f(bot, update, *args, **kwargs)

return wrapper


def _load(filename: str) -> dict:
with open('data/' + filename, 'rb') as ff:
from os.path import exists
if not exists(f'data/{filename}'):
return {}
with open(f'data/{filename}', 'rb') as ff:
return pickle.load(ff)


def _save(obj: dict, filename: str):
with open('data/' + filename, 'wb') as ff:
with open(f'data/{filename}', 'wb') as ff:
pickle.dump(obj, ff, pickle.HIGHEST_PROTOCOL)


def _load_lang():
from os import listdir
from json import load
for lang in listdir("lang"):
if not lang.endswith(".json"):
continue
with open(f"lang/{lang}") as file:
strings = load(file)
lang = lang[:-5]
languages.update({lang: strings})


def _load_all():
global chat_users, spin_name, can_change_name, results_today, results_total
global auto_spins
if not __import__("os").path.exists("users.pkl"):
return
global auto_spins, chat_config
chat_users = _load("users.pkl")
spin_name = _load("spin.pkl")
can_change_name = _load("changers.pkl")
results_today = _load("results.pkl")
results_total = _load("total.pkl")
auto_spins = _load("auto.pkl")
chat_config = _load("config.pkl")
_load_lang()


def save_all():
Expand All @@ -112,6 +128,7 @@ def save_all():
_save(results_today, "results.pkl")
_save(results_total, "total.pkl")
_save(auto_spins, "auto.pkl")
_save(chat_config, "config.pkl")


def clear_data(chat_id: int):
Expand Down Expand Up @@ -190,14 +207,14 @@ def make_top(chat_id: int, *, page: int) -> (str, int):
end = begin + config.TOP_PAGE_SIZE
if len(winners) % config.TOP_PAGE_SIZE != 0:
total_pages += 1
text = f"Статистика пользователей в данном чате: (страница {page} из {total_pages})\n"
text = get_lang(chat_id, 'stats_all')
for user in winners[begin:end]:
username = chat_users[chat_id].get(user[0], f"id{user[0]}")
text += f"*{username}*: {user[1]} раз(а)\n"
text += get_lang(chat_id, 'stats_user_short').format(username, user[1])
return text, total_pages


def can_change_spin_name(chat_id: int, user_id: int, bot: Bot) -> bool:
def is_admin_for_bot(chat_id: int, user_id: int, bot: Bot) -> bool:
return user_id == config.BOT_CREATOR or user_id in get_admins_ids(bot, chat_id) or \
user_id in can_change_name.get(chat_id, [])

Expand Down Expand Up @@ -230,3 +247,20 @@ def get_admins_ids(bot: Bot, chat_id: int) -> list:
admins = bot.get_chat_administrators(chat_id=chat_id)
result = [admin.user.id for admin in admins]
return result


def update_config(chat_id: int, key, value):
if chat_config.get(chat_id) is None:
chat_config[chat_id] = {}
chat_config[chat_id].update({key: value})


def get_config_key(chat_id: int, key, default=None):
return chat_config.get(chat_id, {}).get(key, default)


def get_lang(chat_id: int, key: str):
lang = get_config_key(chat_id, 'lang', default=config.FALLBACK_LANG)
if languages.get(lang) is None or languages.get(lang).get(key) is None:
lang = config.FALLBACK_LANG
return languages.get(lang, {}).get(key, "!!!Translation is missing!!!")
3 changes: 3 additions & 0 deletions lang/en.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"_name": "🇺🇸 English (don't work)"
}
116 changes: 116 additions & 0 deletions lang/ru.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
{
"_copyright": "SpinEverydayBot russian strings.\nCopyright © 2017 Evgeniy Filimonov <https://t.me/evgfilim1>",
"_name": "🇷🇺 Russian/Русский",
"settings": "Настройки бота для чата {0}",
"settings_changed": "Изменено!",
"settings_on": "Включено",
"settings_off": "Отключено",
"settings_turn_on": "✅ Включить",
"settings_turn_off": "❌ Отключить",
"settings_lang": "Язык/Language",
"settings_fast_spin": "Быстрый розыгрыш",
"settings_back": "⬅️ В меню",
"settings_lang_prompt": "Выберите основной язык чата",
"settings_fast_spin_caption": "Быстрый розыгрыш. Если данная опция включена, то будет показано только последнее сообщение текстового набора. Текущий статус: {0}",
"pm_banned": "Для начала, запусти или разбань меня в ЛС",
"start_pm_button": "Написать боту",
"not_in_pm": "Эта команда недоступна в ЛС",
"check_pm": "Отправлено в ЛС",
"default_spin_name": "ноунейм",
"spin_name_current": "Текущее название розыгрыша: *{0} дня*",
"spin_name_changed": "Текст розыгрыша изменён на *{0} дня*",
"already_spin": "Согласно сегодняшнему розыгрышу, *{s} дня* — `{n}`",
"default_spin_texts": [
["Итак, кто же сегодня *{s} дня*?", "_Хмм, интересно..._", "*АГА!*", "Сегодня ты *{s} дня,* {n}"],
["*Колесо Сансары запущено!*", "_Что за дичь?!_", "Ну ок...", "Поздравляю, ты *{s} дня,* {n}"],
["Эмм... Ты уверен?", "Ты *точно* уверен?", "Хотя ладно, процесс уже необратим",
"Сегодня я назначаю тебе должность *{s} дня*, {n}!"],
["_Ищем рандомного кота на улице..._", "_Ищем палку..._", "_Ищем шапку..._", "_Рисуем ASCII-арт..._",
"*Готово!*", "```\n.∧_∧\n( ・ω・。)つ━☆・*。\n⊂  ノ    ・゜+.\nしーJ   °。+ *´¨)\n         .· ´¸.·*´¨) ¸.·*¨)\n          (¸.·´ (¸.·'* ☆ ВЖУХ, И ТЫ {s} ДНЯ,```{n}"]
],
"spin_suffix": "дня",
"locked_buttons": "Нельзя использовать кнопки, пока идёт розыгрыш",
"help_texts": {
"main": [
"*Привет!* Я бот, который делает ежедневные розыгрыши. Чтобы узнать о моих возможностях, выберите пункт меню",
[
"Команды%commands",
"О боте%about"
]
],
"about" : [
"Перемещено в /about",
[
"Назад%main"
]
],
"commands": [
"Выберите нужную команду, чтобы узнать подробнее о ней",
[
"/setname%name",
"/admgroup%admin",
"/count%count",
"/spin%spin",
"/auto%auto",
"/stat%stat",
"Назад%main"
]
],
"name": [
"`/setname` — устанавливает название розыгрыша.\n_Использование:_\n/setname — покажет текущее название розыгрыша\n/setname TEXT — установит названием розыгрыша `TEXT`",
[
"Назад%commands"
]
],
"admin": [
"`/admgroup` — управляет правами.\n_Использование:_\n/admgroup add — разрешает пользователю выполнять административные операции\n/admgroup del — запрещает пользователю выполнять административные операции\n/admgroup list — показывает список тех, кто может выполнять административные операции",
[
"Назад%commands"
]
],
"count": [
"`/count` — считает кол-во пользователей, принимающих участие в розыгрыше",
[
"Назад%commands"
]
],
"spin": [
"`/spin` — запускает розыгрыш. Если розыгрыш уже был запущен в течение текущего дня, показывает результат.",
[
"Назад%commands"
]
],
"auto": [
"`/auto` - управление автоматическими розыгрышами.\n_Использование:_\n/auto set TIME — устанавливает розыгрыш на `TIME` GMT+0 (MSK-3). Формат времени: `hh:mm`\n/auto del — отключает автоматический розыгрыш\n/auto status — просмотр состояния и времени автоматического розыгрыша\n\nВНИМАНИЕ! Если розыгрыш уже был проведён до того, как запустится автоматический розыгрыш, то бот не напишет ничего в чат по наступлению времени розыгрыша",
[
"Назад%commands"
]
],
"stat": [
"`/stat` — просмотр статистики в чате.\n_Использование:_\n/stat — просмотр статистики всего чата или конкретного пользователя\n/stat me — просмотр собственной статистики",
[
"Назад%commands"
]
]
},
"about_text": "*SpinEverydayBot v.{0}*\nПо всем вопросам обращайтесь к {1}\nБот распространяется по лицензии [GNU AGPLv3](https://www.gnu.org/licenses/#AGPL)\n[Репозиторий бота]({2})",
"user_count": "Кол-во людей, участвующих в розыгрыше: _{0}_",
"time_error": "Ошибка! Проверьте время на правильность и отредактируйте сообщение",
"auto_spin_on": "Автоматический розыгрыш установлен в этом чате на {0} GMT+0",
"auto_spin_off": "Автоматический розыгрыш отключен в этом чате",
"auto_spin_set_off": "Теперь автоматический розыгрыш отключен в этом чате",
"auto_spin_still_off": "Автоматический розыгрыш ещё не был включен в этом чате",
"stats_me": "Ваша статистика:\n*{0}*: {1} раз(а)",
"stats_user": "Статистика пользователя *{0}*: {1} раз(а)",
"stats_user_short": "*{0}*: {1} раз(а)\n",
"stats_all": "Статистика пользователей в данном чате: (страница {0} из {1})\n",
"admin_allow": "Теперь этот пользователь *может* изменять название розыгрыша",
"admin_still_allow": "Этот пользователь *уже может* изменять название розыгрыша",
"admin_deny": "Теперь этот пользователь *не может* изменять название розыгрыша",
"admin_still_deny": "Этот пользователь *ещё не может* изменять название розыгрыша",
"admin_list": "Пользователи, которые *могут* изменять название розыгрыша (не считая администраторов):\n```\n{0}\n```",
"not_admin": "Доступ запрещён",
"feedback_prompt": "Введите сообщение, которое будет отправлено создателю бота\nБот принимает текст, изображения и документы\nВведите /cancel для отмены",
"feedback_sent": "Ваше сообщение отправлено!",
"feedback_cancelled": "Отменено"
}

0 comments on commit 869bb52

Please sign in to comment.