def etymology(word): # @@ <nsh> sbp, would it be possible to have a flag for .ety to get 2nd/etc # entries? - http://swhack.com/logs/2006-07-19#T15-05-29 if len(word) > 25: raise ValueError("Word too long: %s[…]" % word[:10]) ety = get(ETYURI % web.quote(word)) if ety.status_code != 200: return None # Let's find it start = ety.text.find("word__defination") start = ety.text.find("<p>", start) stop = ety.text.find("</p>", start) sentence = ety.text[start + 3:stop] # Clean up sentence = unescape(sentence) sentence = sub('<[^<]+?>', '', sentence) maxlength = 275 if len(sentence) > maxlength: sentence = sentence[:maxlength] words = sentence[:-5].split(' ') words.pop() sentence = ' '.join(words) + ' […]' sentence = '"' + sentence.replace('"', "'") + '"' return sentence + ' - ' + (ETYURI % web.quote(word))
def wikt(word): bytes = requests.get(uri % web.quote(word)).text bytes = r_ul.sub('', bytes) mode = None etymology = None definitions = {} for line in bytes.splitlines(): is_new_mode = False if 'id="Etymology' in line: mode = 'etymology' is_new_mode = True else: for pos in PARTS_OF_SPEECH: if 'id="{}"'.format(pos.replace(' ', '_')) in line: mode = pos.lower() is_new_mode = True break if not is_new_mode: # 'id="' can occur in definition lines <li> when <sup> tag is used for references; # make sure those are not excluded (see e.g., abecedarian). if ('id="' in line) and ('<li>' not in line): mode = None elif (mode == 'etymology') and ('<p>' in line): etymology = text(line) elif (mode is not None) and ('<li>' in line): definitions.setdefault(mode, []).append(text(line)) if '<hr' in line: break return etymology, definitions
def say_snippet(bot, trigger, server, query, show_url=True, commanded=False): page_name = query.replace('_', ' ') query = quote(query.replace(' ', '_')) url = 'https://{}/wiki/{}'.format(server, query) # If the trigger looks like another instance of this plugin, assume it is if trigger.startswith(PLUGIN_OUTPUT_PREFIX) and trigger.endswith(' | ' + url): return try: snippet = mw_snippet(server, query) # Coalesce repeated whitespace to avoid problems with <math> on MediaWiki # see https://github.com/sopel-irc/sopel/issues/2259 snippet = re.sub(r"\s+", " ", snippet) except KeyError: msg = 'Error fetching snippet for "{}".'.format(page_name) if commanded: bot.reply(msg) else: bot.say(msg) return msg = '{} | "{}'.format(page_name, snippet) trailing = '"' if show_url: trailing += ' | ' + url bot.say(msg, truncation=' […]', trailing=trailing)
def update_rates(bot): global rates, rates_updated # If we have data that is less than 24h old, return if time.time() - rates_updated < 24 * 60 * 60: LOGGER.debug('Skipping rate update; cache is less than 24h old') return # Update crypto rates LOGGER.debug('Updating crypto rates from %s', CRYPTO_URL) response = requests.get(CRYPTO_URL) response.raise_for_status() rates_crypto = response.json() # Update fiat rates if bot.config.currency.fixer_io_key is not None: LOGGER.debug('Updating fiat rates from Fixer.io') if bot.config.currency.fiat_provider != 'fixer.io': # Warning about future behavior change LOGGER.warning( 'fiat_provider is set to %s, but Fixer.io API key is present; ' 'using Fixer anyway. In Sopel 8, fiat_provider will take precedence.', bot.config.currency.fiat_provider) proto = 'https:' if bot.config.currency.fixer_use_ssl else 'http:' response = requests.get(proto + FIAT_PROVIDERS['fixer.io'].format( web.quote(bot.config.currency.fixer_io_key))) if not response.json()['success']: raise FixerError('Fixer.io request failed with error: {}'.format( response.json()['error'])) else: LOGGER.debug('Updating fiat rates from %s', bot.config.currency.fiat_provider) response = requests.get( FIAT_PROVIDERS[bot.config.currency.fiat_provider]) response.raise_for_status() rates_fiat = response.json() rates = rates_fiat['rates'] rates['EUR'] = 1.0 # Put this here to make logic easier eur_btc_rate = 1 / rates_crypto['rates']['eur']['value'] for rate in rates_crypto['rates']: if rate.upper() not in rates: rates[rate.upper( )] = rates_crypto['rates'][rate]['value'] * eur_btc_rate # if an error aborted the operation prematurely, we want the next call to retry updating rates # therefore we'll update the stored timestamp at the last possible moment rates_updated = time.time() LOGGER.debug('Rate update completed')
def say_section(bot, trigger, server, query, section): page_name = query.replace('_', ' ') query = quote(query.replace(' ', '_')) snippet = mw_section(server, query, section) if not snippet: bot.reply("Error fetching section \"{}\" for page \"{}\".".format(section, page_name)) return msg = '{} - {} | "{}"'.format(page_name, section.replace('_', ' '), snippet) bot.say(msg, truncation=' […]"')
def py(bot, trigger): """Evaluate a Python expression.""" if not trigger.group(2): return bot.reply('I need an expression to evaluate.') query = trigger.group(2) uri = bot.config.py.oblique_instance + 'py/' answer = get(uri + quote(query)).content.decode('utf-8') if answer: bot.say(answer) else: bot.reply('Sorry, no result.')
def py(bot, trigger): """Evaluate a Python expression.""" query = trigger.group(2) if not query: bot.reply('What expression do you want me to evaluate?') return uri = bot.config.py.oblique_instance + 'py/' answer = get(uri + quote(query)).content.decode('utf-8') if answer: bot.say(answer) else: bot.reply('Sorry, no result.')
def f_etymology(bot, trigger): """Look up the etymology of a word""" word = trigger.group(2) try: result = etymology(word) except IOError: msg = "Can't connect to etymonline.com (%s)" % (ETYURI % web.quote(word)) bot.say(msg, trigger.sender) return NOLIMIT except (AttributeError, TypeError): result = None except ValueError as ve: result = str(ve) if result is not None: bot.say(result, trigger.sender) else: uri = ETYSEARCH % web.quote(word) msg = 'Can\'t find the etymology for "%s". Try %s' % (word, uri) bot.say(msg, trigger.sender) return NOLIMIT
def py(bot, trigger): """Evaluate a Python expression.""" if not trigger.group(2): return bot.say("Need an expression to evaluate") query = trigger.group(2) uri = BASE_TUMBOLIA_URI + 'py/' answer = get(uri + quote(query)).content.decode('utf-8') if answer: # bot.say can potentially lead to 3rd party commands triggering. bot.reply(answer) else: bot.reply('Sorry, no result.')
def say_snippet(bot, trigger, server, query, show_url=True): page_name = query.replace('_', ' ') query = quote(query.replace(' ', '_')) try: snippet = mw_snippet(server, query) except KeyError: if show_url: bot.reply("Error fetching snippet for \"{}\".".format(page_name)) return msg = '{} | "{}"'.format(page_name, snippet) msg_url = msg + ' | https://{}/wiki/{}'.format(server, query) if msg_url == trigger: # prevents triggering on another instance of Sopel return if show_url: msg = msg_url bot.say(msg)
def say_snippet(bot, trigger, server, query, show_url=True): page_name = query.replace('_', ' ') query = quote(query.replace(' ', '_')) url = 'https://{}/wiki/{}'.format(server, query) # If the trigger looks like another instance of this plugin, assume it is if trigger.startswith(PLUGIN_OUTPUT_PREFIX) and trigger.endswith(' | ' + url): return try: snippet = mw_snippet(server, query) except KeyError: if show_url: bot.reply("Error fetching snippet for \"{}\".".format(page_name)) return msg = '{} | "{}'.format(page_name, snippet) trailing = '"' if show_url: trailing += ' | ' + url bot.say(msg, truncation=' […]', trailing=trailing)
def mw_section(server, query, section): """ Retrieves a snippet from the specified section from the given page on the given server. """ sections_url = ('https://{0}/w/api.php?format=json&redirects' '&action=parse&prop=sections&page={1}'.format( server, query)) sections = get(sections_url).json() section_number = None for entry in sections['parse']['sections']: if entry['anchor'] == section: section_number = entry['index'] # Needed to handle sections from transcluded pages properly # e.g. template documentation (usually pulled in from /doc subpage). # One might expect this prop to be nullable because in most cases it # will simply repeat the requested page title, but it's always set. fetch_title = quote(entry['fromtitle']) break if not section_number: return None snippet_url = ('https://{0}/w/api.php?format=json&redirects' '&action=parse&page={1}&prop=text' '§ion={2}').format(server, fetch_title, section_number) data = get(snippet_url).json() parser = WikiParser(section.replace('_', ' ')) parser.feed(data['parse']['text']['*']) text = parser.get_result() text = ' '.join(text.split()) # collapse multiple whitespace chars return text
def googleit(bot, trigger): """Let me just… Google that for you.""" if not trigger.group(2): # No input return bot.say('https://www.google.com/') bot.say('https://lmgtfy.com/?q=' + quote(trigger.group(2).replace(' ', '+'), '+'))
def test_quote(text, result): assert quote(text) == result