First commit

This commit is contained in:
Simone Baracchi 2019-02-18 23:30:11 +01:00
parent a75bd9c3c9
commit 246bff21f8
9 changed files with 440 additions and 0 deletions

3
.gitignore vendored
View File

@ -102,3 +102,6 @@ venv.bak/
# mypy
.mypy_cache/
games.db
*.swp

View File

@ -1,2 +1,15 @@
# rpgbot
RPG helper bot for Telegram
Commands:
- /newgame
- /delgame
- /showgame
- /roll
- /player
- /update
- /add
- /del
- /show

8
config.example.py Normal file
View File

@ -0,0 +1,8 @@
# Example config file
# API token
bot_token = 'MY-TG-BOT-TOKEN'
# Telegram admin id
admins = [ADMIN-ID]

8
config.py Normal file
View File

@ -0,0 +1,8 @@
# Example config file
# API token
bot_token = '709882140:AAFegBBjsAk4TEiDBvI5xP_hBArun9ok5L8'
# Telegram admin id
admins = [101097946]

137
db.py Normal file
View File

@ -0,0 +1,137 @@
import sqlite3
db_name = 'games.db'
db_version = 1
ROLE_PLAYER = 10
ROLE_MASTER = 20
def open_connection():
return sqlite3.connect(db_name)
def close_connection(db):
db.close()
def table_exists(db, table):
c = db.cursor()
query = c.execute('''SELECT count(*) FROM sqlite_master WHERE type='table' AND name=?''', (table,))
result = query.fetchone()
if result[0] == 0:
return False
else:
return True
def init():
db = open_connection()
try:
print('Initializing database...')
c = db.cursor()
if not table_exists(db, 'Games'):
c.execute('''CREATE TABLE IF NOT EXISTS Games (gameid integer primary key autoincrement, version int, lastactivity datetime, gamename text)''')
if not table_exists(db, 'Groups'):
c.execute('''CREATE TABLE IF NOT EXISTS Groups (gameid integer, groupid integer primary key, groupname text)''')
if not table_exists(db, 'Players'):
c.execute('''CREATE TABLE IF NOT EXISTS Players (gameid integer, playerid integer, role integer, playername text, PRIMARY KEY(gameid, playerid))''')
if not table_exists(db, 'Contents'):
c.execute('''CREATE TABLE IF NOT EXISTS Contents (gameid integer, playerid integer, container text, key text, value text, PRIMARY KEY(gameid, playerid, container, key))''')
except:
print('failed to initialize database')
raise
db.commit()
close_connection(db)
def new_game(db, admin, playername, gamename, groupid, groupname):
c = db.cursor()
query = c.execute('''INSERT INTO Games(version, lastactivity, gamename) VALUES (?, datetime('now'), ?)''', (db_version, gamename,))
gameid = c.lastrowid
query = c.execute('''INSERT INTO Groups(gameid, groupid, groupname) VALUES (?, ?, ?)''', (gameid, groupid, groupname,))
add_player(db, admin, playername, gameid, ROLE_MASTER)
db.commit()
def del_game(db, gameid):
c = db.cursor()
query = c.execute('''DELETE FROM Games WHERE gameid=?''', (gameid,))
query = c.execute('''DELETE FROM Groups WHERE gameid=?''', (gameid,))
query = c.execute('''DELETE FROM Players WHERE gameid=?''', (gameid,))
query = c.execute('''DELETE FROM Contents WHERE gameid=?''', (gameid,))
db.commit()
def add_player(db, userid, username, gameid, role):
c = db.cursor()
query = c.execute('''SELECT role FROM Players WHERE playerid=? AND gameid=?''', (userid, gameid,))
result = query.fetchone()
if result is not None and result[0] >= role:
role = result[0]
query = c.execute('''INSERT OR REPLACE INTO Players(gameid, playerid, role, playername) VALUES (?, ?, ?, ?)''', (gameid, userid, role, username,))
db.commit()
def number_of_games(db, user):
c = db.cursor()
query = c.execute('''SELECT count(*) FROM Players WHERE playerid=?''', (user,))
result = query.fetchone()
return result[0]
def get_game_from_group(db, groupid):
c = db.cursor()
query = c.execute('''SELECT gameid FROM Groups WHERE groupid=?''', (groupid,))
result = query.fetchone()
return result[0]
def get_game_info(db, gameid):
c = db.cursor()
query = c.execute('''SELECT gamename FROM Games WHERE gameid=?''', (gameid,))
result = query.fetchone()
gamename = result[0]
query = c.execute('''SELECT groupname FROM Groups WHERE gameid=?''', (gameid,))
groups = []
for group in query:
groups.append(group[0])
query = c.execute('''SELECT playername FROM Players WHERE gameid=?''', (gameid,))
players = []
for player in query:
players.append(player[0])
return gamename, groups, players
def number_of_items(db, gameid, playerid):
c = db.cursor()
query = c.execute('''SELECT count(*) FROM Contents WHERE gameid=? AND playerid=?''', (gameid, playerid,))
result = query.fetchone()
return result[0]
def update_item(db, gameid, playerid, container, key, change, relative):
c = db.cursor()
query = c.execute('''SELECT value FROM Contents WHERE gameid=? AND playerid=? AND container=? AND key=?''', (gameid, playerid, container, key,))
result = query.fetchone()
if result is None:
oldvalue = 0
else:
oldvalue = int(result[0])
if relative:
newvalue = oldvalue + change
else:
newvalue = change
query = c.execute('''INSERT OR REPLACE INTO Contents(gameid, playerid, container, key, value) VALUES (?, ?, ?, ?, ?)''', (gameid, playerid, container, key, newvalue,))
db.commit()
return oldvalue, newvalue
def delete_item(db, gameid, playerid, container, key):
c = db.cursor()
query = c.execute('''SELECT value FROM Contents WHERE gameid=? AND playerid=? AND container=? AND key=?''', (gameid, playerid, container, key,))
result = query.fetchone()
if result == None:
return None
oldvalue = result[0]
query = c.execute('''DELETE FROM Contents WHERE gameid=? AND playerid=? AND container=? AND key=?''', (gameid, playerid, container, key,))
db.commit()
return oldvalue
def get_items(db, gameid, playerid):
ret = {}
c = db.cursor()
query = c.execute('''SELECT DISTINCT container FROM Contents WHERE gameid=? AND playerid=?''', (gameid, playerid,))
for container in query:
inner = c.execute('''SELECT key, value FROM Contents WHERE gameid=? AND playerid=? AND container=?''', (gameid, playerid, container[0]))
my_list = {}
for item in inner:
my_list[item[0]] = item[1]
ret[container[0]] = my_list
return ret

