Пример #1
0
def subscriptions(update, context):
    logger.info("[Command /suscripciones]")
    subscribed_deptos = context.chat_data.get("subscribed_deptos", [])
    subscribed_cursos = context.chat_data.get("subscribed_cursos", [])

    sub_deptos_list = ["- <b>({})</b>    <i>{} {}</i>".format(x, DEPTS[x][0], DEPTS[x][1]) for x in subscribed_deptos]
    sub_cursos_list = ["- <b>({}-{})</b>    <i>{} en {} {}</i>"
                           .format(x[0], x[1], x[1], DEPTS[x[0]][0], DEPTS[x[0]][1]) for x in subscribed_cursos]

    result = "<b>Avisos activados:</b> <i>{}</i>\n\n" \
        .format("Sí \U00002714 (Detener: /stop)" if context.chat_data.get("enable", False)
                             else "No \U0000274C (Activar: /start)")

    if sub_deptos_list or sub_cursos_list:
        result += "Actualmente doy los siguientes avisos para este chat:\n\n"
    else:
        result += "Actualmente no tienes suscripciones a ningún departamento o curso.\n" \
                  "Suscribe avisos con /suscribir_depto o /suscribir_curso."

    if sub_deptos_list:
        result += "<b>Avisos por departamento:</b>\n"
        result += "\n".join(sub_deptos_list)
        result += "\n\n"
    if sub_cursos_list:
        result += "<b>Avisos por curso:</b>\n"
        result += "\n".join(sub_cursos_list)
        result += "\n\n"

    if sub_deptos_list or sub_cursos_list:
        result += "<i>Puedes desuscribirte con /desuscribir_depto y /desuscribir_curso.</i>"
    try_msg(context.bot,
            chat_id=update.message.chat_id,
            parse_mode="HTML",
            text=result)
Пример #2
0
def stop(update, context):
    logger.info("[Command /stop]")
    context.chat_data["enable"] = False
    try_msg(context.bot,
            chat_id=update.message.chat_id,
            text="Ok, dejaré de avisar cambios en el catálogo por este chat. "
                 "Puedes volver a activar los avisos enviándome /start nuevamente."
            )
Пример #3
0
def main():
    try:
        with open(path.relpath('config/bot.json'), "r") as bot_config_file:
            data.config = json.load(bot_config_file)
        logger.info("Bot config loaded.")
    except OSError:
        logger.error("Bot config was not found. Can't initialize.")
        return

    try_msg(updater.bot,
            chat_id=admin_ids[0],
            text=f'Bot iniciado. Config:\n<pre>{json.dumps(data.config, indent=2)}</pre>',
            parse_mode="HTML")

    try:
        with open(path.relpath('excluded/catalogdata-{}-{}.json'.format(YEAR, SEMESTER)), "r") as datajsonfile:
            data.current_data = json.load(datajsonfile)
        logger.info("Data loaded from local, initial check for changes will be made.")
        check_first = True
    except OSError:
        logger.info("No local data was found, initial scraping will be made without checking for changes.")
        check_first = False
        data.current_data = scrape_catalog()
        save_catalog()

    data.job_check_changes = jq.run_repeating(check_catalog, interval=data.config["changes_check_interval"],
                                              first=(1 if check_first else None),
                                              name="job_check")
    data.job_check_changes.enabled = data.config["is_checking_changes"]
    data.job_check_results = jq.run_repeating(check_results, interval=data.config["results_check_interval"],
                                              name="job_results")
    data.job_check_results.enabled = data.config["is_checking_results"]

    dp.add_handler(CommandHandler('start', start))
    dp.add_handler(CommandHandler('stop', stop))
    dp.add_handler(CommandHandler('suscribir_depto', subscribe_depto))
    dp.add_handler(CommandHandler('suscribir_curso', subscribe_curso))
    dp.add_handler(CommandHandler('desuscribir_depto', unsubscribe_depto))
    dp.add_handler(CommandHandler('desuscribir_curso', unsubscribe_curso))
    dp.add_handler(CommandHandler('deptos', deptos))
    dp.add_handler(CommandHandler('suscripciones', subscriptions))
    # Admin commands
    dp.add_handler(CommandHandler('force_check', force_check, filters=Filters.user(admin_ids)))
    dp.add_handler(CommandHandler('get_log', get_log, filters=Filters.user(admin_ids)))
    dp.add_handler(CommandHandler('get_chats_data', get_chats_data, filters=Filters.user(admin_ids)))
    dp.add_handler(CommandHandler('notification', notification, filters=Filters.user(admin_ids)))
    dp.add_handler(CommandHandler('force_notification', force_notification, filters=Filters.user(admin_ids)))
    dp.add_handler(CommandHandler('force_check_results', force_check_results, filters=Filters.user(admin_ids)))
    dp.add_handler(CommandHandler('enable_check_results', enable_check_results, filters=Filters.user(admin_ids)))
    dp.add_handler(CommandHandler('enable_check_changes', enable_check_changes, filters=Filters.user(admin_ids)))
    dp.add_handler(CommandHandler('changes_check_interval', changes_check_interval, filters=Filters.user(admin_ids)))
    dp.add_handler(CommandHandler('results_check_interval', results_check_interval, filters=Filters.user(admin_ids)))
    dp.add_handler(CommandHandler('help', admin_help, filters=Filters.user(admin_ids)))

    updater.start_polling()
    updater.idle()
