Module: config

This is all the configuration options that can be put into a config file. The default section is the mastoscore section. Anything defined here is also available in all other sections. Like if mastoscore:hashtag is set to testtag, then querying for fetch:hashtag or graph:hashtag will return testtag unless those sections override it.

All Possible Options

api_base_url: String

URL to contact to login and do stuff. In fetch, this is the URL to start all the fetches. In post, this is the server to connect to and post some toots. When I'm testing, I connect to real servers to test the fetching, and then have a different api_base_url and cred_file defined in the post module to post to my test server.

botusername: String

The name of our bot. It's just a user name. So if api_base_url is https://example.social, and our bot is mastoscore@example.social then this would just be mastoscore. Any toots found in the fetch phase that are attributable to this ID will be dropped from the analysis.

cred_file: String

The name of a file containing some API keys. See Setting Up for details on how to create a cred_file that is compatible with Mastodon.py. Used by the Tooter class.

hashtag: String

Hashtag we are looking for. This is used in all modules in different ways. Omit the # sign. Example: "KungFuSat".

max: Integer

Maximum number of toots to pull from a server. Only used by fetch. Defaults to 2000. By default, Mastodon APIs will rate limit at 300 queries in 5 minutes. By default, Mastodon APIs allow 40 toots per API call. So in theory you can get close to 12000 toots before getting rate limited. Mastodon.py also has code to handle being rate limited. I'm not using any of it right now, and I've never tested what happens when I hit rate limits.

hours_margin: Integer

Number of hours before and after the event time to include in the analysis.

journaldir: String

Directory where the journal JSON files will be written. Must be writeable. Relative to the current working directory when you run mastoscore. Example: /home/person/mastoscore/data.

journalfile: String

Base name of the journal files. Each file will get the server's name appended to it. So if journaldir is /home/person/mastoscore/data and journalfile is testtag-20241116 then we will write results from querying example.social into a file named /home/person/mastoscore/data/testtag-20241116-example.social.json.

lookback: Integer

In the fetch module, this is the Number of days to look backwards. At the moment I'm also experimenting with treating this as hours in the graph module.

top_n: Integer

How many top toots do you want to post? For the top 3, set top_n = 3.

timezone: String

This is an important option. We assume Mastodon servers send us time in GMT. Then we convert them to this local time zone. The official list can be found by looking at the pytz module. Generally they're things like 'UTC' or 'Canada/Pacific', or 'Europe/Madrid'. But you can also do some common things like 'PST8PDT' or 'EST5EDT'.

This is used in fetch and analyse to convert times into timezone-aware datetime objects. It's used in the graph module to set the X axis and put the vertical event lines in the right place. Note that the code tries hard to be timezone-aware. So you could set start_time for the event in one time zone (e.g., 2024-11-16T12:00:00-05:00) and then set timezone to 'America/Los_Angeles' and it should do the right thing.

start_label: String

end_label: String

String for the start-time label or the end-time label. Only used by the graph module. This is the vertical line labeled "start" or "end" in the graph.

start_time: Datetime

end_time: Datetime

A string representing the start time or end time of the event. Only used by the graph module. This is the X coordinate for the vertical line labeled "end". Example: 2024-11-16T12:00:00-05:00. See datetime.date.fromisoformat() for the kinds of strings that are supported.

graph_title: String

String for the title of the graph. Only used by the graph module. Example: "#Monsterdon 2024-11-16 (The Monolith Monsters)"

root_visibility: String

thread_visibility: String

These two options control the visibility attached to the various toots that the system will make. Valid options are those defined by the Mastodon API for statuses. Generally, it's public, unlisted, private, direct. Normally, I set root_visibility to public and thread_visbility to unlisted. So the first toot of the thread is public, and all other replies to it are only seen by direct followers.

tag_users: Boolean

Do you want to tag the users in the toots? If True, then to toot looks like this:

This toot from Random Person (@random@example.net) had 11 boosts

Sticking the @ in front of the userid will cause that to be interpetted as tagging the user. They will get a notification, most of the time, that they were tagged. If set to False, we omit the @:

This toot from Random Person (random@example.net) had 11 boosts

Without tagging, the person won't know they were mentioned. Sometimes that's what you want.

Code Reference

Read in the config file. Return the config dict that gets passed around.

get_debug_level(config, section)

Get debug level from config, handling both string (INFO, DEBUG) and int (10, 20) values. Defaults to logging.ERROR if the debug value is not found in the config.

Parameters:

Name Type Description Default
config ConfigParser

ConfigParser object

required
section str

Section name to read from

required

Returns: Integer log level suitable for logging.setLevel()

Source code in mastoscore/config.py
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
def get_debug_level(config: ConfigParser, section: str) -> int:
    """
    Get debug level from config, handling both string (INFO, DEBUG) and
    int (10, 20) values. Defaults to logging.ERROR if the debug value
    is not found in the config.

    Args:
      config: ConfigParser object
      section: Section name to read from

    Returns:
    Integer log level suitable for logging.setLevel()
    """
    fallback = logging.ERROR
    try:
        debug_str = config.get(section, "debug", fallback=str(fallback))
        if debug_str.isdigit():
            return int(debug_str)
        else:
            return getattr(logging, debug_str.upper(), fallback)
    except Exception:
        return fallback

