fetch messages for channels and display them
This commit is contained in:
		
							parent
							
								
									8718ef85a0
								
							
						
					
					
						commit
						c3f4f5a180
					
				| @ -14,6 +14,7 @@ SET(SOURCES | ||||
|   "include/dc/channel.h" | ||||
|   "include/dc/guild.h" | ||||
|   "include/dc/loop.h" | ||||
|   "include/dc/message.h" | ||||
|   "include/dc/refable.h" | ||||
|   "include/dc/util.h" | ||||
|   "src/account.c" | ||||
| @ -25,6 +26,7 @@ SET(SOURCES | ||||
|   "src/channel.c" | ||||
|   "src/guild.c" | ||||
|   "src/loop.c" | ||||
|   "src/message.c" | ||||
|   "src/refable.c" | ||||
|   "src/util.c" | ||||
|   ) | ||||
|  | ||||
| @ -77,6 +77,11 @@ bool dc_api_create_channel(dc_api_t api, dc_account_t login, | ||||
|                            dc_account_t *recipients, size_t nrecp, | ||||
|                            dc_channel_t *channel); | ||||
| 
 | ||||
| /**
 | ||||
|  * Fetch 50 messages for the given channel. | ||||
|  */ | ||||
| bool dc_api_get_messages(dc_api_t api, dc_account_t login, dc_channel_t c); | ||||
| 
 | ||||
| /**
 | ||||
|  * Fetch a list of friends of the login account "login". The friends are stored | ||||
|  * within the login object. | ||||
|  | ||||
| @ -5,6 +5,7 @@ | ||||
| #include <jansson.h> | ||||
| 
 | ||||
| #include <dc/account.h> | ||||
| #include <dc/message.h> | ||||
| 
 | ||||
| /**
 | ||||
|  * A discord channel. Exactly what it says on the tin. A place where one | ||||
| @ -49,10 +50,16 @@ typedef struct dc_channel_ *dc_channel_t; | ||||
| dc_channel_t dc_channel_new(void); | ||||
| dc_channel_t dc_channel_from_json(json_t *j); | ||||
| 
 | ||||
| char const *dc_channel_id(dc_channel_t c); | ||||
| 
 | ||||
| dc_channel_type_t dc_channel_type(dc_channel_t c); | ||||
| void dc_channel_set_type(dc_channel_t c, dc_channel_type_t t); | ||||
| 
 | ||||
| size_t dc_channel_recipients(dc_channel_t c); | ||||
| dc_account_t dc_channel_nthrecipient(dc_channel_t c, size_t i); | ||||
| 
 | ||||
| size_t dc_channel_messages(dc_channel_t c); | ||||
| dc_message_t dc_channel_nthmessage(dc_channel_t c, size_t i); | ||||
| void dc_channel_addmessages(dc_channel_t c, dc_message_t *m, size_t s); | ||||
| 
 | ||||
| #endif | ||||
|  | ||||
							
								
								
									
										24
									
								
								libdc/include/dc/message.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								libdc/include/dc/message.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,24 @@ | ||||
| #ifndef DC_MESSAGE_H | ||||
| #define DC_MESSAGE_H | ||||
| 
 | ||||
| #include <stdint.h> | ||||
| #include <jansson.h> | ||||
| 
 | ||||
| #include <dc/account.h> | ||||
| 
 | ||||
| struct dc_message_; | ||||
| typedef struct dc_message_ *dc_message_t; | ||||
| 
 | ||||
| dc_message_t dc_message_new(void); | ||||
| dc_message_t dc_message_from_json(json_t *j); | ||||
| json_t *dc_message_to_json(dc_message_t m); | ||||
| 
 | ||||
| char const *dc_message_id(dc_message_t m); | ||||
| char const *dc_message_channel_id(dc_message_t m); | ||||
| char const *dc_message_timestamp(dc_message_t m); | ||||
| char const *dc_message_content(dc_message_t m); | ||||
| dc_account_t dc_message_author(dc_message_t m); | ||||
| 
 | ||||
| int dc_message_compare(dc_message_t *a, dc_message_t *b); | ||||
| 
 | ||||
| #endif | ||||
| @ -2,6 +2,47 @@ | ||||
| 
 | ||||
| #include "internal.h" | ||||
| 
 | ||||
| bool dc_api_get_messages(dc_api_t api, dc_account_t login, dc_channel_t c) | ||||
| { | ||||
|     bool ret = false; | ||||
|     char *url = NULL; | ||||
|     json_t *reply = NULL, *i = NULL; | ||||
|     GPtrArray *msgs = NULL; | ||||
|     size_t idx = 0; | ||||
| 
 | ||||
|     return_if_true(api == NULL || login == NULL || c == NULL, false); | ||||
| 
 | ||||
|     msgs = g_ptr_array_new_with_free_func((GDestroyNotify)dc_unref); | ||||
|     goto_if_true(msgs == NULL, cleanup); | ||||
| 
 | ||||
|     asprintf(&url, "channels/%s/messages", dc_channel_id(c)); | ||||
|     goto_if_true(url == NULL, cleanup); | ||||
| 
 | ||||
|     reply = dc_api_call_sync(api, "GET", TOKEN(login), url, NULL); | ||||
|     goto_if_true(reply == NULL, cleanup); | ||||
|     goto_if_true(!json_is_array(reply), cleanup); | ||||
| 
 | ||||
|     json_array_foreach(reply, idx, i) { | ||||
|         dc_message_t m = dc_message_from_json(i); | ||||
|         g_ptr_array_add(msgs, m); | ||||
|     } | ||||
| 
 | ||||
|     dc_channel_addmessages(c, (dc_message_t*)msgs->pdata, msgs->len); | ||||
|     ret = true; | ||||
| 
 | ||||
| cleanup: | ||||
| 
 | ||||
|     if (msgs != NULL) { | ||||
|         g_ptr_array_unref(msgs); | ||||
|         msgs = NULL; | ||||
|     } | ||||
| 
 | ||||
|     json_decref(reply); | ||||
|     free(url); | ||||
| 
 | ||||
|     return ret; | ||||
| } | ||||
| 
 | ||||
| bool dc_api_create_channel(dc_api_t api, dc_account_t login, | ||||
|                            dc_account_t *recipients, size_t nrecp, | ||||
|                            dc_channel_t *channel) | ||||
|  | ||||
| @ -43,6 +43,8 @@ struct dc_channel_ | ||||
|     /*  application ID of the group DM creator if it is bot-created
 | ||||
|      */ | ||||
|     char *application_id; | ||||
| 
 | ||||
|     GPtrArray *messages; | ||||
| }; | ||||
| 
 | ||||