Пример #4
0
def unsubscribe_curso(update, context):
    logger.info("[Command /desuscribir_curso]")
    if context.args:
        deleted = []
        notsub = []
        failed = []
        failed_depto = []
        for arg in context.args:
            try:
                (d_arg, c_arg) = arg.split("-")
            except ValueError:
                failed.append(arg)
                continue

            if d_arg in DEPTS:
                if "subscribed_cursos" not in context.chat_data:
                    context.chat_data["subscribed_cursos"] = []
                if (d_arg, c_arg) in context.chat_data["subscribed_cursos"]:
                    context.chat_data["subscribed_cursos"].remove((d_arg, c_arg))
                    data.persistence.flush()
                    deleted.append((d_arg, c_arg))
                else:
                    notsub.append((d_arg, c_arg))
            else:
                failed_depto.append((d_arg, c_arg))
        response = ""
        if deleted:
            response += "\U0001F6D1 Dejaré de avisarte sobre cambios en:\n<i>{}</i>\n\n" \
                .format("\n".join([("- " + x[1] + " de " + DEPTS[x[0]][1] + " ({})".format(x[0])) for x in deleted]))
        if notsub:
            response += "\U0001F44D No estás suscrito a\n<i>{}</i>\n\n" \
                .format("\n".join([("- " + x[1] + " de " + DEPTS[x[0]][1] + " ({})".format(x[0])) for x in notsub]))
        if failed_depto:
            response += "\U0001F914 No pude identificar ningún departamento asociado a:\n<i>{}</i>\n\n" \
                .format("\n".join(["- " + x[0] for x in failed_depto]))
            response += "Puedo recordarte la lista de /deptos que reconozco.\n"
        if failed:
            response += "\U0001F914 No pude identificar el par <i>'depto-curso'</i> en:\n<i>{}</i>\n\n"\
                .format("\n".join(["- " + str(x) for x in failed]))
            response += "Guíate por el formato del ejemplo:\n" \
                        "<i>Ej. /desuscribir_curso 5-CC3001 21-MA1002</i>\n"

        response += "\nRecuerda que puedes apagar temporalmente todos los avisos usando /stop, " \
                    "sin perder tus suscripciones"
        try_msg(context.bot,
                chat_id=update.message.chat_id,
                parse_mode="HTML",
                text=response)
    else:
        try_msg(context.bot,
                chat_id=update.message.chat_id,
                parse_mode="HTML",
                text="Indícame qué cursos quieres dejar de monitorear.\n"
                     "<i>Ej. /desuscribir_curso 5-CC3001 21-MA1002</i>\n\n"
                     "Para ver las suscripciones de este chat envía /suscripciones\n"
                     "Para ver la lista de códigos de deptos que reconozco envía /deptos\n")
