]> gitweb.pimeys.fr Git - today.git/blob - today_server.py
[client] Séparation de la config
[today.git] / today_server.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 Partie serveur, prévue pour chercher périodiquement les trucs non lus
9 et répondre à un check.
10
11 """
12
13 import re
14 import BeautifulSoup
15 from lxml import etree
16 import os
17 import sys
18 import urllib
19 import json
20 import traceback
21 import inspect
22 import pprint
23 os.chdir('/home/vincent/scripts/today/')
24 sys.path.append("/home/vincent/scripts/dtc/")
25 import dtc
26
27 #: Fichier où sont stockés les derniers IDs des trucs
28 store_published_file = "lasts_published"
29
30 #: Fichier où est stockée une copie de la liste des derniers IDs *lus*
31 store_seen_file = "lasts_sync"
32
33 #: Afficher du garbage pour débuguer
34 DEBUG = False
35
36 def last_dtc():
37 """Vérifie si il y a des quotes DTC non lues"""
38 return dtc.last_inserted()
39
40 def last_xkcd():
41 p = urllib.urlopen("http://xkcd.com")
42 t = p.read()
43 current_id = int(re.findall("Permanent link to this comic: http://xkcd.com/(.*?)/", t)[0])
44 return current_id
45
46 def last_xantah():
47 p = urllib.urlopen("http://www.adoprixtoxis.com/lite/download/xantah_downloads.php")
48 t = p.read()
49 ids = re.findall("""<div class="top">Xantah (.*?)</div>""", t)
50 ids = [int(i) for i in ids]
51 return max(ids)
52
53 def last_visiteur():
54 p = urllib.urlopen("http://www.levisiteurdufutur.com/episodes.html")
55 t = p.read()
56 # On parse
57 soup = BeautifulSoup.BeautifulSoup(t)
58 # On récupère les différentes saisons
59 saisons = soup.findAll("div", attrs={"id" : "episodes_list"})
60 nsaisons = len(saisons)
61 # La saison en cours est la première dans la liste
62 episodes = saisons[0].findAll("div", attrs={"class" : "thumbCaption"})
63 nepisodes = len(episodes)
64 return nsaisons * 100 + nepisodes
65
66 def parse_youtube(username):
67 """Récupère les vidéos d'une chaîne Youtube"""
68 link = "https://gdata.youtube.com/feeds/api/users/%s/uploads?start-index=1&max-results=50" % (username,)
69 entries = []
70 while link:
71 p = urllib.urlopen(link)
72 t = p.read()
73 x = etree.fromstring(t)
74 # lxml ne supporte pas les namespaces vides dans les requêtes XPath
75 ns = x.nsmap
76 ns["default"] = ns[None]
77 ns.pop(None)
78 # Il y a potentiellement une suite
79 nextlinks = x.xpath("//default:link[@rel='next']", namespaces=ns)
80 if nextlinks:
81 link = nextlinks[0].attrib["href"]
82 else:
83 link = False
84 localentries = x.xpath("//default:entry", namespaces=ns)
85 entries.extend(localentries)
86 titles = [e.xpath(".//default:title", namespaces=ns)[0].text for e in entries]
87 return titles
88
89 def get_season_episode(title):
90 """Récupère les numéros de la saison et de l'épisode. Crash si ne trouve pas."""
91 ep = int(re.findall("ep([0-9]*)", title)[0])
92 saison = int(re.findall("s([0-9]*)", title)[0])
93 return saison, ep
94
95 def last_noob_warpzone():
96 global last_nw
97 # GRUIK
98 if "last_nw" in globals().keys():
99 return last_nw
100 titles = parse_youtube("Funglisoft")
101 noobs = [t.lower().strip() for t in titles if t.lower().strip().startswith("noob")]
102 warpzones = [t.lower().strip() for t in titles if t.lower().strip().startswith("warpzone project")]
103 lasts = []
104 for serie in [noobs, warpzones]:
105 # Les titres sont dans l'ordre antichronologique, on s'arrête donc au premier qu'on comprend
106 for titre in serie:
107 if "noob le film" in titre or "making of" in titre or "noob versus rct" == titre or "extraits ost" in titre:
108 continue
109 try:
110 if DEBUG:
111 print titre
112 saison, ep = get_season_episode(titre)
113 except (ValueError, IndexError) as e:
114 print "%s sur un season_episode warpzone : %s\n" % (e, titre)
115 continue
116 lasts.append([saison, ep])
117 del saison, ep
118 break
119 last_noob = lasts[0][0]*100 + lasts[0][1]
120 last_warp = lasts[1][0]*100 + lasts[1][1]
121 last_nw = [last_noob, last_warp]
122 return last_nw
123
124 def last_noob():
125 return last_noob_warpzone()[0]
126 def last_warpzone():
127 return last_noob_warpzone()[1]
128
129 def last_hugo():
130 titles = parse_youtube("HugoToutSeul")
131 return len(titles)
132
133 def last_norman():
134 titles = parse_youtube("NormanFaitDesVideos")
135 return len(titles)
136
137 def last_cyprien():
138 titles = parse_youtube("MonsieurDream")
139 return len(titles)
140
141 def last_grenier():
142 titles = parse_youtube("joueurdugrenier")
143 return len(titles)
144
145 def last_jl8():
146 rss = urllib.urlopen("http://limbero.org/jl8/rss/")
147 t = rss.read()
148 x = etree.fromstring(t)
149 links = x.xpath("//link")
150 maxnum = links[1].text.split("/")[-1]
151 maxnum = int(maxnum)
152 return maxnum
153
154 def get_file():
155 """Récupère la liste des derniers ids de chaque truc, stockée dans le fichier."""
156 f = open(store_published_file)
157 news = json.load(f)
158 f.close()
159 return news
160
161 def update_file(news):
162 """Met à jour la liste des derniers ids dans le fichier."""
163 f = open(store_published_file, 'w')
164 json.dump(news, f)
165 f.close()
166
167 FETCHS = {
168 "xkcd" : last_xkcd,
169 "dtc" : last_dtc,
170 "xantah" : last_xantah,
171 "visiteur" : last_visiteur,
172 "noob" : last_noob,
173 "warpzone" : last_warpzone,
174 "hugo" : last_hugo,
175 "norman" : last_norman,
176 "cyprien" : last_cyprien,
177 "grenier" : last_grenier,
178 "dc" : last_jl8,
179 }
180
181 def fetch_all():
182 """Va chercher sur les différents sites les nouveaux trucs."""
183 news = {}
184 for (k, f) in FETCHS.iteritems():
185 try:
186 news[k] = f()
187 except Exception as e:
188 errmsg = "Erreur à la récupération de %s :\n" % k
189 errmsg += traceback.format_exc()
190 # On dumpe le contenu local de la mémoire au moment de l'exception
191 fobj = inspect.trace()[-1][0]
192 # On fait un peu de ménage
193 d = {k:v for (k,v) in fobj.f_locals.iteritems() if not k.startswith("_")}
194 # On évite d'envoyer truckLoadsOfShit
195 d = {k: (v if len(str(v)) < 800
196 else str(v)[:400] + "*" * 40 + "TRUNCATED OBJECT" + "*" * 40 + str(v)[-400:])
197 for (k,v) in d.iteritems()}
198 errmsg += "\nContexte : %s\n\n" % (pprint.pformat(d))
199 print errmsg
200 return news
201
202 def sync():
203 """Reçoit une requête de synchronisation."""
204 # On récupère où en est le client sur stdin
205 t = sys.stdin.read()
206 on_client = json.loads(t)
207 # On récupère où en est le serveur dans le fichier idoine
208 if os.path.isfile(store_seen_file):
209 on_server = json.load(open(store_seen_file))
210 else:
211 on_server = {}
212 # On garde le maximum
213 for k in set(on_client.keys() + on_server.keys()):
214 on_server[k] = max(on_client.get(k, 0), on_server.get(k, 0))
215 # On enregistre ce nouveau dico
216 json.dump(on_server, open(store_seen_file, "w"))
217 # On envoie au client ce nouveau dico
218 print json.dumps(on_server)
219
220 if __name__ == "__main__":
221 if "--debug" in sys.argv or "--verbose" in sys.argv:
222 DEBUG = True
223 if sys.argv[1] == "check":
224 news = fetch_all()
225 if "--init" in sys.argv:
226 olds = news
227 else:
228 olds = get_file()
229 olds.update(news)
230 update_file(olds)
231 elif sys.argv[1] == "whatsup":
232 news = get_file()
233 print json.dumps(news)
234 elif sys.argv[1] == "sync":
235 sync()