Module: post

The post command actually uses your credentials to post a few toots of the stats we gathered. I find it very useful to set dry_run to True in the [post] part of the config and see what it's going to post before I actually post.

The JSON files from analyse need to exist.

Dry Run

Running a dry run will spit out the toots in HTML format on stdout. I like to use w3m to display it on the terminal.

mastoscore inifile.ini post | w3m -T text/html

Module for posting toots that summarise what's happening.

NOTE: You can have values like api_base_url, username, or cred_file in the [post] section of the config file and they will override the general options. I use that for testing, where I will fetch() from one server, but post() to another so I don't muck-up people's timelines with test toots.

If post:dry_run is set to True, then it only posts the toots on stdout, it doesn't actually toot them.

post(config)

Expects the config options for the program and the output dictionary from the analyse() method. Connects to the target server and posts the various summaries. Calls post_toots() to actually post (or display) the toots.

Parameters

  • config: A ConfigParser object from the config module
  • tootlist: A list of strings which are the toots to be tooted. This module doesn't change them.

Config Parameters Used

NOTE: This method is very unusual in that it pulls config values from many sections of the config file. Most other methods restrict themselves to only their own section.

Option Description
graph:start_time Start time of the event we're looking at. ISO8601 formatted date string.
graph:end_time End time of the event we're looking at. ISO8601 formatted date string.
analyse:top_n How many top toots we will be reporting
analyse:tag_users Whether we tag users with an @ or not

Returns

Number of toots tooted.

Source code in mastoscore/post.py
def post(config):
    """
    Expects the config options for the program and the output dictionary from the analyse()
    method. Connects to the target server and posts the various summaries. Calls
    [`post_toots()`](module-post.md#mastoscore.post.post_toots) to actually post (or display) the toots.

    # Parameters
    - **config**: A ConfigParser object from the [config](module-config.md) module
    - **tootlist**: A list of strings which are the toots to be tooted. This module doesn't change them.

    # Config Parameters Used

    **NOTE**: This method is very unusual in that it pulls config values from many sections
    of the config file. Most other methods restrict themselves to only their own section.

    | Option | Description |
    | ------- | ------- |
    | `graph:start_time` | Start time of the event we're looking at. ISO8601 formatted date string.
    | `graph:end_time` | End time of the event we're looking at. ISO8601 formatted date string.
    | `analyse:top_n` | How many top toots we will be reporting
    | `analyse:tag_users` | Whether we tag users with an @ or not |

    # Returns

    Number of toots tooted.

    """
    debug = config.getint('post', 'debug')
    # I don't like having 'post' read from 'analyse' but I'm too lazy to do better
    top_n = config.getint('analyse', 'top_n')
    tag_users = config.getboolean('post', 'tag_users')

    logger = logging.getLogger(__name__)
    logging.basicConfig(format='%(levelname)s\t%(message)s')
    logger.setLevel(debug)

    analysis = read_json(config, 'analysis')
    if not analysis:
        logger.error("Couldn't open JSON file")
        return []

    tootlist = []
    text = analysis['preamble']
    text = text + "<ul>" \
        f"<li>{analysis['num_toots']}</li>" \
        f"<li>{analysis['most_toots']}</li>" \
        "</ul><p>Additional details are replies to this toot.</p>"
    tootlist.append(text)

    top = analysis['max_boosts']
    tag = "@" if tag_users else ""
    text = f"<p>The top {top_n} boosted toots:</p><ol>"
    for post in top:
        text = text + f"<li><a href=\"{post['url']}\">This toot</a> from "\
            f"<a href=\"{post['account.url']}\">{post['account.display_name']} "\
            f"({tag}{post['userid']})</a>" \
            f" had {post['reblogs_count']} boosts.</li>\n"
    text = text + "</ol>"
    tootlist.append(text)

    # Now post about faves
    top = analysis['max_faves']
    text = f"<p>The top {top_n} favourited toots:</p><ol>"
    for post in top:
        text = text + f"<li><a href=\"{post['url']}\">This toot</a> from "\
            f"<a href=\"{post['account.url']}\">{post['account.display_name']} "\
            f"({tag}{post['userid']})</a>" \
            f" had {post['favourites_count']} favourites.</li>\n"
    text = text + "</ol>"
    tootlist.append(text)

    # Now post about replies
    top = analysis['max_replies']
    text = f"<p>The top {top_n} most-replied-to toots:</p><ol>"
    for post in top:
        text = text + f"<li><a href=\"{post['url']}\">This toot</a> from "\
            f"<a href=\"{post['account.url']}\">{post['account.display_name']} "\
            f"({tag}{post['userid']})</a>" \
            f" had {post['replies_count']} replies.</li>\n"
    text = text + "</ol>"
    tootlist.append(text)

    tootids = {}
    tootids['tootlist'] = post_toots(config, tootlist)
    # This will open up the hashtag-results.json file and insert all the IDs for all
    # the toots into it. That will be used in the blog and in the graph posting.
    update_json(config, 'analysis', tootids)
    return len(tootlist)