Пример #5
0
def deptos(update, context):
    logger.info("[Command /deptos]")
    deptos_list = ["<b>{}</b> - <i>{} {}</i>".format(x, DEPTS[x][0], DEPTS[x][1]) for x in DEPTS]

    try_msg(context.bot,
            chat_id=update.message.chat_id,
            parse_mode="HTML",
            text="Estos son los códigos que representan a cada departamento o área. "
                 "Utilizaré los mismos códigos que usa U-Campus para facilitar la consistencia\n"
                 "\n{}".format("\n".join(deptos_list)))
Пример #6
0
def enable_check_changes(update, context):
    if int(update.message.from_user.id) in admin_ids:
        logger.info("[Command /enable_check_changes from admin %s]", update.message.from_user.id)
        current = data.job_check_changes.enabled
        data.job_check_changes.enabled = not current
        data.config["is_checking_changes"] = not current
        save_config()
        notif = "Check changes: {}".format(str(data.config["is_checking_changes"]))
        try_msg(context.bot,
                chat_id=admin_ids[0],
                text=notif
                )
        logger.info(notif)
Пример #7
0
def notification(update, context):
    if int(update.message.from_user.id) in admin_ids:
        logger.info("[Command /notification from admin %s]", update.message.from_user.id)
        chats_data = dp.chat_data
        if context.args:
            message = update.message.text
            message = message[message.index(" ")+1:].replace("\\", "")
            for chat_id in chats_data:
                if chats_data[chat_id].get("enable", False):
                    try_msg(context.bot,
                            chat_id=chat_id,
                            text=message,
                            parse_mode="Markdown",
                            )
Пример #8
0
def results_check_interval(update, context):
    if int(update.message.from_user.id) in admin_ids:
        logger.info("[Command /results_check_interval from admin %s]", update.message.from_user.id)
        if context.args:
            try:
                data.config["results_check_interval"] = int(context.args[0])
            except ValueError:
                logger.error(f'{context.args[0]} is not a valid interval value')
                return
            save_config()
            notif = "Results check interval: {} seconds".format(str(data.config["results_check_interval"]))
            try_msg(context.bot,
                    chat_id=admin_ids[0],
                    text=notif
                    )
            logger.info(notif)
Пример #9
0
def start(update, context):
    logger.info("[Command /start]")
    if context.chat_data.get("enable", False):
        try_msg(context.bot,
                chat_id=update.message.chat_id,
                text="¡Mis avisos para este chat ya están activados! El próximo chequeo será aproximadamente a las "
                     + (data.last_check_time + timedelta(seconds=300)).strftime("%H:%M") +
                     ".\nRecuerda configurar los avisos de este chat usando /suscribir_depto o /suscribir_curso"
                )
    else:
        context.chat_data["enable"] = True
        try_msg(context.bot,
                chat_id=update.message.chat_id,
                text="A partir de ahora avisaré por este chat si detecto algún cambio en el catálogo de cursos."
                     "\nRecuerda configurar los avisos de este chat usando /suscribir_depto o /suscribir_curso"
                )
Пример #10
0
def admin_help(update, context):
    if int(update.message.from_user.id) in admin_ids:
        logger.info("[Command /help from admin %s]", update.message.from_user.id)
        try_msg(context.bot,
                chat_id=admin_ids[0],
                text=
                '/force_check\n'
                '/get_log\n'
                '/get_chats_data\n'
                '/notification\n'
                '/force_notification\n'
                '/force_check_results\n'
                '/enable_check_results\n'
                '/enable_check_changes\n'
                '/changes_check_interval\n'
                '/results_check_interval\n'
                '/help\n'
                )
