diff --git a/ASFConnector.py b/ASFConnector.py index 205cbb9..45944c2 100755 --- a/ASFConnector.py +++ b/ASFConnector.py @@ -74,7 +74,7 @@ def get_bot_info(self, bot): def bot_redeem(self, bot, keys): """ Redeems cd-keys on given bot. """ LOG.debug('bot_redeem: bot {}, keys {}'.format(bot, keys)) - assert type(keys) is set or type(keys) is str + assert isinstance(keys, (list, set, str)) resource = '/Bot/' + bot + '/Redeem' if type(keys) is str: payload_keys = [keys] diff --git a/bot.py b/bot.py index a8ee1cc..537d7fb 100755 --- a/bot.py +++ b/bot.py @@ -11,7 +11,7 @@ from ASFConnector import ASFConnector -_REGEX_CDKEY = re.compile(r'\w{5}-\w{5}-\w{5}') +_REGEX_CDKEY = re.compile(r"\b[A-Z0-9]{5}-[A-Z0-9]{5}-[A-Z0-9]{5}\b", re.IGNORECASE) _REGEX_COMMAND_BOT_ARGS = r'^[/!]\w+\s*(?P\w+)?\s+(?P.*)' _REGEX_COMMAND_RAW = r'^[/!](?P(?P\w+).*)' _REGEX_COMMAND = r'^[/!]\w+\s*(?P\w+)?' @@ -139,11 +139,12 @@ def status_command(message): response = asf_connector.get_bot_info(bot_arg) LOG.info("Response to status message: %s", str(response)) reply_to(message, "" + str(response) + "") - except requests.HTTPError as http_error: + except requests.exceptions.HTTPError as http_error: LOG.exception(http_error) response = http_error.response status_code = response.status_code - except requests.ConnectionError as connection_error: + reply_to(message, "ASF HTTP error: {}".format(status_code)) + except requests.exceptions.ConnectionError as connection_error: LOG.exception(connection_error) error_message = str(connection_error.args[0].reason.args[0]).split('>:')[1] error_response = "Couldn't reach the ASF instance: {}".format(error_message) @@ -153,18 +154,30 @@ def status_command(message): LOG.exception(ex) -@bot.message_handler(commands=['redeem']) +@bot.message_handler(func=is_user_message, commands=['redeem']) def redeem_command(message): LOG.debug("Received redeem message: %s", str(message)) - match = re.search(_REGEX_COMMAND_BOT_ARGS, message.text) + match = re.search(_REGEX_COMMAND_BOT_ARGS, message.text, re.DOTALL) if not match: reply_to(message, "Missing arguments. Usage:\n/redeem <bot> <keys>") return bots = match.group('bot') if match.group('bot') else 'ASF' - keys = match.group('arg') - response = asf_connector.bot_redeem(bots, keys) - LOG.info("Response to redeem message: %s", str(response)) - reply_to(message, "" + str(response) + "") + raw_keys = match.group('arg') + keys = _normalize_keys(raw_keys) + if not keys: + reply_to(message, "No valid keys found.") + return + try: + response = asf_connector.bot_redeem(bots, keys) + LOG.info("Response to redeem message: %s", str(response)) + reply_to(message, "" + str(response) + "") + except requests.exceptions.HTTPError as ex: + status_code = ex.response.status_code if ex.response is not None else "?" + LOG.error(ex) + reply_to(message, 'Redeem failed. ASF status code: {}'.format(status_code)) + except Exception as ex: + LOG.exception("Redeem unexpected error") + reply_to(message, 'Redeem failed: {}'.format(str(ex))) @bot.message_handler(func=is_user_message, regexp=_REGEX_COMMAND_RAW) @@ -181,7 +194,7 @@ def command_handler(message): try: asf_response = asf_connector.send_command(command) LOG.info("Command: {}. Response: {}".format(message.text, asf_response)) - response = replace_html_entities(asf_response) + response = replace_html_entities(str(asf_response)) except requests.exceptions.HTTPError as ex: status_code = ex.response.status_code LOG.error(ex) @@ -194,8 +207,8 @@ def check_for_cdkeys(message): """ Sync handler for the rest of the messages. It searchs for cdkeys and redeems them. """ - cdkeys = set(_REGEX_CDKEY.findall(message.text)) - if len(cdkeys) > 0: + cdkeys = _normalize_keys(message.text) + if cdkeys: response = asf_connector.bot_redeem('ASF', cdkeys) reply_to(message, "" + str(response) + "") else: @@ -227,3 +240,18 @@ def replace_html_entities(message: str): except Exception as e: LOG.exception(e) +def _normalize_keys(text_or_iterable): + """Return a list of unique, uppercased CD-keys (5-5-5 format) from a string or iterable.""" + if not text_or_iterable: + return [] + if isinstance(text_or_iterable, str): + found = _REGEX_CDKEY.findall(text_or_iterable.upper()) + if found: + # preserve order and drop duplicates + return list(dict.fromkeys(found)) + # Fallback split on common separators + parts = re.split(r"[\s,;]+", text_or_iterable.strip()) + parts = [p.upper() for p in parts if p] + return list(dict.fromkeys(parts)) + # Iterable case + return list(dict.fromkeys([str(x).upper().strip() for x in text_or_iterable if str(x).strip()]))