| static void dc_channel_free(dc_channel_t c) | ||||
| @ -62,6 +64,11 @@ static void dc_channel_free(dc_channel_t c) | ||||
|         c->recipients = NULL; | ||||
|     } | ||||
| 
 | ||||
|     if (c->messages != NULL) { | ||||
|         g_ptr_array_unref(c->messages); | ||||
|         c->messages = NULL; | ||||
|     } | ||||
| 
 | ||||
|     free(c); | ||||
| } | ||||
| 
 | ||||
| @ -76,6 +83,10 @@ dc_channel_t dc_channel_new(void) | ||||
|         (GDestroyNotify)dc_unref | ||||
|         ); | ||||
| 
 | ||||
|     c->messages = g_ptr_array_new_with_free_func( | ||||
|         (GDestroyNotify)dc_unref | ||||
|         ); | ||||
| 
 | ||||
|     return dc_ref(c); | ||||
| } | ||||
| 
 | ||||
| @ -214,6 +225,12 @@ json_t *dc_channel_to_json(dc_channel_t c) | ||||
|     return j; | ||||
| } | ||||
| 
 | ||||
| char const *dc_channel_id(dc_channel_t c) | ||||
| { | ||||
|     return_if_true(c == NULL, NULL); | ||||
|     return c->id; | ||||
| } | ||||
| 
 | ||||