Пример #11
0
def unsubscribe_depto(update, context):
    logger.info("[Command /desuscribir_depto]")
    if context.args:
        deleted = []
        notsuscribed = []
        failed = []
        for arg in context.args:
            if arg in DEPTS:
                if arg in context.chat_data["subscribed_deptos"]:
                    context.chat_data["subscribed_deptos"].remove(arg)
                    data.persistence.flush()
                    deleted.append(arg)
                else:
                    notsuscribed.append(arg)
            else:
                failed.append(arg)
        response = ""

        if deleted:
            response += "\U0001F6D1 Dejaré de avisarte sobre cambios en:\n<i>{}</i>\n\n" \
                .format("\n".join(["- " + DEPTS[x][1] + " ({})".format(x) for x in deleted]))
        if notsuscribed:
            response += "\U0001F44D No estás suscrito a <i>{}</i>.\n" \
                .format("\n".join(["- " + DEPTS[x][1] + " ({})".format(x) for x in notsuscribed]))
        if failed:
            response += "\U0001F914 No pude identificar ningún departamento asociado a\n:<i>{}</i>\n\n"\
                .format("\n".join(["- " + str(x) for x in failed]))
            response += "Puedo recordarte la lista de /deptos que reconozco.\n"

        response += "\nRecuerda que puedes apagar temporalmente todos los avisos usando /stop, " \
                    "sin perder tus suscripciones"

        try_msg(context.bot,
                chat_id=update.message.chat_id,
                parse_mode="HTML",
                text=response)
    else:
        try_msg(context.bot,
                chat_id=update.message.chat_id,
                parse_mode="HTML",
                text="Indícame qué departamentos quieres dejar de monitorear.\n"
                     "<i>Ej. /desuscribir_depto 5 21</i>\n\n"
                     "Para ver las suscripciones de este chat envía /suscripciones\n"
                     "Para ver la lista de códigos de deptos que reconozco envía /deptos\n")
Пример #12
0
def check_results(context):
    logger.info("Checking for results...")

    response = requests.get("https://www.u-cursos.cl/ingenieria/2/novedades_institucion/")
    soup = BeautifulSoup(response.content, 'html.parser')

    novedad = soup.find("div", class_="objeto")

    novedad_id = novedad["data-id"]
    if novedad_id == data.config["last_novedad_id"]:
        return

    data.config["last_novedad_id"] = novedad_id
    save_config()
    
    title = novedad.find("h1").find("a").contents[0]
    ltitle = title.lower()
    if "resultado" in ltitle and (
            ("modifica" in ltitle) or
            ("modificación" in ltitle) or
            ("modificacion" in ltitle) or
            (("inscripción" in ltitle or "inscripcion" in ltitle) and (
                    "académica" in ltitle or "academica" in ltitle)) or
            (" IA" in title)
    ):
        chats_data = dp.chat_data
        message = ("\U0001F575 ¡Detecté una Novedad sobre los resultados de la Inscripción Académica!\n"
                   "Título: <strong>{}</strong>\n\n"
                   "<a href='https://ucampus.uchile.cl/m/fcfm_ia/resultados'>"
                   "\U0001F50D Ver resultados IA</a>\n"
                   "<a href='https://www.u-cursos.cl/ingenieria/2/novedades_institucion'>"
                   "\U0001F381 Ver Novedades</a>".format(title))
        for chat_id in chats_data:
            if chats_data[chat_id].get("enable", False):
                try_msg(context.bot,
                        chat_id=chat_id,
                        text=message,
                        parse_mode="HTML",
                        disable_web_page_preview=True,
                        )