11
get-sender-id.py Normal file
View File

@ -0,0 +1,11 @@
#!/usr/bin/python
import telepot
from pprint import pprint
import tempconfig
bot = telepot.Bot(tempconfig.bot_token)
response = bot.getUpdates()
# Print all raw messages with chat_id,text,type,username
pprint(response)

43
install.sh Executable file
View File

@ -0,0 +1,43 @@
#!/bin/bash
printf "Installing Packages...\n"
apt-get install -y python nmap dnsutils mtr python-pip && pip install telepot
printf "\n\n--------------------------------\n\n"
echo "Enter your Telegram BOT Token. "
read -r TG_BOT_TOKEN
echo "bot_token = '$TG_BOT_TOKEN'" > tempconfig.py
printf "\n\n--------------------------------\n\n"
echo "Fetching last Telegram messages. This will help finding your Telegram ID."
echo "If you can't see your Telegram ID, try sending a private message to this bot."
python get-sender-id.py | grep "'id'" | uniq -c | awk '{ print $3 }' | sed s'/,//'
rm tempconfig.py
echo "Enter your Telegram ID. This will be the default admin user."
read -r SENDER_ID
cp config.example.py config.py
sed -i s"/MY-TG-BOT-TOKEN/$TG_BOT_TOKEN/" config.py
sed -i s"/MY-SENDER-ID-LIST/$SENDER_ID/" config.py
printf "\n\n--------------------------------\n\n"
echo "Do you want to configure the daemon with systemctl? (y/n)"
read -r DAEMON
case $DAEMON in
N|n)
exit 0
;;
Y|y)
cp systemctl/rpgbot.example.service /tmp/rpgbot.service
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
sed -i s"#MY-PATH#$DIR#" /tmp/rpgbot.service
mv /tmp/rpgbot.service /etc/systemd/system/multi-user.target.wants/rpgbot.service
systemctl daemon-reload
systemctl restart tsh
;;
*)
echo Unrecognized option $DAEMON, exiting
exit 1
;;
esac

204
main.py Executable file
View File

