Skip to content

Commit 49393b7

Browse files
authored
Merge branch 'master' into ticket-69
2 parents d33b148 + 01c1c7e commit 49393b7

File tree

19 files changed

+948
-90
lines changed

19 files changed

+948
-90
lines changed

.github/workflows/pythonapp.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ on:
99
pull_request:
1010
branches: [ master ]
1111

12+
env:
13+
TOKEN: foo
14+
1215
jobs:
1316
build:
1417

README.md

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,12 @@ pip install -e .
3030

3131
y estas listo para trabajar.
3232

33+
`pip install freezegun` para correr los tests.
34+
35+
### Python 3.12
36+
37+
`pip install setuptools`
38+
3339
## Testeo
3440

3541
Para correr el bot ejecutá (desde el virtualenv):
@@ -58,9 +64,8 @@ En este momento ya se puede hablar con el bot. ¿Qué le digo?
5864

5965
* `/su <password>` para reclamar permisos de admin, reemplazando `<password>` por la contraseña que hayamos
6066
elegido en la envvar `PYCAMP_BOT_MASTER_KEY`
61-
* `/agregar_pycamp <pycamp_name>` para crear un pycamp en la deb
67+
* `/empezar_pycamp <pycamp_name>` inicia el flujo de creación de un pycamp. Lo carga en la db, pide fecha de inicio y duración. Lo deja activo.
6268
* `/activar_pycamp <pycamp_name>` activa un pycamp
63-
* `/empezar_pycamp` setea la fecha de inicio del pycamp activo
6469
* `/empezar_carga_proyectos` habilita la carga de los proyectos. En este punto los pycampistas pueden cargar sus proyectos,
6570
enviandole al bot el comando `/cargar_proyecto`
6671
* `/terminar_carga_proyectos` termina carga proyectos
@@ -72,8 +77,22 @@ Para generar el schedule:
7277
* `/cronogramear` te va a preguntar cuantos dias queres cronogramear y cuantos slots por dia tenes y hacer el cronograma.
7378
* `/cambiar_slot` toma un nombre de proyecto y un slot; y te cambia ese proyecto a ese slot.
7479

80+
Para agendar los magos:
81+
82+
1. Todos los candidatos tienen que haberse registrado con `/ser_magx`
83+
2. Tiene que estar creado el schedule de presentaciones de proyectos (`/cronogramear`)
84+
85+
* `/agendar_magx` Asigna un mago por hora durante todo el PyCamp.
86+
* De 9 a 13 y de 14 a 19.
87+
* El primer día arranca después del almuerzo (14hs).
88+
* El último día termina al almuerzo (13hs).
89+
7590
### Flujo pycampista
7691

7792
* `/cargar_proyecto` carga un proyecto (si está habilitada la carga)
7893
* `/votar` envia opciones para votar (si está habilitada la votacion)
7994
* `/ver_cronograma` te muestra el cronograma!
95+
* `/ser_magx` te registra como mago.
96+
* `/ver_magx` Lista los magos registrados.
97+
* `/evocar_magx` llama al mago de turno para pedirle ayuda.
98+
* `/ver_agenda_magx completa` te muestra la agenda de magos del PyCamp. El parámetro `completa` es opcional, si se omite solo muestra los turnos pendientes.
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# https://docs.peewee-orm.com/en/latest/peewee/playhouse.html#schema-migrations
2+
3+
from datetime import datetime, timedelta
4+
from playhouse.migrate import *
5+
import peewee as pw
6+
7+
from pycamp_bot.models import Pycampista, Slot, Pycamp
8+
9+
10+
my_db = pw.SqliteDatabase('pycamp_projects.db')
11+
migrator = SqliteMigrator(my_db)
12+
13+
from pycamp_bot.models import Pycamp
14+
15+
16+
migrate(
17+
migrator.add_column( # wizard_slot_duration = pw.IntegerField(default=60, null=False)
18+
Pycamp._meta.table_name,
19+
'wizard_slot_duration',
20+
Pycamp.wizard_slot_duration
21+
),
22+
migrator.add_column( # current_wizard = pw.ForeignKeyField(Pycampista)
23+
Slot._meta.table_name,
24+
'current_wizard_id',
25+
Slot.current_wizard
26+
),
27+
)
28+
29+
p = Pycamp.get()
30+
p.end = datetime(2024,6,23,23,59,59,99)
31+
p.end = datetime(2024,6,23,23,59,59,999999)
32+
p.save()
33+

setup.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
'munch==2.5.0',
1616
'python-telegram-bot==20.2',
1717
'peewee==3.16.0',
18+
'pytest==8.2.2',
19+
'freezegun==1.5.1',
1820
],
1921
test_suite='tests'
2022
)