get_event_end_time(config)

Calculate event end time from start_time + event_duration.

Parameters:

Name Type Description Default
config ConfigParser

ConfigParser object

required

Returns: datetime.datetime object with timezone

Source code in mastoscore/config.py
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
def get_event_end_time(config: ConfigParser) -> datetime.datetime:
    """
    Calculate event end time from start_time + event_duration.

    Args:
      config: ConfigParser object

    Returns:
    datetime.datetime object with timezone
    """
    start_time = config.get("mastoscore", "start_time")
    event_duration = config.get("mastoscore", "event_duration")
    timezone = pytimezone(config.get("mastoscore", "timezone"))

    start_dt = datetime.datetime.fromisoformat(start_time).astimezone(tz=timezone)
    duration = parse_duration(event_duration)
    return start_dt + duration

get_fetch_window(config)

Calculate fetch time window: [start - margin, end + margin].

Parameters:

Name Type Description Default
config ConfigParser

ConfigParser object

required

Returns: Tuple of (oldest_date, newest_date) as datetime objects with timezone

Source code in mastoscore/config.py
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
def get_fetch_window(config: ConfigParser) -> tuple[datetime.datetime, datetime.datetime]:
    """
    Calculate fetch time window: [start - margin, end + margin].

    Args:
      config: ConfigParser object

    Returns:
    Tuple of (oldest_date, newest_date) as datetime objects with timezone
    """
    start_time = config.get("mastoscore", "start_time")
    hours_margin = config.getint("mastoscore", "hours_margin")
    timezone = pytimezone(config.get("mastoscore", "timezone"))

    start_dt = datetime.datetime.fromisoformat(start_time).astimezone(tz=timezone)
    end_dt = get_event_end_time(config)

    oldest_date = start_dt - datetime.timedelta(hours=hours_margin)
    newest_date = end_dt + datetime.timedelta(hours=hours_margin)

    return oldest_date, newest_date

get_graph_window(config)

Calculate graph time window with 15-minute buffer: [start - margin - 15min, end + margin + 15min].

Parameters:

Name Type Description Default
config ConfigParser

ConfigParser object

required

Returns: Tuple of (graph_start, graph_end) as datetime objects with timezone

Source code in mastoscore/config.py
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
def get_graph_window(config: ConfigParser) -> tuple[datetime.datetime, datetime.datetime]:
    """
    Calculate graph time window with 15-minute buffer: [start - margin - 15min, end + margin + 15min].

    Args:
      config: ConfigParser object

    Returns:
    Tuple of (graph_start, graph_end) as datetime objects with timezone
    """
    oldest_date, newest_date = get_fetch_window(config)

    buffer = datetime.timedelta(minutes=15)
    graph_start = oldest_date - buffer
    graph_end = newest_date + buffer

    return graph_start, graph_end

parse_duration(duration_str)

Parse duration string in HH:MM:SS format.

Parameters:

Name Type Description Default
duration_str str

Duration in HH:MM:SS format (e.g., "1:47:12")

required

Returns: datetime.timedelta object

Source code in mastoscore/config.py
75
76
77
78
79
80
81
82
83
84
85
86
def parse_duration(duration_str: str) -> datetime.timedelta:
    """
    Parse duration string in HH:MM:SS format.

    Args:
      duration_str: Duration in HH:MM:SS format (e.g., "1:47:12")

    Returns:
    datetime.timedelta object
    """
    hours, minutes, seconds = map(int, duration_str.split(':'))
    return datetime.timedelta(hours=hours, minutes=minutes, seconds=seconds)

read_config(config_file)

Read in the config file. Return a ConfigParser of config options that will be passed around throughout the program.

Parameters:

Name Type Description Default
config_file str

The file name of the config file to open.

required

Returns:

If the file can be read and parsed, it returns a ConfigParser object.

If there are any errors, it returns None.

Source code in mastoscore/config.py
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
def read_config(config_file: str) -> ConfigParser | None:
    """
    Read in the config file. Return a `ConfigParser` of config options that
    will be passed around throughout the program.

    Args:
      config_file: The file name of the config file to open.

    Returns:

    If the file can be read and parsed, it returns a `ConfigParser` object.

    If there are any errors, it returns `None`.
    """

    logger = logging.getLogger(__name__)
    logging.basicConfig(
        format="%(asctime)s %(levelname)-8s %(message)s",
        level=logging.ERROR,
        datefmt="%H:%M:%S",
    )
    logger.setLevel(logging.ERROR)

    try:
        extinterp = ExtendedInterpolation()
        config = ConfigParser(default_section="mastoscore", interpolation=extinterp)
        with open(config_file, "r") as inifile:
            config.read_file(inifile)
    except Exception as e:
        logger.error(f"Failed to read {config_file}. Bailing")
        logger.error(e)
        return None

    return config