@ -0,0 +1,204 @@
#!/usr/bin/env python
# Telegram RPG Character Sheet bot
from pprint import pprint
import telepot
import time
import socket
import os
import re
import sys
import json
import random
import config
import db
log_file = 'service.log'
class Operation:
def __init__(self, id, type, name):
self.id = id
self.type = type
self.name = name
def __eq__(self, other):
return self.id == other.id
def die():
os.kill(os.getpid(), signal.SIGINT)
def fatal(msg):
print(msg)
die()
def log(msg):
f = open(log_file, 'a')
f.write(msg + "\n")
f.close()
print(msg)
def log_msg(msg):
chat_name = ''
username = msg['from']['username']
text = msg['text']
if 'title' in msg['chat']:
chat_name = '{} ({})'.format(msg['chat']['title'], username)
else:
chat_name = username
log('{}: {}'.format(chat_name, text))
def send(bot, chat_id, msg):
if msg == None or len(msg) == 0 or len(msg.split()) == 0:
msg = '(no message)'
bot.sendMessage(chat_id, msg)
def process_message(msg):
"""
Process received messages.
msg -- The received message
"""
chat_id = msg['chat']['id']
sender_id = msg['from']['id']
username = msg['from']['username']
text = msg['text']
is_group = msg['chat']['type'] == 'group'
groupname = msg['chat']['title'] if 'title' in msg['chat'] else None
# avoid logging and processing every single message.
if text[0] != '/':
return
log_msg(msg)
dbc = db.open_connection()
is_admin = False
if sender_id in config.admins:
is_admin = True
args=text.split(maxsplit=1)
command = args[0]
if command == '/newgame':
if not is_group:
send(bot, chat_id, 'You must run this command in a group.')
return
if len(args) < 2:
send(bot, chat_id, 'Please specify the game name.')
return
if db.number_of_games(dbc, sender_id) > 10:
send(bot, chat_id, 'You exceeded the maximum number of games. Please close some first.')
return
db.new_game(dbc, sender_id, username, args[1], chat_id, groupname)
send(bot, chat_id, 'New game created: {}.'.format(args[1]))
if command == '/delgame':
if not is_group:
send(bot, chat_id, 'You must run this command in a group.')
return
# TODO
if not is_admin:
send(bot, chat_id, 'You need to be the administrator to close a game.')
return
gameid = db.get_game_from_group(dbc, chat_id)
if gameid is None:
send(bot, chat_id, 'No game found.')
return
db.del_game(dbc, gameid)
send(bot, chat_id, 'GG, humans.')
if command == '/showgame':
if not is_group:
send(bot, chat_id, 'You must run this command in a group.')
return
gameid = db.get_game_from_group(dbc, chat_id)
gamename, groups, players = db.get_game_info(dbc, gameid)
ret = '{}\nGroups: {}\nPlayers: {}'.format(gamename, ', '.join(groups), ', '.join(players))
send(bot, chat_id, ret)
if command == '/roll':
ret = 0
string = ''
for i in range(0, 4):
value = random.randint(-1, 1)
ret += value;
if value == -1:
string += ''
if value == 0:
string += ''
if value == 1:
string += ''
send(bot, chat_id, 'Rolled {} = {}.'.format(string, ret))
if command == '/player':
if not is_group:
send(bot, chat_id, 'You must run this command in a group.')
return
if len(args) < 2:
send(bot, chat_id, 'Please specify the player name.')
return
if db.number_of_games(dbc, sender_id) > 10:
send(bot, chat_id, 'You exceeded the maximum number of games. Please close some first.')
return
gameid = db.get_game_from_group(dbc, chat_id)
db.add_player(dbc, sender_id, args[1], gameid, db.ROLE_PLAYER)
send(bot, chat_id, 'Welcome, {}.'.format(args[1]))
if command == '/update' or command == '/add':
if not is_group:
send(bot, chat_id, 'You must run this command in a group.')
return
gameid = db.get_game_from_group(dbc, chat_id)
if db.number_of_items(dbc, gameid, sender_id) > 50:
send(bot, chat_id, 'You exceeded the maximum number of items. Please delete some first.')
return
args = args[1].split()
if len(args) != 3:
send(bot, chat_id, 'Use the format: [container] [key] [change].')
return
(container, key, change) = args
if command == '/update':
relative = False
else:
relative = True
oldvalue, newvalue = db.update_item(dbc, gameid, sender_id, container, key, int(change), relative=relative)
send(bot, chat_id, 'Updated {}/{} from {} to {} (changed {}).'.format(container, key,
oldvalue, newvalue, newvalue-oldvalue))
if command == '/del':
if not is_group:
send(bot, chat_id, 'You must run this command in a group.')
return
gameid = db.get_game_from_group(dbc, chat_id)
args = args[1].split()
if len(args) != 2:
send(bot, chat_id, 'Use the format: [container] [key].')
return
(container, key) = args
oldvalue = db.delete_item(dbc, gameid, sender_id, container, key)
if oldvalue == None:
send(bot, chat_id, 'No item by that name.')
else:
send(bot, chat_id, 'Deleted {}/{} (was {}).'.format(container, key, oldvalue))
if command == '/show':
if not is_group:
send(bot, chat_id, 'You must run this command in a group.')
return
gameid = db.get_game_from_group(dbc, chat_id)
items = db.get_items(dbc, gameid, sender_id)
ret = ''
if items is None:
send(bot, chat_id, 'No items found.')
return
for container in items:
ret += container + ':\n'
for key, value in items[container].items():
ret += ' - {} ({})\n'.format(key,value)
send(bot, chat_id, ret)
db.close_connection(dbc)
db.init()
bot = telepot.Bot(config.bot_token)
print('Entering message loop.')
bot.message_loop(process_message)
while 1:
time.sleep(10)

View File

@ -0,0 +1,13 @@
[Unit]
Description=RPG Bot daemon
After=network.target
[Service]
WorkingDirectory=MY-PATH
ExecStart=MY-PATH/main.py
KillMode=process
Restart=on-failure
[Install]
WantedBy=multi-user.target