src/pycamp_bot/commands/announcements.py

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from pycamp_bot.commands.auth import get_admins_username
55
from pycamp_bot.logger import logger
66
from pycamp_bot.commands.manage_pycamp import active_needed
7+
from pycamp_bot.utils import escape_markdown
78

89
PROYECTO, LUGAR, MENSAJE = ["proyecto", "lugar", "mensaje"]
910

@@ -78,8 +79,8 @@ async def announce(update: Update, context: CallbackContext) -> str:
7879
if len(_projects) == 0:
7980
await context.bot.send_message(
8081
chat_id=update.message.chat_id,
81-
text=f"No existe el proyecto: *{state.p_name}*.",
82-
parse_mode='Markdown'
82+
text=f"No existe el proyecto: *{escape_markdown(state.p_name)}*.",
83+
parse_mode='MarkdownV2'
8384
)
8485
return ConversationHandler.END
8586
elif not await should_be_able_to_announce(state.username, _projects[0]):
@@ -92,8 +93,8 @@ async def announce(update: Update, context: CallbackContext) -> str:
9293
else:
9394
await context.bot.send_message(
9495
chat_id=update.message.chat_id,
95-
text=f"Anunciando el proyecto: *{_projects[0].name.capitalize()}* !!!",
96-
parse_mode='Markdown'
96+
text=f"Anunciando el proyecto: *{escape_markdown(_projects[0].name).capitalize()}* !!!",
97+
parse_mode='MarkdownV2'
9798
)
9899
state.owner = _projects[0].owner.username
99100
state.current_project = _projects[0]
@@ -184,20 +185,20 @@ async def message_project(update: Update, context: CallbackContext) -> str:
184185
try:
185186
await context.bot.send_message(
186187
chat_id=chat_id,
187-
text=f'''Está por empezar el proyecto *"{(state.p_name).capitalize()}"* a cargo de *@{state.owner}*.\n*¿Dónde?* 👉🏼 {state.lugar}''',
188-
parse_mode='Markdown'
188+
text=f'''Está por empezar el proyecto *"{escape_markdown(state.p_name).capitalize()}"* a cargo de *@{escape_markdown(state.owner)}*.\n*¿Dónde?* 👉🏼 {escape_markdown(state.lugar)}''',
189+
parse_mode='MarkdownV2'
189190
)
190191
if update.message.from_user.username == state.owner:
191192
await context.bot.send_message(
192193
chat_id=chat_id,
193-
text=f'*Project Owner says:* **{state.mensaje}**',
194-
parse_mode='Markdown'
194+
text=f'*Project Owner says:* **{escape_markdown(state.mensaje)}**',
195+
parse_mode='MarkdownV2'
195196
)
196197
else:
197198
await context.bot.send_message(
198199
chat_id=chat_id,
199-
text=f'Admin *@{update.message.from_user.username}* says: **{state.mensaje}**',
200-
parse_mode='Markdown'
200+
text=f'Admin *@{escape_markdown(update.message.from_user.username)}* says: **{escape_markdown(state.mensaje)}**',
201+
parse_mode='MarkdownV2'
201202
)
202203
except Exception as e:
203204
logger.error(f"Error al enviar el mensaje: {e}")

src/pycamp_bot/commands/auth.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@ def is_admin(update, context):
2828

2929
def admin_needed(f):
3030
async def wrap(*args, **kargs):
31-
logger.info('Admin nedeed wrapper')
3231
update, context = args
3332
if is_admin(*args):
3433
return await f(*args)

