]> gitweb.pimeys.fr Git - today.git/blob - today.py
def exists ? U know, any is implemented in python -_-
[today.git] / today.py
1 #!/usr/bin/python
2 # -*- encoding: utf-8 -*-
3
4 """ Codé par 20-100
5 script qui affiche des trucs à penser, des J-n des conneries
6 or that kind of stuff"""
7
8 import time, datetime
9 import re
10 import os
11 import sys
12 import subprocess
13 import json
14 os.chdir('/home/vincent/scripts/today/')
15
16
17 class Config(object):
18 """Configuration (pas de couleurs si on n'output pas dans un terminal"""
19 def __init__(self, color=True):
20 if color:
21 self.endcolor = u"\e[0m"
22 self.blue = u"\e[1;36m"
23 self.red = u"\e[1;31m"
24 self.green = u"\e[1;32m"
25 else:
26 self.endcolor=self.blue=self.red=self.green=""
27 #: Serveur distant où aller récupérer les checks
28 self.distant_server = "pimeys"
29 #: path de today-server.py sur le serveur distant
30 self.path_today_server = "/home/vincent/scripts/today/today_server.py"
31 #: Fichier contenant les anniversaires
32 self.birthdays_file = "birthdays.txt"
33 #: Fichier contenant les évènements à venir
34 self.timers_file = "timers.txt"
35 #: Fichier contenant les fêtes à souhaiter
36 self.saints_file = "saints.json"
37 #: Fichier contenant les ids des derniers trucs vus/lus
38 self.last_seen_file = "lasts"
39 #: Fichier contenant le timestamp de dernière exécution
40 self.lasttime_file = ".lasttime"
41 #: Fichier contenant un booléen mémorisant si il y a quelquechose dans le today du jour et qu'il n'a pas encore été regardé
42 self.something_file = ".something"
43
44 if "--color" in sys.argv:
45 sys.argv.remove("--color")
46 color = True
47 elif sys.stdout.isatty():
48 color = True
49 else:
50 color = False
51
52 config = Config(color=color)
53
54 def print_date(timestamp=None,color=True):
55 """Afficher la date"""
56 if timestamp == None:
57 timestamp = time.time()
58 return time.strftime(" "*30 + config.blue+"%d/%m/%Y %H:%M:%S"+config.endcolor + "\n",
59 time.localtime(timestamp))
60
61 def add_title(titre, texte):
62 """Ajoute un titre à la partie si il y a quelque chose dedans"""
63 if texte:
64 texte = u" %s%s :%s\n" % (config.green, titre, config.endcolor) + texte + "\n"
65 return texte
66
67 def get_now():
68 """Obtenir la date actuelle sous le bon format"""
69 timestamp = time.time()
70 now = datetime.datetime(*time.localtime(timestamp)[:7])
71 return now
72
73 def get_last_seen():
74 """Récupère la liste des derniers trucs vus/lus"""
75 with open(config.last_seen_file) as f:
76 return json.loads(f.read())
77
78 def update_last_seen(newdict):
79 """Met à jour un des derniers trucs vus/lus"""
80 lasts = get_last_seen()
81 lasts.update(newdict)
82 with open(config.last_seen_file, "w") as f:
83 f.write(json.dumps(lasts))
84
85 def parse_datefile(namefile):
86 """Ouvre et parse un fichier avec des lignes de la forme
87 jj/mm/aaaa Truc
88 """
89 with open(namefile) as f:
90 rawdata = [l.strip().decode("utf8") for l in f.readlines()[1:] if not l.strip().startswith("#") and not l.strip() == '']
91 data = []
92 for l in rawdata:
93 date, truc = l.split("\t",1)
94 date = datetime.datetime(*time.strptime(date, "%d/%m/%Y")[:7])
95 data.append([date, truc])
96 return data
97
98
99 def get_timers():
100 """Obtenir la liste des évènements à venir (J-n)"""
101 now = get_now()
102 data = parse_datefile(config.timers_file)
103 timers = []
104 for [date, event] in data:
105 delta = date - now + datetime.timedelta(1)
106 if delta > datetime.timedelta(0):
107 timers.append([event,delta.days])
108 eventsize = max([0]+[len(l[0]) for l in timers])
109 timers = u"\n".join([(u"%%-%ss J-%%s" % eventsize) % (event, timer) for event,timer in timers])
110 timers = add_title(u"Timers", timers)
111 return timers
112
113 def get_birthdays(*search):
114 """Obtenir la liste des anniversaires à venir,
115 ou la liste des anniversaires correspondants à une recherche"""
116 now = get_now()
117 liste = parse_datefile(config.birthdays_file)
118 birthdays = []
119 if len(search) == 0:
120 # Simple demande d'anniversaires courants
121 for date, nom in liste:
122 thisyeardate = datetime.datetime(now.year, date.month, date.day)
123 delta = thisyeardate - now + datetime.timedelta(1)
124 if delta > datetime.timedelta(0):
125 age = now.year - date.year
126 if abs(delta) < datetime.timedelta(1):
127 birthdays.append([config.red, u"%s a %s ans AUJOURD'HUI !" % (nom, age), 0, config.endcolor])
128 elif delta < datetime.timedelta(7):
129 birthdays.append([config.red, u"%s va avoir %s ans" % (nom, age), -delta.days, config.endcolor])
130 elif delta < datetime.timedelta(14):
131 birthdays.append([u"", u"%s va avoir %s ans" % (nom, age), -delta.days, u""])
132 elif datetime.timedelta(30-4) < delta < datetime.timedelta(30+4):
133 birthdays.append([u"", u"%s va avoir %s ans" % (nom, age), -delta.days, u""])
134 else:
135 # Recherche spécifique
136 search = [i.lower() for i in search]
137 tous = ("--all" in search)
138 for date, nom in liste:
139 if tous or any([term.lower() in nom.lower() for term in search]):
140 thisyeardate = datetime.datetime(now.year, date.month, date.day)
141 delta = thisyeardate - now + datetime.timedelta(1)
142 age = now.year - date.year
143 if delta.days<0:
144 birthdays.append([u"", u"%s a eu %s ans" % (nom, age), -delta.days, ""])
145 else:
146 birthdays.append([u"", u"%s va avoir %s ans" % (nom, age), -delta.days, ""])
147 birthdays.sort(lambda x,y: -cmp(x[2], y[2]))
148 eventsize = max([0]+[len(l[1]) for l in birthdays])
149 template = (u"%%s%%-%ss J%%+d%%s" % eventsize)
150 birthdays = u"\n".join([template % tuple(tup) for tup in birthdays])
151 birthdays = add_title(u"Anniversaires", birthdays)
152 return birthdays
153
154 def _parse_saint(s):
155 """Renvoie la liste des fêtes contenue dans un jour"""
156 l = s.split(",")
157 ll = []
158 for st in l:
159 if st[0] == "&":
160 ll.append(["St ", st[1:]])
161 elif st[0] == "!":
162 ll.append(["Ste ", st[1:]])
163 return ll
164
165 def _get_firstnames():
166 """Récupère la liste des noms des gens connus"""
167 birthdays = parse_datefile(config.birthdays_file)
168 firstnames = [b[1].split()[0] for b in birthdays]
169 return firstnames
170
171 def get_saints():
172 """Renvoie la liste des fêtes à souhaiter aujourd'hui et demain"""
173 sourcesaints = json.load(open(config.saints_file))
174 now = get_now()
175 saints = {}
176 saints["today"] = _parse_saint(sourcesaints[now.month - 1][now.day - 1])
177 nbdays = len(sourcesaints[now.month - 1])
178 if now.day == nbdays: # il faut regarder le mois suivant
179 if now.month == 12: # il faut regarder l'année suivante
180 saints["tomorrow"] = _parse_saint(sourcesaints[0][0])
181 else:
182 saints["tomorrow"] = _parse_saint(sourcesaints[now.month][0])
183 else:
184 saints["tomorrow"] = _parse_saint(sourcesaints[now.month - 1][now.day])
185 firstnames = _get_firstnames()
186 towish = {"today" : [], "tomorrow" : []}
187 for day in ["today", "tomorrow"]:
188 ssaints = saints[day]
189 for (sexe, saint) in ssaints:
190 if any([firstname.lower() in saint.lower().split() for firstname in firstnames]):
191 towish[day].append(sexe + saint)
192 ttowish = []
193 if towish["today"]:
194 ttowish.append(u"Aujourd'hui :\n" + "\n".join(towish["today"]))
195 if towish["tomorrow"]:
196 ttowish.append(u"Demain :\n" + "\n".join(towish["tomorrow"]))
197 saints = "\n".join(ttowish)
198 saints = add_title(u"Fêtes à souhaiter", saints)
199 return saints
200
201 def check_birthdays():
202 """Compte combien il y a d'anniversaires à afficher"""
203 birthdays = get_birthdays()
204 if birthdays:
205 n = birthdays.count(u"\n") - 1
206 else:
207 n = 0
208 return n
209
210 def check_saints():
211 """Compte combien il y a de fêtes à afficher"""
212 saints = get_saints()
213 return len(re.findall("\nSt", saints))
214
215 def format_quotes(liste):
216 """Formate les quotes de dicos à texte"""
217 t = (u"\n" + u"_"*80 + u"\n").join([u"%(id)s (%(date)s)\n%(quote)s" % q for q in liste])
218 return t
219
220 def get_dtc(*args):
221 """Récupère les quotes DTC non lues"""
222 if len(args) == 0:
223 last_dtc = get_last_seen().get("dtc", 0)
224 cmd = "~/bin/dtc %s + --json" % (last_dtc+1,)
225 elif len(args) == 1:
226 cmd = "~/bin/dtc %s --json" % args[0]
227 elif len(args) == 2:
228 cmd = "~/bin/dtc %s %s --json" % (args[0], args[1])
229 else:
230 return None
231 proc = subprocess.Popen(["ssh", "-4", config.distant_server, cmd], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
232 out, err = proc.communicate()
233 out += err
234 quotes = json.loads(out)
235 if quotes:
236 last_id = max([q["id"] for q in quotes])
237 update_last_seen({"dtc" : last_id})
238 textquotes = format_quotes(quotes)
239 return textquotes
240
241 def update_xkcd(newid):
242 update_last_seen({"xkcd" : int(newid)})
243
244 def update_xantah(newid):
245 update_last_seen({"xantah" : int(newid)})
246
247 def update_visiteur(newid):
248 update_last_seen({"visiteur" : int(newid)})
249
250 def update_noob(newid):
251 update_last_seen({"noob" : int(newid)})
252
253 def update_warpzone(newid):
254 update_last_seen({"warpzone" : int(newid)})
255
256 def update_hugo(newid):
257 update_last_seen({"hugo" : int(newid)})
258
259 def update_norman(newid):
260 update_last_seen({"norman" : int(newid)})
261
262 def update_cyprien(newid):
263 update_last_seen({"cyprien" : int(newid)})
264
265 def update_grenier(newid):
266 update_last_seen({"grenier" : int(newid)})
267
268
269 THINGS = {
270 "dtc" : u"Quotes DTC",
271 "xkcd" : u"Épisodes de XKCD",
272 "xantah" : u"Épisodes de La Légende de Xantah",
273 "visiteur" : u"Épisodes du Visiteur du Futur",
274 "noob" : u"Épisodes de NOOB",
275 "warpzone" : u"Épisodes de WARPZONE PROJECT",
276 "hugo" : u"Vidéos de Hugo Tout Seul",
277 "norman" : u"Vidéos de Norman",
278 "cyprien" : u"Vidéos de Cyprien",
279 "grenier" : u"Épisodes du joueur du grenier",
280
281 "birthdays" : u"Anniversaires à souhaiter",
282 "saints" : u"Fêtes à souhaiter",
283 }
284
285 def check_all():
286 """Vérifie si il y a des derniers trucs non lus/vus."""
287 cmd = "%s whatsup" % (config.path_today_server,)
288 proc = subprocess.Popen(["ssh", "-4", config.distant_server, cmd], stdout=subprocess.PIPE)
289 out, err = proc.communicate()
290 news = json.loads(out)
291 seen = get_last_seen()
292 news["birthdays"] = check_birthdays()
293 news["saints"] = check_saints()
294 checks = []
295 for (thing, comm) in THINGS.iteritems():
296 n = news[thing] - seen.get(thing, 0)
297 if type(n) != int:
298 print n
299 elif n > 0:
300 checks.append("%s : %s (last : %s)" % (comm, n, news[thing]))
301 checks = u"\n".join(checks)
302 checks = add_title(u"Checks", checks)
303 return checks
304
305 def get_everything():
306 """Récupère toutes les infos"""
307 work = [action() for action in AUTOMATED_ACTIONS.values()]
308 chain = u"\n\n".join([result for result in work if result])
309 return chain
310
311 def _is_there_something_in_today():
312 """Teste si il y a des choses non lue dans le today"""
313 return open(config.something_file, "r").read() == "True"
314
315 def _there_is_something_in_today(something):
316 """Met à jour le something"""
317 f = open(config.something_file, "w")
318 f.write(str(bool(something)))
319 f.close()
320
321 def _get_lasttime():
322 """Récupère la dernière fois que today a été exécuté"""
323 if os.path.isfile(config.lasttime_file):
324 lasttime = open(config.lasttime_file, "r").read()
325 else:
326 lasttime = 0
327 lasttime = datetime.datetime(*time.localtime(float(lasttime))[:7])
328 return lasttime
329
330 def _update_lasttime(when):
331 """Met à jour le timestamp de dernière exécution de today"""
332 f = open(config.lasttime_file, "w")
333 f.write(str(time.mktime(when.timetuple())))
334 f.close()
335
336 def ping():
337 """Dit juste si il y a quelque chose à voir.
338 La première exécution de la journée peut être lente parce qu'elle va bosser avnt de répondre."""
339 now = get_now()
340 lasttime = _get_lasttime()
341 if (lasttime.date() < now.date()):
342 # On a changé de jour
343 _update_lasttime(now)
344 something = get_everything()
345 _there_is_something_in_today(something)
346 if something:
347 return u"You have something in %stoday%s" % (config.red, config.endcolor)
348 else:
349 return u"Nothing in today"
350 else:
351 if _is_there_something_in_today():
352 return u"You have something in %stoday%s" % (config.red, config.endcolor)
353
354 def affiche():
355 """Action par défaut, affiche toutes les infos"""
356 out = print_date()
357 out += get_everything()
358 _there_is_something_in_today(False)
359 return out
360
361 def initialize():
362 """Crée les fichiers (vides) nécessaires au fonctionnement du script"""
363 files = [config.birthdays_file, config.timers_file, config.saints_file,
364 config.last_seen_file]
365 contents = [u"# Anniversaires\n#jj/mm/aaaa Prénom Nom\n",
366 u"# Évènements à venir\n#jj/mm/aaaa Évènement\n",
367 json.dumps([[""]*31]*12).decode(),
368 u"{}"]
369 for ifile in range(len(files)):
370 namefile = files[ifile]
371 if os.path.isfile(namefile):
372 print "%s exists, skipping." % (namefile,)
373 else:
374 f = open(namefile, "w")
375 f.write(contents[ifile].encode("utf-8"))
376 f.close()
377
378 def sync():
379 """Synchronise les last_seen avec le serveur distant qui en garde une copie,
380 le maximum de chaque truc vu est gardé des deux côtés."""
381 lasts = get_last_seen()
382 cmd = "%s sync" % (config.path_today_server,)
383 proc = subprocess.Popen(["ssh", "-4", config.distant_server, cmd],
384 stdin = subprocess.PIPE, stdout=subprocess.PIPE,
385 close_fds = True)
386 lasts_raw = json.dumps(lasts)
387 proc.stdin.write(lasts_raw)
388 proc.stdin.close()
389 out = proc.stdout.read()
390 newdict = json.loads(out)
391 update_last_seen(newdict)
392 print u"Nouvel état : %r" % newdict
393
394
395
396 #: Les actions effectuées lors d'un appel sans paramètres
397 AUTOMATED_ACTIONS = {
398 "timers" : get_timers,
399 "birth" : get_birthdays,
400 "saints" : get_saints,
401 "check" : check_all,
402 }
403
404 #: Les actions qu'on peut effectuer en rajoutant des paramètres
405 OTHER_ACTIONS = {
406 "xkcd" : update_xkcd,
407 "xantah" : update_xantah,
408 "visiteur" : update_visiteur,
409 "noob" : update_noob,
410 "warpzone" : update_warpzone,
411 "hugo" : update_hugo,
412 "norman" : update_norman,
413 "cyprien" : update_cyprien,
414 "grenier" : update_grenier,
415
416 "dtc" : get_dtc,
417 "ping" : ping,
418 "show" : affiche,
419 "sync" : sync,
420
421 "init" : initialize,
422 }
423
424 #: Toutes les actions
425 ACTIONS = dict(AUTOMATED_ACTIONS)
426 ACTIONS.update(OTHER_ACTIONS)
427
428 ACTIONS[None] = affiche # action par défaut
429
430 if __name__ == "__main__":
431 import sys
432 if "--no-color" in sys.argv:
433 config.endcolor, config.red, config.blue = u"", u"", u""
434 if len(sys.argv) == 1:
435 # Juste un today
436 output = ACTIONS[None]()
437 else:
438 commande = sys.argv[1]
439 args = sys.argv[2:]
440 output = ACTIONS[commande](*args)
441 if output:
442 print output