Skip to content

Commit

Permalink
Merge b044756 into e347e6f
Browse files Browse the repository at this point in the history
  • Loading branch information
d-Rickyy-b committed Mar 27, 2021
2 parents e347e6f + b044756 commit d8391fa
Show file tree
Hide file tree
Showing 18 changed files with 322 additions and 27 deletions.
108 changes: 108 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class

# C extensions
*.so

# Distribution / packaging
.Python
env/
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
*.egg-info/
.installed.cfg
*.egg

# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec

# Installer logs
pip-log.txt
pip-delete-this-directory.txt

# Unit test / coverage reports
htmlcov/
.tox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*,cover
.hypothesis/

# Translations
*.mo
*.pot

# Django stuff:
*.log
local_settings.py

# Flask stuff:
instance/
.webassets-cache

# Scrapy stuff:
.scrapy

# Sphinx documentation
docs/_build/

# PyBuilder
target/

# IPython Notebook
.ipynb_checkpoints

# pyenv
.python-version

# celery beat schedule file
celerybeat-schedule

# dotenv
.env

# virtualenv
venv/
ENV/
.venv

# Spyder project settings
.spyderproject

# Rope project settings
.ropeproject


.idea
.svn
.git
__pycache__
*.db
.vs
.github

# Keep logrotated files out of git
logs/*.log*
config.py

*.md
LICENSE
.travis.yml
*_test.py
File renamed without changes.
11 changes: 11 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
FROM python:3-slim

LABEL maintainer="d-Rickyy-b <blackjack@rico-j.de>"
LABEL site="https://github.com/d-Rickyy-b/Python-BlackJackBot"

RUN mkdir -p /blackjackbot/logs
COPY . /blackjackbot
WORKDIR /blackjackbot
RUN pip install --no-cache-dir -r /blackjackbot/requirements.txt

CMD ["python", "/blackjackbot/bot.py"]
27 changes: 19 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,20 +1,31 @@
[![Build Status](https://travis-ci.com/d-Rickyy-b/Python-BlackJackBot.svg?branch=master)](https://travis-ci.com/d-Rickyy-b/Python-BlackJackBot)

[![Build Status](https://github.com/d-Rickyy-b/Python-BlackJackBot/actions/workflows/python-lint-test.yml/badge.svg)](https://github.com/d-Rickyy-b/Python-BlackJackBot/actions/workflows/python-lint-test.yml)
[![Coverage Status](https://coveralls.io/repos/github/d-Rickyy-b/Python-BlackJackBot/badge.svg?branch=rebuild)](https://coveralls.io/github/d-Rickyy-b/Python-BlackJackBot?branch=rebuild)
[![Codacy Badge](https://app.codacy.com/project/badge/Grade/12996d68fc0f436085221ac6b1f525f9)](https://www.codacy.com/manual/d-Rickyy-b/Python-BlackJackBot?utm_source=github.com&amp;utm_medium=referral&amp;utm_content=d-Rickyy-b/Python-BlackJackBot&amp;utm_campaign=Badge_Grade)

# Python-BlackJackBot

This is the code for my Telegram Bot with which you can play the game Black Jack. You can find it here: https://telegram.me/BlackJackBot

The main file, which needs to be executed is 'main.py'. Please create a copy of the config.sample.py, name it config.py and enter the config data (e.g. bot
token).
This is the code for my Telegram Bot with which you can play the game Black Jack.
You can find the hosted version of it here: https://telegram.me/BlackJackBot

## Setup
This project is really easy to set up. No matter which of the following ways you'll use, you'll always need a config file.
To create one, simply copy the existing `config.sample.py` file and name it `config.py`. Enter your bot token and make your changes accordingly.

The bot uses the [python-telegram-bot](https://python-telegram-bot.org/) framework to make Telegram API calls. You can install it like that:
Then you're left with several ways to run this bot.

### 1.) Cloning the repo
If you want to run this code from source, you can just `git clone` this repo.
It's recommended to create a new virtual environment (`python3 -m venv /path/to/venv`).
This bot uses the [python-telegram-bot](https://python-telegram-bot.org/) framework to make Telegram API calls.
You can install it (and potential other requlrements) like that:

``pip install -r requirements.txt``

## Database
Afterwards just run `python3 bot.py` and if done right, you'll be left with a working bot.

### 2.) Docker
This project also contains a `Dockerfile` as well as a pre-built [Docker image](https://hub.docker.com/repository/docker/0rickyy0/blackjackbot) hosted on the official Docker Hub.

The bot uses a SQLite database. The database file is in the "database" directory. It is called 'users.db'. The database gets auto-generated, if it doesn't exist. Make sure the program has write access to the database directory.
You will also find the `docker-compose.yml` file with which you can easily set up your own instance of the bot.
Just specify the path to your config etc. in said docker-compose file.
1 change: 1 addition & 0 deletions blackjackbot/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
reload_lang_command_handler = CommandHandler("reload_lang", admin.reload_languages_cmd)
users_command_handler = CommandHandler("users", admin.users_cmd)
answer_command_handler = CommandHandler("answer", admin.answer_comment_cmd, Filters.reply)
kill_command_handler = CommandHandler("kill", admin.kill_game_cmd, Filters.text)

# Callback handlers
hit_callback_handler = CallbackQueryHandler(game.hit_callback, pattern=r"^hit_[0-9]{7}$")
Expand Down
4 changes: 2 additions & 2 deletions blackjackbot/commands/admin/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
from .functions import notify_admins
from .commands import answer_comment_cmd, reload_languages_cmd, users_cmd
from .commands import answer_comment_cmd, reload_languages_cmd, users_cmd, kill_game_cmd

__all__ = ['answer_comment_cmd', 'reload_languages_cmd', 'users_cmd', 'notify_admins']
__all__ = ["answer_comment_cmd", "reload_languages_cmd", "users_cmd", "notify_admins", "kill_game_cmd"]
31 changes: 30 additions & 1 deletion blackjackbot/commands/admin/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,44 @@

from blackjackbot.commands.admin import notify_admins
from blackjackbot.commands.util.decorators import admin_method
from blackjackbot.errors import NoActiveGameException
from blackjackbot.gamestore import GameStore
from blackjackbot.lang import reload_strings, Translator
from database import Database

logger = logging.getLogger(__name__)


@admin_method
def kill_game_cmd(update, context):
"""Kills the game for a certain chat/group"""
if len(context.args) == 0:
update.message.reply_text("Please provide a chat_id!")

chat_id = context.args[0]
# Input validation for chat_id
if not re.match(r"^-?[0-9]+$", chat_id):
update.message.reply_text("Sorry, the chat_id is invalid!")
return

chat_id = int(chat_id)

try:
_ = GameStore().get_game(chat_id=chat_id)
except NoActiveGameException:
update.message.reply_text("Sorry, there is no running game in a chat with that ID!")
return

logger.info("Admin '{0}' removed game in chat '{1}'".format(update.effective_user.id, chat_id))
GameStore().remove_game(chat_id=chat_id)
update.message.reply_text("Alright, I killed the running game in '{0}'!".format(chat_id))
context.bot.send_message(chat_id=chat_id, text="The creator of this bot stopped your current game of BlackJack.")


@admin_method
def reload_languages_cmd(update, context):
reload_strings()
update.message.reply_text("Reloaded languages & strings!")


@admin_method
Expand Down Expand Up @@ -48,7 +77,7 @@ def answer_comment_cmd(update, context):

chat_id = user[0]

if not re.match(r"^\d+$", chat_id):
if not re.match(r"^-?\d+$", chat_id):
update.message.reply_text("⚠ Malformed chat_id!")
logger.error("Malformed chat_id: {}".format(chat_id))
return
Expand Down
12 changes: 9 additions & 3 deletions blackjackbot/commands/game/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,16 @@ def stop_cmd(update, context):

game = GameStore().get_game(chat.id)

user_id = user.id
try:
game.stop(user.id)
if chat.type == "group" or chat.type == "supergroup":
# If yes, get the chat admins
admins = context.bot.get_chat_administrators(chat_id=chat.id)
# if user.id in chat admin IDs, let them end the game with admin powers
if user.id in [x.user.id for x in admins]:
user_id = -1

game.stop(user_id)
update.effective_message.reply_text(translator("game_ended"))
except errors.InsufficientPermissionsException:
update.effective_message.reply_text(translator("mp_only_creator_can_end"))
Expand Down Expand Up @@ -178,8 +186,6 @@ def stand_callback(update, context):
if not is_button_affiliated(update, context, game, lang_id):
return

remove_inline_keyboard(update, context)

next_player(update, context)


Expand Down
3 changes: 1 addition & 2 deletions blackjackbot/commands/game/functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,6 @@
from database import Database

logger = logging.getLogger(__name__)
logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.DEBUG)
logging.getLogger("telegram").setLevel(logging.ERROR)


def is_button_affiliated(update, context, game, lang_id):
Expand Down Expand Up @@ -72,6 +70,7 @@ def next_player(update, context):
update.callback_query.answer(translator("mp_not_your_turn_callback").format(user.first_name))
return

remove_inline_keyboard(update, context)
game.next_player()
except NoPlayersLeftException:
# TODO merge messages
Expand Down
3 changes: 2 additions & 1 deletion blackjackbot/commands/util/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ def comment_text(update, context):
chat = update.effective_chat
lang_id = Database().get_lang_id(chat.id)

data = [chat.id, user.id, user.first_name, user.last_name, "@" + user.username, user.language_code]
# username can be None, so we need to use str()
data = [chat.id, user.id, user.first_name, user.last_name, "@" + str(user.username), user.language_code]

userdata = " | ".join([str(item) for item in data])
userdata = userdata.replace("\r", "").replace("\n", "")
Expand Down
7 changes: 6 additions & 1 deletion blackjackbot/commands/util/functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,12 @@ def remove_inline_keyboard(update, context):
:return:
"""
if update.effective_message.from_user.id == context.bot.id:
update.effective_message.edit_reply_markup(reply_markup=None)
try:
update.effective_message.edit_reply_markup(reply_markup=None)
except Exception:
# When the message already has no reply markup, simply ignore the exception
# We can't check for message.reply_markup, because it might have been removed earlier
pass


def html_mention(user_id, first_name):
Expand Down
37 changes: 32 additions & 5 deletions blackjackbot/lang/language.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,17 @@

import json
import os
import pathlib
import re
import logging

logger = logging.getLogger(__name__)
dir_path = os.path.dirname(os.path.abspath(__file__))
file_path = os.path.join(dir_path, "strings")

lang_path = pathlib.Path(__file__).parent.absolute()
maintainer_strings_path = lang_path / "strings"
custom_strings_path = lang_path / "custom_strings"

language_paths = [maintainer_strings_path, custom_strings_path]
languages = {}


Expand All @@ -27,9 +32,9 @@ def __call__(self, string):
return self.translate(string)


def reload_strings():
def load_strings_from_dir(directory):
"""Reads the translation files into a dict. Overwrites the dict if already present"""
with os.scandir(file_path) as entries:
with os.scandir(directory) as entries:
for entry in entries:
match = re.search(r"^translations_([a-z]{2}(-[a-z]{2})?)\.json$", entry.name)

Expand All @@ -43,10 +48,32 @@ def reload_strings():
logger.error("Can't open translation file '{}'".format(entry.path))
continue

lang_code = match.group(1)
file_lang_code = match.group(1)

# Make sure the language file got a lang_code specified
lang_code = data.get("lang_code", None)
if not lang_code:
logger.error("No lang_code specified in translation file '{0}'".format(entry.path))
return

if languages.get(lang_code, None):
logger.warning("Overwriting translations for lang_code: {0}".format(lang_code))

logger.info("Loaded translation file {0} - language code: {1}".format(entry.name, lang_code))
languages[lang_code] = data


def reload_strings():
for path in language_paths:
if not path.exists():
logger.warning("The path for translations does not exist: {0}".format(path.name))
continue
load_strings_from_dir(path)

if len(languages) == 0:
logger.error("Coudln't load translations!")


def get_available_languages():
"""Return a list of available languages"""
if len(languages) == 0:
Expand Down
Loading

0 comments on commit d8391fa

Please sign in to comment.