def format_event(bot, event, tzinfo, full=True): """Given a metabot.calendars.base.Calendar event, build a human-friendly representation.""" message = '<b>%s</b>' % event['summary'] for count, url in tickets.get_info(event['description']): if count: message = '%s [<a href="%s">%i tickets remaining</a>]' % ( message, url, count) else: message = '%s [<a href="%s">tickets</a>]' % (message, url) message = '%s\n<a href="%s">%s</a>' % ( message, bot.encode_url('/events %s %s' % (event['local_id'], tzinfo.zone)), humanize_range(event['start'], event['end'], tzinfo)) if event['location']: location_name = event['location'].split(',', 1)[0] location_url = 'https://maps.google.com/maps?' + urllib.parse.urlencode({ 'q': event['location'].encode('utf-8'), }) # yapf: disable message = '%s @ <a href="%s">%s</a>' % (message, location_url, location_name) geo = format_geo(event['location'], event['start']) if geo: message = '%s\n\u26a0 %s' % (message, geo) if full and event['description']: message = '%s\n\n%s' % (message, html.sanitize(event['description'])) return message
def _daily_messages(multibot, records): # pylint: disable=too-many-branches,too-many-locals,too-many-statements now = time.time() for botuser, botconf in multibot.conf['bots'].items(): # pylint: disable=too-many-nested-blocks for groupid, groupconf in botconf['issue37']['moderator'].items(): calcodes, tzinfo, count, days, hour, dow = eventutil.get_group_conf( groupconf) if not tzinfo or not isinstance(hour, int): continue # See https://github.com/nmlorg/metabot/issues/26. bot = ntelebot.bot.Bot(botconf['issue37']['telegram']['token']) bot.multibot = multibot form = lambda event: eventutil.format_event( bot, event, tzinfo, full=False) # pylint: disable=cell-var-from-loop title = lambda event: ' \u2022 ' + event['summary'] nowdt = datetime.datetime.fromtimestamp(now, tzinfo) key = (botuser, groupid) if nowdt.hour == hour and not dow & 1 << nowdt.weekday(): events, alerts = eventutil.get_group_events( bot, calcodes, tzinfo, count, days, now) _handle_alerts(bot, records, groupid, alerts) if events: preambles = groupconf['daily'].get('text', '').splitlines() preamble = (preambles and preambles[nowdt.toordinal() % len(preambles)] or '') text = _format_daily_message(preamble, list(map(form, events))) url = eventutil.get_image(events[0], botconf) message = None if url: try: message = bot.send_photo(chat_id=groupid, photo=url, caption=text, parse_mode='HTML', disable_notification=True) except ntelebot.errors.Error: logging.exception('Downgrading to plain text:') if not message: try: message = bot.send_message( chat_id=groupid, text=text, parse_mode='HTML', disable_web_page_preview=True, disable_notification=True) except ntelebot.errors.Error: logging.exception('While sending to %s:\n%s', groupid, text) if message: records[key] = (now, [event.copy() for event in events], message) elif key in records: lastnow, lastevents, lastmessage = records[key] lastmap = {event['local_id']: event for event in lastevents} events, alerts = eventutil.get_group_events( bot, calcodes, tzinfo, count, days, lastnow) _handle_alerts(bot, records, groupid, alerts) curmap = {event['local_id']: event for event in events} bothevents = events.copy() bothevents.extend(event for event in lastevents if event['local_id'] not in curmap) bothevents.sort(key=operator.itemgetter( 'start', 'end', 'summary', 'local_id')) edits = [] for event in bothevents: lastevent = lastmap.get(event['local_id']) if not lastevent: edits.append(title(event)) edits.append(' \u25e6 New event!') continue event = multibot.multical.get_event(event['local_id'])[1] if not event: edits.append(title(lastevent)) edits.append(' \u25e6 Removed.') continue pairs = ( (lastevent['summary'], event['summary']), (eventutil.humanize_range(lastevent['start'], lastevent['end'], tzinfo), eventutil.humanize_range(event['start'], event['end'], tzinfo)), (lastevent['location'], event['location']), (html.sanitize(lastevent['description'], strip=True), html.sanitize(event['description'], strip=True)), ) # yapf: disable pieces = [] for left, right in pairs: diff = _quick_diff(left, right) if diff: pieces.append(diff) if pieces: edits.append(title(event)) for left, right in pieces: edits.append( ' \u25e6 <i>%s</i> \u2192 <b>%s</b>' % (left, right)) if not edits: continue text = 'Updated:\n' + '\n'.join(edits) try: message = bot.send_message( chat_id=groupid, reply_to_message_id=lastmessage['message_id'], text=text, parse_mode='HTML', disable_web_page_preview=True, disable_notification=True) except ntelebot.errors.Error: logging.exception('While sending to %s:\n%s', groupid, text) continue lastnowdt = datetime.datetime.fromtimestamp(lastnow, tzinfo) preambles = groupconf['daily'].get('text', '').splitlines() preamble = preambles and preambles[lastnowdt.toordinal() % len(preambles)] or '' updated = 'Updated ' + humanize.time(nowdt) groupidnum = int(groupid) if -1002147483647 <= groupidnum < -1000000000000: updated = '<a href="https://t.me/c/%s/%s">%s</a>' % ( -1000000000000 - groupidnum, message['message_id'], updated) else: updated = '%s (%s)' % (updated, message['message_id']) text = '%s\n\n[%s]' % (_format_daily_message( preamble, list(map(form, events))), updated) try: if lastmessage.get('caption'): message = bot.edit_message_caption( chat_id=groupid, message_id=lastmessage['message_id'], caption=text, parse_mode='HTML') else: message = bot.edit_message_text( chat_id=groupid, message_id=lastmessage['message_id'], text=text, parse_mode='HTML', disable_web_page_preview=True) except ntelebot.errors.Error: logging.exception('While sending to %s:\n%s', groupid, text) else: records[key] = (lastnow, [event.copy() for event in events], message)
def test_strip(): """Test stripping logic.""" assert html.sanitize('', True) == '' assert html.sanitize('empty', True) == 'empty' assert html.sanitize('← ← ← \u2190', True) == '← ← ← ←' assert html.sanitize('& & &', True) == '& & &' # From https://core.telegram.org/bots/api#formatting-options. text = """ <b>bold</b>, <strong>bold</strong> <i>italic</i>, <em>italic</em> <a href="http://www.example.com/">inline URL</a> <a href="tg://user?id=123456789">inline mention of a user</a> <code>inline fixed-width code</code> <pre>pre-formatted fixed-width code block</pre> """ assert html.sanitize(text, True) == """ bold, bold italic, italic inline URL inline mention of a user inline fixed-width code pre-formatted fixed-width code block """ assert html.sanitize('first<br>second', True) == 'first\nsecond' assert html.sanitize('<a weird="true">text</a>', True) == 'text' assert html.sanitize('<b>good</b> <span>bad</span>', True) == 'good bad' assert html.sanitize('<b><span>nested</span></b>', True) == 'nested' assert html.sanitize('<span><b>nested</b></span>', True) == 'nested' assert html.sanitize('<b>broken <i>good</i>', True) == 'broken good' assert html.sanitize('&bogus;', True) == '&bogus;' assert html.sanitize('&#bogus;', True) == '&#bogus;' assert html.sanitize('&#xxbogus;', True) == '&#xxbogus;'
def test_sanitize(): """Test sanitization logic.""" assert html.sanitize('') == '' assert html.sanitize('empty') == 'empty' assert html.sanitize('← ← ← \u2190') == '← ← ← ←' assert html.sanitize('& & &') == '& & &' # From https://core.telegram.org/bots/api#formatting-options. text = """ <b>bold</b>, <strong>bold</strong> <i>italic</i>, <em>italic</em> <a href="http://www.example.com/">inline URL</a> <a href="tg://user?id=123456789">inline mention of a user</a> <code>inline fixed-width code</code> <pre>pre-formatted fixed-width code block</pre> """ assert html.sanitize(text) == text assert html.sanitize('a<br>b<div>c</div><p>d</p>') == 'a\nb\nc\nd' assert html.sanitize('<a weird="true">text</a>') == '<a>text</a>' assert html.sanitize('<b>good</b> <span>bad</span>') == '<b>good</b> bad' assert html.sanitize('<b><span>nested</span></b>') == '<b>nested</b>' assert html.sanitize('<span><b>nested</b></span>') == '<b>nested</b>' assert html.sanitize( '<b>broken <i>good</i>') == '<b>broken </b><i>good</i>' assert html.sanitize('&bogus;') == '&bogus;' assert html.sanitize('&#bogus;') == '&#bogus;' assert html.sanitize('&#xxbogus;') == '&#xxbogus;'
def inline(ctx, modconf): # pylint: disable=too-many-branches,too-many-locals """Handle @BOTNAME events.""" user_id = '%s' % ctx.user['id'] userconf = modconf['users'][user_id] calcodes = userconf.get('calendars') timezone = userconf.get('timezone') if not calcodes or not timezone: missing = [] if not calcodes: missing.append('choose one or more calendars') if not timezone: missing.append('set your time zone') return ctx.reply_inline([], is_personal=True, cache_time=30, switch_pm_text='Click to %s!' % humanize.list(missing), switch_pm_parameter='L2V2ZW50cw') calendar_view = ctx.bot.multibot.multical.view(calcodes.split()) tzinfo = pytz.timezone(timezone) terms = ctx.text.lower().split()[1:] full = False if terms and terms[0].lower() == 'full': terms.pop(0) full = True nextid = None results = [] while len(results) < 25: _, event, nextev = calendar_view.get_event(nextid) nextid = nextev and nextev['local_id'] if not event: break if full: text = ('%s %s' % (event['summary'], event['description'])).lower() else: text = event['summary'].lower() for term in terms: if term not in text: break else: subtitle = eventutil.humanize_range(event['start'], event['end'], tzinfo) if event['location']: subtitle = '%s @ %s' % (subtitle, event['location'].split(',', 1)[0]) if full and event['description']: title = '%s \u2022 %s' % (event['summary'], subtitle) description = html.sanitize(event['description'], strip=True) else: title = event['summary'] description = subtitle results.append({ 'description': description, 'input_message_content': { 'disable_web_page_preview': True, 'message_text': eventutil.format_event(ctx.bot, event, tzinfo, full=full), 'parse_mode': 'HTML', }, 'id': event['local_id'], #'thumb_url': icon, 'title': title, 'type': 'article', }) if not nextid: break ctx.reply_inline(results, is_personal=True, cache_time=30, switch_pm_text='Settings', switch_pm_parameter='L2V2ZW50cyBzZXQ')