| dc_channel_type_t dc_channel_type(dc_channel_t c) | ||||
| { | ||||
|     return_if_true(c == NULL, -1); | ||||
| @ -238,3 +255,30 @@ dc_account_t dc_channel_nthrecipient(dc_channel_t c, size_t i) | ||||
|     return_if_true(i >= c->recipients->len, NULL); | ||||
|     return g_ptr_array_index(c->recipients, i); | ||||
| } | ||||
| 
 | ||||
| size_t dc_channel_messages(dc_channel_t c) | ||||
| { | ||||
|     return_if_true(c == NULL || c->messages == NULL, 0); | ||||
|     return c->messages->len; | ||||
| } | ||||
| 
 | ||||
| dc_message_t dc_channel_nthmessage(dc_channel_t c, size_t i) | ||||
| { | ||||
|     return_if_true(c == NULL || c->messages == NULL, NULL); | ||||
|     return_if_true(i >= c->messages->len, NULL); | ||||
|     return g_ptr_array_index(c->messages, i); | ||||
| } | ||||
| 
 | ||||
| void dc_channel_addmessages(dc_channel_t c, dc_message_t *m, size_t s) | ||||
| { | ||||
|     return_if_true(c == NULL || c->messages == NULL,); | ||||
|     return_if_true(m == NULL || s == 0,); | ||||
| 
 | ||||
|     size_t i = 0; | ||||
| 
 | ||||
|     for (i = 0; i < s; i++) { | ||||
|         g_ptr_array_add(c->messages, dc_ref(m[i])); | ||||
|     } | ||||
| 
 | ||||
|     g_ptr_array_sort(c->messages, (GCompareFunc)dc_message_compare); | ||||
| } | ||||
|  | ||||
| @ -30,4 +30,6 @@ | ||||
| #define return_if_true(v,r) do { if (v) return r; } while(0) | ||||
| #define goto_if_true(v,l) do { if (v) goto l; } while(0) | ||||
| 
 | ||||
| #define TOKEN(l) (dc_account_token(l)) | ||||
| 
 | ||||
| #endif | ||||
|  | ||||
							
								
								
									
										155
									
								
								libdc/src/message.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										155
									
								
								libdc/src/message.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,155 @@ | ||||
| #include <dc/message.h> | ||||
| #include "internal.h" | ||||
| 
 | ||||
| struct dc_message_ | ||||
| { | ||||
|     dc_refable_t ref; | ||||
| 
 | ||||
|     char *id; | ||||
|     char *timestamp; | ||||
|     char *content; | ||||
|     char *channel_id; | ||||
| 
 | ||||
|     time_t ts; | ||||
| 
 | ||||
|     dc_account_t author; | ||||
| }; | ||||
| 
 | ||||
| static void dc_message_parse_timestamp(dc_message_t m); | ||||
| 
 | ||||
| static void dc_message_free(dc_message_t m) | ||||
| { | ||||
|     return_if_true(m == NULL,); | ||||
| 
 | ||||
|     free(m->id); | ||||
|     free(m->timestamp); | ||||
|     free(m->content); | ||||
|     free(m->channel_id); | ||||
| 
 | ||||
|     dc_unref(m->author); | ||||
| 
 | ||||
|     free(m); | ||||
| } | ||||
| 
 | ||||
| dc_message_t dc_message_new(void) | ||||
| { | ||||
|     dc_message_t m = calloc(1, sizeof(struct dc_message_)); | ||||
|     return_if_true(m == NULL, NULL); | ||||
| 
 | ||||
|     m->ref.cleanup = (dc_cleanup_t)dc_message_free; | ||||
|     return dc_ref(m); | ||||
| } | ||||
| 
 | ||||
