def load_character(team_id: str, name: str) -> Tuple[Character, str]:
    """Load a character sheet

    Arg:
        team_id (str): ID of the Slack workspace
        name (str): Name of the character
    Returns:
        - (Character) Desired character sheet
        - (str): Absolute path to the character sheet, in case you must save it later
    """

    config = get_config()
    sheet_path = config.get_character_sheet_path(team_id, name)
    return Character.from_yaml(sheet_path), os.path.abspath(sheet_path)
Beispiel #2
0
def test_sheet_path():
    config = get_config()
    return config.get_character_sheet_path('TP3LCSL2Z', 'adrianna')
import logging
import os
from argparse import ArgumentParser, Namespace
from datetime import datetime
from typing import List, NoReturn

import requests

from modron.characters import list_available_characters, load_character
from modron.config import get_config
from modron.dice import DiceRoll, dice_regex
from modron.interact import SlashCommandPayload
from modron.interact.base import InteractionModule

logger = logging.getLogger(__name__)
config = get_config()


def _render_dice_rolls(roll: DiceRoll) -> List[str]:
    """Render the dice values in an pretty HTML format

    Args:
        roll (DiceRoll): Dice roll to be rendered
    Returns:
        ([str]) Rendered form of each dice
    """

    output = []
    for (value, rolls), d in zip(roll.results, roll.dice):
        # Get whether the dice was used
        used_ix = rolls.index(value)
Beispiel #4
0
def create_app(test_config=None):
    """Create the flask app"""

    logging.basicConfig(level=logging.INFO,
                        format='%(asctime)s - %(threadName)s - %(name)s - %(levelname)s - %(message)s',
                        handlers=[RotatingFileHandler('modron.log', mode='a',
                                                      maxBytes=1024 * 1024 * 2,
                                                      backupCount=1),
                                  logging.StreamHandler(sys.stdout)])
    logger = logging.getLogger(__name__)

    # Load the system configuration
    config = get_config()

    # Record the start time, used for status information
    start_time = datetime.now()

    # Get the secure tokens
    OAUTH_ACCESS_TOKENS = os.environ.get('OAUTH_ACCESS_TOKENS')
    SIGNING_SECRET = os.environ.get('SLACK_SIGNING_SECRET')
    CLIENT_SECRET = os.environ.get('CLIENT_SECRET')
    CLIENT_ID = os.environ.get('CLIENT_ID')

    # Make the Flask app
    app = Flask('modron', template_folder='./views/templates', static_folder="./views/static")
    app.jinja_env.filters['quote_plus'] = quote_plus
    app.secret_key = CLIENT_SECRET

    def get_netloc(url):
        p = urlparse(url)
        return f'{p.scheme}://{p.netloc}'
    app.jinja_env.filters['get_netloc'] = get_netloc
    app.jinja_env.filters['humanize_td'] = humanize.naturaldelta
    app.jinja_env.filters['humanize_size'] = humanize.naturalsize

    # Store some details about the runtime configuration
    app.config['start_time'] = start_time
    app.config['team_config'] = config
    app.config['CLIENT_SECRET'] = CLIENT_SECRET
    app.config['CLIENT_ID'] = CLIENT_ID

    # Register the views
    from .views import status, auth, players
    app.register_blueprint(status.bp)
    app.register_blueprint(auth.bp)
    app.register_blueprint(players.bp)

    # Store the clients
    clients = {}
    app.config['clients'] = clients

    # Make the Slack client and Events adapter
    if OAUTH_ACCESS_TOKENS is None:
        logger.warning('OAUTH_ACCESS_TOKENS was unset. Skipping all Slack-related functionality')
        return app

    for token in OAUTH_ACCESS_TOKENS.split(":"):
        client = BotClient(token=token)
        client.team_info()
        clients[client.team_id] = client
    event_adapter = SlackEventAdapter(SIGNING_SECRET, "/slack/events", app)
    logger.info(f'Finished initializing {len(clients)} Slack clients')

    # Check that we have configurations for each team
    authed_teams = set(clients.keys())
    missing_config = authed_teams.difference(config.team_options.keys())
    if len(missing_config) > 0:
        raise ValueError(f'Missing configuration data for {len(missing_config)} teams: {", ".join(missing_config)}')

    # Make the services
    app.config['services'] = {'reminder': {}, 'backup': {}}
    reminder_threads = {}
    for team_id, team_config in config.team_options.items():
        if team_id not in clients:
            logging.warning(f'Missing OAuth Token for {team_id}')
            continue
        client = clients[team_id]

        # Start the reminder thread
        if team_config.reminders:
            reminder = ReminderService(clients[team_id], team_config.reminder_channel,
                                       team_config.watch_channels)
            reminder.start()
            reminder_threads[team_id] = reminder
            app.config['services']['reminder'][team_config.name] = reminder
        else:
            logger.info(f'No reminders for {team_config.name}')

        # Start the backup thread
        if team_config.backup_channels is not None:
            backup = BackupService(client, frequency=timedelta(days=1), channel_regex=team_config.backup_channels)
            backup.start()
            app.config['services']['backup'][team_config.name] = backup
        else:
            logger.info(f'No backup for {team_config.name}')

    # Generate the slash command responder
    modules = [
        DiceRollInteraction(clients),
        StatisticModule(clients),
        ReminderModule(clients, reminder_threads),
        NPCGenerator(clients),
        CharacterSheet(clients)
    ]
    modron_cmd_parser = assemble_parser(modules)

    @app.route('/modron', methods=('POST',))
    def modron_slash_cmd():
        payload = SlashCommandPayload(**request.form.to_dict())
        return handle_slash_command(payload, parser=modron_cmd_parser)

    @app.route('/oauth', methods=('GET',))
    def slack_auth():
        # Get the request code from the user
        code = request.args.get('code', None)
        logger.info('Received an authorization code. About to exchange it for a token')

        # Query Slack to get the token
        res = requests.post(
            url="https://slack.com/api/oauth.v2.access",
            data={
                'code': code,
                'client_secret': CLIENT_SECRET,
                'client_id': CLIENT_ID,
                'redirect_uri': request.base_url
            }
        )
        with open('received-tokens.json', 'w') as fp:
            json.dump(res.json(), fp)
        return "Success!"

    # Register the events
    event_adapter.on('message', f=partial(status_check, clients=clients, start_time=start_time))

    return app