Introduction
Introduction Statistics Contact Development Disclaimer Help
cgi.c - frontends - front-ends for some sites (experiment)
Log
Files
Refs
README
LICENSE
---
cgi.c (10496B)
---
1 #include <sys/socket.h>
2 #include <sys/types.h>
3
4 #include <ctype.h>
5 #include <errno.h>
6 #include <netdb.h>
7 #include <stdarg.h>
8 #include <stdio.h>
9 #include <stdlib.h>
10 #include <string.h>
11 #include <time.h>
12 #include <unistd.h>
13
14 #include "https.h"
15 #include "json.h"
16 #include "twitch.h"
17 #include "util.h"
18
19 #define OUT(s) (fputs((s), stdout))
20
21 extern char **environ;
22
23 /* page variables */
24 static char *title = "", *pagetitle = "", *classname = "";
25
26 static int
27 gamecmp_name(const void *v1, const void *v2)
28 {
29 struct game *g1 = (struct game *)v1;
30 struct game *g2 = (struct game *)v2;
31
32 return strcmp(g1->name, g2->name);
33 }
34
35 void
36 header(void)
37 {
38 OUT(
39 "<!DOCTYPE html>\n"
40 "<html>\n"
41 "<head>\n"
42 "<title>");
43 if (title[0]) {
44 xmlencode(title);
45 OUT(" - ");
46 }
47 if (pagetitle[0]) {
48 xmlencode(pagetitle);
49 OUT(" - ");
50 }
51 OUT("Twitch.tv</title>\n"
52 " <meta http-equiv=\"Content-Type\" content=\"text/html; …
53 " <meta http-equiv=\"Content-Language\" content=\"en\" />…
54 " <link href=\"twitch.css\" rel=\"stylesheet\" type=\"tex…
55 "</head>\n"
56 "<body class=\"");
57 xmlencode(classname);
58 OUT(
59 "\">\n"
60 "<div id=\"menuwrap\">\n"
61 "<div id=\"menu\">\n"
62 "<span id=\"links\">\n"
63 " <a href=\"featured\">Featured</a> | \n"
64 " <a href=\"games\">Games</a> | \n"
65 " <a href=\"vods\">VODS</a> |\n"
66 " <a href=\"https://git.codemadness.org/frontends/\">Sour…
67 " <a href=\"links\">Links</a>\n"
68 "</span>\n"
69 "</div></div>\n"
70 " <hr class=\"hidden\" />\n"
71 " <div id=\"mainwrap\">\n"
72 " <div id=\"main\">\n");
73
74 if (pagetitle[0] || title[0]) {
75 OUT("<h1><a href=\"\">");
76 if (title[0])
77 xmlencode(title);
78 if (pagetitle[0]) {
79 if (title[0])
80 OUT(" - ");
81 xmlencode(pagetitle);
82 }
83 OUT("</a></h1>\n");
84 }
85 }
86
87 void
88 footer(void)
89 {
90 OUT("</div></div></body></html>\n");
91 }
92
93 void
94 render_links(void)
95 {
96 OUT("Content-Type: text/html; charset=utf-8\r\n\r\n");
97
98 header();
99 OUT("<ul>\n"
100 "<li><a href=\"https://mpv.io/installation/\">mpv player</a></li…
101 "<li><a href=\"https://github.com/ytdl-org/youtube-dl\">youtube-…
102 "<li><a href=\"https://www.videolan.org/\">VLC</a></li>\n"
103 "<li><a href=\"https://dev.twitch.tv/docs\">Twitch.tv API</a></l…
104 "</ul>\n");
105 footer();
106 }
107
108 void
109 render_games_top(struct games_response *r)
110 {
111 struct game *game;
112 size_t i;
113
114 OUT("Content-Type: text/html; charset=utf-8\r\n\r\n");
115
116 header();
117 OUT("<table class=\"table\" border=\"0\">");
118 OUT("<thead>\n<tr>");
119 OUT("<th align=\"left\" class=\"name\">Name</th>");
120 OUT("</tr>\n</thead>\n<tbody>\n");
121 for (i = 0; i < r->nitems; i++) {
122 game = &(r->data[i]);
123
124 OUT("<tr><td><a href=\"streams?game_id=");
125 xmlencode(game->id);
126 OUT("\">");
127 xmlencode(game->name);
128 OUT("</a></td></tr>\n");
129 }
130 OUT("</tbody></table>");
131 footer();
132 }
133
134 void
135 render_streams(struct streams_response *r, const char *game_id)
136 {
137 struct stream *stream;
138 size_t i;
139
140 OUT("Content-Type: text/html; charset=utf-8\r\n\r\n");
141
142 header();
143 OUT("<table class=\"table\" border=\"0\">");
144 OUT("<thead>\n<tr>");
145 if (!game_id[0])
146 OUT("<th align=\"left\" class=\"game\">Game</th>");
147
148 OUT("<th align=\"left\" class=\"name\">Name</th>");
149 OUT("<th align=\"left\" class=\"title\">Title</th>");
150 OUT("<th align=\"right\" class=\"viewers\">Viewers</th>");
151 OUT("</tr>\n</thead>\n<tbody>\n");
152 for (i = 0; i < r->nitems; i++) {
153 stream = &(r->data[i]);
154
155 OUT("<tr>");
156
157 if (!game_id[0]) {
158 OUT("<td>");
159 if (r->data[i].game) {
160 OUT("<a href=\"streams?game_id=");
161 xmlencode(r->data[i].game->id);
162 OUT("\">");
163 xmlencode(r->data[i].game->name);
164 OUT("</a>");
165 }
166 OUT("</td>");
167 }
168
169 OUT("<td class=\"name\">");
170 if (stream->user) {
171 OUT("<a href=\"https://www.twitch.tv/");
172 xmlencode(stream->user->login);
173 OUT("\">");
174 xmlencode(stream->user_name);
175 OUT("</a>");
176 } else {
177 xmlencode(stream->user_name);
178 }
179 OUT("</td>");
180
181 OUT("<td class=\"title\">");
182 if (stream->user) {
183 OUT("<a href=\"https://www.twitch.tv/");
184 xmlencode(stream->user->login);
185 OUT("\">");
186 }
187 if (stream->language[0]) {
188 OUT("[");
189 xmlencode(stream->language);
190 OUT("] ");
191 }
192 xmlencode(stream->title);
193 if (stream->user)
194 OUT("</a>");
195
196 OUT("</td>");
197
198 OUT("<td align=\"right\" class=\"viewers\">");
199 printf("%lld", stream->viewer_count);
200 OUT("</td>");
201
202 OUT("</tr>\n");
203 }
204 OUT("</tbody></table>");
205 footer();
206 }
207
208 void
209 render_videos_atom(struct videos_response *r, const char *login)
210 {
211 struct video *video;
212 size_t i;
213
214 OUT("Content-Type: text/xml; charset=utf-8\r\n\r\n");
215
216 OUT("<feed xmlns=\"http://www.w3.org/2005/Atom\" xml:lang=\"en\"…
217 for (i = 0; i < r->nitems; i++) {
218 video = &(r->data[i]);
219
220 OUT("<entry>\n");
221 OUT("\t<title type=\"text\">");
222 xmlencode(video->title);
223 OUT("</title>\n");
224 OUT("\t<link rel=\"alternate\" type=\"text/html\" href=\…
225 xmlencode(video->url);
226 OUT("\" />\n");
227 OUT("\t<id>");
228 xmlencode(video->url);
229 OUT("</id>\n");
230 OUT("\t<updated>");
231 xmlencode(video->created_at);
232 OUT("</updated>\n");
233 OUT("\t<published>");
234 xmlencode(video->created_at);
235 OUT("</published>\n");
236 OUT("</entry>\n");
237 }
238 OUT("</feed>\n");
239 }
240
241 void
242 render_videos(struct videos_response *r, const char *login)
243 {
244 struct video *video;
245 size_t i;
246
247 OUT("Content-Type: text/html; charset=utf-8\r\n\r\n");
248
249 header();
250
251 OUT("<form method=\"get\" action=\"\">\n");
252 OUT("\t<input type=\"search\" name=\"login\" value=\"\" placehol…
253 OUT("\t<input type=\"submit\" name=\"list\" value=\"List vods\" …
254 OUT("</form>\n<hr/>\n");
255
256 /* no results or no user_id parameter: quick exit */
257 if (r == NULL) {
258 footer();
259 return;
260 }
261
262 /* link to atom format. */
263 if (login[0]) {
264 OUT("<p><a href=\"?login=");
265 xmlencode(login);
266 OUT("&amp;format=atom\">Atom feed</a></p>\n");
267 }
268
269 OUT("<table class=\"table\" border=\"0\">");
270 OUT("<thead>\n<tr>");
271 OUT("<th align=\"left\" class=\"created_at\">Created</th>");
272 OUT("<th align=\"left\" class=\"title\">Title</th>");
273 OUT("<th align=\"right\" class=\"duration\">Duration</th>");
274 OUT("<th align=\"right\" class=\"views\">Views</th>");
275 OUT("</tr>\n</thead>\n<tbody>\n");
276 for (i = 0; i < r->nitems; i++) {
277 video = &(r->data[i]);
278
279 OUT("<tr>");
280
281 OUT("<td>");
282 xmlencode(video->created_at);
283 OUT("</td>");
284
285 OUT("<td class=\"wrap\"><a href=\"");
286 xmlencode(video->url);
287 OUT("\">");
288 xmlencode(video->title);
289 OUT("</a></td>");
290
291 OUT("<td align=\"right\"><a href=\"");
292 xmlencode(video->url);
293 OUT("\">");
294 xmlencode(video->duration);
295 OUT("</a></td>");
296
297 OUT("<td align=\"right\">");
298 printf("%lld", video->view_count);
299 OUT("</td>");
300
301 OUT("</tr>\n");
302 }
303 OUT("</tbody></table>");
304 footer();
305 }
306
307 void
308 handle_streams(void)
309 {
310 struct streams_response *r;
311 struct users_response *ru = NULL;
312 struct games_response *rg = NULL;
313 char game_id[32] = "";
314 char *p, *querystring;
315
316 pagetitle = "Streams";
317 classname = "streams";
318
319 /* parse "game_id" parameter */
320 if ((querystring = getenv("QUERY_STRING"))) {
321 if ((p = getparam(querystring, "game_id"))) {
322 if (decodeparam(game_id, sizeof(game_id), p) == …
323 game_id[0] = '\0';
324 }
325 }
326
327 if (game_id[0])
328 r = twitch_streams_bygame(game_id);
329 else
330 r = twitch_streams();
331
332 if (r == NULL)
333 return;
334
335 /* find detailed games data with streams */
336 if (!game_id[0])
337 rg = twitch_streams_games(r);
338
339 /* find detailed user data with streams */
340 ru = twitch_streams_users(r);
341
342 if (pledge("stdio", NULL) == -1) {
343 OUT("Status: 500 Internal Server Error\r\n\r\n");
344 exit(1);
345 }
346
347 render_streams(r, game_id);
348
349 free(r);
350 free(rg);
351 free(ru);
352 }
353
354 void
355 handle_videos(void)
356 {
357 struct videos_response *r = NULL;
358 struct users_response *ru = NULL;
359 char user_id[32] = "", login[64] = "", format[6] = "";
360 char *p, *querystring;
361
362 pagetitle = "Videos";
363 classname = "videos";
364
365 /* parse "user_id" or "login" parameter */
366 if ((querystring = getenv("QUERY_STRING"))) {
367 if ((p = getparam(querystring, "user_id"))) {
368 if (decodeparam(user_id, sizeof(user_id), p) == …
369 user_id[0] = '\0';
370 }
371 if ((p = getparam(querystring, "login"))) {
372 if (decodeparam(login, sizeof(login), p) == -1)
373 login[0] = '\0';
374 }
375 if ((p = getparam(querystring, "format"))) {
376 if (decodeparam(format, sizeof(format), p) == -1)
377 format[0] = '\0';
378 }
379 }
380
381 /* no parameter given, show form */
382 if (!user_id[0] && !login[0]) {
383 if (pledge("stdio", NULL) == -1) {
384 OUT("Status: 500 Internal Server Error\r\n\r\n");
385 exit(1);
386 }
387
388 render_videos(r, "");
389 return;
390 }
391
392 if (user_id[0]) {
393 r = twitch_videos_byuserid(user_id);
394 } else {
395 ru = twitch_users_bylogin(login);
396 if (ru && ru->nitems > 0)
397 r = twitch_videos_byuserid(ru->data[0].id);
398 }
399
400 if (pledge("stdio", NULL) == -1) {
401 OUT("Status: 500 Internal Server Error\r\n\r\n");
402 exit(1);
403 }
404
405 if (r && r->nitems > 0)
406 title = r->data[0].user_name;
407
408 if (!strcmp(format, "atom"))
409 render_videos_atom(r, login);
410 else
411 render_videos(r, login);
412
413 free(ru);
414 free(r);
415 }
416
417 void
418 handle_games_top(void)
419 {
420 struct games_response *r;
421
422 pagetitle = "Top 100 games";
423 classname = "topgames";
424
425 if (!(r = twitch_games_top()))
426 return;
427
428 if (pledge("stdio", NULL) == -1) {
429 OUT("Status: 500 Internal Server Error\r\n\r\n");
430 exit(1);
431 }
432
433 /* sort by name alphabetically, NOTE: the results are the top 100
434 sorted by viewcount. View counts are not visible in the new
435 Helix API data). */
436 qsort(r->data, r->nitems, sizeof(r->data[0]), gamecmp_name);
437
438 render_games_top(r);
439 free(r);
440 }
441
442 void
443 handle_links(void)
444 {
445 if (pledge("stdio", NULL) == -1) {
446 OUT("Status: 500 Internal Server Error\r\n\r\n");
447 exit(1);
448 }
449
450 pagetitle = "Links";
451 classname = "links";
452
453 render_links();
454 }
455
456 int
457 main(void)
458 {
459 char *path, *pathinfo;
460
461 if (pledge("stdio dns inet rpath unveil", NULL) == -1 ||
462 unveil(TLS_CA_CERT_FILE, "r") == -1 ||
463 unveil(NULL, NULL) == -1) {
464 OUT("Status: 500 Internal Server Error\r\n\r\n");
465 exit(1);
466 }
467
468 if (!(pathinfo = getenv("PATH_INFO")))
469 pathinfo = "/";
470
471 path = pathinfo;
472 if (path[0] == '/')
473 path++;
474
475 if (!strcmp(path, "") ||
476 !strcmp(path, "featured") ||
477 !strcmp(path, "streams")) {
478 /* featured / by game id */
479 handle_streams();
480 } else if (!strcmp(path, "topgames") ||
481 !strcmp(path, "games")) {
482 handle_games_top();
483 } else if (!strcmp(path, "videos") ||
484 !strcmp(path, "vods")) {
485 handle_videos();
486 } else if (!strcmp(path, "links")) {
487 handle_links();
488 } else {
489 OUT("Status: 404 Not Found\r\n\r\n");
490 exit(1);
491 }
492
493 return 0;
494 }
You are viewing proxied material from codemadness.org. The copyright of proxied material belongs to its original authors. Any comments or complaints in relation to proxied material should be directed to the original authors of the content concerned. Please see the disclaimer for more details.