nmlorg / metabot

Modularized, multi-account bot.
https://metabot.readthedocs.io/
5 stars 0 forks source link

Allow admins to customize the format of daily announcements (or /events output in general) #31

Open nmlorg opened 5 years ago

nmlorg commented 5 years ago

The public instance is up to 4 groups using daily announcements (#25), including one that announces the ends of giveaways, so the "event"-focussed wording is awkward, and one whose admin just wants to be able to tweak the formatting for aesthetic reasons.

In the original bot, I had experimented with using Jinja2 to build the responses to /-commands (which has conceptually been replaced with MessageBuilder). It might be nice to revisit this, actually allowing bot admins to construct responses using Jinja2 (or some other lightweight (but has at least conditionals and loops) template language).

nmlorg commented 5 years ago

For example, in the original bot, /help was:

  dispatcher.add_handler(ext.CommandHandler('help', slash_help.help))
  dispatcher.add_handler(ext.MessageHandler(ext.Filters.private, slash_help.short))

 ...

  def help(self, bot, update):
    update.ctx.reply_template(
        'commands.html', {
            'commands': self.get_commands(update),
            'inlines': self.get_inlines(),
        })

  def short(self, bot, update):
    update.ctx.reply_template(
        'commands.html', {
            'commands': self.get_commands(update),
            'inlines': self.get_inlines(),
            'short': True,
        })

with commands.html being:

{% if short or ctx.group %}
  {{'/help'|botcmdlink('Details')}}
  {% for command, desc in commands|sort if desc %}
    • /{{command}}
  {% endfor %}
  {% for keyword, desc in inlines|sort if desc %}
    &#x2022; <code>@{{bot.username}} {{keyword}}</code>
  {% endfor %}
{% else %}
  I look for messages that start with a slash and the name of a command from this list:<br>

  {% for command, desc in commands|sort if desc %}
    <br>
    /{{command}} - {{desc}}<br>
  {% endfor %}
  <br>
  I also look for messages that start with <code>@{{bot.username}}</code> in any chat (even private
  chats with other people and group chats I'm not in):<br>

  {% for keyword, desc in inlines|sort if desc %}
    <br>
    <code>@{{bot.username}} {{keyword}}</code> - {{desc}}<br>
  {% endfor %}
{% endif %}
nmlorg commented 2 months ago

I'm working through a new templating syntax, and right now I'm playing with the idea of using {...} for optional content, so the current format of:

Event Title Event Time @ Event Location

might be:

<b>$title</b>
<a href="$detailsurl">$datetime</a>{ @ <a href="$mapurl">$locationname</a>}

to make it so:

{"summary": "Alpha Title", "start": [noon], "location": "Alpha Location, Rest of Location"}

is rendered as:

<b>Alpha Title</b>
<a href="https://t.me/botname/start=...">Jul 23, noon</a> @ <a href="https://maps.google.com/?q=Alpha Location, Rest of Location">Alpha Location</a>

but:

{"summary": "Bravo Title", "start": [noon], "location": ""}

is rendered as:

<b>Bravo Title</b>
<a href="https://t.me/botname/start=...">Jul 23, noon</a>
nmlorg commented 1 month ago

An alternative would be to let bot admins define interstitial format strings, so a bot might process an event:

event = {
    'location': 'Location < of & event, Rest of Location',
    'start': 12345,
    'summary': 'Name < of & $event',
}

into:

escaped_values = {
    'date': 'Wed, Jul 31',
    'datetime': 'NOW Wed, Jul 31, 10-11am',
    'details_url': 'https://t.me/botusername?start=L2V2ZW50cw',
    'location_name': 'Location &lt; of &amp; event',
    'location_url': 'https://maps.google.com/maps?q=Location+%3C+of+%26+event%2C+Rest+of+Location',
    'time': 'NOW 10-11am',
    'title': 'Name &lt; of &amp; $event',
}

then process:

groupconf = {
    'daily': {
        'hour: 9,
    }
    'eventformats': {
        'titles': {
            'default': '',
            'sameday': '$date',
            'samelocation': '$location',
        },
        'events': {
            'default': '$title\n$datetime$location',
            'sameday': '$time - $title$location',
            'samelocation': '$datetime - $title',
        },
        'components': {
            'date': '$date',
            'datetime': '<a href="$details_url">$datetime</a>',
            'location': ' @ <a href="$location_url">$location_name</a>',
            'time': '<a href="$details_url">$time</a>',
            'title': '<b>$title</b>',
        },
    },
    'maxeventscount': 10,
    'timezone': 'America/Los_Angeles',
}

first into:

components = {
    'date': 'Wed, Jul 31',
    'datetime': '<a href="https://t.me/botusername?start=L2V2ZW50cw">NOW Wed, Jul 31, 10-11am</a>',
    'location': ' @ <a href="https://maps.google.com/maps?q=Location+%3C+of+%26+event%2C+Rest+of+Location">Location &lt; of &amp; event</a>',
    'time': '<a href="https://t.me/botusername?start=L2V2ZW50cw">NOW 10-11am</a>',
    'title': '<b>Name &lt; of &amp; $event</b>',
}

then, if the bot bundles the event into sameday style, it'd process groupconf.eventformats.events.sameday ('$time - $title$location') to:

<a href="https://t.me/botusername?start=L2V2ZW50cw">NOW 10-11am</a> - <b>Name &lt; of &amp; $event</b> @ <a href="https://maps.google.com/maps?q=Location+%3C+of+%26+event%2C+Rest+of+Location">Location &lt; of &amp; event</a>


Meanwhile:

event = {
    'location': '',
    'start': 12345,
    'summary': 'Name < of & $event',
}

would process into:

escaped_values = {
    'date': 'Wed, Jul 31',
    'datetime': 'NOW Wed, Jul 31, 10-11am',
    'details_url': 'https://t.me/botusername?start=L2V2ZW50cw',
    'time': 'NOW 10-11am',
    'title': 'Name &lt; of &amp; $event',
}

where $location_name and $location_url are left undefined entirely. Then:

groupconf = {
    'eventformats': {
        'components': {
            'date': '$date',
            'datetime': '<a href="$details_url">$datetime</a>',
            'location': ' @ <a href="$location_url">$location_name</a>',
            'time': '<a href="$details_url">$time</a>',
            'title': '<b>$title</b>',
        },
    },
}

would process into:

components = {
    'date': 'Wed, Jul 31',
    'datetime': '<a href="https://t.me/botusername?start=L2V2ZW50cw">NOW Wed, Jul 31, 10-11am</a>',
    'location': '',
    'time': '<a href="https://t.me/botusername?start=L2V2ZW50cw">NOW 10-11am</a>',
    'title': '<b>Name &lt; of &amp; $event</b>',
}

because attempting to dereference escaped_values['location_url'] (and escaped_values['location_name']) would raise a KeyError (that the formatter would catch and then emit ''), so the final result would be:

<a href="https://t.me/botusername?start=L2V2ZW50cw">NOW 10-11am</a> - <b>Name &lt; of &amp; $event</b>


Depending on exactly how the UI ends up developing, this might be more approachable for non-technical bot admins, but I feel like there's still too much unnecessary complexity. (If nothing else, instead of explicitly marking sections as optional, relying on the first-pass processor choosing to simply not set escaped_values['location_url'] in order to cause the interstitial processor to set $location to '' instead of ' @ <a href=""></a>' — it's not super intuitive why that would happen other than that it has to happen.)