💾 Archived View for gemini.rmf-dev.com › repo › Vaati › tuimarket › files › 3d413e1b46c15fa5937d3873… captured on 2023-03-20 at 18:37:11. Gemini links have been rewritten to link to archived content
-=-=-=-=-=-=-
0 #include <stdio.h>
1 #include <stdlib.h>
2 #include <stdint.h>
3 #include <unistd.h>
4 #include <fcntl.h>
5 #include <string.h>
6 #include <pwd.h>
7 #include <time.h>
8 #include <pthread.h>
9 #include <errno.h>
10 #include <curl/curl.h>
11 #include "termbox.h"
12 #include "strlcpy.h"
13
14 #ifndef PATH_MAX
15 #define PATH_MAX 1024
16 #endif
17 #define SIZEOF(X) sizeof(X) / sizeof(*X)
18
19 #define INTERVAL 5 /* update informations every x seconds */
20
21 struct symbol {
22 char symbol[16];
23 char name[256];
24 char state[128];
25 float price;
26 float previous_price;
27 struct symbol *next;
28 };
29 struct symbol *symbols = NULL;
30
31 const char query[] =
32 "https://query1.finance.yahoo.com/v7/finance/"
33 "quote?lang=en-US®ion=US&corsDomain=finance.yahoo.com&"
34 "fields=regularMarketChange,regularMarketPrice,shortName&"
35 "symbols=%s";
36
37 char *url = NULL;
38
39 const char *paths[] = {
40 ".config/tuimarket/symbols",
41 ".tuimarket/symbols",
42 ".tuimarket_symbols",
43 };
44
45 struct mem {
46 char *memory;
47 size_t size;
48 };
49 struct mem chunk;
50
51 static size_t writecb(void *contents, size_t size, size_t nmemb, void *userp) {
52
53 size_t realsize = size * nmemb;
54 struct mem *mem = (struct mem*)userp;
55
56 mem->memory = realloc(mem->memory, mem->size + realsize + 1);
57 if(mem->memory == NULL) {
58 printf("not enough memory\n");
59 return 0;
60 }
61
62 memcpy(&(mem->memory[mem->size]), contents, realsize);
63 mem->size += realsize;
64 mem->memory[mem->size] = 0;
65 return realsize;
66 }
67
68 char *handle_url(char *url, size_t *len) {
69
70 CURL *curl_handle;
71 CURLcode res;
72
73 chunk.memory = malloc(1);
74 chunk.size = 0;
75
76 curl_handle = curl_easy_init();
77 curl_easy_setopt(curl_handle, CURLOPT_URL, url);
78 curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, writecb);
79 curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, (void*)&chunk);
80 curl_easy_setopt(curl_handle, CURLOPT_USERAGENT, "libcurl-agent/1.0");
81
82 res = curl_easy_perform(curl_handle);
83 curl_easy_cleanup(curl_handle);
84
85 if(res != CURLE_OK) {
86 printf("curl_easy_perform() failed: %s\n",
87 curl_easy_strerror(res));
88 return NULL;
89 }
90
91 *len = chunk.size;
92
93 return chunk.memory;
94 }
95
96 static ssize_t get_home(char *buf, size_t length) {
97
98 struct passwd *pw;
99 char *home;
100 int fd;
101
102 home = getenv("HOME");
103 if (home) {
104 fd = open(home, O_DIRECTORY);
105 if (fd > -1) {
106 close(fd);
107 return strlcpy(buf, home, length);
108 }
109 }
110
111 pw = getpwuid(geteuid());
112 if (!pw) return -1;
113 fd = open(pw->pw_dir, O_DIRECTORY);
114 if (fd < 0) {
115 close(fd);
116 return -1;
117 }
118 return strlcpy(buf, pw->pw_dir, length);
119 }
120
121 static int load_symbols() {
122
123 FILE *f = NULL;
124 size_t i;
125 ssize_t len;
126 char home[PATH_MAX], path[PATH_MAX];
127
128 len = get_home(home, sizeof(home));
129 if (len == -1) return -1;
130
131 i = 0;
132 while (i < SIZEOF(paths)) {
133 snprintf(path, sizeof(path), "%s/%s", home, paths[i++]);
134 f = fopen(path, "r");
135 if (f) break;
136 }
137 if (!f) return -1;
138
139 while (1) {
140
141 struct symbol s = {0}, *new;
142 size_t len;
143
144 if (!fgets(s.symbol, sizeof(s.symbol), f)) break;
145
146 len = strnlen(s.symbol, sizeof(s.symbol));
147 if (!len || len > sizeof(s.symbol)) break;
148
149 if (s.symbol[len - 1] == '\n') s.symbol[len - 1] = '\0';
150
151 new = malloc(sizeof(struct symbol));
152 if (!new) return -1;
153
154 *new = s;
155 new->next = symbols;
156 symbols = new;
157 }
158 fclose(f);
159
160 return 0;
161 }
162
163 const char str_price[] = "\"regularMarketPrice\":";
164 const char str_old_price[] = "\"regularMarketPreviousClose\":";
165 const char str_state[] = "\"marketState\":\"";
166 const char str_name[] = "\"shortName\":\"";
167
168 static int find_copy(const char *haystack, const char *needle,
169 size_t needle_len, char stop, char *buf, size_t len) {
170
171 char *start, *end;
172
173 start = strstr(haystack, needle);
174 if (!start) return -1;
175
176 start += needle_len - 1;
177 end = start;
178 while (*end && *end != stop) end++;
179
180 if (!*end || (size_t)(end - start) > len) return -1;
181
182 memcpy(buf, start, end - start);
183 buf[end - start] = '\0';
184
185 return 0;
186 }
187
188 static char* make_url() {
189
190 size_t len, i;
191 struct symbol *symbol;
192 char *list, *data;
193
194 len = 0;
195 symbol = symbols;
196 while (symbol) {
197 len += strnlen(symbol->symbol, sizeof(symbol->symbol)) + 1;
198 symbol = symbol->next;
199 }
200 len++;
201
202 list = malloc(len);
203 if (!list) {
204 printf("not enough memory\n");
205 return NULL;
206 }
207
208 i = 0;
209 symbol = symbols;
210 while (symbol) {
211 i += strlcpy(&list[i], symbol->symbol, len - i);
212 list[i++] = ',';
213 symbol = symbol->next;
214 }
215 list[i - 1] = '\0';
216
217 len += sizeof(query);
218 data = malloc(len);
219 snprintf(data, len, query, list);
220
221 free(list);
222
223 return data;
224 }
225
226 static int update_symbols() {
227
228 struct symbol *symbol;
229 char buf[64], *start, *end, *data;
230 size_t len;
231 int ret = -1;
232
233 data = handle_url(url, &len);
234 if (!data) return -1;
235
236 symbol = symbols;
237 start = data;
238 end = start + len - 1;
239 *end = 0;
240 next:
241 if (find_copy(data, str_price, sizeof(str_price), ',', buf,
242 sizeof(buf)))
243 goto clean;
244 symbol->price = atof(buf);
245
246 if (find_copy(data, str_old_price, sizeof(str_old_price), ',', buf,
247 sizeof(buf)))
248 goto clean;
249 symbol->previous_price = atof(buf);
250
251 if (find_copy(data, str_state, sizeof(str_state), '"', symbol->state,
252 sizeof(symbol->state)))
253 goto clean;
254
255 if (find_copy(data, str_name, sizeof(str_name), '"', symbol->name,
256 sizeof(symbol->name)))
257 goto clean;
258
259 data = strstr(data, str_price);
260 while (data && *data && data < end) {
261 if (*data == '{') {
262 if (!strstr(data, str_price)) break;
263 symbol = symbol->next;
264 goto next;
265 }
266 data++;
267 }
268
269 ret = 0;
270 clean:
271 free(start);
272
273 return ret;
274 }
275
276 void *update_thread(void *ptr) {
277 int *run = ptr, counter;
278 while (*run) {
279 update_symbols();
280 counter = 0;
281 while (counter++ < INTERVAL && *run)
282 sleep(1);
283 }
284 return ptr;
285 }
286
287 #define COL_SYMBOL 2
288 #define COL_NAME (COL_SYMBOL + sizeof("Symbol |") + 1)
289 #define COL_VARIATION (-(signed)sizeof("Variation") - 8)
290 #define COL_PRICE (COL_VARIATION -(signed)sizeof("| Price") - 3)
291
292 int main(int argc, char *argv[]) {
293
294 int scroll = 0, run;
295 pthread_t thread;
296
297 if (!argc) return sizeof(*argv);
298
299 if (load_symbols()) {
300 printf("cannot find symbols file\n");
301 return -1;
302 }
303
304 url = make_url();
305 if (!url) return -1;
306
307 curl_global_init(CURL_GLOBAL_ALL);
308
309 if (tb_init()) {
310 printf("tb_init: %s\n", strerror(errno));
311 return -1;
312 }
313
314 run = 1;
315 pthread_create(&thread, NULL, update_thread, &run);
316
317 while (1) {
318
319 struct tb_event ev;
320 struct symbol *symbol;
321 int i, w, h, bottom;
322
323 w = tb_width();
324 h = tb_height();
325
326 tb_clear();
327 i = 0;
328 while (i++ < w) tb_set_cell(i, 0, ' ', TB_BLACK, TB_WHITE);
329
330 tb_print(COL_SYMBOL - 2, 0, TB_BLACK, TB_WHITE, " Symbol");
331 tb_print(COL_NAME - 2, 0, TB_BLACK, TB_WHITE, "| Name");
332 tb_print(w + COL_PRICE - 2, 0, TB_BLACK, TB_WHITE, "| Price");
333 tb_print(w + COL_VARIATION - 2, 0,
334 TB_BLACK, TB_WHITE, "| Variation");
335
336 i = 1;
337 symbol = symbols;
338 bottom = 1;
339 while (symbol) {
340 int gain, j;
341 if (scroll >= i) {
342 symbol = symbol->next;
343 i++;
344 continue;
345 }
346 gain = (symbol->price >= symbol->previous_price);
347 tb_print(COL_SYMBOL, i - scroll,
348 TB_DEFAULT, TB_DEFAULT,
349 symbol->symbol);
350 tb_print(COL_NAME, i - scroll, TB_DEFAULT, TB_DEFAULT,
351 symbol->name);
352
353 j = w + COL_PRICE - 2;
354 while (j++ < w)
355 tb_set_cell(j, i, ' ', TB_DEFAULT, TB_DEFAULT);
356 tb_printf(w + COL_PRICE, i - scroll,
357 TB_DEFAULT, TB_DEFAULT,
358 "%.2f", symbol->price);
359 tb_printf(w + COL_VARIATION + gain, i - scroll,
360 gain ? TB_GREEN : TB_RED,
361 TB_DEFAULT, "%.2f (%.2f%%)",
362 symbol->price - symbol->previous_price,
363 symbol->price / symbol->previous_price
364 * 100 - 100);
365 symbol = symbol->next;
366 if (i - scroll > h) {
367 bottom = 0;
368 break;
369 }
370 i++;
371 }
372
373 tb_present();
374
375 if (!tb_peek_event(&ev, 1000)) {
376 if (ev.key == TB_KEY_ESC || ev.ch == 'q') break;
377 if ((ev.key == TB_KEY_ARROW_DOWN || ev.ch == 'j') &&
378 !bottom) scroll++;
379 if ((ev.key == TB_KEY_ARROW_UP || ev.ch == 'k') &&
380 scroll) scroll--;
381 }
382
383 }
384
385 run = 0;
386 tb_shutdown();
387 curl_global_cleanup();
388 free(url);
389 while (symbols) {
390 struct symbol *s = symbols;
391 symbols = symbols->next;
392 free(s);
393 }
394
395 return 0;
396 }
397