-
Notifications
You must be signed in to change notification settings - Fork 1
/
strings.py
361 lines (309 loc) · 14.4 KB
/
strings.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
"""
This module contains all string and some helper functions related to messaging.
"""
import types
from collections import OrderedDict
import scoring
from scoring import GOOD_KEY, BAD_KEY, ScoreObject
#####################
# MESSAGE CONSTANTS #
#####################
BUTTONS_ERROR_MSG = "Valitse yksi annetuista vaihtoehdoista."
RETURN_BUTTON_MESSAGE = "« Alkuun"
RETURN_MESSAGE = "alkuun"
GROUP_REPLY_MESSAGE = "Lähetä komentoja yksityisviestillä."
DID_NOT_UNDERSTAND_MESSAGE = "En ymmärrä. Käytä jotakin annetuista komennoista tai kokeile /help."
UNKNOWN_COMMAND_MESSAGE = "En ymmärrä komentoa. Käytä jotakin annetuista komennoista tai kokeile /help."
START_ADD_EVENT_MESSAGE = "Mihin kategoriaan haluat lisätä pisteitä?"
NOT_PARTICIPANT_MESSAGE = "Et ole minkään ilmoittautuneen joukkueen jäsen. Jos haluat vielä mukaan kilpailuun, ole yhteydessä käyttäjään @martong."
USER_HISTORY_MESSAGE = """
Viimeisimmät lisäämäsi tapahtumat:
{}"""
USER_HISTORY_COUNT_PROMPT = "\n\nPoista tapahtuma antamalla sen numero."
NO_USER_HISTORY_MESSAGE = "Et ole lisännyt tänään vielä yhtään tapahtumaa."
USER_HISTORY_COUNT_ERROR_MESSAGE = "Anna kokonaisluku välillä 1-{}"
ITEM_REMOVED_SUCCESS_MESSAGE = "Tapahtuma poistettiin onnistuneesti."
ALL_ITEMS_ADDED_FOR_TODAY_MESSAGE = """
Olet jo lisännyt kaikki mahdolliset tapahtumat eiliselle. Hienoa!
Voit poistaa lisäämiäsi tapahtumia komennolla /poista.
""" #TODO: good?
ADDING_MANY_START_MESSAGE = "Lisätään jäljellä olevat kategoriat eiliselle."
ADDING_MANY_FINISHED_MESSAGE = "Kaikki päivän tapahtumat on nyt lisätty." #TODO: good?
ADDING_MANY_CANCEL_MESSAGE = "Lopeta"
ADDING_MANY_CANCELING_MESSAGE = "Selvä."
ITEM_ALREADY_ADDED_FOR_TODAY_MESSAGE = """
Olet jo lisännyt tuon kategorian tänään. Voit muuttaa sitä poistamalla sen komennollla /poista ja lisäämällä sen uudelleen.
Kokeile jotakin toista kategoriaa.
"""
HELP_MESSAGE = """Hyvinvointibotin avulla voit syöttää pisteitä itsellesi ja joukkueellesi Hottiksen hyvinvointikilpailussa. Syöttääksesi pisteitä sinun pitää olla kilpailuun ilmoittautuneen joukkueen jäsen. Jos haluat osallistua kilpailuun jälkikäteen, ota yhteyttä käyttäjään @martong.
Pisteitä voi kerätä kategorioista liikunta, alkoholi, ruoka, vapaa-aika, stressi ja uni. Jokaiseen kategoriaan voi lisätä kerran päivässä pisteitä, ja annetut vastaukset tulisi antaa edellisen päivän perusteella. Eri vastauksista voit saada joko hyvinvointi-, tai pahoinvointipisteitä. Komennolla /lisaa voit lisätä yhteen kategoriaan pisteitä. Komennolla /lisaapaiva voit lisätä päivän pisteet kaikkiin kategorioihin. Komennolla /poista voit poistaa saman päivänä lisäämiäsi pisteitä.
Komennot:
/help - Tulosta ohje.
/lisaa - Lisää pisteitä yhteen kategoriaan.
/lisaapaiva - Lisää pisteitä kaikkiin kategorioihin kerralla.
/poista - Poista pisteitä.
/rank - Näytä parhaat joukkueet.
/info - Näytä tietoja sinusta ja joukkueestasi.
""" # TODO: format admin # TODO: add all implemented commands
#TODO: good?
RANK_MESSAGE = """
*Parhaat joukkueet:*
*Hyvinvointisarja:*
Sijoitus - joukkue (hyvinvointipisteet)
{}
*Pahoinvointisarja:*
Sijoitus - joukkue (pahoinvointipisteet)
{}
Pisteet päivitetty {}.
"""
COMPETITION_OVER_MESSAGE = """
Hyvinvointikilpailu on päättynyt. Kiitos osallistumisesta! Tulokset julkaistaan killan pikkujouluissa 13.11., joten kannattaa tulla paikalle!
Hyvinvointikisa järjestettiin tänä vuonna hyvin pitkän tauon jälkeen ja ensimmäistä kertaa Telegram-bottina. Kaikki ei varmastikaan mennyt ensimmäisellä iteraatiolla nappiin, joten toivoisimme palautetta osallistujilta, jotta ensi kerralla kilpailusta saataisiin entistä parempi! Palautteen antamiseen ei mene pitkään, eikä kaikkiin kysymyksiin tarvitse vastata.
Palautelomake löytyy [täältä](http://???).
"""
#TODO: good?
INFO_MESSAGE = """
Tietoja käyttäjästä {username}:
Joukkue: {team}
Joukkueen jäsenet: {team_members}
Joukkueen sijoitukset ja pisteet:
Hyvinvointi: {team_rank_good} / {n_teams} ({good_index:.2f})
Pahoinvointi: {team_rank_bad} / {n_teams} ({bad_index:.2f})
Viimeksi lisäämäsi tapahtumat:
{history_str}
"""
"""
The STRING_TREE object contains all possible chains of discussion. It is a dict
containing dicts. Each sub-dictionary corresponds to a message. Each dict
should have the following key-value pairs:
msg - the message the bot sends when at this point in the conversation
(the root node doesn't have this)
errorMessage - a message that the bot should send when the reply wasn't
sensible
Furthermore, each non-leaf node should have the attributes
children - multiple choices, which the user can select from using buttons
OR
child AND validation_func - in this case, the user is expected to input
free-form data, such as a number. this data should be validated using
the function validation_func, which should return either the validated
value (such as a number extracted from a string) or None.
If a node has neither of the above, it is assumed to be a leaf node, and the
conversation is terminated after it. A leaf node may have a key-value pair
score_func, which evaluates the score of a given conversation chain.
"""
STRING_TREE = {
#### root has no 'msg' (good idea?)
"msg": START_ADD_EVENT_MESSAGE,
"errorMessage" : BUTTONS_ERROR_MSG,
"children" : {
#TODO: change to OrderedDict?
"liikunta" : {
"msg" : """Asteikolla 0-5, kuinka intensiivistä liikuntaa harrastit eilen?
Intensiivisyyden tasoissa 1 on reipas kävely, 3 runsaasti hengästyttävä liikunta ja 5 full HD body attack.""",
"errorMessage" : "Anna vastaukseksi numero välillä 0-5.", #TODO: add 'peruuta kirjoittamalla alkuun' tms
"validation_func": scoring.liikunta_validate_intensity,
"child" : {
"msg" : """Kuinka kauan liikunta kesti tunteina? (max 12 h)
Eri aikoihin tehtyjä suorituksia voi summata yhteen, mutta yksittäisten suoritusten tulee olla kestoltaan vähintään 15 minuuttia.""",
"errorMessage" : "Korkein hyväksytty tuntimäärä on 12 tuntia.",
"validation_func": scoring.liikunta_validate_duration,
"child" : {
"msg" : "Jee jee!", #TODO: good?
"score_func": scoring.liikunta_score
}
}
},
"alkoholi" : {
"msg": """
Kuinka rankasti tuli dokattua eilen?
Voit tulkita tasoja esimerkiksi seuraavasti:
No blast - "Ehkä otin, ehkä en."
Medium blast - "Kun otan, niin juon."
Full blast - Tiedät, mitä tämä tarkoittaa.
Bläkäri - "Vain Bläkkisvuohi muistaa."
""",
"errorMessage": BUTTONS_ERROR_MSG,
"children": OrderedDict([
("ei ollenkaan!", {
"msg": "Hienoa!",
"score_func": lambda h: ScoreObject(0, GOOD_KEY, h),
}),
("no blast", {
"msg": "Toivottavasti pari lasillista rentoutti!",
"score_func": lambda h: ScoreObject(1, BAD_KEY, h),
}),
("medium blast", {
"msg": "Nää on näitä.",
"score_func": lambda h: ScoreObject(2, BAD_KEY, h),
}),
("full blast", {
"msg": "Hienosti. ",
"score_func": lambda h: ScoreObject(3, BAD_KEY, h),
}),
("bläkäri", {
"msg": "Tsemppiä tähän päivään!",
"score_func": lambda h: ScoreObject(4, BAD_KEY, h),
}),
]),
},
"ruoka" : {
"msg" : """
Miten hyvin söit eilen?
Voit tulkita vaihtoehtoja seuraavasti:
Tavallista paremmin - Laitoit esimerkiksi itse ruokaa tai muuten kiinnitit huomiota aterioihisi.
Normipäivä - Sinulle tyypilliset ruokailutottumukset.
Huonosti - Nälkä yllätti pahasti tai tuli mässäiltyä.
""",
"errorMessage" : BUTTONS_ERROR_MSG,
"children" : OrderedDict([
("tavallista paremmin", {
"msg" : "Hienoa! Jatka samaan malliin.", # TODO
"score_func" : lambda h: ScoreObject(1, GOOD_KEY, h),
}),
("normipäivä", {
"msg" : "Selvä homma.", #TODO
"score_func" : lambda h: ScoreObject(0.1, GOOD_KEY, h),
}),
("huonosti", {
"msg" : "Sellaista sattuu!", #TODO
"score_func" : lambda h: ScoreObject(1, BAD_KEY, h),
}),
]),
},
"vapaa-aika" : {
"msg" : """
Kuinka paljon vietit vapaa-aikaa eilen?
Voit tulkita vaihtoehtoja seuraavasti:
Runsaasti - Vapaa-aikaa oli tavallista enemmän ja se oli rentouttavaa.
Sopivasti - Vapaa-aikaa oli sen verran kuin itse toivot jokaiselle päivälle.
En riittävästi - Vapaa-aikaa ei ollut tarpeeksi, jotta päivän stressi purkautuisi.
""",
"errorMessage" : BUTTONS_ERROR_MSG,
"children" : OrderedDict([
("runsaasti", {
"msg" : "Mahtavaa!",
"score_func" : lambda h: ScoreObject(1, GOOD_KEY, h),
}),
("sopivasti", {
"msg" : "Selvä homma.",
"score_func" : lambda h: ScoreObject(0.2, GOOD_KEY, h),
}),
("en riittävästi", {
"msg" : "Muista ottaa aikaa myös itsellesi!",
"score_func" : lambda h: ScoreObject(1, BAD_KEY, h),
}),
]),
},
"stressi" : {
"msg" : "Kuinka stressaantunut olit eilen?",
"errorMessage" : BUTTONS_ERROR_MSG,
"children" : OrderedDict([
("paljon", {
"msg" : "Voi ei! Jos stressi jatkuu pitkään, pyri vähentämään stressiä aiheuttavia asioita.",
"score_func" : lambda h: ScoreObject(2, BAD_KEY, h),
}),
("vähän", {
"msg" : "Toivottavasti stressitasot pysyvät kohtuullisina!",
"score_func" : lambda h: ScoreObject(1, BAD_KEY, h),
}),
("en lainkaan", {
"msg" : "Mahtavaa!",
"score_func" : lambda h: ScoreObject(0.1, GOOD_KEY, h),
}),
]),
},
"uni" : {
"msg" : """Kuinka hyvin nukuit viime yönä?
Voit tulkita vastausvaihtoehtoja esimerkiksi seuraavasti:
Tosi hyvin - yli kahdeksan tuntia tai poikkeuksellisen laadukasta unta
Riittävästi - 7-8 tuntia tai muuten sinulle riittoisat yöunet
Melko huonosti - alle 7 tuntia tai muuten huonolaatuiset yöunet
Todella huonosti - alle 5 tuntia tai poikkeuksellisen huonolaatuiset yöunet
""",
"errorMessage" : BUTTONS_ERROR_MSG,
"children" : OrderedDict([
("tosi hyvin", {
"msg" : "Mahtavaa!",
"score_func" : lambda h: ScoreObject(1, GOOD_KEY, h),
}),
("riittävästi", {
"msg" : "Kiva!",
"score_func" : lambda h: ScoreObject(0.2, GOOD_KEY, h),
}),
("melko huonosti", {
"msg" : "Voi ei! Koita pian lyhentää univelkaa.",
"score_func" : lambda h: ScoreObject(0.5, BAD_KEY, h,)
}),
("todella huonosti", {
"msg" : "Voi ei! Koita pian lyhentää univelkaa.",
"score_func" : lambda h: ScoreObject(1, BAD_KEY, h),
}),
]),
},
},
"root": True
}
def verifyTree(tree, verbose = False):
"""
recursively traverse the message tree and check that each node has certain attributes
"""
if tree != STRING_TREE:
# every other node than the root should have 'msg'
assert "msg" in tree, tree
if "score_func" in tree:
assert type(tree["score_func"]) == types.FunctionType
if "children" in tree:
assert "errorMessage" in tree, tree
for childName, child in tree["children"].items():
assert childName == childName.lower(), childName
verifyTree(child, verbose)
elif "child" in tree:
assert "errorMessage" in tree, tree
assert "validation_func" in tree, tree
verifyTree(tree["child"], verbose)
else:
if verbose:
print("verifyTree(): found leaf node: {}".format(tree))
assert "score_func" in tree, "no score function found for leaf {}".format(tree)
verifyTree(STRING_TREE)
# this error is thrown if a message is invalid in a given context
class InvalidMessageError(Exception): pass
class StringTreeParser():
def __init__(self):
self.root = STRING_TREE
self.current_message = STRING_TREE
def goForward(self, message_str):
"""
Attempt to advance the conversation based on message_str. If the
message is invalid, raise ValueError so the caller can send the
errorMessage of the current message.
Returns a pair (next_message, validated_value), where next_message is
the subtree dict of the rest of the conversation and validated_value is
the validated value of message_str (such as a number), if applicable,
or None.
"""
next_message = None
validated_value = None
node = self.current_message
if "children" in node:
children = node["children"]
if message_str not in children:
raise InvalidMessageError("invalid button")
else:
self.current_message = children[message_str]
next_message = self.current_message
elif "child" in node:
# validate message
validated_value = node["validation_func"](message_str)
if validated_value is None:
raise InvalidMessageError("invalid value")
else:
self.current_message = node["child"]
next_message = self.current_message
return (next_message, validated_value)
def reset(self):
self.current_message = self.root
def get_categories(self):
return list(self.root["children"].keys())
def is_at_root(self):
return self.current_message == self.root