| dc_message_t dc_message_from_json(json_t *j) | ||||
| { | ||||
|     dc_message_t m = NULL; | ||||
|     json_t *val = NULL; | ||||
|     return_if_true(j == NULL || !json_is_object(j), NULL); | ||||
| 
 | ||||
|     m = dc_message_new(); | ||||
|     return_if_true(m == NULL, NULL); | ||||
| 
 | ||||
|     val = json_object_get(j, "id"); | ||||
|     goto_if_true(val == NULL || !json_is_string(val), error); | ||||
|     m->id = strdup(json_string_value(val)); | ||||
| 
 | ||||
|     val = json_object_get(j, "timestamp"); | ||||
|     goto_if_true(val == NULL || !json_is_string(val), error); | ||||
|     m->timestamp = strdup(json_string_value(val)); | ||||
| 
 | ||||
|     dc_message_parse_timestamp(m); | ||||
| 
 | ||||
|     val = json_object_get(j, "content"); | ||||
|     goto_if_true(val == NULL || !json_is_string(val), error); | ||||
|     m->content = strdup(json_string_value(val)); | ||||
| 
 | ||||
|     val = json_object_get(j, "channel_id"); | ||||
|     goto_if_true(val == NULL || !json_is_string(val), error); | ||||
|     m->channel_id = strdup(json_string_value(val)); | ||||
| 
 | ||||
|     val = json_object_get(j, "author"); | ||||
|     goto_if_true(val == NULL || !json_is_object(val), error); | ||||
|     m->author = dc_account_from_json(val); | ||||
| 
 | ||||
|     return m; | ||||
| 
 | ||||
| error: | ||||
| 
 | ||||
|     dc_unref(m); | ||||
|     return NULL; | ||||
| } | ||||
| 
 | ||||
| json_t *dc_message_to_json(dc_message_t m) | ||||
| { | ||||
|     return_if_true(m == NULL, NULL); | ||||
| 
 | ||||
|     json_t *j = json_object(); | ||||
|     return_if_true(j == NULL, NULL); | ||||
| 
 | ||||
|     if (m->id != NULL) { | ||||
|         json_object_set_new(j, "id", json_string(m->id)); | ||||
|     } | ||||
| 
 | ||||
|     if (m->timestamp != NULL) { | ||||
|         json_object_set_new(j, "timestamp", json_string(m->timestamp)); | ||||
|     } | ||||
| 
 | ||||
|     if (m->channel_id != NULL) { | ||||
|         json_object_set_new(j, "channel_id", json_string(m->channel_id)); | ||||
|     } | ||||
| 
 | ||||
|     if (m->author != NULL) { | ||||
|         json_t *a = dc_account_to_json(m->author); | ||||
|         json_object_set_new(j, "author", a); | ||||
|     } | ||||
| 
 | ||||
|     json_object_set_new(j, "content", json_string(m->content)); | ||||
| 
 | ||||
|     return j; | ||||
| } | ||||
| 
 | ||||
| static void dc_message_parse_timestamp(dc_message_t m) | ||||
| { | ||||
|     return_if_true(m == NULL || m->timestamp == NULL,); | ||||
|     struct tm t = {0}; | ||||
| 
 | ||||
|     strptime(m->timestamp, "%Y-%m-%dT%H:%M:%S", &t); | ||||
|     m->ts = timegm(&t); | ||||
| } | ||||
| 
 | ||||
| int dc_message_compare(dc_message_t *a, dc_message_t *b) | ||||
| { | ||||
|     return_if_true(a == NULL || *a == NULL || | ||||
|                    b == NULL || *b == NULL, 0); | ||||
|     return (*a)->ts - (*b)->ts; | ||||
| } | ||||
| 
 | ||||
| char const *dc_message_id(dc_message_t m) | ||||
| { | ||||
|     return_if_true(m == NULL, NULL); | ||||
|     return m->id; | ||||
| } | ||||
| 
 | ||||
| char const *dc_message_channel_id(dc_message_t m) | ||||
| { | ||||
|     return_if_true(m == NULL, NULL); | ||||
|     return m->channel_id; | ||||
| } | ||||
| 
 | ||||
