Merge pull request #29 from ThomasRubini/server_doc

This commit is contained in:
Thomas Rubini 2023-01-13 10:06:21 +01:00 committed by GitHub
commit e3f779abeb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 113 additions and 56 deletions

View File

@ -5,6 +5,14 @@ import os
from truthseeker import discord_bot from truthseeker import discord_bot
class TruthSeekerApp(flask.Flask): class TruthSeekerApp(flask.Flask):
"""
Main class of the app
A single instance 'APP' of this class will be created and shared across the files
The class itself is a child class of flask.Flask and has property representing other services
:attr SocketIO socketio_app: the SocketIO service
:attr DiscordBot discord_bot: the Discord Bot service
"""
def __init__(self): def __init__(self):
super().__init__("truthseeker") super().__init__("truthseeker")
@ -26,7 +34,10 @@ class TruthSeekerApp(flask.Flask):
def run_app(self): def run_app(self):
self.socketio_app.run(self) self.socketio_app.run(self)
def set_app_secret(self): def set_app_secret(self) -> None:
"""
Set the secret used by flask
"""
if os.path.isfile("instance/secret.txt"): if os.path.isfile("instance/secret.txt"):
f = open("instance/secret.txt", "r") f = open("instance/secret.txt", "r")
self.config["SECRET_KEY"] = f.read() self.config["SECRET_KEY"] = f.read()
@ -41,7 +52,10 @@ class TruthSeekerApp(flask.Flask):
f.close() f.close()
print("Generated secret and wrote to secret.txt !") print("Generated secret and wrote to secret.txt !")
def get_discord_bot_token(self): def get_discord_bot_token(self) -> str:
"""
Get the token used by the discord bot
"""
if os.path.isfile("instance/discord_bot_token.txt"): if os.path.isfile("instance/discord_bot_token.txt"):
f = open("instance/discord_bot_token.txt", "r") f = open("instance/discord_bot_token.txt", "r")
token = f.read() token = f.read()

View File

@ -3,10 +3,14 @@ import threading
import truthseeker import truthseeker
import asyncio import asyncio
async def empty_coro():
return
class DiscordBot: class DiscordBot:
"""
Wrapper around a discord bot, providing utility methods to interact with it
:attr Client bot: the underlying discord bot from discord.py
:attr TextChannel __channel__: the channel used by the bot to send messages
"""
def __init__(self): def __init__(self):
self.bot = discord.Client(intents=discord.Intents.default()) self.bot = discord.Client(intents=discord.Intents.default())
self.__channel__ = None self.__channel__ = None
@ -19,7 +23,10 @@ class DiscordBot:
self.__setup__channel__() self.__setup__channel__()
self.update_games_presence() self.update_games_presence()
def __setup__channel__(self): def __setup__channel__(self) -> None:
"""
Setup the channel that the bot will send message in
"""
if len(self.bot.guilds) == 1: if len(self.bot.guilds) == 1:
self.__channel__ = discord.utils.get(self.bot.guilds[0].channels, name="bot") self.__channel__ = discord.utils.get(self.bot.guilds[0].channels, name="bot")
else: else:
@ -31,6 +38,9 @@ class DiscordBot:
return thr return thr
def API(func): def API(func):
"""
Decorator used to wrap APIs methods, to handle thread context change, and ready check
"""
def decorator(self, *args, **kwargs): def decorator(self, *args, **kwargs):
if self.bot and self.bot.is_ready(): if self.bot and self.bot.is_ready():
self.event_loop.create_task(func(self, *args, **kwargs)) self.event_loop.create_task(func(self, *args, **kwargs))
@ -39,7 +49,10 @@ class DiscordBot:
return decorator return decorator
@API @API
async def update_games_presence(self): async def update_games_presence(self) -> None:
"""
Update the bot's status using the app's current context
"""
games_n = len(truthseeker.APP.games_list) games_n = len(truthseeker.APP.games_list)
activity_name = f"Handling {games_n} game{'' if games_n==1 else 's'} !" activity_name = f"Handling {games_n} game{'' if games_n==1 else 's'} !"
activity = discord.Activity(name=activity_name, type=discord.ActivityType.watching) activity = discord.Activity(name=activity_name, type=discord.ActivityType.watching)
@ -47,7 +60,10 @@ class DiscordBot:
@API @API
async def send_message(self, text): async def send_message(self, text):
"""
Send a message to the channel used by the bot
"""
if self.__channel__: if self.__channel__:
await self.__channel__.send(text) await self.__channel__.send(text)
else: else:
print("channel member not defined, not sending discord message") print("channel not defined, not sending discord message")