src/pycamp_bot/commands/help_msg.py

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
/pycamps: lista todos los pycamps.
88
/cargar_proyecto: empieza la conversacion de carga de proyecto.
99
/proyectos: te muestra la informacion de todos los proyectos y sus responsables.
10+
/mis_proyectos: te muestra día y horario de los proyectos que votaste.
1011
/ser_magx: te agrega la lista de Magx.
1112
/evocar_magx: pingea a la/el Magx de turno, informando que necesitas su\
1213
ayuda. Con un gran poder, viene una gran responsabilidad.
@@ -19,26 +20,26 @@
1920
/ayuda: esta ayuda.'''
2021

2122
HELP_MESSAGE = '''
22-
Este bot facilita la carga, administración y procesamiento de\
23+
Este bot facilita la carga, administración y procesamiento de \
2324
proyectos y votos durante el PyCamp
2425
2526
El proceso se divide en 3 etapas:
2627
27-
*Primera etapa*: Lxs responsables de los proyectos cargan sus proyectos\
28-
mediante el comando **/cargar_proyecto**. Solo un responsable carga el\
29-
proyecto, y luego si hay otrxs responsables adicionales, pueden\
28+
*Primera etapa*: Lxs responsables de los proyectos cargan sus proyectos \
29+
mediante el comando **/cargar_proyecto**. Solo un responsable carga el \
30+
proyecto, y luego si hay otrxs responsables adicionales, pueden \
3031
agregarse con el comando /ownear.
3132
32-
*Segunda etapa*: Mediante el comando **/elegir_proyectos** todxs lxs participantes\
33-
seleccionan los proyectos que se expongan. Esto se puede hacer a medida que\
34-
se expone, o al haber finalizado todas las exposiciones. Si no se está\
35-
segurx de un proyecto, conviene no seleccionar nada, ya que luego podés\
36-
volver a ejecutar el comando y darle que si aquellas cosas que no tocaste. NO\
33+
*Segunda etapa*: Mediante el comando **/elegir_proyectos** todxs lxs participantes \
34+
seleccionan los proyectos que se expongan. Esto se puede hacer a medida que \
35+
se expone, o al haber finalizado todas las exposiciones. Si no se está \
36+
segurx de un proyecto, conviene no seleccionar nada, ya que luego podés \
37+
volver a ejecutar el comando y darle que si aquellas cosas que no tocaste. NO \
3738
SE PUEDE CAMBIAR TU RESPUESTA UNA VEZ HECHO.
3839
39-
*Tercera etapa*: Lxs admins mergean los proyectos que se haya decidido\
40-
mergear durante las exposiciones (Por tematica similar, u otros\
41-
motivos), y luego se procesan los datos para obtener el cronograma\
40+
*Tercera etapa*: Lxs admins mergean los proyectos que se haya decidido \
41+
mergear durante las exposiciones (Por tematica similar, u otros \
42+
motivos), y luego se procesan los datos para obtener el cronograma \
4243
final.
4344
4445
''' + user_commands_help
@@ -49,15 +50,15 @@
4950
Pycamp
5051
------
5152
/agregar_pycamp (pycamp): Agrega un pycamp.
52-
/activar_pycamp (pycamp): Setea un pycamp como activo (si ya hay uno activo lo\
53+
/activar_pycamp (pycamp): Setea un pycamp como activo (si ya hay uno activo lo \
5354
desactiva).
5455
/empezar_carga_proyectos: Habilita la carga de proyectos en el pycamp activo.
5556
/terminar_carga_proyectos: Deshabilita la carga de proyectos en el pycamp activo.
5657
/empezar_seleccion_proyectos: Habilita la seleccion sobre los proyectos del pycamp activo.
5758
/terminar_seleccion_proyectos: Deshabilita la seleccion sobre los proyectos del pycamp activo.
58-
/empezar_pycamp: Setea el tiempo de inicio del pycamp activo.\
59+
/empezar_pycamp: Setea el tiempo de inicio del pycamp activo. \
5960
Por default usa datetime.now()
60-
/terminar_pycamp: Setea el timepo de fin del pycamp activo.\
61+
/terminar_pycamp: Setea el timepo de fin del pycamp activo. \
6162
Por default usa datetime.now()
6263
/cronogramear: Te pregunta cuantos dias y que slot tiene tu pycamp \
6364
y genera el cronograma.

src/pycamp_bot/commands/manage_pycamp.py

Lines changed: 90 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,16 @@
11
import datetime
2-
from telegram.ext import CommandHandler
2+
from telegram.ext import ConversationHandler, CommandHandler, MessageHandler, filters
33
from pycamp_bot.models import Pycamp
44
from pycamp_bot.models import Pycampista
55
from pycamp_bot.models import PycampistaAtPycamp
66
from pycamp_bot.commands.auth import admin_needed
77
from pycamp_bot.logger import logger
8+
from pycamp_bot.utils import escape_markdown
9+
10+
11+
SET_DATE_STATE = "set_fate"
12+
SET_DURATION_STATE = "set_duration"
13+
WRAP_UP_STATE = "wrap_up"
814

915

1016
def get_pycamp_by_name(name):
@@ -68,36 +74,101 @@ async def set_active_pycamp(update, context):
6874

6975
@admin_needed
7076
async def add_pycamp(update, context):
71-
parameters = update.message.text.split(' ')
72-
if not len(parameters) == 2:
77+
parameters = update.message.text.split(' ', 1)
78+
if len(parameters) < 2:
7379
await context.bot.send_message(
7480
chat_id=update.message.chat_id,
75-
text="El comando necesita un parametro (pycamp name)")
81+
text="El comando necesita un parametro (headquarters)")
82+
return
83+
hq = parameters[1].strip()
84+
if not hq:
85+
await context.bot.send_message(
86+
chat_id=update.message.chat_id,
87+
text="El parámetro headquarters no puede ser vacío")
7688
return
7789

78-
pycamp = Pycamp.get_or_create(headquarters=parameters[1])[0]
90+
pycamp = Pycamp.get_or_create(headquarters=hq, active=True)[0]
91+
pycamp.set_as_only_active()
92+
logger.info('Creado: {}'.format(pycamp))
7993

94+
msg = "El Pycamp {} fue creado.\n¿Cuándo empieza? (formato yyyy-mm-dd)"
8095
await context.bot.send_message(
8196
chat_id=update.message.chat_id,
82-
text="El Pycamp {} fue creado.".format(pycamp.headquarters))
97+
text=msg.format(pycamp.headquarters)
98+
)
99+
return SET_DATE_STATE
83100

84101

85-
@active_needed
86-
@admin_needed
87-
async def start_pycamp(update, context):
88-
parameters = update.message.text.split(' ')
89-
if len(parameters) == 2:
90-
date = datetime.datetime.fromisoformat(parameters[1])
91-
else:
92-
date = datetime.datetime.now()
102+
async def define_start_date(update, context):
103+
text = update.message.text
104+
try:
105+
start_date = datetime.datetime.fromisoformat(text)
106+
except ValueError:
107+
await context.bot.send_message(
108+
chat_id=update.message.chat_id,
109+
text="mmm no entiendo esa fecha\. El formato esperado es `yyyy-mm-dd`\. ¿De nuevo?",
110+
parse_mode="MarkdownV2"
111+
)
112+
return SET_DATE_STATE
113+
114+
_, pycamp = get_active_pycamp()
115+
pycamp.init = start_date
116+
pycamp.save()
93117

94-
is_active, pycamp = get_active_pycamp()
95-
pycamp.init = date
118+
await context.bot.send_message(
119+
chat_id=update.message.chat_id,
120+
text="¿Cuantos días dura el PyCamp?"
121+
)
122+
return SET_DURATION_STATE
123+
124+
125+
async def define_duration(update, context):
126+
text = update.message.text.strip()
127+
try:
128+
duration = int(text)
129+
except ValueError:
130+
await context.bot.send_message(
131+
chat_id=update.message.chat_id,
132+
text="mmm no entiendo. Poné un número entero porfa."
133+
)
134+
return SET_DURATION_STATE
135+
136+
_, pycamp = get_active_pycamp()
137+
pycamp.end = pycamp.init + datetime.timedelta(
138+
days=duration - 1,
139+
hours=23,
140+
minutes=59,
141+
seconds=59,
142+
milliseconds=99
143+
)
96144
pycamp.save()
97145

146+
msg = "Listo, el PyCamp '{}' está activo, desde el {} hasta el {}".format(
147+
pycamp.headquarters,
148+
pycamp.init.date(),
149+
pycamp.end.date()
150+
)
151+
await context.bot.send_message(
152+
chat_id=update.message.chat_id,
153+
text=msg
154+
)
155+
156+
157+
async def cancel(update, context):
98158
await context.bot.send_message(
99159
chat_id=update.message.chat_id,
100-
text="Empezó Pycamp :) ! {}".format(date))
160+
text="Se canceló la carga del PyCamp...")
161+
return ConversationHandler.END
162+
163+
164+
load_start_pycamp = ConversationHandler(
165+
entry_points=[CommandHandler('empezar_pycamp', add_pycamp)],
166+
states={
167+
SET_DATE_STATE: [MessageHandler(filters.TEXT, define_start_date)],
168+
SET_DURATION_STATE: [MessageHandler(filters.TEXT, define_duration)]
169+
},
170+
fallbacks=[CommandHandler('cancel', cancel)]
171+
)
101172

102173

103174
@active_needed
@@ -110,6 +181,7 @@ async def end_pycamp(update, context):
110181
date = datetime.datetime.now()
111182

112183
is_active, pycamp = get_active_pycamp()
184+
pycamp.active = False
113185
pycamp.end = date
114186
pycamp.save()
115187

@@ -162,19 +234,14 @@ async def list_pycampistas(update, context):
162234

163235

164236
def set_handlers(application):
165-
application.add_handler(
166-
CommandHandler('empezar_pycamp', start_pycamp))
237+
application.add_handler(load_start_pycamp)
167238
application.add_handler(
168239
CommandHandler('terminar_pycamp', end_pycamp))
169240
application.add_handler(
170241
CommandHandler('activar_pycamp', set_active_pycamp))
171-
application.add_handler(
172-
CommandHandler('agregar_pycamp', add_pycamp))
173242
application.add_handler(
174243
CommandHandler('pycamps', list_pycamps))
175244
application.add_handler(
176245
CommandHandler('voy_al_pycamp', add_pycampista_to_pycamp))
177246
application.add_handler(
178247
CommandHandler('pycampistas', list_pycampistas))
179-
180-

0 commit comments

Comments
 (0)