| char const *dc_message_timestamp(dc_message_t m) | ||||
| { | ||||
|     return_if_true(m == NULL, NULL); | ||||
|     return m->timestamp; | ||||
| } | ||||
| 
 | ||||
| char const *dc_message_content(dc_message_t m) | ||||
| { | ||||
|     return_if_true(m == NULL, NULL); | ||||
|     return m->content; | ||||
| } | ||||
| 
 | ||||
| dc_account_t dc_message_author(dc_message_t m) | ||||
| { | ||||
|     return_if_true(m == NULL, NULL); | ||||
|     return m->author; | ||||
| } | ||||
| @ -58,6 +58,8 @@ wchar_t *util_readkey(int esc, WINDOW *win); | ||||
| 
 | ||||
| void exit_main(void); | ||||
| 
 | ||||
| wchar_t *s_convert(char const *s); | ||||
| 
 | ||||
| int strwidth(char const *string); | ||||
| char *read_char(FILE *stream); | ||||
| 
 | ||||
|  | ||||
| @ -39,6 +39,11 @@ bool ncdc_cmd_login(ncdc_mainwindow_t n, size_t ac, wchar_t **av) | ||||
|         goto cleanup; | ||||
|     } | ||||
| 
 | ||||
|     if (!dc_api_get_friends(api, acc)) { | ||||
|         LOG(n, L"login: %ls: failed to load friends", av[1]); | ||||
|         goto cleanup; | ||||
|     } | ||||
| 
 | ||||
|     current_account = dc_ref(acc); | ||||
|     LOG(n, L"login: %ls: authentication successful", av[1]); | ||||
|     ret = true; | ||||
|  | ||||
| @ -48,12 +48,16 @@ bool ncdc_cmd_msg(ncdc_mainwindow_t n, size_t ac, wchar_t **av) | ||||
|     if (c == NULL) { | ||||
|         /* no? create a new window and switch to it
 | ||||
|          */ | ||||
| 
 | ||||
|         if (!dc_api_create_channel(api, current_account, &f, 1, &c)) { | ||||
|             LOG(n, L"msg: failed to create channel"); | ||||
|             goto cleanup; | ||||
|         } | ||||
| 
 | ||||
|         if (!dc_api_get_messages(api, current_account, c)) { | ||||
|             LOG(n, L"msg: failed to fetch messages in channel"); | ||||
|             goto cleanup; | ||||
|         } | ||||
| 
 | ||||
|         v = ncdc_textview_new(); | ||||
|         goto_if_true(v == NULL, cleanup); | ||||
| 
 | ||||
|  | ||||
| @ -133,16 +133,11 @@ wchar_t const *ncdc_textview_nthline(ncdc_textview_t v, size_t idx) | ||||
|     return g_ptr_array_index(v->par, idx); | ||||
| } | ||||
| 
 | ||||