View File

@ -3,20 +3,15 @@ import random
from truthseeker.logic.data_persistance.data_access import * from truthseeker.logic.data_persistance.data_access import *
from datetime import datetime, timedelta from datetime import datetime, timedelta
from truthseeker import APP from truthseeker import APP
from typing import Union, Optional
# Map of all actively running games
# games_list["game.game_id"]-> game info linked to that id
def random_string(length: int) ->str: def random_string(length: int) ->str:
""" """
This function create a random string as long as the lint passed as This function create a random string as long as the lint passed as
parameter parameter
: param length: the lenght of the random string :param length: the length of the random string to create
: type length : int :return: a random string
: return : a random string
: return type : string
""" """
return "".join(random.choice(string.ascii_letters) for _ in range(length)) return "".join(random.choice(string.ascii_letters) for _ in range(length))
@ -24,13 +19,13 @@ class Member:
""" """
stores information related to the member of a given game stores information related to the member of a given game
Member.username : The username of this member :attr str username: The username of this member
Member.socker : The reference to the socket to talk to this member :attr TODO progress: TODO
:attr TODO results: TODO
""" """
def __init__(self, username): def __init__(self, username):
self.username = username self.username = username
self.socket = None
self.progress = 0 self.progress = 0
self.results = None self.results = None
@ -44,10 +39,14 @@ class Game:
""" """
The game info class stores all information linked to a active game The game info class stores all information linked to a active game
Game.game_id : str, the game identifier of the game :attr str game_id: str, the game identifier of the game
Game.owner : Member, the game identifier of the game :attr owner Member: the player start created the game. It is also stored in self.members
Game.members : Member[], the members of the game :attr Member[] members: the members of the game
:attr bool has_started: TODO
:attr TODO gamedata: TODO
:attr TODO reaction_table: TODO
""" """
def __init__(self): def __init__(self):
self.game_id = None self.game_id = None
self.owner = None self.owner = None
@ -56,12 +55,21 @@ class Game:
self.gamedata = {} self.gamedata = {}
self.reaction_table = {} self.reaction_table = {}
def set_owner(self, username): def set_owner(self, username: str) -> Member:
"""
Set the owner of the game
:param username: the username of the owner.
:return: the Member object created by this method
"""
self.owner = Member(username) self.owner = Member(username)
self.members.append(self.owner) self.members.append(self.owner)
return self.owner return self.owner
def generateGameResults(self): def generateGameResults(self) -> None:
"""
TODO + TODO RET TYPE
"""
data = {} data = {}
npcs = data["npcs"] = {} npcs = data["npcs"] = {}
for npc_id in self.gamedata["npcs"]: for npc_id in self.gamedata["npcs"]:
@ -75,29 +83,50 @@ class Game:
player_results[member.username] = member.results player_results[member.username] = member.results
return data return data
def generate_data(self): def generate_data(self) -> None:
"""
TODO
"""
#TODO Get language from player #TODO Get language from player
self.gamedata, self.reaction_table = generateGameData("FR") self.gamedata, self.reaction_table = generateGameData("FR")
def get_member(self, username): def get_member(self, username: str) -> Union[Member, None]:
"""
Get a Member object from a username
:param username: the username of the member to search for
:return the member corresponding to the username, or None if none if found:
"""
for member in self.members: for member in self.members:
if member.username == username: if member.username == username:
return member return member
def add_member(self, username): def add_member(self, username: str) -> Union[Member, None]:
"""
Add a Member to the game
:param username: the username of the member to add
:return: the Member created, or None if a Member with this username already exists in the game
"""
if self.get_member(username): if self.get_member(username):
return None return None
member = Member(username) member = Member(username)
self.members.append(member) self.members.append(member)
return member return member
def get_npc_reaction(self,npc_id,reaction): def get_npc_reaction(self, npc_id, reaction) -> None:
"""
TODO + TODO TYPES
"""
if npc_id not in self.reaction_table.keys(): if npc_id not in self.reaction_table.keys():
return 0 return 0
reaction_id = self.reaction_table[npc_id][int(reaction)] reaction_id = self.reaction_table[npc_id][int(reaction)]
return read_image(f"./truthseeker/static/images/npc/{npc_id}/{reaction_id}.png") return read_image(f"./truthseeker/static/images/npc/{npc_id}/{reaction_id}.png")
def getPlayerResults(self,responses: dict): def getPlayerResults(self, responses: dict) -> None:
"""
TODO + TODO RETTYPE
"""
results = {} results = {}
try: try:
for npc_id in responses: for npc_id in responses:
@ -108,7 +137,12 @@ class Game:
return False return False
def has_finished(self): def has_finished(self) -> bool:
"""
Checks if the game has finished by checking if every Member has submitted answers
:return: True if the game has finished, else False
"""
for member in self.members: for member in self.members:
if member.results == None : return False if member.results == None : return False
return True return True
@ -119,13 +153,12 @@ class Game:
def __repr__(self) -> str: def __repr__(self) -> str:
return self.__str__() return self.__str__()
def create_game(owner): def create_game(owner: str) -> Game:
""" """
This function creates a new game by creating a Game object and stores This function creates a new game by creating a Game object and stores
it into the games_list dictionnary it into the games_list dictionnary
: return : a new Game :return: a new Game
: return type : Game
""" """
game = Game() game = Game()
game.owner = owner game.owner = owner
@ -134,32 +167,26 @@ def create_game(owner):
APP.games_list[game.game_id] = game APP.games_list[game.game_id] = game
return game return game
def get_game(game_id): def get_game(game_id: str) -> Union[Game, None]:
if game_id in APP.games_list: """
return APP.games_list[game_id] Get a game from its ID
else:
return None
def get_game_info(game_id): :param game_id: the id of the game to search
""" if not flask.session: :return: the Game object or None if not found
return {"error": 1, "msg": "No session"}
game = game_logic.get_game(flask.session["game_id"])
if game == None:
return {"error": 1, "msg": "this game doesn't exist"}
This function retrieve a the Game object linked to the game_id
passed as parametter
: param game_id : the lenght of the random string
: type game_id : str
: return : The Game Object linked to the game_id
: return type : Game
""" """
if game_id in APP.games_list: if game_id in APP.games_list:
return APP.games_list[game_id] return APP.games_list[game_id]
else: else:
return None return None
def check_username(username): def check_username(username: str) -> bool:
"""
Check if a username is valid using a set of rules
:param username: the username to check
:return: True or False depending on if the rules are respected
"""
if not username: if not username:
return False return False
if not username.isalnum(): if not username.isalnum():
@ -178,10 +205,10 @@ def generateNpcText(npc: tables.Npc, lang: str) -> dict:
data["QA_1"] = getTextFromLid(lang, getNpcRandomAnswer(npc,1).TEXT_LID) data["QA_1"] = getTextFromLid(lang, getNpcRandomAnswer(npc,1).TEXT_LID)
return data return data
def generateNpcReactions(npc : tables.Npc) ->list: def generateNpcReactions(npc: tables.Npc) ->list:
return getNpcRandomTraitId(npc) return getNpcRandomTraitId(npc)
def generatePlaceData(npcs :list, places: list, lang : str) -> dict: def generatePlaceData(npcs: list, places: list, lang: str) -> dict:
data = {} data = {}
random.shuffle(npcs) random.shuffle(npcs)
for place in places: for place in places:
@ -230,4 +257,4 @@ def getTraitIdFromString(trait):
return getTraitFromText(trait) return getTraitFromText(trait)
def get_npc_image(npc_id): def get_npc_image(npc_id):
return read_image(f"./truthseeker/static/images/npc/{npc_id}/0.png") return read_image(f"./truthseeker/static/images/npc/{npc_id}/0.png")