Пример #13
0
def subscribe_depto(update, context):
    logger.info("[Command /suscribir_depto]")
    if context.args:
        added = []
        already = []
        failed = []
        for arg in context.args:
            if arg in DEPTS:
                if "subscribed_deptos" not in context.chat_data:
                    context.chat_data["subscribed_deptos"] = []
                if arg not in context.chat_data["subscribed_deptos"]:
                    context.chat_data["subscribed_deptos"].append(arg)
                    data.persistence.flush()
                    added.append(arg)
                else:
                    already.append(arg)
            else:
                failed.append(arg)
        response = ""
        if added:
            response += "\U0001F4A1 Te avisaré sobre los cambios en:\n<i>{}</i>\n\n" \
                .format("\n".join(["- " + DEPTS[x][1] + " ({})".format(x) for x in added]))
        if already:
            response += "\U0001F44D Ya te habías suscrito a:\n<i>{}</i>\n\n" \
                .format("\n".join(["- " + DEPTS[x][1] + " ({})".format(x) for x in already]))
        if failed:
            response += "\U0001F914 No pude identificar ningún departamento asociado a:\n<i>{}</i>\n\n"\
                .format("\n".join(["- " + str(x) for x in failed]))
            response += "Puedo recordarte la lista de /deptos que reconozco.\n"

        try_msg(context.bot,
                chat_id=update.message.chat_id,
                parse_mode="HTML",
                text=response)

        if added and not context.chat_data.get("enable", False):
            try_msg(context.bot,
                    chat_id=update.message.chat_id,
                    parse_mode="HTML",
                    text="He registrado tus suscripciones ¡Pero los avisos para este chat están desactivados!.\n"
                         "Actívalos enviándome /start")
    else:
        try_msg(context.bot,
                chat_id=update.message.chat_id,
                parse_mode="HTML",
                text="Debes decirme qué departamentos deseas monitorear.\n<i>Ej. /suscribir_depto 5 21 9</i>\n\n"
                     "Para ver la lista de códigos de deptos que reconozco envía /deptos")
Пример #14
0
def notify_changes(all_changes, context):
    chats_data = dp.chat_data
    changes_dict = {}
    for d_id in all_changes:
        changes_str = changes_to_string(all_changes[d_id], d_id)
        changes_dict[d_id] = changes_str

    # for chat_id in admin_ids:  # DEBUG, send only to admin
    for chat_id in chats_data:
        if chats_data[chat_id].setdefault("enable", False):
            try:
                subscribed_deptos = chats_data[chat_id].setdefault("subscribed_deptos", [])
                subscribed_cursos = chats_data[chat_id].setdefault("subscribed_cursos", [])
                dept_matches = [x for x in subscribed_deptos if x in all_changes]
                curso_matches = [x for x in subscribed_cursos if (x[0] in all_changes
                                                                  and (x[1] in all_changes[x[0]].get("added", []) or
                                                                       x[1] in all_changes[x[0]].get("deleted", []) or
                                                                       x[1] in all_changes[x[0]].get("modified", {})))]

                if dept_matches or curso_matches:
                    deptos_messages = []
                    for d_id in dept_matches:
                        deptos_messages.append("<b>Cambios en {}</b>"
                                               "\n{}\n"
                                               "<a href='https://ucampus.uchile.cl/m/fcfm_catalogo/"
                                               "?semestre={}{}&depto={}'>"
                                               "\U0001F50D Ver catálogo</a>"
                                               .format(DEPTS[d_id][1], changes_dict[d_id], YEAR, SEMESTER, d_id))
                    cursos_messages = []
                    for d_c_id in curso_matches:
                        d_id = d_c_id[0]
                        c_id = d_c_id[1]
                        change_type_str = ""
                        curso_changes_str = ""
                        if c_id in all_changes[d_id].get("added", []):
                            change_type_str = "Curso añadido:"
                            curso_changes_str = added_curso_string(c_id, d_id)
                        elif c_id in all_changes[d_id].get("deleted", []):
                            change_type_str = "Curso eliminado:"
                            curso_changes_str = deleted_curso_string(c_id, d_id)
                        elif c_id in all_changes[d_id].get("modified", {}):
                            change_type_str = "Curso modificado:"
                            curso_changes_str = modified_curso_string(c_id, d_id, all_changes[d_id]["modified"][c_id])
                            cursos_messages.append("<b>{}</b>"
                                                   "\n{}\n"
                                                   "<a href='https://ucampus.uchile.cl/m/fcfm_catalogo/"
                                                   "?semestre={}{}&depto={}'>"
                                                   "\U0001F50D Ver catálogo</a>"
                                                   .format(change_type_str, curso_changes_str, YEAR, SEMESTER, d_id))

                    t = threading.Thread(target=notify_thread,
                                         args=(context, chat_id, deptos_messages, cursos_messages))
                    t.start()
            except (Unauthorized, BadRequest):
                continue
            except Exception as e:
                logger.exception("Uncaught exception occurred when notifying chat:")
                logger.error("Notification process will continue regardless.")
                try_msg(context.bot,
                        chat_id=admin_ids[0],
                        text="Ayuda, ocurrió un error al notificar y no supe qué hacer uwu.\n{}: {}"
                        .format(str(type(e).__name__), str(e)))
                continue