| void ncdc_textview_render(ncdc_textview_t v, WINDOW *win, int lines, int cols) | ||||
| static void | ||||
| ncdc_textview_render_par(ncdc_textview_t v, WINDOW *win, int lines, int cols) | ||||
| { | ||||
|     ssize_t i = 0, needed_lines = 0, atline = 0; | ||||
| 
 | ||||
|     werase(win); | ||||
| 
 | ||||
|     if (v->par == NULL || v->par->len == 0) { | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     for (i = v->par->len-1; i >= 0; i--) { | ||||
|         wchar_t const *w = ncdc_textview_nthline(v, i); | ||||
|         size_t sz = wcslen(w); | ||||
| @ -159,3 +154,92 @@ void ncdc_textview_render(ncdc_textview_t v, WINDOW *win, int lines, int cols) | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| static wchar_t *ncdc_textview_format(dc_message_t m) | ||||
| { | ||||
|     wchar_t *c = NULL, *author = NULL, *message = NULL; | ||||
|     size_t clen = 0; | ||||
|     FILE *f = open_wmemstream(&c, &clen); | ||||
|     wchar_t *ret = NULL; | ||||
|     dc_account_t a = dc_message_author(m); | ||||
| 
 | ||||
|     return_if_true(f == NULL, NULL); | ||||
| 
 | ||||
|     author = s_convert(dc_account_fullname(a)); | ||||
|     goto_if_true(author == NULL, cleanup); | ||||
| 
 | ||||
|     message = s_convert(dc_message_content(m)); | ||||
|     goto_if_true(message == NULL, cleanup); | ||||
| 
 | ||||
|     fwprintf(f, L"< %ls> %ls", author, message); | ||||
| 
 | ||||
|     fclose(f); | ||||
|     f = NULL; | ||||
| 
 | ||||
|     ret = c; | ||||
|     c = NULL; | ||||
| 
 | ||||
| cleanup: | ||||
| 
 | ||||
|     if (f != NULL) { | ||||
|         fclose(f); | ||||
|     } | ||||
| 
 | ||||
|     free(author); | ||||
|     free(message); | ||||
|     free(c); | ||||
| 
 | ||||
|     return ret; | ||||
| } | ||||
| 
 | ||||
| static void | ||||
| ncdc_textview_render_msgs(ncdc_textview_t v, WINDOW *win, int lines, int cols) | ||||
| { | ||||
|     ssize_t i = 0, atline = 0, msgs = 0; | ||||
| 
 | ||||
|     msgs = dc_channel_messages(v->channel); | ||||
|     atline = lines; | ||||
| 
 | ||||
|     for (i = msgs-1; i >= 0; i--) { | ||||
|         dc_message_t m = dc_channel_nthmessage(v->channel, i); | ||||
|         wchar_t *s = ncdc_textview_format(m); | ||||
|         wchar_t const *end = s, *last = NULL; | ||||
|         size_t len = 0; | ||||
|         size_t needed_lines = 0; | ||||
| 
 | ||||
|         /* count each line, and, see if it is longer than COLS
 | ||||
|          */ | ||||
|         while ((end = wcschr(end, '\n')) != NULL) { | ||||
|             ++needed_lines; | ||||
| 
 | ||||
|             len = wcswidth(last, (end - last)); | ||||
|             needed_lines += (len % cols); | ||||
|             last = end; | ||||
|         } | ||||
| 
 | ||||
|         if (last == NULL) { | ||||
|             last = s; | ||||
|         } | ||||
| 
 | ||||
|         len = wcswidth(last, wcslen(last)); | ||||
|         needed_lines += (len / cols) + 1; | ||||
| 
 | ||||
|         if ((atline - needed_lines) >= 0) { | ||||
|             atline -= needed_lines; | ||||
|             mvwaddwstr(win, atline, 0, s); | ||||
|         } | ||||
| 
 | ||||
|         free(s); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void ncdc_textview_render(ncdc_textview_t v, WINDOW *win, int lines, int cols) | ||||
| { | ||||
|     werase(win); | ||||
| 
 | ||||
|     if (v->par != NULL && v->par->len > 0) { | ||||
|         ncdc_textview_render_par(v, win, lines, cols); | ||||
|     } else if (v->channel != NULL) { | ||||
|         ncdc_textview_render_msgs(v, win, lines, cols); | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -168,3 +168,17 @@ char *w_convert(wchar_t const *w) | ||||
|     wcstombs(ptr, w, sz); | ||||
|     return ptr; | ||||
| } | ||||
| 
 | ||||
| wchar_t *s_convert(char const *w) | ||||
| { | ||||
|     size_t sz = 0; | ||||
|     wchar_t *ptr = NULL; | ||||
| 
 | ||||
|     sz = mbstowcs(NULL, w, 0); | ||||
| 
 | ||||
|     ptr = calloc(sz+1, sizeof(wchar_t)); | ||||
|     return_if_true(ptr == NULL, NULL); | ||||
| 
 | ||||
|     mbstowcs(ptr, w, sz); | ||||
|     return ptr; | ||||
| } | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user