Compare commits

..

No commits in common. "new_rule" and "master" have entirely different histories.

644 changed files with 330 additions and 2430 deletions

View file

@ -1,60 +0,0 @@
name: Docker build
on:
schedule:
- cron: '25 15 * * *'
push:
branches:
- master
env:
# Use docker.io for Docker Hub if empty
REGISTRY: ghcr.io
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
# This is used to complete the identity challenge
# with sigstore/fulcio when running outside of PRs.
id-token: write
steps:
- name: Checkout repository
uses: actions/checkout@v3
# Workaround: https://github.com/docker/build-push-action/issues/461
- name: Setup Docker buildx
uses: docker/setup-buildx-action@v2
- name: Log into registry ${{ env.REGISTRY }}
uses: docker/login-action@v2
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract Docker metadata
id: meta
uses: docker/metadata-action@v4
with:
images: ${{ env.REGISTRY }}/${{ github.repository }}
tags: |
type=schedule
type=ref,event=branch
type=ref,event=tag
type=raw,value=latest,enable={{is_default_branch}}
- name: Build and push Docker image
id: build-and-push
uses: docker/build-push-action@v4
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max

6
.gitignore vendored
View file

@ -27,8 +27,6 @@ var/
.installed.cfg
*.egg
venv/
# 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.
@ -71,7 +69,3 @@ target/
# Database file
uno.sqlite3
images/api_auth.json
images/sticker_config.json
images/sticker_uploader.session

View file

@ -10,6 +10,7 @@ COPY . .
RUN cd locales && find . -maxdepth 2 -type d -name 'LC_MESSAGES' -exec ash -c 'msgfmt {}/unobot.po -o {}/unobot.mo' \;
RUN pip install -r requirements.txt
VOLUME /app/data
ENV UNO_DB /app/data/uno.sqlite3

View file

@ -4,11 +4,10 @@ url = "https://pypi.org/simple"
verify_ssl = true
[dev-packages]
telethon = "*"
[packages]
python-telegram-bot = "==13.11"
python-telegram-bot = "==8.1.1"
pony = "*"
[requires]
python_version = "3.11"
python_version = "3.7"

150
Pipfile.lock generated
View file

