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…
Reference in New Issue
Block a user