Пример #15
0
def check_catalog(context):
    try:
        data.new_data = scrape_catalog()

        all_changes = {}

        for d_id in DEPTS:
            old_cursos_data = data.current_data.get(d_id, {})
            new_cursos_data = data.new_data.get(d_id, {})
            if len(old_cursos_data) >= 3 and len(new_cursos_data) == 0:
                data.new_data.update({d_id: old_cursos_data})
                logger.exception(
                    f'All cursos in ({d_id}) {DEPTS[d_id][1]} were deleted. Skipping this depto and keeping old information.')
                try_msg(context.bot,
                        chat_id=admin_ids[0],
                        text=f'Todos los cursos de {DEPTS[d_id][1]} fueron borrados. Me saltaré este departamento y mantendré la información anterior.')
                continue

            old_cursos = old_cursos_data.keys()
            new_cursos = new_cursos_data.keys()
            ocs = set(old_cursos)
            ncs = set(new_cursos)
            added = [x for x in new_cursos if x not in ocs]
            deleted = [x for x in old_cursos if x not in ncs]
            inter = [x for x in old_cursos if x in ncs]
            modified = {}
            d_data = data.current_data[d_id]
            d_new_data = data.new_data[d_id]
            for c_id in inter:
                mods = {}
                if d_data[c_id]["nombre"] != d_new_data[c_id]["nombre"]:
                    mods["nombre"] = [d_data[c_id]["nombre"], d_new_data[c_id]["nombre"]]
                old_secciones = set(d_data[c_id]["secciones"].keys())
                new_secciones = set(d_new_data[c_id]["secciones"].keys())
                changes_sec = {}
                added_sec = new_secciones - old_secciones
                deleted_sec = old_secciones - new_secciones
                inter_sec = old_secciones & new_secciones
                modified_sec = {}
                for s_id in inter_sec:
                    mods_sec = {}
                    s_id = str(s_id)
                    if d_data[c_id]["secciones"][s_id]["profesores"] \
                            != d_new_data[c_id]["secciones"][s_id]["profesores"]:
                        mods_sec["profesores"] = [d_data[c_id]["secciones"][s_id]["profesores"],
                                                  d_new_data[c_id]["secciones"][s_id]["profesores"]]
                    if d_data[c_id]["secciones"][s_id]["cupos"] != d_new_data[c_id]["secciones"][s_id]["cupos"]:
                        mods_sec["cupos"] = [d_data[c_id]["secciones"][s_id]["cupos"],
                                             d_new_data[c_id]["secciones"][s_id]["cupos"]]
                    if d_data[c_id]["secciones"][s_id]["horarios"] != d_new_data[c_id]["secciones"][s_id]["horarios"]:
                        mods_sec["horarios"] = [d_data[c_id]["secciones"][s_id]["horarios"],
                                                d_new_data[c_id]["secciones"][s_id]["horarios"]]
                    if len(mods_sec) > 0:
                        modified_sec[s_id] = mods_sec

                if len(added_sec) > 0:
                    changes_sec["added"] = added_sec
                if len(deleted_sec) > 0:
                    changes_sec["deleted"] = deleted_sec
                if len(modified_sec) > 0:
                    changes_sec["modified"] = modified_sec

                if len(changes_sec) > 0:
                    mods["secciones"] = changes_sec

                if len(mods) > 0:
                    modified[c_id] = mods

            if added or deleted or modified:
                all_changes[d_id] = {}
                if added:
                    all_changes[d_id]["added"] = added
                if deleted:
                    all_changes[d_id]["deleted"] = deleted
                if modified:
                    all_changes[d_id]["modified"] = modified

        if len(all_changes) > 0:
            logger.info("Changes detected on %s", str([x for x in all_changes]))
            notify_changes(all_changes, context)
        else:
            logger.info("No changes detected")
        data.current_data = data.new_data
        data.last_check_time = datetime.now()

        save_catalog()

    except AllDeletedException as e:
        logger.error("All cursos were deleted. Aborting check and keeping old information.")
        try_msg(context.bot,
                chat_id=admin_ids[0],
                text="Se han borrado todos los cursos. Se mantendrá la información anterior y se ignorará este check.")
    except Exception as e:
        logger.exception("Uncaught exception occurred:")
        try_msg(context.bot,
                chat_id=admin_ids[0],
                text="Ayuda, ocurrió un error y no supe qué hacer uwu.\n{}: {}".format(str(type(e).__name__), str(e)))
