├── files ├── heman.jpg ├── 0I-aulas.pdf ├── Plandeestudios.png └── Plandeestudios-93.png ├── requirements.txt ├── tg_ids.py ├── invitacionMail.py ├── README.md ├── utils └── hora_feliz_dia.py ├── campus.py ├── LICENSE ├── handlers └── update_groups.py ├── conciertos.py ├── models.py ├── .gitignore ├── deletablecommandhandler.py ├── river.py ├── orga2Utils.py ├── labos.py ├── vencimientoFinales.py ├── install.py └── dcubabot.py /files/heman.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/comcomUBA/dcubabot/HEAD/files/heman.jpg -------------------------------------------------------------------------------- /files/0I-aulas.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/comcomUBA/dcubabot/HEAD/files/0I-aulas.pdf -------------------------------------------------------------------------------- /files/Plandeestudios.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/comcomUBA/dcubabot/HEAD/files/Plandeestudios.png -------------------------------------------------------------------------------- /files/Plandeestudios-93.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/comcomUBA/dcubabot/HEAD/files/Plandeestudios-93.png -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | pony 2 | icalevents 3 | pytz 4 | requests 5 | python_telegram_bot==13.11 6 | robobrowser==0.5.3 7 | Werkzeug==0.15.5 8 | -------------------------------------------------------------------------------- /tg_ids.py: -------------------------------------------------------------------------------- 1 | DC_GROUP_CHATID = -1001067544716 2 | CODEPERS_CHATID = "-1001625164045" 3 | ROZEN_CHATID = 137497264 4 | DGARRO_CHATID = 187622583 5 | NOTICIAS_CHATID = "@NoticiasDC" 6 | -------------------------------------------------------------------------------- /invitacionMail.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | from models import * 4 | init_db("dcubabot.sqlite3") 5 | 6 | with db_session: 7 | buttons = select(l for l in Obligatoria if l.validated).order_by(lambda l: l.name) 8 | print(list(buttons)) 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # dcubabot 2 | 3 | La idea de este repositorio es migrar desde el viejo repositorio las funcionalidades que tiene el bot de forma estructurada, prolija y segura. Muy pronto traeremos bardearmarto, tranquilos. 4 | 5 | Mientras seguimos la migración todos son libres de colaborar con pull requests. 6 | 7 | Para probar el bot en acción en Telegram es necesario crear un archivo `tokenz.py` que asigne a la variable `token` el token del bot con el que se desea hacer las pruebas. 8 | 9 | Para instalar las dependecias correr lo siguiente: 10 | ```bash 11 | $ pip3 install -r requirements.txt 12 | ``` 13 | -------------------------------------------------------------------------------- /utils/hora_feliz_dia.py: -------------------------------------------------------------------------------- 1 | # !/usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | 4 | # https://stackoverflow.com/a/11236372/1576803 5 | import datetime 6 | 7 | import pytz 8 | 9 | 10 | def get_hora_feliz_dia(): 11 | tz = pytz.timezone("America/Argentina/Buenos_Aires") 12 | now = datetime.datetime.now(tz).date() 13 | midnight = tz.localize(datetime.datetime.combine(now, datetime.time(0, 0, 3)), is_dst=None) 14 | return midnight.astimezone(pytz.utc).time() 15 | 16 | 17 | def get_hora_update_groups(): 18 | tz = pytz.timezone("America/Argentina/Buenos_Aires") 19 | now = datetime.datetime.now(tz).date() 20 | midnight = tz.localize(datetime.datetime.combine(now, datetime.time(0, 1, 3)), is_dst=None) 21 | return midnight.astimezone(pytz.utc).time() 22 | -------------------------------------------------------------------------------- /campus.py: -------------------------------------------------------------------------------- 1 | import requests 2 | 3 | def is_campus_up(): 4 | 5 | response_threshold = 3 6 | timeout = 5 7 | url = "https://campus.exactas.uba.ar" 8 | 9 | try: 10 | response = requests.get(url, timeout=timeout) 11 | response_time = response.elapsed.total_seconds() 12 | response.raise_for_status() 13 | 14 | msg = "" 15 | if response_time > response_threshold: 16 | msg = "El campus pareciera estar andando medio lenteja :/" 17 | else: 18 | msg = "El campus pareciera estar andando :)" 19 | 20 | except requests.exceptions.Timeout: 21 | msg = "Tardó bocha y no recibí respuesta. Debe estar caído o andando lento :(" 22 | except requests.exceptions.ConnectionError: 23 | msg = "Hubo un error de conexión, debe estar caído. Espero no sea época de parciales..." 24 | except requests.exceptions.HTTPError: 25 | msg = f"Recibí una respuesta del campus con error. {response.status_code}: {response.reason}" 26 | except: 27 | msg = "Hubo un error y no tengo idea de qué fue. Rezale a Shannon." 28 | 29 | return msg 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Uriel Jonathan Rozenberg 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /handlers/update_groups.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | from time import sleep 4 | 5 | from tg_ids import DC_GROUP_CHATID 6 | 7 | from pony.orm import db_session, select 8 | from telegram import Update 9 | from telegram.ext import Updater, CallbackContext 10 | 11 | from models import Listable 12 | 13 | 14 | def update_group_url(context: CallbackContext, chat_id: str) -> (str, str, bool): 15 | try: 16 | url = context.bot.export_chat_invite_link(chat_id=chat_id) 17 | return chat_id, url, True # too GO-like huh? 18 | except: # TODO: filter excepts 19 | return None, None, False # too GO-like huh? 20 | 21 | 22 | def update_groups(context: CallbackContext): 23 | with db_session: 24 | chats = list(select((l.id, l.chat_id, l.name) for l in Listable if l.validated)) 25 | for id, (chat_id, url, validated), name in [(id,update_group_url(context, chat_id), name) for id, chat_id, name in 26 | chats]: 27 | sleep(1) 28 | if not validated: 29 | with db_session: 30 | Listable[id].validated = False 31 | context.bot.send_message(chat_id=DC_GROUP_CHATID, text=f"El grupo {name} murió 💀") 32 | else: 33 | with db_session: 34 | Listable[id].url = url 35 | 36 | 37 | def actualizar_grupos(update: Update, context: CallbackContext): 38 | update_groups(context) 39 | -------------------------------------------------------------------------------- /conciertos.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | from datetime import datetime 4 | from dataclasses import dataclass 5 | from robobrowser import RoboBrowser 6 | 7 | @dataclass 8 | class Concierto: 9 | titulo: str 10 | fecha: datetime.date 11 | 12 | @staticmethod 13 | def parse(evento): 14 | try: 15 | info = json.loads(evento.select("script")[0].text)[0] 16 | titulo = info["name"] 17 | fecha = info["startDate"].split("T")[0] 18 | 19 | fecha = datetime.strptime(fecha, "%Y-%m-%d").date() 20 | 21 | return Concierto( 22 | titulo, 23 | fecha, 24 | ) 25 | except: 26 | return None 27 | 28 | def fetch_conciertos(): 29 | browser = RoboBrowser(parser="html.parser") 30 | 31 | browser.open("https://www.songkick.com/venues/4514007-estadio-river-plate/calendar") 32 | 33 | conciertos = [] 34 | 35 | for el in browser.select(".microformat"): 36 | concierto = Concierto.parse(el) 37 | if concierto: 38 | conciertos.append(concierto) 39 | 40 | return conciertos 41 | 42 | def hay_concierto(dt: datetime): 43 | fecha = dt.date() 44 | 45 | conciertos = fetch_conciertos() 46 | 47 | for c in conciertos: 48 | if c.fecha != fecha: 49 | continue 50 | 51 | return True, c 52 | 53 | return False, None 54 | 55 | if __name__ == "__main__": 56 | print(hay_concierto(datetime.today())) 57 | -------------------------------------------------------------------------------- /models.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | 4 | from pony.orm import * 5 | import datetime 6 | 7 | db = Database() 8 | 9 | 10 | class Command(db.Entity): 11 | name = Required(str) 12 | description = Optional(str) 13 | enabled = Required(bool, default=True) 14 | 15 | 16 | class SentMessage(db.Entity): 17 | command = Required(str) 18 | chat_id = Required(int, size=64) 19 | message_id = Required(int, size=64) 20 | timestamp = Required(datetime.datetime, default=datetime.datetime.utcnow) 21 | 22 | 23 | class Listable(db.Entity): 24 | name = Required(str) 25 | url = Required(str) 26 | chat_id = Optional(str) 27 | validated = Required(bool, default=False) 28 | 29 | 30 | class Obligatoria(Listable): 31 | cubawiki_url = Optional(str) 32 | 33 | 34 | class Optativa(Listable): 35 | pass 36 | 37 | 38 | class ECI(Listable): 39 | pass 40 | 41 | 42 | class Otro(Listable): 43 | pass 44 | 45 | 46 | class Grupo(Listable): 47 | pass 48 | 49 | class GrupoOptativa(Listable): 50 | pass 51 | 52 | class GrupoOtros(Listable): 53 | pass 54 | 55 | # TODO: Subclasificar con validable 56 | class Noticia(db.Entity): 57 | text = Required(str) 58 | date = Required(datetime.date, default=datetime.date.today()) 59 | validado = Required(bool, default=True) 60 | 61 | 62 | class Noitip(db.Entity): 63 | text = Required(str) 64 | 65 | 66 | class AsmInstruction(db.Entity): 67 | mnemonic = Required(str) 68 | summary = Required(str) 69 | url = Required(str) 70 | 71 | 72 | class File(db.Entity): 73 | path = Required(str) 74 | file_id = Required(str) 75 | 76 | 77 | def init_db(path): 78 | db.bind('sqlite', path, create_db=True) 79 | db.generate_mapping(create_tables=True) 80 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | 49 | # Translations 50 | *.mo 51 | *.pot 52 | 53 | # Django stuff: 54 | *.log 55 | local_settings.py 56 | 57 | # Flask stuff: 58 | instance/ 59 | .webassets-cache 60 | 61 | # Scrapy stuff: 62 | .scrapy 63 | 64 | # Sphinx documentation 65 | docs/_build/ 66 | 67 | # PyBuilder 68 | target/ 69 | 70 | # Jupyter Notebook 71 | .ipynb_checkpoints 72 | 73 | # pyenv 74 | .python-version 75 | 76 | # celery beat schedule file 77 | celerybeat-schedule 78 | 79 | # SageMath parsed files 80 | *.sage.py 81 | 82 | # dotenv 83 | .env 84 | 85 | # virtualenv 86 | .venv 87 | venv/ 88 | ENV/ 89 | 90 | # Spyder project settings 91 | .spyderproject 92 | .spyproject 93 | 94 | # Rope project settings 95 | .ropeproject 96 | 97 | # mkdocs documentation 98 | /site 99 | 100 | # mypy 101 | .mypy_cache/ 102 | 103 | # idea 104 | .idea/ 105 | 106 | ########### 107 | logs.txt 108 | tokenz.py 109 | *.sqlite3 110 | -------------------------------------------------------------------------------- /deletablecommandhandler.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | 4 | from telegram.ext import CommandHandler 5 | from telegram.error import BadRequest 6 | from models import * 7 | import logging 8 | logger = logging.getLogger("DCUBABOT") 9 | 10 | 11 | class DeletableCommandHandler(CommandHandler): 12 | def _message_in_time_range(self, message): 13 | time_ellapsed = datetime.datetime.utcnow() - message.timestamp 14 | return time_ellapsed < datetime.timedelta(hours=24) 15 | 16 | def handle_update(self, update, dispatcher, check_result, context=None): 17 | #context.dispatcher = dispatcher 18 | context.sent_messages = [] 19 | super().handle_update(update, dispatcher, check_result, context) 20 | 21 | with db_session: 22 | # Delete previous messages sent with the command in the group 23 | for message in select(m for m in SentMessage if 24 | m.command == self.command[0] and 25 | m.chat_id == update.effective_chat.id): 26 | if self._message_in_time_range(message): 27 | try: 28 | context.bot.delete_message(chat_id=message.chat_id, 29 | message_id=message.message_id) 30 | except BadRequest as e: 31 | logger.info("Menssage already deleted, tabunn") 32 | message.delete() 33 | 34 | # Insert new sent messages for later delete (only in groups) 35 | for message in context.sent_messages: 36 | if message.chat.type != "private": 37 | SentMessage(command=self.command[0], chat_id=message.chat.id, 38 | message_id=message.message_id) 39 | -------------------------------------------------------------------------------- /river.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | from dataclasses import dataclass 3 | from robobrowser import RoboBrowser 4 | 5 | RIVER = "River Plate" 6 | UNSPECIFIED_TIMES = {"A confirmar", ""} 7 | 8 | @dataclass 9 | class Partido: 10 | equipo_local: str 11 | equipo_visitante: str 12 | copa: str 13 | fecha: datetime.date 14 | hora: datetime.time 15 | 16 | @property 17 | def es_local(self): 18 | return self.equipo_local == RIVER 19 | 20 | @staticmethod 21 | def parse(el): 22 | equipos = [e.strip() for e in el.select("b")[0].text.split("vs.")] 23 | line2 = el.select("p")[0].text 24 | copa, _, line2 = line2.partition(" • ") 25 | _fecha, _, line2 = line2.partition(" - ") 26 | _hora = line2 27 | 28 | if not RIVER in equipos: 29 | raise ValueError 30 | 31 | _dow, dmy = _fecha.split(" ") 32 | fecha = datetime.strptime(dmy, "%d/%m/%Y").date() 33 | 34 | hora = None 35 | if _hora not in UNSPECIFIED_TIMES: 36 | fmt = None 37 | if "." in _hora: 38 | fmt = "%H.%M" 39 | else: 40 | fmt = "%H" 41 | 42 | hora = datetime.strptime(_hora, fmt).time() 43 | 44 | return Partido( 45 | equipos[0], 46 | equipos[1], 47 | copa, 48 | fecha, 49 | hora, 50 | ) 51 | 52 | def fetch_partidos(): 53 | browser = RoboBrowser(parser="html.parser") 54 | browser.open("https://www.cariverplate.com.ar/calendario-de-partidos") 55 | 56 | partidos = [] 57 | 58 | for el in browser.select(".d_calendario"): 59 | partidos.append(Partido.parse(el)) 60 | 61 | return partidos 62 | 63 | def es_local(dt: datetime): 64 | fecha = dt.date() 65 | 66 | partidos = fetch_partidos() 67 | 68 | for p in partidos: 69 | if p.fecha != fecha: 70 | continue 71 | 72 | if not p.es_local: 73 | continue 74 | 75 | return True, p 76 | 77 | return False, None 78 | 79 | if __name__ == "__main__": 80 | print(es_local(datetime.today())) 81 | -------------------------------------------------------------------------------- /orga2Utils.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | 4 | # Local imports 5 | from models import * 6 | 7 | 8 | def noitip(update, context): 9 | with db_session: 10 | random_noitip = Noitip.select_random(1)[0].text 11 | msg = update.message.reply_text(random_noitip, quote=False) 12 | context.sent_messages.append(msg) 13 | 14 | 15 | def asm(update, context): 16 | if not context.args: 17 | msg = update.message.reply_text( 18 | "No me pasaste ninguna instrucción.", quote=False) 19 | context.sent_messages.append(msg) 20 | return 21 | 22 | mnemonic = " ".join(context.args).upper() 23 | with db_session: 24 | possibles = [i for i in list(AsmInstruction.select()) 25 | if levenshtein(mnemonic, i.mnemonic.upper()) < 2] 26 | if not possibles: 27 | msg = update.message.reply_text( 28 | "No pude encontrar esa instrucción.", quote=False) 29 | else: 30 | instr_match = [i for i in possibles if i.mnemonic.upper() == mnemonic] 31 | if instr_match: 32 | response_text = "" 33 | response_text += "\n".join(getasminfo(i) for i in instr_match) 34 | else: 35 | response_text = ("No pude encontrar esa instrucción.\n" 36 | "Quizás quisiste decir:\n") 37 | response_text += "\n".join(getasminfo(i) for i in possibles) 38 | msg = update.message.reply_text(response_text, quote=False) 39 | context.sent_messages.append(msg) 40 | 41 | 42 | def levenshtein(string1, string2): 43 | len1 = len(string1) + 1 44 | len2 = len(string2) + 1 45 | 46 | tbl = {} 47 | for i in range(len1): 48 | tbl[i, 0] = i 49 | for j in range(len2): 50 | tbl[0, j] = j 51 | for i in range(1, len1): 52 | for j in range(1, len2): 53 | cost = 0 if string1[i - 1] == string2[j - 1] else 1 54 | tbl[i, j] = min(tbl[i, j - 1] + 1, tbl[i - 1, j] + 55 | 1, tbl[i - 1, j - 1] + cost) 56 | 57 | return tbl[i, j] 58 | 59 | 60 | def getasminfo(instr): 61 | return '[%s] Descripción: %s.\nMás info: %s' % ( 62 | instr.mnemonic, 63 | instr.summary, 64 | instr.url) 65 | -------------------------------------------------------------------------------- /labos.py: -------------------------------------------------------------------------------- 1 | from icalevents import icaldownload, icalparser 2 | from datetime import datetime, timedelta 3 | from pytz import timezone 4 | 5 | 6 | def __calendar_url(id): 7 | return 'https://calendar.google.com/calendar/ical/' + str(id) + \ 8 | '%40group.calendar.google.com/public/basic.ics' 9 | 10 | 11 | urls = { 12 | 'Labo 1': __calendar_url('s57pjfhll16pqpu456eonvb760'), 13 | 'Labo 2': __calendar_url('bupjqrv2va0nb3bv3o1f1jqtc0'), 14 | 'Labo 3 (Graduados)': __calendar_url('n3sd0tpdt0o5855evq8uvsi3vo'), 15 | 'Labo 4': __calendar_url('4lbn08p17sv8s2pfqoophliag4'), 16 | 'Labo 5': __calendar_url('fu35kvoh4i2looi4drjf17k1ts'), 17 | 'Labo 6': __calendar_url('g3jdt9mmsrqllp7hqfotvttsck'), 18 | 'Labo 7 (Turing)': __calendar_url('krs8uvil4o36kd3a3ad5qs57dg') 19 | } 20 | 21 | calendars = { 22 | # 'name': (events: List[Event], loaded: Datetime, span: Timedelta, raw: Str) 23 | 'Labo 1': (None,), 24 | 'Labo 2': (None,), 25 | 'Labo 3 (Graduados)': (None,), 26 | 'Labo 4': (None,), 27 | 'Labo 5': (None,), 28 | 'Labo 6': (None,), 29 | 'Labo 7 (Turing)': (None,), 30 | } 31 | 32 | tz = timezone('America/Buenos_Aires') 33 | 34 | 35 | # Devuelve el momento actual con nuestra zona horaria. 36 | def aware_now(): 37 | return datetime.now(tz=tz) 38 | 39 | 40 | # Decide si vale la pena o no recargar un calendario. 41 | def should_reload(name): 42 | calendar = calendars[name] 43 | return aware_now() - timedelta(hours=12) > calendar[1] 44 | 45 | 46 | # Carga un calendario desde una url y lo guarda con la fecha de carga. 47 | def load_calendar(name, retries=3): 48 | url = urls[name] 49 | 50 | while retries > 0: 51 | try: 52 | now = aware_now() 53 | span = timedelta(weeks=4) 54 | calendar_raw = icaldownload.ICalDownload().data_from_url(url) 55 | events = icalparser.parse_events(calendar_raw, 56 | start=now - span, 57 | end=now + span) 58 | calendars[name] = (events, now, span, calendar_raw) 59 | return calendars[name] 60 | except Exception: 61 | retries -= 1 62 | 63 | # Debería levantar una excepción? 64 | return None 65 | 66 | 67 | # Dado el nombre de un calendario lo devuelve (y recarga si es necesario). 68 | def get_calendar(name): 69 | if calendars[name][0] is None: 70 | retries = 100 71 | elif should_reload(name): 72 | retries = 3 73 | else: 74 | retries = 0 75 | 76 | # Si load_calendar falló fallbackeo 77 | return load_calendar(name, retries) or calendars[name] 78 | 79 | 80 | # Repite el siguiente valor del generador, útil para ver si un generador está 81 | # vacío sin romperlo. 82 | def repeat_next(generator): 83 | empty = object() 84 | _next = next(generator, empty) 85 | 86 | if _next is empty: 87 | return 88 | 89 | yield _next 90 | yield _next 91 | yield from generator 92 | 93 | 94 | # Este sería el API que exponemos. 95 | # El datetime no puede ser naive. 96 | def events_at(time): 97 | for name in calendars: 98 | calendar = get_calendar(name) 99 | 100 | # Vemos si podemos usar los eventos en caché o tenemos que parsear raw 101 | if calendar[1] - calendar[2] <= time <= calendar[1] + calendar[2]: 102 | events_gen = (e for e in calendar[0] if e.start <= time <= e.end) 103 | else: 104 | events_gen = (e for e in icalparser.parse_events(calendar[3], 105 | start=time, 106 | end=time)) 107 | events = repeat_next(events_gen) 108 | 109 | if next(events, None) is None: 110 | yield '[%s] No tiene nada reservado' % name 111 | 112 | for event in events: 113 | yield '[%s] %s' % (name, event.summary) 114 | 115 | 116 | # Llamado periódicamente para forzar la actualización de los calendarios 117 | def update(context): 118 | for name in calendars: 119 | load_calendar(name) 120 | -------------------------------------------------------------------------------- /vencimientoFinales.py: -------------------------------------------------------------------------------- 1 | import re 2 | from abc import abstractmethod 3 | 4 | VERANO = "ver" 5 | PCUAT = "1c" 6 | SCUAT = "2c" 7 | INVIERNO = "inv" 8 | 9 | VERANOS = ["v", "ver", "verano"] 10 | INVIERNOS = ["i", "inv", "invierno"] 11 | 12 | PRIMEROS = VERANOS + [PCUAT] 13 | SEGUNDOS = INVIERNOS + [SCUAT] 14 | 15 | VENC_FEB_TXT = "Febrero/Marzo de " 16 | EXT_ABR_TXT = "Abril de " 17 | VENC_JUL_TXT = "Julio/Agosto de " 18 | EXT_SEP_TXT = "Septiembre de " 19 | 20 | ## CARGAR EXCEPCIONES 21 | # año : cuatrimestres de validez (default 8) 22 | EXCEPCIONES = { "2016": 12, 23 | "2017": 11, 24 | "2018": 10, 25 | "2019": 9} 26 | 27 | class Cursada(): 28 | @classmethod 29 | def nueva(self, cuatri, anio, validez): 30 | clase = next(sc for sc in self.__subclasses__() if sc.accepts(cuatri)) 31 | return clase(cuatri, anio, validez) 32 | 33 | def __init__(self, cuatri, anio, validez): 34 | self.anio = int(anio) 35 | self.cuatri = cuatri 36 | self.validez = validez 37 | self.set_vencimientos() 38 | 39 | def fecha_aprobacion(self): 40 | return f"{self.cuatri} de {self.anio}" 41 | 42 | class PrimerSemestre(Cursada): 43 | 44 | @classmethod 45 | def accepts(self, cuatri): 46 | return cuatri in PRIMEROS 47 | 48 | def set_vencimientos(self): 49 | self.anio_venc = self.anio + self.validez//2 50 | 51 | if self.validez % 2 == 0: 52 | self.fecha_vencimiento = VENC_FEB_TXT + str(self.anio_venc) 53 | self.fecha_extension = EXT_ABR_TXT + str(self.anio_venc) 54 | else: 55 | self.fecha_vencimiento = VENC_JUL_TXT + str(self.anio_venc) 56 | self.fecha_extension = EXT_SEP_TXT + str(self.anio_venc) 57 | 58 | class SegundoSemestre(Cursada): 59 | 60 | @classmethod 61 | def accepts(self, cuatri): 62 | return cuatri in SEGUNDOS 63 | 64 | def set_vencimientos(self): 65 | self.anio_venc = self.anio + self.validez//2 66 | 67 | if self.validez % 2 == 0: 68 | self.fecha_vencimiento = VENC_JUL_TXT + str(self.anio_venc) 69 | self.fecha_extension = EXT_SEP_TXT + str(self.anio_venc) 70 | else: 71 | self.anio_venc += 1 72 | self.fecha_vencimiento = VENC_FEB_TXT + str(self.anio_venc) 73 | self.fecha_extension = EXT_ABR_TXT + str(self.anio_venc) 74 | 75 | def parse_cuatri_y_anio(linea): 76 | # regex para parametros. 77 | r_entrada = r"^(?P[12]c|v(er(ano)?)?|i(nv(ierno)?)?)(?P20\d{2})$" 78 | entrada = re.search(r_entrada, linea) 79 | if not entrada: 80 | raise 81 | 82 | cuatri = entrada.group("cuatri") 83 | anio = entrada.group("anio") 84 | 85 | return cuatri, anio 86 | 87 | def calcular_vencimiento(cuatri, anio): 88 | 89 | # Unificar strings de verano/invierno 90 | cuatri = unificar_especiales(cuatri) 91 | 92 | txt_excepcion = "" 93 | cuatris_validez = 8 94 | 95 | if anio in EXCEPCIONES: 96 | cuatris_validez = EXCEPCIONES[anio] 97 | txt_excepcion = "\n\n*El vencimiento de tu cursada fue extendido por resolución* [1082/21](http://www.fcen.uba.ar/ConsejoDirectivo/index/ver_resolucion?url=2021-7-12) *del Consejo Directivo. Por esta razón la fecha que te muestra el bot es superior a los 8 cuatrimestres habituales, contando el cuatrimestre cursado.*\n\n" 98 | 99 | cursada = Cursada.nueva(cuatri, anio, cuatris_validez) 100 | 101 | mje = armar_texto(cursada, txt_excepcion) 102 | 103 | return mje 104 | 105 | def unificar_especiales(cuatri): 106 | if cuatri in VERANOS: 107 | cuatri = VERANO 108 | elif cuatri in INVIERNOS: 109 | cuatri = INVIERNO 110 | return cuatri 111 | 112 | def armar_texto(cursada, txt_excepcion): 113 | mje = f"""Materia aprobada en {cursada.fecha_aprobacion()}. 114 | 115 | Última fecha en la cual podés rendir: 116 | *{cursada.fecha_vencimiento}.* 117 | 118 | Fecha complementaria: 119 | *{cursada.fecha_extension}* \*. 120 | 121 | (\*) Depende de la aprobación del CD para ese año. Consultar en el Depto. de Estudiantes (Pab. 2).""" 122 | mje += txt_excepcion 123 | 124 | return mje 125 | -------------------------------------------------------------------------------- /install.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | 4 | 5 | from models import * 6 | 7 | 8 | @db_session 9 | def check_or_install_command(**kwargs): 10 | try: 11 | if not Command.get(name=kwargs["name"]): 12 | print(Command(**kwargs)) 13 | 14 | except Exception as e: 15 | print("elol", e) 16 | 17 | 18 | def install_check_or_install_commands(): 19 | 20 | with db_session: 21 | 22 | check_or_install_command(name="start") 23 | check_or_install_command( 24 | name="help", 25 | description="Muestra este mensaje horrible") 26 | 27 | check_or_install_command( 28 | name="estasvivo", 29 | description="Responde un mensaje corto para ver si el bot " 30 | "esta al día y activo") 31 | 32 | check_or_install_command( 33 | name="listar", 34 | description="Muestra todos los grupos de materias obligatorias " 35 | "conocidos por el bot") 36 | 37 | check_or_install_command( 38 | name="listaroptativa", 39 | description="Muestra todos los grupos de materias optativas " 40 | "conocidos por el bot") 41 | 42 | check_or_install_command( 43 | name="listareci", 44 | description="Muestra todos los grupos de cursos de la ECI") 45 | 46 | check_or_install_command( 47 | name="listarotro", 48 | description="Muestra todos los grupos relacionados a la gente de este grupo " 49 | "(algo así como off-topics)") 50 | 51 | check_or_install_command( 52 | name="listarlabos", 53 | description="Lista las reservaciones de los laboratorios de la facultad") 54 | 55 | check_or_install_command(name="cubawiki") 56 | 57 | check_or_install_command( 58 | name="noitip", 59 | description="Escuchar un tip de noit de orga2") 60 | 61 | check_or_install_command( 62 | name="asm", 63 | description="Información sobre una instrucción de Intel 64 o IA-32") 64 | 65 | check_or_install_command( 66 | name="agregargrupo", 67 | description="Sugiere este grupo a la lista de grupos") 68 | 69 | check_or_install_command( 70 | name="agregaroptativa", 71 | description="Sugiere este grupo a la lista de optativas") 72 | 73 | check_or_install_command( 74 | name="agregarotros", 75 | description="Sugiere este grupo a la lista de grupos off-topic") 76 | 77 | check_or_install_command( 78 | name="agregareci", 79 | description="Sugiere este grupo a la lista de grupos de la ECI") 80 | 81 | check_or_install_command( 82 | name="flan", 83 | description="Muestra el grafo de materias de la carrera " 84 | "con correlatividades") 85 | 86 | check_or_install_command( 87 | name="flanviejo", 88 | description="Muestra el grafo de materias del plan viejo " 89 | "de la carrera con correlatividades" 90 | ) 91 | 92 | check_or_install_command( 93 | name="aulas", 94 | description="Mostrar las aulas de cero mas infinito") 95 | 96 | check_or_install_command(name="sugerir") 97 | 98 | check_or_install_command(name="sugerirNoticia") 99 | 100 | check_or_install_command( 101 | name="checodepers", 102 | description="Envia un mensaje con tus consultas a los codepers" 103 | "para que elles se pongan en contacto con vos") 104 | 105 | check_or_install_command( 106 | name="checodeppers", 107 | description="Envia un mensaje con tus consultas a los codepers" 108 | "para que elles se pongan en contacto con vos") 109 | 110 | check_or_install_command( 111 | name="campusvivo", 112 | description="Envia un mensaje al campus para ver si está funcionando.") 113 | 114 | check_or_install_command( 115 | name="cuandovence", 116 | description="Dada la fecha de aprobación de TPs te dice la última fecha de final a la que te podés presentar.") 117 | 118 | check_or_install_command( 119 | name="colaborar", 120 | description="Devuelve el repositorio donde se desarrolla el DCUBABOT.") 121 | 122 | # Administration commands 123 | check_or_install_command(name="togglecommand") 124 | 125 | 126 | if __name__ == '__main__': 127 | db.bind('sqlite', "dcubabot.sqlite3", create_db=True) 128 | db.drop_table("Command", if_exists=True, with_all_data=True) 129 | db.generate_mapping(create_tables=True) 130 | install_check_or_install_commands() 131 | -------------------------------------------------------------------------------- /dcubabot.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | 4 | # STL imports 5 | import sys 6 | import logging 7 | import pytz 8 | import datetime 9 | import random 10 | 11 | # Non STL imports 12 | from telegram import InlineKeyboardButton, InlineKeyboardMarkup, ChatAction, ParseMode, Update 13 | from telegram.ext import ( 14 | Updater, Filters, MessageHandler, CallbackQueryHandler, CallbackContext, CommandHandler) 15 | from typing import Dict, Final 16 | 17 | # Local imports 18 | # from tokenz import * 19 | from handlers.update_groups import update_groups, actualizar_grupos 20 | from models import * 21 | from deletablecommandhandler import DeletableCommandHandler 22 | import labos 23 | import river 24 | import conciertos 25 | from campus import is_campus_up 26 | from utils.hora_feliz_dia import get_hora_feliz_dia, get_hora_update_groups 27 | from vencimientoFinales import calcular_vencimiento, parse_cuatri_y_anio 28 | from orga2Utils import noitip, asm 29 | from tg_ids import DC_GROUP_CHATID, ROZEN_CHATID, DGARRO_CHATID, CODEPERS_CHATID, NOTICIAS_CHATID 30 | 31 | # TODO:Move this out of here 32 | logging.basicConfig( 33 | level=logging.INFO, 34 | format='[%(asctime)s] - [%(name)s] - [%(levelname)s] - %(message)s', 35 | filename="bots.log") 36 | 37 | # Globals ...... yes, globals 38 | logger = logging.getLogger("DCUBABOT") 39 | admin_ids = [ROZEN_CHATID, DGARRO_CHATID] # @Rozen, @dgarro 40 | command_handlers = {} 41 | bsasTz = pytz.timezone("America/Argentina/Buenos_Aires") 42 | 43 | 44 | def error_callback(update, context): 45 | logger.exception(context.error) 46 | 47 | 48 | def start(update, context): 49 | msg = update.message.reply_text( 50 | "Hola, ¿qué tal? ¡Mandame /help si no sabés qué puedo hacer!", 51 | quote=False) 52 | context.sent_messages.append(msg) 53 | 54 | 55 | def help(update, context): 56 | message_text = "" 57 | with db_session: 58 | for command in select(c for c in Command 59 | if c.description and c.enabled).order_by(lambda c: c.name): 60 | message_text += "/" + command.name + " - " + command.description + "\n" 61 | msg = update.message.reply_text(message_text, quote=False) 62 | context.sent_messages.append(msg) 63 | 64 | 65 | def estasvivo(update, context): 66 | msg = update.message.reply_text("Sí, estoy vivo.", quote=False) 67 | context.sent_messages.append(msg) 68 | 69 | 70 | def list_buttons(update, context, listable_type): 71 | with db_session: 72 | buttons = select(l for l in listable_type if l.validated).order_by( 73 | lambda l: l.name) 74 | keyboard = [] 75 | columns = 3 76 | for k in range(0, len(buttons), columns): 77 | row = [InlineKeyboardButton( 78 | text=button.name, url=button.url, callback_data=button.url) 79 | for button in buttons[k:k + columns]] 80 | 81 | keyboard.append(row) 82 | reply_markup = InlineKeyboardMarkup(keyboard) 83 | msg = update.message.reply_text(text="Grupos: ", disable_web_page_preview=True, 84 | reply_markup=reply_markup, quote=False) 85 | context.sent_messages.append(msg) 86 | 87 | 88 | def listar(update, context): 89 | list_buttons(update, context, Grupo) 90 | 91 | 92 | def listaroptativa(update, context): 93 | list_buttons(update, context, GrupoOptativa) 94 | 95 | 96 | def listareci(update, context): 97 | list_buttons(update, context, ECI) 98 | 99 | 100 | def listarotro(update, context): 101 | list_buttons(update, context, GrupoOtros) 102 | 103 | 104 | def cubawiki(update, context): 105 | with db_session: 106 | group = select(o for o in Obligatoria if o.chat_id == update.message.chat.id and 107 | o.cubawiki_url is not None).first() 108 | if group: 109 | msg = update.message.reply_text(group.cubawiki_url, quote=False) 110 | context.sent_messages.append(msg) 111 | 112 | 113 | def log_message(update, context): 114 | user = str(update.message.from_user.id) 115 | chat = str(update.message.chat.id) 116 | # EAFP 117 | try: 118 | user_at_group = user + " @ " + update.message.chat.title 119 | except Exception: 120 | user_at_group = user 121 | user_at_group = f"{user_at_group}({chat})" 122 | logger.info(user_at_group + ": " + update.message.text) 123 | 124 | 125 | def felizdia_text(today): 126 | meses = ["Enero", "Febrero", "Marzo", "Abril", "Mayo", "Junio", "Julio", 127 | "Agosto", "Septiembre", "Octubre", "Noviembre", "Diciembre"] 128 | dia = str(today.day) 129 | mes = int(today.month) 130 | 131 | if mes == 3 and today.day == 8: 132 | return "Hoy es 8 de Marzo" 133 | else: 134 | mes = meses[mes - 1] 135 | return "Feliz " + dia + " de " + mes 136 | 137 | 138 | def felizdia(context): 139 | if uniform(0, 7) > 1: 140 | return 141 | today = datetime.date.today() 142 | chat_id = DC_GROUP_CHATID 143 | context.bot.send_message(chat_id=chat_id, text=felizdia_text(today)) 144 | 145 | 146 | def suggest_listable(update, context, listable_type): 147 | try: 148 | name, url = " ".join(context.args).split("|") 149 | if not (name and url): 150 | raise Exception("not userneim") 151 | except Exception: 152 | msg = update.message.reply_text("Hiciste algo mal, la idea es que pongas:\n" + 153 | update.message.text.split()[0] + 154 | " |", 155 | quote=False) 156 | context.sent_messages.append(msg) 157 | return 158 | with db_session: 159 | group = listable_type(name=name, url=url) 160 | keyboard = [ 161 | [ 162 | InlineKeyboardButton( 163 | text="Aceptar", callback_data=f"Listable|{group.id}|1"), 164 | InlineKeyboardButton( 165 | text="Rechazar", callback_data=f"Listable|{group.id}|0") 166 | ] 167 | ] 168 | reply_markup = InlineKeyboardMarkup(keyboard) 169 | context.bot.sendMessage(chat_id=ROZEN_CHATID, 170 | text=listable_type.__name__ + ": " + name + "\n" + url, 171 | reply_markup=reply_markup) 172 | msg = update.message.reply_text("OK, se lo mando a Rozen.", quote=False) 173 | context.sent_messages.append(msg) 174 | 175 | 176 | def sugerirgrupo(update, context): 177 | suggest_listable(update, context, Obligatoria) 178 | 179 | 180 | def sugeriroptativa(update, context): 181 | suggest_listable(update, context, Optativa) 182 | 183 | 184 | def sugerireci(update, context): 185 | suggest_listable(update, context, ECI) 186 | 187 | 188 | def sugerirotro(update, context): 189 | suggest_listable(update, context, Otro) 190 | 191 | 192 | def listarlabos(update, context): 193 | args = context.args 194 | mins = int(args[0]) if len(args) > 0 else 0 195 | instant = labos.aware_now() + datetime.timedelta(minutes=mins) 196 | respuesta = '\n'.join(labos.events_at(instant)) 197 | msg = update.message.reply_text(text=respuesta, quote=False) 198 | context.sent_messages.append(msg) 199 | 200 | 201 | def flan(update, context): 202 | responder_imagen(update, context, 'files/Plandeestudios.png') 203 | 204 | def flanviejo(update, context): 205 | responder_imagen(update, context, 'files/Plandeestudios-93.png') 206 | 207 | def aulas(update, context): 208 | responder_documento(update, context, 'files/0I-aulas.pdf') 209 | 210 | 211 | def togglecommand(update, context): 212 | if context.args and update.message.from_user.id in admin_ids: 213 | command_name = context.args[0] 214 | if command_name not in command_handlers: 215 | update.message.reply_text(text=f"No existe el comando /{command_name}.", 216 | quote=False) 217 | return 218 | with db_session: 219 | command = Command.get(name=command_name) 220 | command.enabled = not command.enabled 221 | if command.enabled: 222 | action = "activado" 223 | context.dispatcher.add_handler(command_handlers[command_name]) 224 | else: 225 | action = "desactivado" 226 | context.dispatcher.remove_handler( 227 | command_handlers[command_name]) 228 | update.message.reply_text(text=f"Comando /{command_name} {action}.", 229 | quote=False) 230 | 231 | 232 | def sugerir(update, context): 233 | update.message.reply_text( 234 | text=f"Ahora en mas las sugerencias las vamos a tomar en github:\n " 235 | "https://github.com/comcomUBA/dcubabot/issues", quote=False) 236 | 237 | 238 | def sugerirNoticia(update, context): 239 | user = update.message.from_user 240 | name = user.first_name # Agarro el nombre para ver quien fue 241 | # /sugerirNoticia 242 | texto = str.join(" ", context.args) 243 | try: 244 | # Esto es re cabeza pero no me acuerdo por que está asi 245 | if not (texto and isinstance(texto, str)): 246 | raise Exception 247 | except Exception: 248 | update.message.reply_text( 249 | text="Loc@, pusisiste algo mal, la idea es q pongas:\n " 250 | "/sugerirNoticia ") 251 | return 252 | try: 253 | with db_session: 254 | noticia = Noticia(text=texto) 255 | commit() # Hago el commmit para que tenga un id 256 | idNoticia = noticia.id 257 | keyboard = [ 258 | [ 259 | InlineKeyboardButton("Aceptar", callback_data="Noticia|" + 260 | str(noticia.id) + '|1'), 261 | InlineKeyboardButton( 262 | "Rechazar", callback_data="noticia|" + str(noticia.id) + '|0') 263 | ] 264 | ] 265 | reply_markup = InlineKeyboardMarkup(keyboard) 266 | context.bot.sendMessage(chat_id=ROZEN_CHATID, text=f"Noticia-{name}: {texto}", 267 | reply_markup=reply_markup, parse_mode=ParseMode.MARKDOWN) 268 | update.message.reply_text(text="Ok, se lo pregunto a Rozen") 269 | except Exception as inst: 270 | logger.exception(inst) 271 | 272 | 273 | # Manda una imagen a partir de su path al chat del update dado 274 | def mandar_imagen(chat_id, context, file_path): 275 | context.bot.sendChatAction(chat_id=chat_id, action=ChatAction.UPLOAD_PHOTO) 276 | with db_session: 277 | file = File.get(path=file_path) 278 | if file: 279 | msg = context.bot.send_photo( 280 | chat_id=chat_id, photo=file.file_id, allow_sending_without_reply=True) 281 | else: 282 | msg = context.bot.send_photo( 283 | chat_id=chat_id, photo=open(file_path, 'rb'), allow_sending_without_reply=True) 284 | with db_session: 285 | File(path=file_path, file_id=msg.photo[0].file_id) 286 | 287 | # context.sent_messages.append(msg) 288 | 289 | 290 | # Manda un documento a partir de su path al chat del update dado 291 | def mandar_pdf(chat_id, context, file_path): 292 | context.bot.sendChatAction( 293 | chat_id=chat_id, action=ChatAction.UPLOAD_DOCUMENT) 294 | with db_session: 295 | file = File.get(path=file_path) 296 | if file: 297 | msg = context.bot.send_document( 298 | chat_id=chat_id, document=file.file_id, allow_sending_without_reply=True) 299 | else: 300 | msg = context.bot.send_document( 301 | chat_id=chat_id, document=open(file_path, 'rb'), allow_sending_without_reply=True) 302 | with db_session: 303 | File(path=file_path, file_id=msg.document[0].file_id) 304 | 305 | 306 | # Responde una imagen a partir de su path al chat del update dado 307 | def responder_imagen(update, context, file_path): 308 | mandar_imagen(update.message.chat_id, context, file_path) 309 | 310 | 311 | ''' La funcion button se encarga de tomar todos los botones 312 | que se apreten en el bot (y que no sean links)''' 313 | 314 | 315 | # TODO: Posiblemente usar Double Dispatch para ver como 316 | # Cada Boton de validacion hace lo mismo o no 317 | 318 | # Responde un documento a partir de su path al chat del update dado 319 | def responder_documento(update, context, file_path): 320 | mandar_pdf(update.message.chat_id, context, file_path) 321 | 322 | 323 | def button(update, context): 324 | query = update.callback_query 325 | message = query.message 326 | buttonType, id, action = query.data.split("|") 327 | with db_session: 328 | if buttonType == "Listable": 329 | group = Listable[int(id)] 330 | if action == "1": 331 | group.validated = True 332 | action_text = "\n¡Aceptado!" 333 | else: 334 | group.delete() 335 | action_text = "\n¡Rechazado!" 336 | context.bot.editMessageText(chat_id=message.chat_id, 337 | message_id=message.message_id, 338 | text=message.text + action_text) 339 | if buttonType == "Noticia": 340 | noticia = Noticia[int(id)] 341 | if action == "1": 342 | noticia.validated = True 343 | action_text = "\n¡Aceptado!" 344 | context.bot.sendMessage(chat_id=NOTICIAS_CHATID, 345 | text=noticia.text, parse_mode=ParseMode.MARKDOWN) 346 | else: 347 | noticia.delete() 348 | action_text = "\n¡Rechazado!" 349 | context.bot.editMessageText(chat_id=message.chat_id, 350 | message_id=message.message_id, 351 | text=message.text + action_text) 352 | 353 | 354 | def actualizarPartidos(context): 355 | hoy = datetime.datetime.now() 356 | mañana = hoy + datetime.timedelta(days=1) 357 | local, partido = river.es_local(mañana) 358 | 359 | if not local: 360 | return 361 | 362 | def partido_msg(context): 363 | if partido.hora is None: 364 | horario = "hora a confirmar" 365 | else: 366 | horario = partido.hora.strftime("a las %H:%M") 367 | 368 | msg = f"Mañana juega River, {horario}" 369 | msg += f"\n(contra {partido.equipo_visitante}, {partido.copa})" 370 | 371 | context.bot.sendMessage(chat_id=NOTICIAS_CHATID, text=msg) 372 | 373 | # la hora local es UTC así que especificamos el timezone que corresponde para el aviso acá 374 | avisoHora = hoy.replace(hour=20, tzinfo=bsasTz) # 8pm argentina, buenos aires 375 | context.job_queue.run_once(callback=partido_msg, when=avisoHora) 376 | 377 | # para testearlo 378 | # partido_msg(context) 379 | 380 | def actualizarConciertos(context): 381 | hoy = datetime.datetime.now() 382 | mañana = hoy + datetime.timedelta(days=1) 383 | hay_concierto, concierto = conciertos.hay_concierto(mañana) 384 | 385 | if not hay_concierto: 386 | return 387 | 388 | def concierto_msg(context): 389 | msg = f"Mañana hay un concierto en River\n{concierto.titulo}" 390 | context.bot.sendMessage(chat_id=NOTICIAS_CHATID, text=msg) 391 | 392 | avisoHora = hoy.replace(hour=20, tzinfo=bsasTz) # 8pm argentina, buenos aires 393 | context.job_queue.run_once(callback=concierto_msg, when=avisoHora) 394 | 395 | def actualizarRiver(context): 396 | actualizarPartidos(context) 397 | actualizarConciertos(context) 398 | 399 | def add_all_handlers(dispatcher): 400 | descriptions = [] 401 | dispatcher.add_handler(MessageHandler( 402 | (Filters.text | Filters.command), log_message), group=1) 403 | with db_session: 404 | for command in select(c for c in Command): 405 | handler = DeletableCommandHandler( 406 | command.name, globals()[command.name]) 407 | command_handlers[command.name] = handler 408 | if command.enabled: 409 | dispatcher.add_handler(handler) 410 | if command.description: 411 | descriptions.append((command.name, command.description)) 412 | dispatcher.add_handler(CallbackQueryHandler(button)) 413 | print(descriptions) 414 | dispatcher.bot.set_my_commands(descriptions) 415 | 416 | 417 | def checodepers(update, context): 418 | if not context.args: 419 | ejemplo = """ Ejemplo de uso: 420 | /checodepers Hola, tengo un mensaje mucho muy importante que me gustaria que respondan 421 | """ 422 | msg = update.message.reply_text(ejemplo, quote=False) 423 | context.sent_messages.append(msg) 424 | return 425 | user = update.message.from_user 426 | try: 427 | if not user.username: 428 | raise Exception("not userneim") 429 | message = " ".join(context.args) 430 | context.bot.sendMessage( 431 | chat_id=CODEPERS_CHATID, text=f"{user.first_name}(@{user.username}) : {message}") 432 | except Exception: 433 | try: 434 | context.bot.forward_message( 435 | CODEPERS_CHATID, update.message.chat_id, update.message.message_id) 436 | logger.info(f"Malio sal {str(user)}") 437 | except Exception as e: 438 | update.message.reply_text( 439 | "La verdad me re rompí, avisale a roz asi ve que onda", quote=False) 440 | logger.error(e) 441 | return 442 | msg = update.message.reply_text( 443 | "OK, se lo mando a les codepers.", quote=False) 444 | context.sent_messages.append(msg) 445 | 446 | 447 | def checodeppers(update, context): 448 | checodepers(update, context) 449 | 450 | 451 | def campusvivo(update, context): 452 | msg = update.message.reply_text("Bancá que me fijo...", quote=False) 453 | 454 | campus_response_text = is_campus_up() 455 | 456 | context.bot.editMessageText(chat_id=msg.chat_id, 457 | message_id=msg.message_id, 458 | text=msg.text + "\n" + campus_response_text) 459 | 460 | context.sent_messages.append(msg) 461 | 462 | 463 | def cuandovence(update, context): 464 | ejemplo = "\nCuatris: 1c, 2c, i, inv, invierno, v, ver, verano.\nEjemplo: /cuandovence verano2010" 465 | if not context.args: 466 | ayuda = "Pasame cuatri y año en que aprobaste los TPs." + ejemplo 467 | msg = update.message.reply_text(ayuda, quote=False) 468 | context.sent_messages.append(msg) 469 | return 470 | try: 471 | linea_entrada = "".join(context.args).lower() 472 | cuatri, anio = parse_cuatri_y_anio(linea_entrada) 473 | except Exception: 474 | msg = update.message.reply_text( 475 | "¿Me pasás las cosas bien? Es cuatri+año." + ejemplo, quote=False) 476 | context.sent_messages.append(msg) 477 | return 478 | 479 | vencimiento = calcular_vencimiento(cuatri, anio) 480 | msg = update.message.reply_text( 481 | vencimiento, quote=False, parse_mode=ParseMode.MARKDOWN,disable_web_page_preview=True) 482 | context.sent_messages.append(msg) 483 | 484 | 485 | def colaborar(update, context): 486 | msg = update.message.reply_text( 487 | "Se puede colaborar con el DCUBA bot en https://github.com/comcomUBA/dcubabot", quote=False) 488 | context.sent_messages.append(msg) 489 | 490 | 491 | def agregar(update: Update, context: CallbackContext, grouptype, groupString): 492 | try: 493 | url = context.bot.export_chat_invite_link( 494 | chat_id=update.message.chat.id) 495 | name = update.message.chat.title 496 | chat_id = str(update.message.chat.id) 497 | except: # TODO: filter excepts 498 | update.message.reply_text( 499 | text=f"Mirá, no puedo hacerle un link a este grupo, proba haciendome admin", quote=False) 500 | return 501 | with db_session: 502 | group = grouptype.get(chat_id=chat_id) 503 | if group: 504 | group.url = url 505 | group.name = name 506 | update.message.reply_text( 507 | text=f"Datos del grupo actualizados", quote=False) 508 | return 509 | group = grouptype(name=name, url=url, chat_id=chat_id) 510 | keyboard = [ 511 | [ 512 | InlineKeyboardButton( 513 | text="Aceptar", callback_data=f"Listable|{group.id}|1"), 514 | InlineKeyboardButton( 515 | text="Rechazar", callback_data=f"Listable|{group.id}|0") 516 | ] 517 | ] 518 | reply_markup = InlineKeyboardMarkup(keyboard) 519 | context.bot.sendMessage(chat_id=ROZEN_CHATID, 520 | text=f"{groupString}: {name}\n{url}", 521 | reply_markup=reply_markup) 522 | msg = update.message.reply_text("OK, se lo mando a Rozen.", quote=False) 523 | context.sent_messages.append(msg) 524 | 525 | 526 | def agregargrupo(update: Update, context: CallbackContext): 527 | agregar(update, context, Grupo, "grupo") 528 | 529 | 530 | def agregaroptativa(update: Update, context: CallbackContext): 531 | agregar(update, context, GrupoOptativa, "optativa") 532 | 533 | 534 | def agregarotros(update: Update, context: CallbackContext): 535 | agregar(update, context, GrupoOtros, "otro") 536 | 537 | 538 | def agregareci(update: Update, context: CallbackContext): 539 | agregar(update, context, ECI, "eci") 540 | 541 | 542 | def main(): 543 | try: 544 | global update_id 545 | # Telegram bot Authorization Token 546 | botname = "DCUBABOT" 547 | print("Iniciando DCUBABOT") 548 | logger.info("Iniciando") 549 | random.seed() 550 | init_db("dcubabot.sqlite3") 551 | updater = Updater(token=token, use_context=True) 552 | dispatcher = updater.dispatcher 553 | 554 | updater.job_queue.run_daily( 555 | callback=felizdia, time=get_hora_feliz_dia()) 556 | updater.job_queue.run_daily( 557 | callback=update_groups, time=get_hora_update_groups()) 558 | 559 | updater.job_queue.run_once(callback=actualizarRiver, when=0) 560 | updater.job_queue.run_daily( 561 | callback=actualizarRiver, time=datetime.time()) 562 | 563 | # , Filters.user(user_id=137497264) )) 564 | dispatcher.add_handler(CommandHandler( 565 | "actualizar_grupos", actualizar_grupos)) 566 | 567 | updater.job_queue.run_repeating( 568 | callback=labos.update, interval=datetime.timedelta(hours=1)) 569 | dispatcher.add_error_handler(error_callback) 570 | add_all_handlers(dispatcher) 571 | # Start running the bot 572 | 573 | print([j for j in updater.job_queue.jobs()]) 574 | updater.start_polling(clean=True) 575 | except Exception as inst: 576 | logger.critical("ERROR AL INICIAR EL DCUBABOT") 577 | logger.exception(inst) 578 | 579 | 580 | if __name__ == '__main__': 581 | from tokenz import * 582 | 583 | main() 584 | --------------------------------------------------------------------------------