post_toots(config, tootlist)

Given the config and list of toots, create a Tooter and toot them.

Parameters

  • config: A ConfigParser object from the config module
  • tootlist: A list of strings which are the toots to be tooted. This module doesn't change them.

Config Parameters Used

Option Description
post:dry_run Boolean. If True, print HTML on stdout. If False, really post toots.
post:root_visibility The very first post in the thread will be set to this visibility. Usually 'public'. Must be a valid string for mastodon statuses which currently are: public, unlisted, private, and direct.
post:thread_visibility All toots are tooted as replies to each other. root → first → second, etc. All posts other than the root will have thread_visibility visibility.
post:hashtag We tag all the toots with the hashtag.
post:api_base_url Implicitly used when we create our Tooter
post:cred_file Implicitly used when we create our Tooter

Returns

Number of toots tooted.

TODO

  • Take the maximum post size into account and break into multiple toots.
Source code in mastoscore/post.py
def post_toots(config, tootlist) -> List[str]:
    """
    Given the config and list of toots, create a [Tooter](module-tooter.md) and toot them.

    # Parameters
    - **config**: A ConfigParser object from the [config](module-config.md) module
    - **tootlist**: A list of strings which are the toots to be tooted. This module doesn't change them.

    # Config Parameters Used
    | Option | Description |
    | ------- | ------- |
    | `post:dry_run` | Boolean. If True, print HTML on stdout. If False, really post toots. |
    | `post:root_visibility` | The very first post in the thread will be set to this visibility. Usually 'public'. Must be a valid string for [mastodon statuses](https://docs.joinmastodon.org/methods/statuses/#form-data-parameters) which currently are: `public`, `unlisted`, `private`, and `direct`. |
    | `post:thread_visibility` | All toots are tooted as replies to each other. root &rarr; first &rarr; second, etc. All posts other than the root will have `thread_visibility` visibility. |
    | `post:hashtag` | We tag all the toots with the hashtag. |
    | `post:api_base_url` | Implicitly used when we create our [Tooter](module-tooter.md) |
    | `post:cred_file` | Implicitly used when we create our [Tooter](module-tooter.md) |

    # Returns

    Number of toots tooted.

    # TODO
    - Take the maximum post size into account and break into multiple toots.
    """

    debug = config.getint('post', 'debug')
    dry_run = config.getboolean('post', 'dry_run')
    root_visibility = config.get('post', 'root_visibility')
    thread_visibility = config.get('post', 'thread_visibility')
    hashtag = config.get('post', 'hashtag')

    logger = logging.getLogger(__name__)
    logging.basicConfig(format='%(levelname)s\t%(message)s')
    logger.setLevel(debug)

    if dry_run:
        logger.warning(f"dry_run is true. Not actually tooting")
        t = None
    else:
        try:
            t = Tooter(config, 'post')
        except Exception as e:
            logger.critical("Failed to create 'post' Tooter")
            logger.critical(e)
            return []

    reply = None
    next_status = {}
    n = 1
    # if we're a dry run, put HTML header and footer so we can pipe it
    # to `w3m -T text/html` for pretty viewing
    if dry_run:
        print(HTML_HEAD)
    # First an anchor post with some details
    uidlist = []
    for toot in tootlist:
        visibility = root_visibility if n == 1 else thread_visibility
        text = toot + f"<p>#{hashtag} {n}/{len(tootlist)+1}</p>"

        if t is None:
            print(f"{text}\nVisibility: {visibility}")
            next_status['id'] = n
            uidlist.append(str(n))
        else:
            try:
                next_status = t.status_post(text, visibility=visibility, language='en',
                                            in_reply_to_id=reply, content_type="text/html")
                uidlist.append(str(next_status))
                logger.info(f"anchor: {n}")
            except Exception as e:
                logger.error(f"anchor post")
                logger.error(e)
                return uidlist    # Now boost about boosts
        n = n+1
        reply = next_status['id']
    logger.info(f"Posted {n} toots")
    if dry_run:
        print(HTML_TAIL)
    return uidlist