Пример #16
0
def subscribe_curso(update, context):
    logger.info("[Command /suscribir_curso]")
    if context.args:
        added = []
        already = []
        unknown = []
        failed = []
        failed_depto = []
        for arg in context.args:
            try:
                (d_arg, c_arg) = arg.split("-")
                c_arg = c_arg.upper()
            except ValueError:
                failed.append(arg)
                continue

            if d_arg in DEPTS:
                if "subscribed_cursos" not in context.chat_data:
                    context.chat_data["subscribed_cursos"] = []
                if (d_arg, c_arg) not in context.chat_data["subscribed_cursos"]:
                    context.chat_data["subscribed_cursos"].append((d_arg, c_arg))
                    data.persistence.flush()
                    is_curso_known = c_arg in data.current_data[d_arg]
                    if is_curso_known:
                        added.append((d_arg, c_arg))
                    else:
                        unknown.append((d_arg, c_arg))
                else:
                    already.append((d_arg, c_arg))
            else:
                failed_depto.append((d_arg, c_arg))
        response = ""
        if added:
            response += "\U0001F4A1 Te avisaré sobre cambios en:\n<i>{}</i>\n\n" \
                .format("\n".join(["- " + (x[1] + " de " + DEPTS[x[0]][1] + " ({})".format(x[0])) for x in added]))
        if unknown:
            response += "\U0001F4A1 Actualmente no tengo registros de:\n<i>{}</i>\n" \
                .format("\n".join(["- " + (x[1] + " en " + DEPTS[x[0]][1] + " ({})".format(x[0])) for x in unknown]))
            response += "Te avisaré si aparece algún curso con ese código en ese depto.\n\n"
        if already:
            response += "\U0001F44D Ya estabas suscrito a:\n<i>{}</i>.\n\n" \
                .format("\n".join(["- " + (x[1] + " de " + DEPTS[x[0]][1] + " ({})".format(x[0])) for x in already]))
        if failed_depto:
            response += "\U0001F914 No pude identificar ningún departamento asociado a:\n<i>{}</i>\n\n" \
                .format("\n".join(["- " + x[0] for x in failed_depto]))
            response += "Puedo recordarte la lista de /deptos que reconozco.\n"
        if failed:
            response += "\U0001F914 No pude identificar el par <i>'depto-curso'</i> en:\n<i>{}</i>\n\n"\
                .format("\n".join(["- " + str(x) for x in failed]))
            response += "Guíate por el formato del ejemplo:\n" \
                        "<i>Ej. /suscribir_curso 5-CC3001 21-MA1002</i>\n"

        try_msg(context.bot,
                chat_id=update.message.chat_id,
                parse_mode="HTML",
                text=response)

        if (added or unknown) and not context.chat_data.get("enable", False):
            try_msg(context.bot,
                    chat_id=update.message.chat_id,
                    parse_mode="HTML",
                    text="He registrado tus suscripciones ¡Pero los avisos para este chat están desactivados!\n"
                         "Actívalos enviándome /start")
    else:
        try_msg(context.bot,
                chat_id=update.message.chat_id,
                parse_mode="HTML",
                text="Debes decirme qué cursos deseas monitorear en la forma <i>'depto-curso'</i> para registrarlo.\n"
                     "<i>Ej. /suscribir_curso 5-CC3001 21-MA1002</i>\n\n"
                     "Para ver la lista de códigos de deptos que reconozco envía /deptos")