@ -1,11 +1,11 @@
{
"_meta": {
"hash": {
"sha256": "87f82f4abefdefd3b212fa99f5cbf6e222d6855aa7574d7a94fbf51b33cc342f"
"sha256": "de56c4d5f516205e99d141cd7d372f67b602b6f981306971c01ffe25a5abf5c6"
},
"pipfile-spec": 6,
"requires": {
"python_version": "3.11"
"python_version": "3.7"
},
"sources": [
{
@ -16,150 +16,34 @@
]
},
"default": {
"apscheduler": {
"hashes": [
"sha256:3bb5229eed6fbbdafc13ce962712ae66e175aa214c69bed35a06bffcf0c5e244",
"sha256:e8b1ecdb4c7cb2818913f766d5898183c7cb8936680710a4d3a966e02262e526"
],
"version": "==3.6.3"
},
"cachetools": {
"hashes": [
"sha256:2cc0b89715337ab6dbba85b5b50effe2b0c74e035d83ee8ed637cf52f12ae001",
"sha256:61b5ed1e22a0924aed1d23b478f37e8d52549ff8a961de2909c69bf950020cff"
],
"markers": "python_version ~= '3.5'",
"version": "==4.2.2"
},
"certifi": {
"hashes": [
"sha256:35824b4c3a97115964b408844d64aa14db1cc518f6562e8d7261699d1350a9e3",
"sha256:4ad3232f5e926d6718ec31cfc1fcadfde020920e278684144551c91769c7bc18"
"sha256:046832c04d4e752f37383b628bc601a7ea7211496b4638f6514d0e5b9acc4939",
"sha256:945e3ba63a0b9f577b1395204e13c3a231f9bc0223888be653286534e5873695"
],
"markers": "python_version >= '3.6'",
"version": "==2022.12.7"
"version": "==2019.6.16"
},
"future": {
"hashes": [
"sha256:67045236dcfd6816dc439556d009594abf643e5eb48992e36beac09c2ca659b8"
],
"version": "==0.17.1"
},
"pony": {
"hashes": [
"sha256:5f45fc67587f4520c560a57148cc573b097d42f82f5cb200d72c957b5708198d",
"sha256:608a1c1d662983bad2590e650f2bbc1cd6ed48558894ad8f50da4739ff98f614"
"sha256:55bb9d4d12029d8c2bbbc7a284970e72225035db7e6370c0a15ec93d1886fe88"
],
"index": "pypi",
"version": "==0.7.16"
"version": "==0.7.10"
},
"python-telegram-bot": {
"hashes": [
"sha256:534f5bb0ff4ca34c9252e97e0b3bcdab81d97be0eb4821682a361cb426c00e55",
"sha256:baeff704baa2ac3dc17a944c02da888228ad258e89be2e5bcbd13a8a5102d573"
"sha256:238c4a88b09d93c52d413bcf7e7fe14dfeb02f5f9222ffe4cafd4bd3d55489a3",
"sha256:997983e5082dc6aa811bce3a6014731201fc64b0a9c02fdb26beac686029d94b"
],
"index": "pypi",
"version": "==13.11"
},
"pytz": {
"hashes": [
"sha256:1d8ce29db189191fb55338ee6d0387d82ab59f3d00eac103412d64e0ebd0c588",
"sha256:a151b3abb88eda1d4e34a9814df37de2a80e301e68ba0fd856fb9b46bfbbbffb"
],
"version": "==2023.3"
},
"pytz-deprecation-shim": {
"hashes": [
"sha256:8314c9692a636c8eb3bda879b9f119e350e93223ae83e70e80c31675a0fdc1a6",
"sha256:af097bae1b616dde5c5744441e2ddc69e74dfdcb0c263129610d85b87445a59d"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'",
"version": "==0.1.0.post0"
},
"setuptools": {
"hashes": [
"sha256:257de92a9d50a60b8e22abfcbb771571fde0dbf3ec234463212027a4eeecbe9a",
"sha256:e728ca814a823bf7bf60162daf9db95b93d532948c4c0bea762ce62f60189078"
],
"markers": "python_version >= '3.7'",
"version": "==67.6.1"
},
"six": {
"hashes": [
"sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926",
"sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==1.16.0"
},
"tornado": {
"hashes": [
"sha256:1d54d13ab8414ed44de07efecb97d4ef7c39f7438cf5e976ccd356bebb1b5fca",
"sha256:20f638fd8cc85f3cbae3c732326e96addff0a15e22d80f049e00121651e82e72",
"sha256:5c87076709343557ef8032934ce5f637dbb552efa7b21d08e89ae7619ed0eb23",
"sha256:5f8c52d219d4995388119af7ccaa0bcec289535747620116a58d830e7c25d8a8",
"sha256:6fdfabffd8dfcb6cf887428849d30cf19a3ea34c2c248461e1f7d718ad30b66b",
"sha256:87dcafae3e884462f90c90ecc200defe5e580a7fbbb4365eda7c7c1eb809ebc9",
"sha256:9b630419bde84ec666bfd7ea0a4cb2a8a651c2d5cccdbdd1972a0c859dfc3c13",
"sha256:b8150f721c101abdef99073bf66d3903e292d851bee51910839831caba341a75",
"sha256:ba09ef14ca9893954244fd872798b4ccb2367c165946ce2dd7376aebdde8e3ac",
"sha256:d3a2f5999215a3a06a4fc218026cd84c61b8b2b40ac5296a6db1f1451ef04c1e",
"sha256:e5f923aa6a47e133d1cf87d60700889d7eae68988704e20c75fb2d65677a8e4b"
],
"markers": "python_version >= '3.7'",
"version": "==6.2"
},
"tzdata": {
"hashes": [
"sha256:11ef1e08e54acb0d4f95bdb1be05da659673de4acbd21bf9c69e94cc5e907a3a",
"sha256:7e65763eef3120314099b6939b5546db7adce1e7d6f2e179e3df563c70511eda"
],
"markers": "platform_system == 'Windows'",
"version": "==2023.3"
},
"tzlocal": {
"hashes": [
"sha256:3f21d09e1b2aa9f2dacca12da240ca37de3ba5237a93addfd6d593afe9073355",
"sha256:b44c4388f3d34f25862cfbb387578a4d70fec417649da694a132f628a23367e2"
],
"markers": "python_version >= '3.7'",
"version": "==4.3"
"version": "==8.1.1"
}
},
"develop": {
"pyaes": {
"hashes": [
"sha256:02c1b1405c38d3c370b085fb952dd8bea3fadcee6411ad99f312cc129c536d8f"
],
"version": "==1.6.1"
},
"pyasn1": {
"hashes": [
"sha256:014c0e9976956a08139dc0712ae195324a75e142284d5f87f1a87ee1b068a359",
"sha256:03840c999ba71680a131cfaee6fab142e1ed9bbd9c693e285cc6aca0d555e576",
"sha256:0458773cfe65b153891ac249bcf1b5f8f320b7c2ce462151f8fa74de8934becf",
"sha256:08c3c53b75eaa48d71cf8c710312316392ed40899cb34710d092e96745a358b7",
"sha256:39c7e2ec30515947ff4e87fb6f456dfc6e84857d34be479c9d4a4ba4bf46aa5d",
"sha256:5c9414dcfede6e441f7e8f81b43b34e834731003427e5b09e4e00e3172a10f00",
"sha256:6e7545f1a61025a4e58bb336952c5061697da694db1cae97b116e9c46abcf7c8",
"sha256:78fa6da68ed2727915c4767bb386ab32cdba863caa7dbe473eaae45f9959da86",
"sha256:7ab8a544af125fb704feadb008c99a88805126fb525280b2270bb25cc1d78a12",
"sha256:99fcc3c8d804d1bc6d9a099921e39d827026409a58f2a720dcdb89374ea0c776",
"sha256:aef77c9fb94a3ac588e87841208bdec464471d9871bd5050a287cc9a475cd0ba",
"sha256:e89bf84b5437b532b0803ba5c9a5e054d21fec423a89952a74f87fa2c9b7bce2",
"sha256:fec3e9d8e36808a28efb59b489e4528c10ad0f480e57dcc32b4de5c9d8c9fdf3"
],
"version": "==0.4.8"
},
"rsa": {
"hashes": [
"sha256:90260d9058e514786967344d0ef75fa8727eed8a7d2e43ce9f4bcf1b536174f7",
"sha256:e38464a49c6c85d7f1351b0126661487a7e0a14a50f1675ec50eb34d4f20ef21"
],
"markers": "python_version >= '3.6' and python_version < '4'",
"version": "==4.9"
},
"telethon": {
"hashes": [
"sha256:613bae42acb5f2eeb1a0b92614e323021c66f374db62adf9826ea0c2c9120bb1",
"sha256:893c10f133974fba4c53eb1736b6514d596d1cd94c83436a711f3345df945199"
],
"index": "pypi",
"version": "==1.28.2"
}
}
"develop": {}
}

View file

@ -2,18 +2,17 @@
The following awesome people contributed to this project by translating it:
| Locale | Translators |
| ------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
| ca_CA | [retiolus](https://github.com/retiolus) |
| de_DE | [Jannes Höke](https://github.com/jh0ker) |
| es_ES | [José.A Rojo](https://github.com/J4RV), [Ricardo Valverde Hernández](https://telegram.me/rivh1), Victor, Yuga |
| id_ID | [Erwin Guo](https://www.facebook.com/erwinfransiscus) |
| it_IT | Carola Mariano, ɳick |
| ml_IN | [Adhith T](https://github.com/adhitht123) |
| pt_BR | [Iuri Guilherme](https://github.com/iuriguilherme), [João Rodrigo Couto de Oliveira](http://twitter.com/JoaoRodrigoJR) |
| vi_VN | [Lê Minh Sơn](https://github.com/leminhson06) |
| zh_CN | [imlonghao](https://github.com/imlonghao), [XhyEax](https://github.com/XhyEax) |
| zh_HK | [Jed Cheng](https://www.facebook.com/profile.php?id=100002258388821) |
| zh_TW | [Eugene Lam](https://www.facebook.com/eugenelam1118), [jimchen5209](https://www.youtube.com/user/jimchen5209), [pan93412](https://www.github.com/pan93412) |
| Locale | Translators |
|--------|-------------|
| ca_CA | [retiolus](https://github.com/retiolus) |
| de_DE | [Jannes Höke](https://github.com/jh0ker) |
| es_ES | [José.A Rojo](https://github.com/J4RV), [Ricardo Valverde Hernández](https://telegram.me/rivh1), Victor, Yuga |
| id_ID | [Erwin Guo](https://www.facebook.com/erwinfransiscus) |
| it_IT | Carola Mariano, ɳick |
| ml_IN | [Adhith T](https://github.com/adhitht123) |
| pt_BR | [Iuri Guilherme](https://github.com/iuriguilherme), [João Rodrigo Couto de Oliveira](http://twitter.com/JoaoRodrigoJR) |
| zh_CN | [imlonghao](https://github.com/imlonghao), [XhyEax](https://github.com/XhyEax) |
| zh_HK | [Jed Cheng](https://www.facebook.com/profile.php?id=100002258388821) |
| zh_TW | [Eugene Lam](https://www.facebook.com/eugenelam1118), [jimchen5209](https://www.youtube.com/user/jimchen5209), [pan93412](https://www.github.com/pan93412) |
Please add yourself here alphabetically when you submit your first translation.

View file

@ -6,8 +6,6 @@ import card as c
from datetime import datetime
from telegram import Message, Chat
from telegram.ext import CallbackContext
from apscheduler.jobstores.base import JobLookupError
from config import TIME_REMOVAL_AFTER_SKIP, MIN_FAST_TURN_TIME
from errors import DeckEmptyError, NotEnoughPlayersError
@ -113,7 +111,7 @@ def do_play_card(bot, player, result_id):
if us.stats:
us.games_played += 1
if game.players_won == 0:
if game.players_won is 0:
us.first_places += 1
game.players_won += 1
@ -155,15 +153,11 @@ def do_call_bluff(bot, player):
chat = game.chat
if player.prev.bluffing:
draw_prev = 4
draw_next = game.draw_counter - draw_prev
draw_next_text = ". " + __("Giving {count} cards to {name}").format(count=draw_next, name=player.user.first_name) if draw_next > 0 else ""
send_async(bot, chat.id,
text=__("Bluff called! Giving {count} cards to {name}" + draw_next_text,
text=__("Bluff called! Giving 4 cards to {name}",
multi=game.translate)
.format(name=player.prev.user.first_name, count=draw_prev))
.format(name=player.prev.user.first_name))
game.draw_counter = draw_prev
try:
player.prev.draw()
except DeckEmptyError:
@ -171,21 +165,12 @@ def do_call_bluff(bot, player):
text=__("There are no more cards in the deck.",
multi=game.translate))
game.draw_counter = draw_next
try:
player.draw()
except DeckEmptyError:
send_async(bot, player.game.chat.id,
text=__("There are no more cards in the deck.",
multi=game.translate))
else:
game.draw_counter += 2
send_async(bot, chat.id,
text=__("{name1} didn't bluff! Giving {count} cards to {name2}",
text=__("{name1} didn't bluff! Giving 6 cards to {name2}",
multi=game.translate)
.format(name1=player.prev.user.first_name,
count=game.draw_counter,
name2=player.user.first_name))
try:
player.draw()
@ -206,10 +191,7 @@ def start_player_countdown(bot, game, job_queue):
if game.mode == 'fast':
if game.job:
try:
game.job.schedule_removal()
except JobLookupError:
pass
game.job.schedule_removal()
job = job_queue.run_once(
#lambda x,y: do_skip(bot, player),
@ -223,9 +205,9 @@ def start_player_countdown(bot, game, job_queue):
player.game.job = job
def skip_job(context: CallbackContext):
player = context.job.context.player
def skip_job(bot, job):
player = job.context.player
game = player.game
if game_is_running(game):
job_queue = context.job.context.job_queue
do_skip(context.bot, player, job_queue)
job_queue = job.context.job_queue
do_skip(bot, player, job_queue)

203
bot.py
View file

@ -21,9 +21,9 @@ import logging
from datetime import datetime
from telegram import ParseMode, InlineKeyboardMarkup, \
InlineKeyboardButton, Update
InlineKeyboardButton
from telegram.ext import InlineQueryHandler, ChosenInlineResultHandler, \
CommandHandler, MessageHandler, Filters, CallbackQueryHandler, CallbackContext
CommandHandler, MessageHandler, Filters, CallbackQueryHandler
from telegram.ext.dispatcher import run_async
import card as c
@ -49,10 +49,9 @@ logging.basicConfig(
level=logging.INFO
)
logger = logging.getLogger(__name__)
logging.getLogger('apscheduler').setLevel(logging.WARNING)
@user_locale
def notify_me(update: Update, context: CallbackContext):
def notify_me(bot, update):
"""Handler for /notify_me command, pm people for next game"""
chat_id = update.message.chat_id
if update.message.chat.type == 'private':
@ -68,25 +67,15 @@ def notify_me(update: Update, context: CallbackContext):
@user_locale
def new_game(update: Update, context: CallbackContext):
def new_game(bot, update):
"""Handler for the /new command"""
chat_id = update.message.chat_id
if update.message.chat.type == 'private':
help_handler(update, context)
help_handler(bot, update)
else:
try:
_game = gm.chatid_games[chat_id][-1]
except (KeyError, IndexError):
pass
else:
send_async(bot, chat_id,
text=_("There is already a game running in this chat. Join the "
"game with /join"))
return
if update.message.chat_id in gm.remind_dict:
for user in gm.remind_dict[update.message.chat_id]:
send_async(bot,
@ -100,88 +89,88 @@ def new_game(update: Update, context: CallbackContext):
game.starter = update.message.from_user
game.owner.append(update.message.from_user.id)
game.mode = DEFAULT_GAMEMODE
send_async(context.bot, chat_id,
send_async(bot, chat_id,
text=_("Created a new game! Join the game with /join "
"and start the game with /start"))
@user_locale
def kill_game(update: Update, context: CallbackContext):
def kill_game(bot, update):
"""Handler for the /kill command"""
chat = update.message.chat
user = update.message.from_user
games = gm.chatid_games.get(chat.id)
if update.message.chat.type == 'private':
help_handler(update, context)
help_handler(bot, update)
return
if not games:
send_async(context.bot, chat.id,
send_async(bot, chat.id,
text=_("There is no running game in this chat."))
return
game = games[-1]
if user_is_creator_or_admin(user, game, context.bot, chat):
if user_is_creator_or_admin(user, game, bot, chat):
try:
gm.end_game(chat, user)
send_async(context.bot, chat.id, text=__("Game ended!", multi=game.translate))
send_async(bot, chat.id, text=__("Game ended!", multi=game.translate))
except NoGameInChatError:
send_async(context.bot, chat.id,
send_async(bot, chat.id,
text=_("The game is not started yet. "
"Join the game with /join and start the game with /start"),
reply_to_message_id=update.message.message_id)
else:
send_async(context.bot, chat.id,
send_async(bot, chat.id,
text=_("Only the game creator ({name}) and admin can do that.")
.format(name=game.starter.first_name),
reply_to_message_id=update.message.message_id)
@user_locale
def join_game(update: Update, context: CallbackContext):
def join_game(bot, update):
"""Handler for the /join command"""
chat = update.message.chat
if update.message.chat.type == 'private':
help_handler(update, context)
help_handler(bot, update)
return
try:
gm.join_game(update.message.from_user, chat)
except LobbyClosedError:
send_async(context.bot, chat.id, text=_("The lobby is closed"))
send_async(bot, chat.id, text=_("The lobby is closed"))
except NoGameInChatError:
send_async(context.bot, chat.id,
send_async(bot, chat.id,
text=_("No game is running at the moment. "
"Create a new game with /new"),
reply_to_message_id=update.message.message_id)
except AlreadyJoinedError:
send_async(context.bot, chat.id,
send_async(bot, chat.id,
text=_("You already joined the game. Start the game "
"with /start"),
reply_to_message_id=update.message.message_id)
except DeckEmptyError:
send_async(context.bot, chat.id,
send_async(bot, chat.id,
text=_("There are not enough cards left in the deck for "
"new players to join."),
reply_to_message_id=update.message.message_id)
else:
send_async(context.bot, chat.id,
send_async(bot, chat.id,
text=_("Joined the game"),
reply_to_message_id=update.message.message_id)
@user_locale
def leave_game(update: Update, context: CallbackContext):
def leave_game(bot, update):
"""Handler for the /leave command"""
chat = update.message.chat
user = update.message.from_user
@ -189,7 +178,7 @@ def leave_game(update: Update, context: CallbackContext):
player = gm.player_for_user_in_chat(user, chat)
if player is None:
send_async(context.bot, chat.id, text=_("You are not playing in a game in "
send_async(bot, chat.id, text=_("You are not playing in a game in "
"this group."),
reply_to_message_id=update.message.message_id)
return
@ -201,23 +190,23 @@ def leave_game(update: Update, context: CallbackContext):
gm.leave_game(user, chat)
except NoGameInChatError:
send_async(context.bot, chat.id, text=_("You are not playing in a game in "
send_async(bot, chat.id, text=_("You are not playing in a game in "
"this group."),
reply_to_message_id=update.message.message_id)
except NotEnoughPlayersError:
gm.end_game(chat, user)
send_async(context.bot, chat.id, text=__("Game ended!", multi=game.translate))
send_async(bot, chat.id, text=__("Game ended!", multi=game.translate))
else:
if game.started:
send_async(context.bot, chat.id,
send_async(bot, chat.id,
text=__("Okay. Next Player: {name}",
multi=game.translate).format(
name=display_name(game.current_player.user)),
reply_to_message_id=update.message.message_id)
else:
send_async(context.bot, chat.id,
send_async(bot, chat.id,
text=__("{name} left the game before it started.",
multi=game.translate).format(
name=display_name(user)),
@ -225,11 +214,11 @@ def leave_game(update: Update, context: CallbackContext):
@user_locale
def kick_player(update: Update, context: CallbackContext):
def kick_player(bot, update):
"""Handler for the /kick command"""
if update.message.chat.type == 'private':
help_handler(update, context)
help_handler(bot, update)
return
chat = update.message.chat
@ -239,20 +228,20 @@ def kick_player(update: Update, context: CallbackContext):
game = gm.chatid_games[chat.id][-1]
except (KeyError, IndexError):
send_async(context.bot, chat.id,
send_async(bot, chat.id,
text=_("No game is running at the moment. "
"Create a new game with /new"),
reply_to_message_id=update.message.message_id)
return
if not game.started:
send_async(context.bot, chat.id,
send_async(bot, chat.id,
text=_("The game is not started yet. "
"Join the game with /join and start the game with /start"),
reply_to_message_id=update.message.message_id)
return
if user_is_creator_or_admin(user, game, context.bot, chat):
if user_is_creator_or_admin(user, game, bot, chat):
if update.message.reply_to_message:
kicked = update.message.reply_to_message.from_user
@ -261,40 +250,40 @@ def kick_player(update: Update, context: CallbackContext):
gm.leave_game(kicked, chat)
except NoGameInChatError:
send_async(context.bot, chat.id, text=_("Player {name} is not found in the current game.".format(name=display_name(kicked))),
send_async(bot, chat.id, text=_("Player {name} is not found in the current game.".format(name=display_name(kicked))),
reply_to_message_id=update.message.message_id)
return
except NotEnoughPlayersError:
gm.end_game(chat, user)
send_async(context.bot, chat.id,
send_async(bot, chat.id,
text=_("{0} was kicked by {1}".format(display_name(kicked), display_name(user))))
send_async(context.bot, chat.id, text=__("Game ended!", multi=game.translate))
send_async(bot, chat.id, text=__("Game ended!", multi=game.translate))
return
send_async(context.bot, chat.id,
send_async(bot, chat.id,
text=_("{0} was kicked by {1}".format(display_name(kicked), display_name(user))))
else:
send_async(context.bot, chat.id,
send_async(bot, chat.id,
text=_("Please reply to the person you want to kick and type /kick again."),
reply_to_message_id=update.message.message_id)
return
send_async(context.bot, chat.id,
send_async(bot, chat.id,
text=__("Okay. Next Player: {name}",
multi=game.translate).format(
name=display_name(game.current_player.user)),
reply_to_message_id=update.message.message_id)
else:
send_async(context.bot, chat.id,
send_async(bot, chat.id,
text=_("Only the game creator ({name}) and admin can do that.")
.format(name=game.starter.first_name),
reply_to_message_id=update.message.message_id)
def select_game(update: Update, context: CallbackContext):
def select_game(bot, update):
"""Handler for callback queries to select the current game"""
chat_id = int(update.callback_query.data)
@ -310,15 +299,16 @@ def select_game(update: Update, context: CallbackContext):
text=_("Game not found."))
return
def selected():
@run_async
def selected(bot):
back = [[InlineKeyboardButton(text=_("Back to last group"),
switch_inline_query='')]]
context.bot.answerCallbackQuery(update.callback_query.id,
bot.answerCallbackQuery(update.callback_query.id,
text=_("Please switch to the group you selected!"),
show_alert=False,
timeout=TIMEOUT)
context.bot.editMessageText(chat_id=update.callback_query.message.chat_id,
bot.editMessageText(chat_id=update.callback_query.message.chat_id,
message_id=update.callback_query.message.message_id,
text=_("Selected group: {group}\n"
"<b>Make sure that you switch to the correct "
@ -328,11 +318,11 @@ def select_game(update: Update, context: CallbackContext):
parse_mode=ParseMode.HTML,
timeout=TIMEOUT)
dispatcher.run_async(selected)
selected(bot)
@game_locales
def status_update(update: Update, context: CallbackContext):
def status_update(bot, update):
"""Remove player from game if user leaves the group"""
chat = update.message.chat
@ -347,17 +337,17 @@ def status_update(update: Update, context: CallbackContext):
pass
except NotEnoughPlayersError:
gm.end_game(chat, user)
send_async(context.bot, chat.id, text=__("Game ended!",
send_async(bot, chat.id, text=__("Game ended!",
multi=game.translate))
else:
send_async(context.bot, chat.id, text=__("Removing {name} from the game",
send_async(bot, chat.id, text=__("Removing {name} from the game",
multi=game.translate)
.format(name=display_name(user)))
@game_locales
@user_locale
def start_game(update: Update, context: CallbackContext):
def start_game(bot, update, args, job_queue):
"""Handler for the /start command"""
if update.message.chat.type != 'private':
@ -366,16 +356,16 @@ def start_game(update: Update, context: CallbackContext):
try:
game = gm.chatid_games[chat.id][-1]
except (KeyError, IndexError):
send_async(context.bot, chat.id,
send_async(bot, chat.id,
text=_("There is no game running in this chat. Create "
"a new one with /new"))
return
if game.started:
send_async(context.bot, chat.id, text=_("The game has already started"))
send_async(bot, chat.id, text=_("The game has already started"))
elif len(game.players) < MIN_PLAYERS:
send_async(context.bot, chat.id,
send_async(bot, chat.id,
text=__("At least {minplayers} players must /join the game "
"before you can start it").format(minplayers=MIN_PLAYERS))
@ -393,29 +383,30 @@ def start_game(update: Update, context: CallbackContext):
multi=game.translate)
.format(name=display_name(game.current_player.user)))
@run_async
def send_first():
"""Send the first card and player"""
context.bot.sendSticker(chat.id,
bot.sendSticker(chat.id,
sticker=c.STICKERS[str(game.last_card)],
timeout=TIMEOUT)
context.bot.sendMessage(chat.id,
bot.sendMessage(chat.id,
text=first_message,
reply_markup=InlineKeyboardMarkup(choice),
timeout=TIMEOUT)
dispatcher.run_async(send_first)
start_player_countdown(context.bot, game, context.job_queue)
send_first()
start_player_countdown(bot, game, job_queue)
elif len(context.args) and context.args[0] == 'select':
elif len(args) and args[0] == 'select':
players = gm.userid_players[update.message.from_user.id]
groups = list()
for player in players:
title = player.game.chat.title
if player == gm.userid_current[update.message.from_user.id]:
if player is gm.userid_current[update.message.from_user.id]:
title = '- %s -' % player.game.chat.title
groups.append(
@ -423,23 +414,23 @@ def start_game(update: Update, context: CallbackContext):
callback_data=str(player.game.chat.id))]
)
send_async(context.bot, update.message.chat_id,
send_async(bot, update.message.chat_id,
text=_('Please select the group you want to play in.'),
reply_markup=InlineKeyboardMarkup(groups))
else:
help_handler(update, context)
help_handler(bot, update)
@user_locale
def close_game(update: Update, context: CallbackContext):
def close_game(bot, update):
"""Handler for the /close command"""
chat = update.message.chat
user = update.message.from_user
games = gm.chatid_games.get(chat.id)
if not games:
send_async(context.bot, chat.id,
send_async(bot, chat.id,
text=_("There is no running game in this chat."))
return
@ -447,12 +438,12 @@ def close_game(update: Update, context: CallbackContext):
if user.id in game.owner:
game.open = False
send_async(context.bot, chat.id, text=_("Closed the lobby. "
send_async(bot, chat.id, text=_("Closed the lobby. "
"No more players can join this game."))
return
else:
send_async(context.bot, chat.id,
send_async(bot, chat.id,
text=_("Only the game creator ({name}) and admin can do that.")
.format(name=game.starter.first_name),
reply_to_message_id=update.message.message_id)
@ -460,14 +451,14 @@ def close_game(update: Update, context: CallbackContext):
@user_locale
def open_game(update: Update, context: CallbackContext):
def open_game(bot, update):
"""Handler for the /open command"""
chat = update.message.chat
user = update.message.from_user
games = gm.chatid_games.get(chat.id)
if not games:
send_async(context.bot, chat.id,
send_async(bot, chat.id,
text=_("There is no running game in this chat."))
return
@ -475,11 +466,11 @@ def open_game(update: Update, context: CallbackContext):
if user.id in game.owner:
game.open = True
send_async(context.bot, chat.id, text=_("Opened the lobby. "
send_async(bot, chat.id, text=_("Opened the lobby. "
"New players may /join the game."))
return
else:
send_async(context.bot, chat.id,
send_async(bot, chat.id,
text=_("Only the game creator ({name}) and admin can do that.")
.format(name=game.starter.first_name),
reply_to_message_id=update.message.message_id)
@ -487,14 +478,14 @@ def open_game(update: Update, context: CallbackContext):
@user_locale
def enable_translations(update: Update, context: CallbackContext):
def enable_translations(bot, update):
"""Handler for the /enable_translations command"""
chat = update.message.chat
user = update.message.from_user
games = gm.chatid_games.get(chat.id)
if not games:
send_async(context.bot, chat.id,
send_async(bot, chat.id,
text=_("There is no running game in this chat."))
return
@ -502,12 +493,12 @@ def enable_translations(update: Update, context: CallbackContext):
if user.id in game.owner:
game.translate = True
send_async(context.bot, chat.id, text=_("Enabled multi-translations. "
send_async(bot, chat.id, text=_("Enabled multi-translations. "
"Disable with /disable_translations"))
return
else:
send_async(context.bot, chat.id,
send_async(bot, chat.id,
text=_("Only the game creator ({name}) and admin can do that.")
.format(name=game.starter.first_name),
reply_to_message_id=update.message.message_id)
@ -515,14 +506,14 @@ def enable_translations(update: Update, context: CallbackContext):
@user_locale
def disable_translations(update: Update, context: CallbackContext):
def disable_translations(bot, update):
"""Handler for the /disable_translations command"""
chat = update.message.chat
user = update.message.from_user
games = gm.chatid_games.get(chat.id)
if not games:
send_async(context.bot, chat.id,
send_async(bot, chat.id,
text=_("There is no running game in this chat."))
return
@ -530,13 +521,13 @@ def disable_translations(update: Update, context: CallbackContext):
if user.id in game.owner:
game.translate = False
send_async(context.bot, chat.id, text=_("Disabled multi-translations. "
send_async(bot, chat.id, text=_("Disabled multi-translations. "
"Enable them again with "
"/enable_translations"))
return
else:
send_async(context.bot, chat.id,
send_async(bot, chat.id,
text=_("Only the game creator ({name}) and admin can do that.")
.format(name=game.starter.first_name),
reply_to_message_id=update.message.message_id)
@ -545,14 +536,14 @@ def disable_translations(update: Update, context: CallbackContext):
@game_locales
@user_locale
def skip_player(update: Update, context: CallbackContext):
def skip_player(bot, update):
"""Handler for the /skip command"""
chat = update.message.chat
user = update.message.from_user
player = gm.player_for_user_in_chat(user, chat)
if not player:
send_async(context.bot, chat.id,
send_async(bot, chat.id,
text=_("You are not playing in a game in this chat."))
return
@ -567,19 +558,19 @@ def skip_player(update: Update, context: CallbackContext):
# You can skip yourself even if you have time left (you'll still draw)
if delta < skipped_player.waiting_time and player != skipped_player:
n = skipped_player.waiting_time - delta
send_async(context.bot, chat.id,
send_async(bot, chat.id,
text=_("Please wait {time} second",
"Please wait {time} seconds",
n)
.format(time=n),
reply_to_message_id=update.message.message_id)
else:
do_skip(context.bot, player)
do_skip(bot, player)
@game_locales
@user_locale
def reply_to_query(update: Update, context: CallbackContext):
def reply_to_query(bot, update):
"""
Handler for inline queries.
Builds the result list for inline queries and answers to the client.
@ -647,13 +638,13 @@ def reply_to_query(update: Update, context: CallbackContext):
if players and game and len(players) > 1:
switch = _('Current game: {game}').format(game=game.chat.title)
answer_async(context.bot, update.inline_query.id, results, cache_time=0,
answer_async(bot, update.inline_query.id, results, cache_time=0,
switch_pm_text=switch, switch_pm_parameter='select')
@game_locales
@user_locale
def process_result(update: Update, context: CallbackContext):
def process_result(bot, update, job_queue):
"""
Handler for chosen inline results.
Checks the players actions and acts accordingly.
@ -680,46 +671,38 @@ def process_result(update: Update, context: CallbackContext):
mode = result_id[5:]
game.set_mode(mode)
logger.info("Gamemode changed to {mode}".format(mode = mode))
send_async(context.bot, chat.id, text=__("Gamemode changed to {mode}".format(mode = mode)))
send_async(bot, chat.id, text=__("Gamemode changed to {mode}".format(mode = mode)))
return
elif len(result_id) == 36: # UUID result
return
elif int(anti_cheat) != last_anti_cheat:
send_async(context.bot, chat.id,
send_async(bot, chat.id,
text=__("Cheat attempt by {name}", multi=game.translate)
.format(name=display_name(player.user)))
return
elif result_id == 'call_bluff':
reset_waiting_time(context.bot, player)
do_call_bluff(context.bot, player)
reset_waiting_time(bot, player)
do_call_bluff(bot, player)
elif result_id == 'draw':
reset_waiting_time(context.bot, player)
do_draw(context.bot, player)
reset_waiting_time(bot, player)
do_draw(bot, player)
elif result_id == 'pass':
game.turn()
elif result_id in c.COLORS:
game.choose_color(result_id)
else:
reset_waiting_time(context.bot, player)
if game.mode == "text":
sticker_id = c.STICKERS.get(result_id)
if sticker_id:
context.bot.sendSticker(chat.id,
sticker=sticker_id,
timeout=TIMEOUT)
else:
logger.warning(f"no sticker found for {result_id=}")
do_play_card(context.bot, player, result_id)
reset_waiting_time(bot, player)
do_play_card(bot, player, result_id)
if game_is_running(game):
nextplayer_message = (
__("Next player: {name}", multi=game.translate)
.format(name=display_name(game.current_player.user)))
choice = [[InlineKeyboardButton(text=_("Make your choice!"), switch_inline_query_current_chat='')]]
send_async(context.bot, chat.id,
send_async(bot, chat.id,
text=nextplayer_message,
reply_markup=InlineKeyboardMarkup(choice))
start_player_countdown(context.bot, game, context.job_queue)
start_player_countdown(bot, game, job_queue)
def reset_waiting_time(bot, player):

354
card.py
View file

@ -60,252 +60,122 @@ DRAW_FOUR = 'draw_four'
SPECIALS = (CHOOSE, DRAW_FOUR)
CARDS_CLASSIC = {
"normal": {
"b_0": "BQADBAAD2QEAAl9XmQAB--inQsYcLTsC",
"b_1": "BQADBAAD2wEAAl9XmQABBzh4U-rFicEC",
"b_2": "BQADBAAD3QEAAl9XmQABo3l6TT0MzKwC",
"b_3": "BQADBAAD3wEAAl9XmQAB2y-3TSapRtIC",
"b_4": "BQADBAAD4QEAAl9XmQABT6nhOuolqKYC",
"b_5": "BQADBAAD4wEAAl9XmQABwRfmekGnpn0C",
"b_6": "BQADBAAD5QEAAl9XmQABQITgUsEsqxsC",
"b_7": "BQADBAAD5wEAAl9XmQABVhPF6EcfWjEC",
"b_8": "BQADBAAD6QEAAl9XmQABP6baig0pIvYC",
"b_9": "BQADBAAD6wEAAl9XmQAB0CQdsQs_pXIC",
"b_draw": "BQADBAAD7QEAAl9XmQAB00Wii7R3gDUC",
"b_skip": "BQADBAAD8QEAAl9XmQAB_RJHYKqlc-wC",
"b_reverse": "BQADBAAD7wEAAl9XmQABo7D0B9NUPmYC",
"g_0": "BQADBAAD9wEAAl9XmQABb8CaxxsQ-Y8C",
"g_1": "BQADBAAD-QEAAl9XmQAB9B6ti_j6UB0C",
"g_2": "BQADBAAD-wEAAl9XmQABYpLjOzbRz8EC",
"g_3": "BQADBAAD_QEAAl9XmQABKvc2ZCiY-D8C",
"g_4": "BQADBAAD_wEAAl9XmQABJB52wzPdHssC",
"g_5": "BQADBAADAQIAAl9XmQABp_Ep1I4GA2cC",
"g_6": "BQADBAADAwIAAl9XmQABaaMxxa4MihwC",
"g_7": "BQADBAADBQIAAl9XmQABv5Q264Crz8gC",
"g_8": "BQADBAADBwIAAl9XmQABjMH-X9UHh8sC",
"g_9": "BQADBAADCQIAAl9XmQAB26fZ2fW7vM0C",
"g_draw": "BQADBAADCwIAAl9XmQAB64jIZrgXrQUC",
"g_skip": "BQADBAADDwIAAl9XmQAB17yhhnh46VQC",
"g_reverse": "BQADBAADDQIAAl9XmQAB_xcaab0DkegC",
"r_0": "BQADBAADEQIAAl9XmQABiUfr1hz-zT8C",
"r_1": "BQADBAADEwIAAl9XmQAB5bWfwJGs6Q0C",
"r_2": "BQADBAADFQIAAl9XmQABHR4mg9Ifjw0C",
"r_3": "BQADBAADFwIAAl9XmQABYBx5O_PG2QIC",
"r_4": "BQADBAADGQIAAl9XmQABTQpGrlvet3cC",
"r_5": "BQADBAADGwIAAl9XmQABbdLt4gdntBQC",
"r_6": "BQADBAADHQIAAl9XmQABqEI274p3lSoC",
"r_7": "BQADBAADHwIAAl9XmQABCw8u67Q4EK4C",
"r_8": "BQADBAADIQIAAl9XmQAB8iDJmLxp8ogC",
"r_9": "BQADBAADIwIAAl9XmQAB_HCAww1kNGYC",
"r_draw": "BQADBAADJQIAAl9XmQABuz0OZ4l3k6MC",
"r_skip": "BQADBAADKQIAAl9XmQAC2AL5Ok_ULwI",
"r_reverse": "BQADBAADJwIAAl9XmQABu2tIeQTpDvUC",
"y_0": "BQADBAADKwIAAl9XmQAB_nWoNKe8DOQC",
"y_1": "BQADBAADLQIAAl9XmQABVprAGUDKgOQC",
"y_2": "BQADBAADLwIAAl9XmQABqyT4_YTm54EC",
"y_3": "BQADBAADMQIAAl9XmQABGC-Xxg_N6fIC",
"y_4": "BQADBAADMwIAAl9XmQABbc-ZGL8kApAC",
"y_5": "BQADBAADNQIAAl9XmQAB67QJZIF6XAcC",
"y_6": "BQADBAADNwIAAl9XmQABJg_7XXoITsoC",
"y_7": "BQADBAADOQIAAl9XmQABVrd7OcS2k34C",
"y_8": "BQADBAADOwIAAl9XmQABRpJSahBWk3EC",
"y_9": "BQADBAADPQIAAl9XmQAB9MwJWKLJogYC",
"y_draw": "BQADBAADPwIAAl9XmQABaPYK8oYg84cC",
"y_skip": "BQADBAADQwIAAl9XmQABO_AZKtxY6IMC",
"y_reverse": "BQADBAADQQIAAl9XmQABZdQFahGG6UQC",
"draw_four": "BQADBAAD9QEAAl9XmQABVlkSNfhn76cC",
"colorchooser": "BQADBAAD8wEAAl9XmQABl9rUOPqx4E4C",
},
"not_playable": {
"b_0": "BQADBAADRQIAAl9XmQAB1IfkQ5xAiK4C",
"b_1": "BQADBAADRwIAAl9XmQABbWvhTeKBii4C",
"b_2": "BQADBAADSQIAAl9XmQABS1djHgyQokMC",
"b_3": "BQADBAADSwIAAl9XmQABwQ6VTbgY-MIC",
"b_4": "BQADBAADTQIAAl9XmQABAlKUYha8YccC",
"b_5": "BQADBAADTwIAAl9XmQABMvx8xVDnhUEC",
"b_6": "BQADBAADUQIAAl9XmQABDEbhP1Zd31kC",
"b_7": "BQADBAADUwIAAl9XmQABXb5XQBBaAnIC",
"b_8": "BQADBAADVQIAAl9XmQABgL5HRDLvrjgC",
"b_9": "BQADBAADVwIAAl9XmQABtO3XDQWZLtYC",
"b_draw": "BQADBAADWQIAAl9XmQAB2kk__6_2IhMC",
"b_skip": "BQADBAADXQIAAl9XmQABEGJI6CaH3vcC",
"b_reverse": "BQADBAADWwIAAl9XmQAB_kZA6UdHXU8C",
"g_0": "BQADBAADYwIAAl9XmQABGD5a9oG7Yg4C",
"g_1": "BQADBAADZQIAAl9XmQABqwABZHAXZIg0Ag",
"g_2": "BQADBAADZwIAAl9XmQABTI3mrEhojRkC",
"g_3": "BQADBAADaQIAAl9XmQABVi3rUyzWS3YC",
"g_4": "BQADBAADawIAAl9XmQABZIf5ThaXnpUC",
"g_5": "BQADBAADbQIAAl9XmQABNndVJSQCenIC",
"g_6": "BQADBAADbwIAAl9XmQABpoy1c4ZkrvwC",
"g_7": "BQADBAADcQIAAl9XmQABDeaT5fzxwREC",
"g_8": "BQADBAADcwIAAl9XmQABLIQ06ZM5NnAC",
"g_9": "BQADBAADdQIAAl9XmQABel-mC7eXGsMC",
"g_draw": "BQADBAADdwIAAl9XmQABOHEpxSztCf8C",
"g_skip": "BQADBAADewIAAl9XmQABDaQdMxjjPsoC",
"g_reverse": "BQADBAADeQIAAl9XmQABek1lGz7SJNAC",
"r_0": "BQADBAADfQIAAl9XmQABWrxoiXcsg0EC",
"r_1": "BQADBAADfwIAAl9XmQABlav-bkgSgRcC",
"r_2": "BQADBAADgQIAAl9XmQABDjZkqfJ4AdAC",
"r_3": "BQADBAADgwIAAl9XmQABT7lH7VVcy3MC",
"r_4": "BQADBAADhQIAAl9XmQAB1arPC5x0LrwC",
"r_5": "BQADBAADhwIAAl9XmQABWvs7xkCDldkC",
"r_6": "BQADBAADiQIAAl9XmQABjwABH5ZonWn8Ag",
"r_7": "BQADBAADiwIAAl9XmQABjekJfm4fBDIC",
"r_8": "BQADBAADjQIAAl9XmQABqFjchpsJeEkC",
"r_9": "BQADBAADjwIAAl9XmQAB-sKdcgABdNKDAg",
"r_draw": "BQADBAADkQIAAl9XmQABtw9RPVDHZOQC",
"r_skip": "BQADBAADlQIAAl9XmQABtG2GixCxtX4C",
"r_reverse": "BQADBAADkwIAAl9XmQABz2qyEbabnVsC",
"y_0": "BQADBAADlwIAAl9XmQABAb3ZwTGS1lMC",
"y_1": "BQADBAADmQIAAl9XmQAB9v5qJk9R0x8C",
"y_2": "BQADBAADmwIAAl9XmQABCsgpRHC2g-cC",
"y_3": "BQADBAADnQIAAl9XmQAB3kLLXCv-qY0C",
"y_4": "BQADBAADnwIAAl9XmQAB7R_y-NexNLIC",
"y_5": "BQADBAADoQIAAl9XmQABl-7mwsjD-cMC",
"y_6": "BQADBAADowIAAl9XmQABwbVsyv2MfPkC",
"y_7": "BQADBAADpQIAAl9XmQABoBqC0JsemVwC",
"y_8": "BQADBAADpwIAAl9XmQABpkwAAeh9ldlHAg",
"y_9": "BQADBAADqQIAAl9XmQABpSBEUfd4IM8C",
"y_draw": "BQADBAADqwIAAl9XmQABMt-2zW0VYb4C",
"y_skip": "BQADBAADrwIAAl9XmQABIDf-_TuuxtEC",
"y_reverse": "BQADBAADrQIAAl9XmQABm9M0Zh-_UwkC",
"draw_four": "BQADBAADYQIAAl9XmQAB_HWlvZIscDEC",
"colorchooser": "BQADBAADXwIAAl9XmQABY_ksDdMex-wC",
},
}
CARDS_CLASSIC_COLORBLIND = {
"normal": {
"colorchooser": "CAADBAADrg4AAvX2mVEpx_BiDIE5nQI",
"draw_four": "CAADBAADYRAAArnkmVGmqXHhjWEBxAI",
"r_0": "CAADBAAD6A8AAn_ckVHPWHqiBR_3jAI",
"r_1": "CAADBAAD5Q0AAg-ImVEx-blQI88RrQI",
"r_2": "CAADBAAD1g0AAuMjmVEkQsVhN49DMAI",
"r_3": "CAADBAADlhAAAqy4mVHWovoaWfQG_gI",
"r_4": "CAADBAADCRoAAqf_kVFnl8ACL1rjpwI",
"r_5": "CAADBAADVw8AAjmamVEEv2TVeL9cpQI",
"r_6": "CAADBAADHQ4AAuuUkVH2I-yn6nRBVAI",
"r_7": "CAADBAADNQ8AArP1kVF5rqHtk0pQ-AI",
"r_8": "CAADBAAD1BAAAuQDkVEPiIodUi6WvwI",
"r_9": "CAADBAAD2Q4AAq1nkFHM6z5C0Kff2QI",
"r_draw": "CAADBAADvQ8AAqZukFGEmkRSoSZQEwI",
"r_reverse": "CAADBAAD5RAAAg89mVE8-EY_2DifcAI",
"r_skip": "CAADBAADRg4AAp8bmVFOC6xdEZZRwwI",
"g_0": "CAADBAADTg4AAoQxmFF07jR_vfB4xgI",
"g_1": "CAADBAADQg4AAhkgmFGlsif9nNtXwgI",
"g_2": "CAADBAAD2BUAAue_mFGENiPSjZxbiQI",
"g_3": "CAADBAADpw4AAjO9mFHAOz8KD2n7BwI",
"g_4": "CAADBAADRhAAAqF7kFEcwLalLfDfaAI",
"g_5": "CAADBAADAg8AAqXLmFHJyg2F_ybbvwI",
"g_6": "CAADBAADVhYAAtK7mVGigRq_EkCuVgI",
"g_7": "CAADBAAD2RIAArccmFEj-8LIVNAbsgI",
"g_8": "CAADBAAD6AwAAuvmmFHBRarMimOWawI",
"g_9": "CAADBAADExEAAsNkmVFr8DaHGOwsggI",
"g_draw": "CAADBAADhA8AArxYmVH9ch5Jp00AAboC",
"g_reverse": "CAADBAADMhAAAvVOmFGH284LIY7cegI",
"g_skip": "CAADBAADbBcAAqinkVEOwkJtDRfk2gI",
"b_0": "CAADBAAD-BAAAkj8kFG61GJdw29QOAI",
"b_1": "CAADBAADcRMAAu-EmFFT1i4LcqO4OQI",
"b_2": "CAADBAAD0xQAAqVhmVHyrFSAbxtfjwI",
"b_3": "CAADBAADNg0AAn-xmFHev8IdF_ie0wI",
"b_4": "CAADBAADlQ4AAjZamVFcIL_pVB5cFwI",
"b_5": "CAADBAADrgwAAuL5mVHvEBZ8CG5p5QI",
"b_6": "CAADBAADDhUAAuGRmVGQYvmEOxczBAI",
"b_7": "CAADBAADIxEAAv_dmFEuVt39kkgZgwI",
"b_8": "CAADBAAD2w0AAoE6kVHG7WscV4F2hwI",
"b_9": "CAADBAADvQ0AArRMmVErWaSRP_giKQI",
"b_draw": "CAADBAADlw4AAjF_kFHPWSoYKBwtwQI",
"b_reverse": "CAADBAADog8AAqDJmVEJQp5WocnUnQI",
"b_skip": "CAADBAAD-QwAAgbZmFGltUlnslDNUQI",
"y_0": "CAADBAADrQ4AAr5WmVHNf69eBn2YOAI",
"y_1": "CAADBAADcg8AAmqKmVHfVeUI3u_i7AI",
"y_2": "CAADBAADkA4AAuDImFEQ8qjFlcKplQI",
"y_3": "CAADBAAD-QwAAmromFGAqVn-Y8N72wI",
"y_4": "CAADBAADjQ4AAmNLmFG80k7kfgx1NAI",
"y_5": "CAADBAADqQ8AAmgYmFH1_ey_bMQNYwI",
"y_6": "CAADBAADdQ0AAuWcmFEbG_gm1wGYCQI",
"y_7": "CAADBAAD6QwAApQAAZhRI8OfRvLX3vkC",
"y_8": "CAADBAADARAAAi-2kVEifJ-O9WVilgI",
"y_9": "CAADBAADxA0AAhQ8mFHjnl9tUCHSLAI",
"y_draw": "CAADBAADzw4AAncZmVEhLhX17eqX8AI",
"y_reverse": "CAADBAADTxAAAqgFmVEJRBw4eWgnDwI",
"y_skip": "CAADBAADPhYAAiGbkFG9hptFPLgj7wI",
},
"not_playable": {
"colorchooser": "CAADBAADpQ4AAlfDmFFHGkwyGFeCFQI",
"draw_four": "CAADBAADMRMAAv7amFHvKGLoNyFbNQI",
"r_0": "CAADBAADsBMAAuGdkFHTZ-jl4eNn-gI",
"r_1": "CAADBAADVA4AAhpfkFEKt19qveGSPgI",
"r_2": "CAADBAADrw0AAoWsmVHguULNoYJwUwI",
"r_3": "CAADBAADzxMAAjvkkFFdtKJu5WGwUgI",
"r_4": "CAADBAAD1Q8AAoHZkFFvyQnFHzfwiQI",
"r_5": "CAADBAADWxEAAvkHkFGUo86qxKV0kwI",
"r_6": "CAADBAAD_hIAAjx0mVGmlm-b_FHQBAI",
"r_7": "CAADBAADmhEAAslomFHOv7bqcDJkDAI",
"r_8": "CAADBAADtw0AAgqVmVG2HdSbcJYxZgI",
"r_9": "CAADBAADNxEAAuF6mVE3WzTMJkSVAgI",
"r_draw": "CAADBAADVxAAAiNukFE1K2xORNnfMwI",
"r_reverse": "CAADBAADQxMAAvH0mVHKznpt-uu9ngI",
"r_skip": "CAADBAADZA4AApbPkFFB9E2Px-HFpgI",
"g_0": "CAADBAAD8w4AAjDEmFG7DwKggUEj9QI",
"g_1": "CAADBAAD2g0AAo_DmVHIPG84WdIo1wI",
"g_2": "CAADBAADEhEAAoRXmVGIG2nuN45P6AI",
"g_3": "CAADBAADug8AAsSRmFFzk0TcRuG8VAI",
"g_4": "CAADBAADrQ8AAvgmkFESfo9BjF7-3gI",
"g_5": "CAADBAADVhAAAnPqkFFtxtFX9HlT-AI",
"g_6": "CAADBAADMg8AAiSBmFHIQw1jFjv6UwI",
"g_7": "CAADBAADvREAAv0BkVGDq3H1DCq_DQI",
"g_8": "CAADBAADWQ4AAhOEkVG96JDgCtFrEwI",
"g_9": "CAADBAAD2xYAAruDmFFAUMFryEwjoAI",
"g_draw": "CAADBAADLA4AAu9tkVGTzBbeeYydIQI",
"g_reverse": "CAADBAADVAwAAhYYmFExJS0ozE8-rAI",
"g_skip": "CAADBAADYg4AAulsmFHxOkaz9OsTiwI",
"b_0": "CAADBAADVxUAAtnOkFEIAAGw5CZEIxgC",
"b_1": "CAADBAAD1RAAAnQqkFF9kDqD0wp3ngI",
"b_2": "CAADBAADZg4AAvcUmVHTXwldirf1hAI",
"b_3": "CAADBAADfBAAAkX1mVHw0CWX0h31iQI",
"b_4": "CAADBAADPBAAAuTCmFFDpvXzes4qjwI",
"b_5": "CAADBAADTQ4AAsWQmVHcrxDQUWOB4AI",
"b_6": "CAADBAAD_hAAAoUhmVG8kjd65J8EngI",
"b_7": "CAADBAADlRAAArtjkFGko5TuFNnncwI",
"b_8": "CAADBAADZQ8AAltEmFE_fDYIXBrV3QI",
"b_9": "CAADBAADrhAAAtM-mVGwhrWTD9IaYgI",
"b_draw": "CAADBAADtQ0AAnVbmFGC1hI60JaOQQI",
"b_reverse": "CAADBAADShEAAlcOmFHStPeFzfVIEwI",
"b_skip": "CAADBAAD_xEAAgZFmVFMRA1J8Y1gxAI",
"y_0": "CAADBAAD7xAAAqjjmFHnCu7eKJvSBgI",
"y_1": "CAADBAADJQwAAp6tmFE2zDPVMieQ2QI",
"y_2": "CAADBAADNA4AAl2mmVFpQOxJ41gk_gI",
"y_3": "CAADBAAD3A4AAsxPmFGyZFv42UlxAQI",
"y_4": "CAADBAADwg8AAm88kVEc9HZpl2gmzQI",
"y_5": "CAADBAAD5hIAAkQ6mFHS-aGVuYZAnAI",
"y_6": "CAADBAADvQ8AAs3RmVHVkVBfEF7eIwI",
"y_7": "CAADBAAD1gwAAjlbmFGGH6rBdqP8QQI",
"y_8": "CAADBAADbg8AAqvXkVH1ESeZFcGVrgI",
"y_9": "CAADBAADOQ8AAnjokVG96pmCP7aZ3AI",
"y_draw": "CAADBAAD6w4AAgsJmVETUteFwqTVJgI",
"y_reverse": "CAADBAADtg8AAqiFmFFwothyN9TrXwI",
"y_skip": "CAADBAADSxEAAhcSmFGu_F5LffmsZgI",
},
}
STICKERS_OPTIONS = {
"option_draw": "BQADBAAD-AIAAl9XmQABxEjEcFM-VHIC",
"option_pass": "BQADBAAD-gIAAl9XmQABcEkAAbaZ4SicAg",
"option_bluff": "BQADBAADygIAAl9XmQABJoLfB9ntI2UC",
"option_info": "BQADBAADxAIAAl9XmQABC5v3Z77VLfEC",
}
# TODO: Support multiple card packs
# For now, just use classic colorblind
STICKERS = {
**CARDS_CLASSIC_COLORBLIND["normal"],
**STICKERS_OPTIONS,
'b_0': 'BQADBAAD2QEAAl9XmQAB--inQsYcLTsC',
'b_1': 'BQADBAAD2wEAAl9XmQABBzh4U-rFicEC',
'b_2': 'BQADBAAD3QEAAl9XmQABo3l6TT0MzKwC',
'b_3': 'BQADBAAD3wEAAl9XmQAB2y-3TSapRtIC',
'b_4': 'BQADBAAD4QEAAl9XmQABT6nhOuolqKYC',
'b_5': 'BQADBAAD4wEAAl9XmQABwRfmekGnpn0C',
'b_6': 'BQADBAAD5QEAAl9XmQABQITgUsEsqxsC',
'b_7': 'BQADBAAD5wEAAl9XmQABVhPF6EcfWjEC',
'b_8': 'BQADBAAD6QEAAl9XmQABP6baig0pIvYC',
'b_9': 'BQADBAAD6wEAAl9XmQAB0CQdsQs_pXIC',
'b_draw': 'BQADBAAD7QEAAl9XmQAB00Wii7R3gDUC',
'b_skip': 'BQADBAAD8QEAAl9XmQAB_RJHYKqlc-wC',
'b_reverse': 'BQADBAAD7wEAAl9XmQABo7D0B9NUPmYC',
'g_0': 'BQADBAAD9wEAAl9XmQABb8CaxxsQ-Y8C',
'g_1': 'BQADBAAD-QEAAl9XmQAB9B6ti_j6UB0C',
'g_2': 'BQADBAAD-wEAAl9XmQABYpLjOzbRz8EC',
'g_3': 'BQADBAAD_QEAAl9XmQABKvc2ZCiY-D8C',
'g_4': 'BQADBAAD_wEAAl9XmQABJB52wzPdHssC',
'g_5': 'BQADBAADAQIAAl9XmQABp_Ep1I4GA2cC',
'g_6': 'BQADBAADAwIAAl9XmQABaaMxxa4MihwC',
'g_7': 'BQADBAADBQIAAl9XmQABv5Q264Crz8gC',
'g_8': 'BQADBAADBwIAAl9XmQABjMH-X9UHh8sC',
'g_9': 'BQADBAADCQIAAl9XmQAB26fZ2fW7vM0C',
'g_draw': 'BQADBAADCwIAAl9XmQAB64jIZrgXrQUC',
'g_skip': 'BQADBAADDwIAAl9XmQAB17yhhnh46VQC',
'g_reverse': 'BQADBAADDQIAAl9XmQAB_xcaab0DkegC',
'r_0': 'BQADBAADEQIAAl9XmQABiUfr1hz-zT8C',
'r_1': 'BQADBAADEwIAAl9XmQAB5bWfwJGs6Q0C',
'r_2': 'BQADBAADFQIAAl9XmQABHR4mg9Ifjw0C',
'r_3': 'BQADBAADFwIAAl9XmQABYBx5O_PG2QIC',
'r_4': 'BQADBAADGQIAAl9XmQABTQpGrlvet3cC',
'r_5': 'BQADBAADGwIAAl9XmQABbdLt4gdntBQC',
'r_6': 'BQADBAADHQIAAl9XmQABqEI274p3lSoC',
'r_7': 'BQADBAADHwIAAl9XmQABCw8u67Q4EK4C',
'r_8': 'BQADBAADIQIAAl9XmQAB8iDJmLxp8ogC',
'r_9': 'BQADBAADIwIAAl9XmQAB_HCAww1kNGYC',
'r_draw': 'BQADBAADJQIAAl9XmQABuz0OZ4l3k6MC',
'r_skip': 'BQADBAADKQIAAl9XmQAC2AL5Ok_ULwI',
'r_reverse': 'BQADBAADJwIAAl9XmQABu2tIeQTpDvUC',
'y_0': 'BQADBAADKwIAAl9XmQAB_nWoNKe8DOQC',
'y_1': 'BQADBAADLQIAAl9XmQABVprAGUDKgOQC',
'y_2': 'BQADBAADLwIAAl9XmQABqyT4_YTm54EC',
'y_3': 'BQADBAADMQIAAl9XmQABGC-Xxg_N6fIC',
'y_4': 'BQADBAADMwIAAl9XmQABbc-ZGL8kApAC',
'y_5': 'BQADBAADNQIAAl9XmQAB67QJZIF6XAcC',
'y_6': 'BQADBAADNwIAAl9XmQABJg_7XXoITsoC',
'y_7': 'BQADBAADOQIAAl9XmQABVrd7OcS2k34C',
'y_8': 'BQADBAADOwIAAl9XmQABRpJSahBWk3EC',
'y_9': 'BQADBAADPQIAAl9XmQAB9MwJWKLJogYC',
'y_draw': 'BQADBAADPwIAAl9XmQABaPYK8oYg84cC',
'y_skip': 'BQADBAADQwIAAl9XmQABO_AZKtxY6IMC',
'y_reverse': 'BQADBAADQQIAAl9XmQABZdQFahGG6UQC',
'draw_four': 'BQADBAAD9QEAAl9XmQABVlkSNfhn76cC',
'colorchooser': 'BQADBAAD8wEAAl9XmQABl9rUOPqx4E4C',
'option_draw': 'BQADBAAD-AIAAl9XmQABxEjEcFM-VHIC',
'option_pass': 'BQADBAAD-gIAAl9XmQABcEkAAbaZ4SicAg',
'option_bluff': 'BQADBAADygIAAl9XmQABJoLfB9ntI2UC',
'option_info': 'BQADBAADxAIAAl9XmQABC5v3Z77VLfEC'
}
STICKERS_GREY = {
**CARDS_CLASSIC_COLORBLIND["not_playable"],
'b_0': 'BQADBAADRQIAAl9XmQAB1IfkQ5xAiK4C',
'b_1': 'BQADBAADRwIAAl9XmQABbWvhTeKBii4C',
'b_2': 'BQADBAADSQIAAl9XmQABS1djHgyQokMC',
'b_3': 'BQADBAADSwIAAl9XmQABwQ6VTbgY-MIC',
'b_4': 'BQADBAADTQIAAl9XmQABAlKUYha8YccC',
'b_5': 'BQADBAADTwIAAl9XmQABMvx8xVDnhUEC',
'b_6': 'BQADBAADUQIAAl9XmQABDEbhP1Zd31kC',
'b_7': 'BQADBAADUwIAAl9XmQABXb5XQBBaAnIC',
'b_8': 'BQADBAADVQIAAl9XmQABgL5HRDLvrjgC',
'b_9': 'BQADBAADVwIAAl9XmQABtO3XDQWZLtYC',
'b_draw': 'BQADBAADWQIAAl9XmQAB2kk__6_2IhMC',
'b_skip': 'BQADBAADXQIAAl9XmQABEGJI6CaH3vcC',
'b_reverse': 'BQADBAADWwIAAl9XmQAB_kZA6UdHXU8C',
'g_0': 'BQADBAADYwIAAl9XmQABGD5a9oG7Yg4C',
'g_1': 'BQADBAADZQIAAl9XmQABqwABZHAXZIg0Ag',
'g_2': 'BQADBAADZwIAAl9XmQABTI3mrEhojRkC',
'g_3': 'BQADBAADaQIAAl9XmQABVi3rUyzWS3YC',
'g_4': 'BQADBAADawIAAl9XmQABZIf5ThaXnpUC',
'g_5': 'BQADBAADbQIAAl9XmQABNndVJSQCenIC',
'g_6': 'BQADBAADbwIAAl9XmQABpoy1c4ZkrvwC',
'g_7': 'BQADBAADcQIAAl9XmQABDeaT5fzxwREC',
'g_8': 'BQADBAADcwIAAl9XmQABLIQ06ZM5NnAC',
'g_9': 'BQADBAADdQIAAl9XmQABel-mC7eXGsMC',
'g_draw': 'BQADBAADdwIAAl9XmQABOHEpxSztCf8C',
'g_skip': 'BQADBAADewIAAl9XmQABDaQdMxjjPsoC',
'g_reverse': 'BQADBAADeQIAAl9XmQABek1lGz7SJNAC',
'r_0': 'BQADBAADfQIAAl9XmQABWrxoiXcsg0EC',
'r_1': 'BQADBAADfwIAAl9XmQABlav-bkgSgRcC',
'r_2': 'BQADBAADgQIAAl9XmQABDjZkqfJ4AdAC',
'r_3': 'BQADBAADgwIAAl9XmQABT7lH7VVcy3MC',
'r_4': 'BQADBAADhQIAAl9XmQAB1arPC5x0LrwC',
'r_5': 'BQADBAADhwIAAl9XmQABWvs7xkCDldkC',
'r_6': 'BQADBAADiQIAAl9XmQABjwABH5ZonWn8Ag',
'r_7': 'BQADBAADiwIAAl9XmQABjekJfm4fBDIC',
'r_8': 'BQADBAADjQIAAl9XmQABqFjchpsJeEkC',
'r_9': 'BQADBAADjwIAAl9XmQAB-sKdcgABdNKDAg',
'r_draw': 'BQADBAADkQIAAl9XmQABtw9RPVDHZOQC',
'r_skip': 'BQADBAADlQIAAl9XmQABtG2GixCxtX4C',
'r_reverse': 'BQADBAADkwIAAl9XmQABz2qyEbabnVsC',
'y_0': 'BQADBAADlwIAAl9XmQABAb3ZwTGS1lMC',
'y_1': 'BQADBAADmQIAAl9XmQAB9v5qJk9R0x8C',
'y_2': 'BQADBAADmwIAAl9XmQABCsgpRHC2g-cC',
'y_3': 'BQADBAADnQIAAl9XmQAB3kLLXCv-qY0C',
'y_4': 'BQADBAADnwIAAl9XmQAB7R_y-NexNLIC',
'y_5': 'BQADBAADoQIAAl9XmQABl-7mwsjD-cMC',
'y_6': 'BQADBAADowIAAl9XmQABwbVsyv2MfPkC',
'y_7': 'BQADBAADpQIAAl9XmQABoBqC0JsemVwC',
'y_8': 'BQADBAADpwIAAl9XmQABpkwAAeh9ldlHAg',
'y_9': 'BQADBAADqQIAAl9XmQABpSBEUfd4IM8C',
'y_draw': 'BQADBAADqwIAAl9XmQABMt-2zW0VYb4C',
'y_skip': 'BQADBAADrwIAAl9XmQABIDf-_TuuxtEC',
'y_reverse': 'BQADBAADrQIAAl9XmQABm9M0Zh-_UwkC',
'draw_four': 'BQADBAADYQIAAl9XmQAB_HWlvZIscDEC',
'colorchooser': 'BQADBAADXwIAAl9XmQABY_ksDdMex-wC'
}

View file

@ -1,8 +0,0 @@
version: "3.9"
services:
unobot:
container_name: unobot
build:
context: .
dockerfile: Dockerfile
restart: unless-stopped

View file

@ -58,7 +58,7 @@ class Game(object):
current_player = self.current_player
itplayer = current_player.next
players.append(current_player)
while itplayer and itplayer != current_player:
while itplayer and itplayer is not current_player:
players.append(itplayer)
itplayer = itplayer.next
return players
@ -121,7 +121,7 @@ class Game(object):
self.logger.debug("Draw counter increased by 2")
elif card.value == c.REVERSE:
# Special rule for two players
if self.current_player == self.current_player.next.next:
if self.current_player is self.current_player.next.next:
self.turn()
else:
self.reverse()

View file

@ -110,7 +110,7 @@ class GameManager(object):
for g in games:
for p in g.players:
if p.user.id == user.id:
if p == g.current_player:
if p is g.current_player:
g.turn()
p.leave()

View file

@ -1,4 +0,0 @@
{
"api_id": 0,
"api_hash": ""
}

View file

@ -1,92 +0,0 @@
"""
Script to build the classic colorblind deck from the classic deck.
Requires imagemagick to be installed and in the path.
"""
from pathlib import Path
from shutil import copyfile
from subprocess import run
IMAGES_DIR = Path(__file__).resolve().parent
CLASSIC_DIR = IMAGES_DIR / "classic"
COLORBLIND_DIR = IMAGES_DIR / "classic_colorblind"
COLORBLIND_OVERLAY_DIR = IMAGES_DIR / "colorblind_overlay"
COLORS = ["r", "g", "b", "y"]
NUMBERS = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "draw", "reverse", "skip"]
SPECIALS = ["colorchooser", "draw_four"]
def overlay_image(color, number):
base = CLASSIC_DIR / "png" / f"{color}_{number}.png"
overlay = COLORBLIND_OVERLAY_DIR / f"{color}.png"
out = COLORBLIND_DIR / "png" / f"{color}_{number}.png"
run(["magick", "convert", str(base), str(overlay), "-composite", str(out)])
def create_not_playable(card):
base = COLORBLIND_DIR / "png" / f"{card}.png"
overlay = COLORBLIND_OVERLAY_DIR / "not_playable.png"
out = COLORBLIND_DIR / "png_not_playable" / f"{card}.png"
run(
[
"magick",
"convert",
str(base),
"-modulate",
"75,20",
"-brightness-contrast",
"0x10",
str(overlay),
"-composite",
str(out),
]
)
def convert_png_to_webp(suffix):
for color in COLORS:
for number in NUMBERS:
card = f"{color}_{number}"
png = COLORBLIND_DIR / f"png{suffix}" / f"{card}.png"
webp = COLORBLIND_DIR / f"webp{suffix}" / f"{card}.webp"
run(["magick", "convert", str(png), "-define", "webp:lossless=true", str(webp)])
for special in SPECIALS:
png = COLORBLIND_DIR / f"png{suffix}" / f"{special}.png"
webp = COLORBLIND_DIR / f"webp{suffix}" / f"{special}.webp"
run(["magick", "convert", str(png), "-define", "webp:lossless=true", str(webp)])
def main():
(COLORBLIND_DIR / "png").mkdir(parents=True, exist_ok=True)
(COLORBLIND_DIR / "png_not_playable").mkdir(parents=True, exist_ok=True)
(COLORBLIND_DIR / "webp").mkdir(parents=True, exist_ok=True)
(COLORBLIND_DIR / "webp_not_playable").mkdir(parents=True, exist_ok=True)
for color in COLORS:
for number in NUMBERS:
overlay_image(color, number)
for special in SPECIALS:
copyfile(
CLASSIC_DIR / "png" / f"{special}.png",
COLORBLIND_DIR / "png" / f"{special}.png",
)
for color in COLORS:
for number in NUMBERS:
create_not_playable(f"{color}_{number}")
for special in SPECIALS:
create_not_playable(special)
convert_png_to_webp("")
convert_png_to_webp("_not_playable")
if __name__ == "__main__":
main()

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

Some files were not shown because too many files have changed in this diff Show more