ncdc - discord client
This commit is contained in:
		
						commit
						8972538d13
					
				
							
								
								
									
										47
									
								
								CMakeLists.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								CMakeLists.txt
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,47 @@ | ||||
| CMAKE_MINIMUM_REQUIRED(VERSION 3.0) | ||||
| 
 | ||||
| FIND_PACKAGE(PkgConfig) | ||||
| FIND_PACKAGE(Threads) | ||||
| 
 | ||||
| PKG_CHECK_MODULES(JANSSON REQUIRED jansson) | ||||
| PKG_CHECK_MODULES(CURL REQUIRED libcurl) | ||||
| PKG_CHECK_MODULES(EVENT REQUIRED libevent libevent_pthreads) | ||||
| PKG_CHECK_MODULES(GLIB2 REQUIRED glib-2.0) | ||||
| 
 | ||||
| SET(TARGET "ncdc") | ||||
| 
 | ||||
| SET(SOURCES | ||||
|   "include/ncdc/account.h" | ||||
|   "include/ncdc/api.h" | ||||
|   "include/ncdc/apisync.h" | ||||
|   "include/ncdc/ncdc.h" | ||||
|   "include/ncdc/refable.h" | ||||
|   "src/account.c" | ||||
|   "src/api.c" | ||||
|   "src/apisync.c" | ||||
|   "src/ncdc.c" | ||||
|   "src/refable.c" | ||||
|   ) | ||||
| 
 | ||||
| ADD_DEFINITIONS("-Wall -Werror -std=c11 -D_GNU_SOURCE") | ||||
| 
 | ||||
| INCLUDE_DIRECTORIES("include" | ||||
|   ${JANSSON_INCLUDE_DIRS} | ||||
|   ${CURL_INCLUDE_DIRS} | ||||
|   ${EVENT_INCLUDE_DIRS} | ||||
|   ${GLIB2_INCLUDE_DIRS} | ||||
|   ) | ||||
| LINK_DIRECTORIES(${JANSSON_LIBRARY_DIRS} | ||||
|   ${CURL_LIBRARY_DIRS} | ||||
|   ${EVENT_LIBRARY_DIRS} | ||||
|   ${GLIB2_LIBRARY_DIRS} | ||||
|   ) | ||||
| 
 | ||||
| ADD_EXECUTABLE(${TARGET} ${SOURCES}) | ||||
| TARGET_LINK_LIBRARIES(${TARGET} | ||||
|   ${JANSSON_LIBRARIES} | ||||
|   ${CURL_LIBRARIES} | ||||
|   ${EVENT_LIBRARIES} | ||||
|   ${GLIB2_LIBRARIES} | ||||
|   ${CMAKE_THREAD_LIBS_INIT} | ||||
|   ) | ||||
							
								
								
									
										25
									
								
								include/ncdc/account.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								include/ncdc/account.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,25 @@ | ||||
| #ifndef NCDC_ACCOUNT_H | ||||
| #define NCDC_ACCOUNT_H | ||||
| 
 | ||||
| #include <ncdc/ncdc.h> | ||||
| 
 | ||||
| struct ncdc_account_; | ||||
| typedef struct ncdc_account_ *ncdc_account_t; | ||||
| 
 | ||||
| ncdc_account_t ncdc_account_new(void); | ||||
| ncdc_account_t ncdc_account_new2(char const *email, char const *pass); | ||||
| 
 | ||||
| void ncdc_account_set_email(ncdc_account_t a, char const *email); | ||||
| char const *ncdc_account_email(ncdc_account_t a); | ||||
| 
 | ||||
| void ncdc_account_set_password(ncdc_account_t a, char const *password); | ||||
| char const *ncdc_account_password(ncdc_account_t a); | ||||
| 
 | ||||
| void ncdc_account_set_id(ncdc_account_t a, char const *id); | ||||
| char const *ncdc_account_id(ncdc_account_t a); | ||||
| 
 | ||||
| void ncdc_account_set_token(ncdc_account_t a, char const *token); | ||||
| char const *ncdc_account_token(ncdc_account_t a); | ||||
| bool ncdc_account_has_token(ncdc_account_t a); | ||||
| 
 | ||||
| #endif | ||||
							
								
								
									
										34
									
								
								include/ncdc/api.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								include/ncdc/api.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,34 @@ | ||||
| #ifndef NCDC_API_H | ||||
| #define NCDC_API_H | ||||
| 
 | ||||
| #include <ncdc/ncdc.h> | ||||
| 
 | ||||
| #include <ncdc/apisync.h> | ||||
| #include <ncdc/account.h> | ||||
| 
 | ||||
| struct ncdc_api_; | ||||
| typedef struct ncdc_api_ *ncdc_api_t; | ||||
| 
 | ||||
| ncdc_api_t ncdc_api_new(void); | ||||
| 
 | ||||
| void ncdc_api_set_curl_multi(ncdc_api_t api, CURLM *curl); | ||||
| void ncdc_api_set_event_base(ncdc_api_t api, struct event_base *base); | ||||
| 
 | ||||
| /* call this function in case the MULTI has told us that some
 | ||||
|  * transfer has finished. | ||||
|  */ | ||||
| void ncdc_api_signal(ncdc_api_t api, CURL *easy, int code); | ||||
| 
 | ||||
| /* internal curl stuff
 | ||||
|  */ | ||||
| ncdc_api_sync_t ncdc_api_call(ncdc_api_t api, char const *token, | ||||
|                               char const *method, json_t *j); | ||||
| json_t *ncdc_api_call_sync(ncdc_api_t api, char const *token, | ||||
|                            char const *method, json_t *j); | ||||
| 
 | ||||
| bool ncdc_api_authenticate(ncdc_api_t api, ncdc_account_t account); | ||||
| bool ncdc_api_userinfo(ncdc_api_t api, ncdc_account_t logion, | ||||
|                        ncdc_account_t user); | ||||
| 
 | ||||
| 
 | ||||
| #endif | ||||
							
								
								
									
										20
									
								
								include/ncdc/apisync.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								include/ncdc/apisync.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,20 @@ | ||||
| #ifndef NCDC_API_ASYNC_H | ||||
| #define NCDC_API_ASYNC_H | ||||
| 
 | ||||
| #include <ncdc/ncdc.h> | ||||
| 
 | ||||
| struct ncdc_api_sync_; | ||||
| typedef struct ncdc_api_sync_ *ncdc_api_sync_t; | ||||
| 
 | ||||
| ncdc_api_sync_t ncdc_api_sync_new(CURLM *curl, CURL *easy); | ||||
| 
 | ||||
| FILE *ncdc_api_sync_stream(ncdc_api_sync_t sync); | ||||
| char const *ncdc_api_sync_data(ncdc_api_sync_t sync); | ||||
| size_t ncdc_api_sync_datalen(ncdc_api_sync_t sync); | ||||
| int ncdc_api_sync_code(ncdc_api_sync_t sync); | ||||
| struct curl_slist *ncdc_api_sync_list(ncdc_api_sync_t sync); | ||||
| 
 | ||||
| bool ncdc_api_sync_wait(ncdc_api_sync_t sync); | ||||
| void ncdc_api_sync_finish(ncdc_api_sync_t sync, int code); | ||||
| 
 | ||||
| #endif | ||||
							
								
								
									
										31
									
								
								include/ncdc/ncdc.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								include/ncdc/ncdc.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,31 @@ | ||||
| #ifndef NCDC_H | ||||
| #define NCDC_H | ||||
| 
 | ||||
| #include <stdlib.h> | ||||
| #include <stdint.h> | ||||
| #include <stdbool.h> | ||||
| #include <string.h> | ||||
| 
 | ||||
| #include <unistd.h> | ||||
| #include <sys/stat.h> | ||||
| #include <errno.h> | ||||
| 
 | ||||
| #include <pthread.h> | ||||
| 
 | ||||
| #include <curl/curl.h> | ||||
| 
 | ||||
| #include <jansson.h> | ||||
| 
 | ||||
| #include <glib.h> | ||||
| 
 | ||||
| #include <event.h> | ||||
| #include <event2/thread.h> | ||||
| 
 | ||||
| //#define DEBUG
 | ||||
| 
 | ||||
| #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) | ||||
| 
 | ||||
| extern char *ncdc_private_dir; | ||||
| 
 | ||||
| #endif | ||||
							
								
								
									
										16
									
								
								include/ncdc/refable.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								include/ncdc/refable.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,16 @@ | ||||
| #ifndef NCDC_REFABLE_H | ||||
| #define NCDC_REFABLE_H | ||||
| 
 | ||||
| #include <ncdc/ncdc.h> | ||||
| 
 | ||||
| typedef void (*cleanup_t)(void *); | ||||
| 
 | ||||
| typedef struct { | ||||
|     int ref; | ||||
|     cleanup_t cleanup; | ||||
| } ncdc_refable_t; | ||||
| 
 | ||||
| void *ncdc_ref(void *); | ||||
| void ncdc_unref(void *); | ||||
| 
 | ||||
| #endif | ||||
							
								
								
									
										121
									
								
								src/account.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										121
									
								
								src/account.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,121 @@ | ||||
| #include <ncdc/account.h> | ||||
| #include <ncdc/refable.h> | ||||
| 
 | ||||
| #include <stdlib.h> | ||||
| #include <string.h> | ||||
| #include <stdio.h> | ||||
| 
 | ||||
| struct ncdc_account_ | ||||
| { | ||||
|     ncdc_refable_t ref; /* do not move anything above ref */ | ||||
| 
 | ||||
|     char *email; | ||||
|     char *password; | ||||
| 
 | ||||
|     /* internal ID
 | ||||
|      */ | ||||
|     char *id; | ||||
| 
 | ||||
|     /* authentication token
 | ||||
|      */ | ||||
|     char *token; | ||||
| }; | ||||
| 
 | ||||
| static void ncdc_account_free(ncdc_account_t ptr) | ||||
| { | ||||
|     return_if_true(ptr == NULL,); | ||||
| 
 | ||||
|     free(ptr->email); | ||||
|     free(ptr->password); | ||||
| 
 | ||||
|     free(ptr); | ||||
| } | ||||
| 
 | ||||
| ncdc_account_t ncdc_account_new(void) | ||||
| { | ||||
|     ncdc_account_t ptr = calloc(1, sizeof(struct ncdc_account_)); | ||||
| 
 | ||||
|     ptr->ref.cleanup = (cleanup_t)ncdc_account_free; | ||||
| 
 | ||||
|     return ncdc_ref(ptr); | ||||
| } | ||||
| 
 | ||||
| ncdc_account_t ncdc_account_new2(char const *email, char const *pass) | ||||
| { | ||||
|     ncdc_account_t ptr = ncdc_account_new(); | ||||
| 
 | ||||
|     if (ptr != NULL) { | ||||
|         ncdc_account_set_email(ptr, email); | ||||
|         ncdc_account_set_password(ptr, pass); | ||||
|     } | ||||
| 
 | ||||
|     return ptr; | ||||
| } | ||||
| 
 | ||||
| void ncdc_account_set_email(ncdc_account_t a, char const *email) | ||||
| { | ||||
|     return_if_true(a == NULL,); | ||||
|     return_if_true(email == NULL,); | ||||
| 
 | ||||
|     free(a->email); | ||||
|     a->email = strdup(email); | ||||
| } | ||||
| 
 | ||||
| char const *ncdc_account_email(ncdc_account_t a) | ||||
| { | ||||
|     return_if_true(a == NULL, NULL); | ||||
|     return a->email; | ||||
| } | ||||
| 
 | ||||
| void ncdc_account_set_password(ncdc_account_t a, char const *password) | ||||
| { | ||||
|     return_if_true(a == NULL,); | ||||
|     return_if_true(password == NULL,); | ||||
| 
 | ||||
|     free(a->password); | ||||
|     a->password = strdup(password); | ||||
| } | ||||
| 
 | ||||
| char const *ncdc_account_password(ncdc_account_t a) | ||||
| { | ||||
|     return_if_true(a == NULL, NULL); | ||||
|     return a->password; | ||||
| } | ||||
| 
 | ||||
| void ncdc_account_set_token(ncdc_account_t a, char const *token) | ||||
| { | ||||
|     return_if_true(a == NULL,); | ||||
| 
 | ||||
|     free(a->token); | ||||
|     a->token = NULL; | ||||
| 
 | ||||
|     if (token != NULL) { | ||||
|         a->token = strdup(token); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| char const *ncdc_account_token(ncdc_account_t a) | ||||
| { | ||||
|     return_if_true(a == NULL, NULL); | ||||
|     return a->token; | ||||
| } | ||||
| 
 | ||||
| bool ncdc_account_has_token(ncdc_account_t a) | ||||
| { | ||||
|     return_if_true(a == NULL, false); | ||||
|     return_if_true(a->token == NULL, false); | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
| void ncdc_account_set_id(ncdc_account_t a, char const *id) | ||||
| { | ||||
|     return_if_true(a == NULL,); | ||||
|     free(a->id); | ||||
|     a->id = strdup(id); | ||||
| } | ||||
| 
 | ||||
| char const *ncdc_account_id(ncdc_account_t a) | ||||
| { | ||||
|     return_if_true(a == NULL,NULL); | ||||
|     return a->id; | ||||
| } | ||||
							
								
								
									
										363
									
								
								src/api.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										363
									
								
								src/api.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,363 @@ | ||||
| #include <ncdc/api.h> | ||||
| #include <ncdc/refable.h> | ||||
| 
 | ||||
| #define DISCORD_URL "https://discordapp.com/api/v6"
 | ||||
| 
 | ||||
| #define DISCORD_USERAGENT "Mozilla/5.0 (X11; Linux x86_64; rv:67.0) Gecko/20100101 Firefox/67.0" | ||||
| #define DISCORD_API_AUTH "auth/login" | ||||
| 
 | ||||
| struct ncdc_api_ | ||||
| { | ||||
|     ncdc_refable_t ref; | ||||
| 
 | ||||
|     struct event_base *base; | ||||
|     CURLM *curl; | ||||
| 
 | ||||
|     GHashTable *syncs; | ||||
| 
 | ||||
|     char *cookie; | ||||
| }; | ||||
| 
 | ||||
| static void ncdc_api_free(ncdc_api_t ptr) | ||||
| { | ||||
|     return_if_true(ptr == NULL,); | ||||
| 
 | ||||
|     if (ptr->syncs != NULL) { | ||||
|         g_hash_table_unref(ptr->syncs); | ||||
|         ptr->syncs = NULL; | ||||
|     } | ||||
| 
 | ||||
|     free(ptr); | ||||
| } | ||||
| 
 | ||||
| ncdc_api_t ncdc_api_new(void) | ||||
| { | ||||
|     ncdc_api_t ptr = calloc(1, sizeof(struct ncdc_api_)); | ||||
|     return_if_true(ptr == NULL, NULL); | ||||
| 
 | ||||
|     ptr->ref.cleanup = (cleanup_t)ncdc_api_free; | ||||
| 
 | ||||
|     ptr->syncs = g_hash_table_new_full(g_direct_hash, g_direct_equal, | ||||
|                                        NULL, ncdc_unref | ||||
|         ); | ||||
|     if (ptr->syncs == NULL) { | ||||
|         free(ptr); | ||||
|         return NULL; | ||||
|     } | ||||
| 
 | ||||
|     return ncdc_ref(ptr); | ||||
| } | ||||
| 
 | ||||
| void ncdc_api_set_curl_multi(ncdc_api_t api, CURLM *curl) | ||||
| { | ||||
|     return_if_true(api == NULL,); | ||||
|     return_if_true(curl == NULL,); | ||||
| 
 | ||||
|     api->curl = curl; | ||||
| } | ||||
| 
 | ||||
| void ncdc_api_set_event_base(ncdc_api_t api, struct event_base *base) | ||||
| { | ||||
|     return_if_true(api == NULL,); | ||||
|     return_if_true(base == NULL,); | ||||
| 
 | ||||
|     api->base = base; | ||||
| } | ||||
| 
 | ||||
| void ncdc_api_signal(ncdc_api_t api, CURL *easy, int code) | ||||
| { | ||||
|     ncdc_api_sync_t sync = NULL; | ||||
| 
 | ||||
|     return_if_true(api == NULL,); | ||||
|     return_if_true(easy == NULL,); | ||||
| 
 | ||||
|     sync = g_hash_table_lookup(api->syncs, easy); | ||||
|     if (sync != NULL) { | ||||
|         ncdc_api_sync_finish(sync, code); | ||||
|         g_hash_table_remove(api->syncs, easy); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| #ifdef DEBUG | ||||
| static int debug_callback(CURL *handle, curl_infotype type, | ||||
|                           char *data, size_t size, | ||||
|                           void *userptr | ||||
|     ) | ||||
| { | ||||
|     switch (type) { | ||||
|     case CURLINFO_TEXT: printf("+T: %s", data); break; | ||||
|     case CURLINFO_HEADER_IN: printf(">H: %s", data); break; | ||||
|     case CURLINFO_HEADER_OUT: printf("<H: %s", data); break; | ||||
|     case CURLINFO_DATA_IN: printf(">D: %s\n", data); break; | ||||
|     case CURLINFO_DATA_OUT: printf("<D: %s\n", data); break; | ||||
|     case CURLINFO_SSL_DATA_IN: | ||||
|     case CURLINFO_SSL_DATA_OUT: | ||||
|     default: break; | ||||
|     } | ||||
| 
 | ||||
|     return 0; | ||||
| } | ||||
| #endif | ||||
| 
 | ||||
| static int header_callback(char *data, size_t sz, size_t num, ncdc_api_t api) | ||||
| { | ||||
|     char *ptr =  NULL; | ||||
| 
 | ||||
|     if ((ptr = strstr(data, "set-cookie")) != NULL) { | ||||
|         free(api->cookie); | ||||
|         api->cookie = NULL; | ||||
| 
 | ||||
|         if ((ptr = strstr(data, ":")) != NULL) { | ||||
|             api->cookie = strdup(ptr+1); | ||||
|             if ((ptr = strstr(api->cookie, ";")) != NULL) { | ||||
|                 *ptr = '\0'; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     return sz * num; | ||||
| } | ||||
| 
 | ||||
| static ncdc_api_sync_t ncdc_api_post(ncdc_api_t api, | ||||
|                                      char const *url, | ||||
|                                      char const *token, | ||||
|                                      char const *data, int64_t len) | ||||
| { | ||||
|     return_if_true(api == NULL, NULL); | ||||
|     return_if_true(api->curl == NULL, NULL); | ||||
|     return_if_true(url == NULL, NULL); | ||||
| 
 | ||||
|     CURL *c = NULL; | ||||
|     bool ret = false; | ||||
|     ncdc_api_sync_t sync = NULL; | ||||
|     struct curl_slist *l = NULL; | ||||
|     char *tmp = NULL; | ||||
|     int ptr = 0; | ||||
| 
 | ||||
|     c = curl_easy_init(); | ||||
|     goto_if_true(c == NULL, cleanup); | ||||
| 
 | ||||
|     sync = ncdc_api_sync_new(api->curl, c); | ||||
|     goto_if_true(c == NULL, cleanup); | ||||
| 
 | ||||
|     curl_easy_setopt(c, CURLOPT_URL, url); | ||||
|     curl_easy_setopt(c, CURLOPT_WRITEFUNCTION, fwrite); | ||||
|     curl_easy_setopt(c, CURLOPT_WRITEDATA, ncdc_api_sync_stream(sync)); | ||||
|     curl_easy_setopt(c, CURLOPT_HEADERFUNCTION, header_callback); | ||||
|     curl_easy_setopt(c, CURLOPT_HEADERDATA, api); | ||||
| 
 | ||||
|     if (api->cookie != NULL) { | ||||
|         curl_easy_setopt(c, CURLOPT_COOKIE, api->cookie); | ||||
|     } | ||||
| 
 | ||||
|     l = ncdc_api_sync_list(sync); | ||||
|     if (data != NULL) { | ||||
|         curl_slist_append(l, "Content-Type: application/json"); | ||||
|     } | ||||
|     curl_slist_append(l, "Accept: application/json"); | ||||
|     curl_slist_append(l, "User-Agent: " DISCORD_USERAGENT); | ||||
|     curl_slist_append(l, "Pragma: no-cache"); | ||||
|     curl_slist_append(l, "Cache-Control: no-cache"); | ||||
| 
 | ||||
|     if (token != NULL) { | ||||
|         asprintf(&tmp, "Authorization: %s", token); | ||||
|         curl_slist_append(l, tmp); | ||||
|         free(tmp); | ||||
|         tmp = NULL; | ||||
|     } | ||||
| 
 | ||||
|     curl_easy_setopt(c, CURLOPT_HTTPHEADER, l); | ||||
|     curl_easy_setopt(c, CURLOPT_FORBID_REUSE, 1L); | ||||
|     //curl_easy_setopt(c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
 | ||||
|     curl_easy_setopt(c, CURLOPT_FOLLOWLOCATION, 1L); | ||||
| 
 | ||||
| #ifdef DEBUG | ||||
|     curl_easy_setopt(c, CURLOPT_VERBOSE, 1L); | ||||
|     curl_easy_setopt(c, CURLOPT_DEBUGFUNCTION, debug_callback); | ||||
| #endif | ||||
| 
 | ||||
|     if (data != NULL) { | ||||
|         curl_easy_setopt(c, CURLOPT_POST, 1UL); | ||||
|         curl_easy_setopt(c, CURLOPT_POSTREDIR, CURL_REDIR_POST_ALL); | ||||
|         curl_easy_setopt(c, CURLOPT_COPYPOSTFIELDS, data); | ||||
|         if (len >= 0) { | ||||
|             curl_easy_setopt(c, CURLOPT_POSTFIELDSIZE_LARGE, len); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     if (curl_multi_add_handle(api->curl, c) != CURLM_OK) { | ||||
|         goto cleanup; | ||||
|     } | ||||
| 
 | ||||
|     g_hash_table_insert(api->syncs, c, ncdc_ref(sync)); | ||||
|     curl_multi_socket_action(api->curl, CURL_SOCKET_TIMEOUT, 0, &ptr); | ||||
| 
 | ||||
|     ret = true; | ||||
| 
 | ||||
| cleanup: | ||||
| 
 | ||||
|     if (!ret) { | ||||
|         ncdc_unref(sync); | ||||
|         sync = NULL; | ||||
|     } | ||||
| 
 | ||||
|     return sync; | ||||
| } | ||||
| 
 | ||||
| ncdc_api_sync_t ncdc_api_call(ncdc_api_t api, char const *token, | ||||
|                               char const *method, json_t *j) | ||||
| { | ||||
|     char *data = NULL; | ||||
|     char *url = NULL; | ||||
|     ncdc_api_sync_t s = NULL; | ||||
| 
 | ||||
|     asprintf(&url, "%s/%s", DISCORD_URL, method); | ||||
|     goto_if_true(url == NULL, cleanup); | ||||
| 
 | ||||
|     if (j != NULL) { | ||||
|         data = json_dumps(j, JSON_COMPACT); | ||||
|         goto_if_true(data == NULL, cleanup); | ||||
|     } | ||||
| 
 | ||||
|     s = ncdc_api_post(api, url, token, data, -1); | ||||
|     goto_if_true(s == NULL, cleanup); | ||||
| 
 | ||||
| cleanup: | ||||
| 
 | ||||
|     free(data); | ||||
|     data = NULL; | ||||
| 
 | ||||
|     free(url); | ||||
|     url = NULL; | ||||
| 
 | ||||
|     return s; | ||||
| } | ||||
| 
 | ||||
| json_t *ncdc_api_call_sync(ncdc_api_t api, char const *token, | ||||
|                            char const *method, json_t *j) | ||||
| { | ||||
|     ncdc_api_sync_t s = NULL; | ||||
|     json_t *reply = NULL; | ||||
| 
 | ||||
|     s = ncdc_api_call(api, token, method, j); | ||||
|     goto_if_true(s == NULL, cleanup); | ||||
| 
 | ||||
|     if (!ncdc_api_sync_wait(s)) { | ||||
|         goto cleanup; | ||||
|     } | ||||
| 
 | ||||
| #ifdef DEBUG | ||||
|     printf("api_call_sync: %d\n", ncdc_api_sync_code(s)); | ||||
| #endif | ||||
| 
 | ||||
|     reply = json_loadb(ncdc_api_sync_data(s), | ||||
|                        ncdc_api_sync_datalen(s), | ||||
|                        0, NULL | ||||
|         ); | ||||
| 
 | ||||
| cleanup: | ||||
| 
 | ||||
|     ncdc_unref(s); | ||||
|     s = NULL; | ||||
| 
 | ||||
|     return reply; | ||||
| } | ||||
| 
 | ||||
| static bool ncdc_api_error(json_t *j, int *code, char const **message) | ||||
| { | ||||
|     return_if_true(j == NULL, false); | ||||
| 
 | ||||
|     bool error = false; | ||||
|     json_t *c = NULL, *m = NULL; | ||||
| 
 | ||||
|     c = json_object_get(j, "code"); | ||||
|     if (c != NULL) { | ||||
|         error = true; | ||||
|         if (code != NULL) { | ||||
|             *code = json_integer_value(c); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     m = json_object_get(j, "message"); | ||||
|     if (m != NULL) { | ||||
|         error = true; | ||||
|         if (message != NULL) { | ||||
|             *message = json_string_value(m); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     return error; | ||||
| } | ||||
| 
 | ||||
| bool ncdc_api_authenticate(ncdc_api_t api, ncdc_account_t account) | ||||
| { | ||||
|     json_t *j = json_object(), *reply = NULL, *token = NULL; | ||||
|     bool ret = false; | ||||
| 
 | ||||
|     json_object_set_new(j, "email", | ||||
|                         json_string(ncdc_account_email(account)) | ||||
|         ); | ||||
|     json_object_set_new(j, "password", | ||||
|                         json_string(ncdc_account_password(account)) | ||||
|         ); | ||||
| 
 | ||||
|     reply = ncdc_api_call_sync(api, NULL, DISCORD_API_AUTH, j); | ||||
|     goto_if_true(reply == NULL, cleanup); | ||||
| 
 | ||||
|     if (ncdc_api_error(j, NULL, NULL)) { | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     token = json_object_get(reply, "token"); | ||||
|     if (token == NULL || !json_is_string(token)) { | ||||
|         goto cleanup; | ||||
|     } | ||||
| 
 | ||||
|     ncdc_account_set_token(account, json_string_value(token)); | ||||
|     ret = true; | ||||
| 
 | ||||
| cleanup: | ||||
| 
 | ||||
|     if (j != NULL) { | ||||
|         json_decref(j); | ||||
|         j = NULL; | ||||
|     } | ||||
| 
 | ||||
|     if (reply != NULL) { | ||||
|         json_decref(reply); | ||||
|         reply = NULL; | ||||
|     } | ||||
| 
 | ||||
|     return ret; | ||||
| } | ||||
| 
 | ||||
| bool ncdc_api_userinfo(ncdc_api_t api, ncdc_account_t login, | ||||
|                        ncdc_account_t user) | ||||
| { | ||||
|     char *url = NULL; | ||||
|     json_t *reply = NULL; | ||||
|     bool ret = false; | ||||
| 
 | ||||
|     return_if_true(api == NULL, false); | ||||
|     return_if_true(login == NULL, false); | ||||
|     return_if_true(user == NULL, false); | ||||
| 
 | ||||
|     asprintf(&url, "users/%s", ncdc_account_id(user)); | ||||
| 
 | ||||
|     reply = ncdc_api_call_sync(api, ncdc_account_token(login), url, NULL); | ||||
|     goto_if_true(reply == NULL, cleanup); | ||||
| 
 | ||||
|     /* TODO: parse json and store info in user
 | ||||
|      */ | ||||
| 
 | ||||
|     ret = true; | ||||
| 
 | ||||
| cleanup: | ||||
| 
 | ||||
|     if (reply != NULL) { | ||||
|         json_decref(reply); | ||||
|         reply = NULL; | ||||
|     } | ||||
| 
 | ||||
|     return ret; | ||||
| } | ||||
							
								
								
									
										128
									
								
								src/apisync.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										128
									
								
								src/apisync.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,128 @@ | ||||
| #include <ncdc/apisync.h> | ||||
| #include <ncdc/refable.h> | ||||
| 
 | ||||
| struct ncdc_api_sync_ | ||||
| { | ||||
|     ncdc_refable_t ref; | ||||
| 
 | ||||
|     int code; | ||||
| 
 | ||||
|     char *buffer; | ||||
|     size_t bufferlen; | ||||
|     FILE *stream; | ||||
| 
 | ||||
|     pthread_mutex_t mtx; | ||||
|     pthread_cond_t cnd; | ||||
| 
 | ||||
|     CURL *easy; | ||||
|     CURLM *curl; | ||||
|     struct curl_slist *list; | ||||
| }; | ||||
| 
 | ||||
| static void ncdc_api_sync_free(ncdc_api_sync_t s) | ||||
| { | ||||
|     return_if_true(s == NULL,); | ||||
| 
 | ||||
|     pthread_mutex_lock(&s->mtx); | ||||
| 
 | ||||
|     pthread_cond_destroy(&s->cnd); | ||||
|     pthread_mutex_destroy(&s->mtx); | ||||
| 
 | ||||
|     curl_multi_remove_handle(s->curl, s->easy); | ||||
|     curl_easy_cleanup(s->easy); | ||||
| 
 | ||||
|     if (s->stream != NULL) { | ||||
|         fclose(s->stream); | ||||
|         s->stream = NULL; | ||||
|     } | ||||
| 
 | ||||
|     free(s->buffer); | ||||
|     s->buffer = NULL; | ||||
|     s->bufferlen = 0; | ||||
| 
 | ||||
|     curl_slist_free_all(s->list); | ||||
|     s->list = NULL; | ||||
| 
 | ||||
|     free(s); | ||||
| } | ||||
| 
 | ||||
| ncdc_api_sync_t ncdc_api_sync_new(CURLM *curl, CURL *easy) | ||||
| { | ||||
|     ncdc_api_sync_t ptr = calloc(1, sizeof(struct ncdc_api_sync_)); | ||||
|     return_if_true(ptr == NULL, NULL); | ||||
| 
 | ||||
|     ptr->easy = easy; | ||||
|     ptr->ref.cleanup = (cleanup_t)ncdc_api_sync_free; | ||||
| 
 | ||||
|     ptr->stream = open_memstream(&ptr->buffer, &ptr->bufferlen); | ||||
|     if (ptr->stream == NULL) { | ||||
|         free(ptr); | ||||
|         return NULL; | ||||
|     } | ||||
| 
 | ||||
|     pthread_mutex_init(&ptr->mtx, NULL); | ||||
|     pthread_cond_init(&ptr->cnd, NULL); | ||||
| 
 | ||||
|     ptr->list = curl_slist_append(NULL, ""); | ||||
| 
 | ||||
|     return ncdc_ref(ptr); | ||||
| } | ||||
| 
 | ||||
| struct curl_slist *ncdc_api_sync_list(ncdc_api_sync_t sync) | ||||
| { | ||||
|     return_if_true(sync == NULL, NULL); | ||||
|     return sync->list; | ||||
| } | ||||
| 
 | ||||
| FILE *ncdc_api_sync_stream(ncdc_api_sync_t sync) | ||||
| { | ||||
|     return_if_true(sync == NULL, NULL); | ||||
|     return sync->stream; | ||||
| } | ||||
| 
 | ||||
| char const *ncdc_api_sync_data(ncdc_api_sync_t sync) | ||||
| { | ||||
|     return_if_true(sync == NULL, NULL); | ||||
|     return sync->buffer; | ||||
| } | ||||
| 
 | ||||
| size_t ncdc_api_sync_datalen(ncdc_api_sync_t sync) | ||||
| { | ||||
|     return_if_true(sync == NULL, 0L); | ||||
|     return sync->bufferlen; | ||||
| } | ||||
| 
 | ||||
| int ncdc_api_sync_code(ncdc_api_sync_t sync) | ||||
| { | ||||
|     return_if_true(sync == NULL, 0L); | ||||
|     return sync->code; | ||||
| } | ||||
| 
 | ||||
| bool ncdc_api_sync_wait(ncdc_api_sync_t sync) | ||||
| { | ||||
|     return_if_true(sync == NULL, false); | ||||
| 
 | ||||
|     if (sync->stream == NULL && sync->buffer != NULL) { | ||||
|         return true; | ||||
|     } | ||||
| 
 | ||||
|     pthread_mutex_lock(&sync->mtx); | ||||
|     pthread_cond_wait(&sync->cnd, &sync->mtx); | ||||
|     pthread_mutex_unlock(&sync->mtx); | ||||
| 
 | ||||
|     return (sync->stream == NULL && sync->buffer != NULL); | ||||
| } | ||||
| 
 | ||||
| void ncdc_api_sync_finish(ncdc_api_sync_t sync, int code) | ||||
| { | ||||
|     return_if_true(sync == NULL,); | ||||
| 
 | ||||
|     pthread_mutex_lock(&sync->mtx); | ||||
|     sync->code = code; | ||||
|     if (sync->stream != NULL) { | ||||
|         fclose(sync->stream); | ||||
|         sync->stream = NULL; | ||||
|     } | ||||
|     pthread_cond_broadcast(&sync->cnd); | ||||
|     pthread_mutex_unlock(&sync->mtx); | ||||
| } | ||||
							
								
								
									
										280
									
								
								src/ncdc.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										280
									
								
								src/ncdc.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,280 @@ | ||||
| #include <ncdc/ncdc.h> | ||||
| #include <ncdc/api.h> | ||||
| 
 | ||||
| #include <stdio.h> | ||||
| #include <unistd.h> | ||||
| 
 | ||||
| /* event base for libevent
 | ||||
|  */ | ||||
| struct event_base *base = NULL; | ||||
| struct event *stdin_ev = NULL; | ||||
| struct event *timer = NULL; | ||||
| 
 | ||||
| /* we loop in a different thread
 | ||||
|  */ | ||||
| static bool done = false; | ||||
| static pthread_t looper; | ||||
| 
 | ||||
| char *ncdc_private_dir = NULL; | ||||
| char *ncdc_config_file = NULL; | ||||
| 
 | ||||
| static GKeyFile *config = NULL; | ||||
| 
 | ||||
| /* global curl multi for API access
 | ||||
|  */ | ||||
| CURLM *curl = NULL; | ||||
| 
 | ||||
| ncdc_api_t api = NULL; | ||||
| 
 | ||||
| static void handle_multi_info(void); | ||||
| 
 | ||||
| static void sighandler(int sig) | ||||
| { | ||||
|     exit(3); | ||||
| } | ||||
| 
 | ||||
| static void cleanup(void) | ||||
| { | ||||
|     if (stdin_ev != NULL) { | ||||
|         event_del(stdin_ev); | ||||
|         event_free(stdin_ev); | ||||
|         stdin_ev = NULL; | ||||
|     } | ||||
| 
 | ||||
|     if (timer != NULL) { | ||||
|         evtimer_del(timer); | ||||
|         event_free(timer); | ||||
|         timer = NULL; | ||||
|     } | ||||
| 
 | ||||
|     done = true; | ||||
|     pthread_join(looper, NULL); | ||||
| 
 | ||||
|     curl_multi_cleanup(curl); | ||||
|     curl = NULL; | ||||
| 
 | ||||
|     event_base_free(base); | ||||
|     base = NULL; | ||||
| } | ||||
| 
 | ||||
| static void stdin_handler(int sock, short what, void *data) | ||||
| { | ||||
| } | ||||
| 
 | ||||
| static void mcurl_socket_handler(int sock, short what, void *data) | ||||
| { | ||||
|     int unused = 0; | ||||
| 
 | ||||
|     if ((what & EV_READ) == EV_READ) { | ||||
|         curl_multi_socket_action(curl, sock, CURL_CSELECT_IN, &unused); | ||||
|     } else if ((what & EV_WRITE) == EV_WRITE) { | ||||
|         curl_multi_socket_action(curl, sock, CURL_CSELECT_OUT, &unused); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| static void timer_handler(int sock, short what, void *data) | ||||
| { | ||||
|     int running = 0; | ||||
|     curl_multi_socket_action(curl, CURL_SOCKET_TIMEOUT, 0, &running); | ||||
| } | ||||
| 
 | ||||
| static int mcurl_timer(CURLM *curl, long timeout, void *ptr) | ||||
| { | ||||
|     int running = 0; | ||||
|     struct timeval tm; | ||||
| 
 | ||||
|     if (timeout == -1) { | ||||
|         evtimer_del(timer); | ||||
|     } else if (timeout == 0) { | ||||
|         curl_multi_socket_action(curl, CURL_SOCKET_TIMEOUT, 0, &running); | ||||
|     } else if (timeout > 0) { | ||||
|         tm.tv_sec = timeout / 1000; | ||||
|         tm.tv_usec = (timeout % 1000) * 1000; | ||||
|         evtimer_add(timer, &tm); | ||||
|     } | ||||
| 
 | ||||
|     return 0; | ||||
| } | ||||
| 
 | ||||
| static int | ||||
| mcurl_handler(CURL *easy, curl_socket_t s, int what, void *userp, void *socketp) | ||||
| { | ||||
|     struct event *event = (struct event *)socketp; | ||||
| 
 | ||||
|     if (what == CURL_POLL_REMOVE) { | ||||
|         if (event != NULL) { | ||||
|             event_del(event); | ||||
|             event_free(event); | ||||
|             curl_multi_assign(curl, s, NULL); | ||||
|         } | ||||
|     } else { | ||||
|         int stat = | ||||
|             ((what & CURL_POLL_IN) ? EV_READ : 0) | | ||||
|             ((what & CURL_POLL_OUT) ? EV_WRITE : 0) | | ||||
|             EV_PERSIST | ||||
|             ; | ||||
| 
 | ||||
|         if (event == NULL) { | ||||
|             event = event_new(base, s, stat, mcurl_socket_handler, NULL); | ||||
|             if (event == NULL) { | ||||
|                 return 0; | ||||
|             } | ||||
|             curl_multi_assign(curl, s, event); | ||||
|         } else { | ||||
|             event_del(event); | ||||
|             event_assign(event, base, s, stat, mcurl_socket_handler, NULL); | ||||
|             event_add(event, NULL); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     return 0; | ||||
| } | ||||
| 
 | ||||
| static bool init_everything(void) | ||||
| { | ||||
|     evthread_use_pthreads(); | ||||
| 
 | ||||
|     base = event_base_new(); | ||||
|     return_if_true(base == NULL, false); | ||||
| 
 | ||||
|     /* add handle for STDIN, this info will then be fed to the UI
 | ||||
|      */ | ||||
|     stdin_ev = event_new(base, 0 /* stdin */, EV_READ|EV_PERSIST, | ||||
|                          stdin_handler, NULL | ||||
|         ); | ||||
|     return_if_true(stdin_ev == NULL, false); | ||||
|     event_add(stdin_ev, NULL); | ||||
| 
 | ||||
|     timer = evtimer_new(base, timer_handler, NULL); | ||||
|     return_if_true(timer == NULL, false); | ||||
| 
 | ||||
|     /* create curl multi and feed that to the API too
 | ||||
|      */ | ||||
|     curl = curl_multi_init(); | ||||
|     return_if_true(curl == NULL, false); | ||||
| 
 | ||||
|     curl_multi_setopt(curl, CURLMOPT_SOCKETFUNCTION, mcurl_handler); | ||||
|     curl_multi_setopt(curl, CURLMOPT_TIMERFUNCTION, mcurl_timer); | ||||
| 
 | ||||
|     /* initialise event
 | ||||
|      */ | ||||
|     api = ncdc_api_new(); | ||||
|     return_if_true(api == NULL, false); | ||||
| 
 | ||||
|     ncdc_api_set_event_base(api, base); | ||||
|     ncdc_api_set_curl_multi(api, curl); | ||||
| 
 | ||||
|     config = g_key_file_new(); | ||||
|     return_if_true(config == NULL, false); | ||||
| 
 | ||||
|     g_key_file_load_from_file(config, ncdc_config_file, 0, NULL); | ||||
| 
 | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
| static void handle_multi_info(void) | ||||
| { | ||||
|     struct CURLMsg *msg = NULL; | ||||
|     int remain = 0; | ||||
| 
 | ||||
|     /* check for finished multi curls
 | ||||
|      */ | ||||
|     msg = curl_multi_info_read(curl, &remain); | ||||
|     if (msg != NULL) { | ||||
|         if (remain <= 0) { | ||||
|             if (evtimer_pending(timer, NULL)) { | ||||
|                 evtimer_del(timer); | ||||
|             } | ||||
|         } | ||||
|         if (msg->msg == CURLMSG_DONE) { | ||||
|             ncdc_api_signal(api, msg->easy_handle, msg->data.result); | ||||
|         } | ||||
|     } else { | ||||
|         usleep(10 * 1000); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| static void *loop_thread(void *arg) | ||||
| { | ||||
|     int ret = 0; | ||||
| 
 | ||||
|     while (!done) { | ||||
|         ret = event_base_loop(base, EVLOOP_ONCE|EVLOOP_NONBLOCK); | ||||
|         if (ret < 0) { | ||||
|             break; | ||||
|         } | ||||
| 
 | ||||
|         handle_multi_info(); | ||||
|     } | ||||
| 
 | ||||
|     return NULL; | ||||
| } | ||||
| 
 | ||||
| static ncdc_account_t account_from_config(void) | ||||
| { | ||||
|     char const *email = NULL; | ||||
|     char const *password = NULL; | ||||
|     void *ptr = NULL; | ||||
| 
 | ||||
|     email = g_key_file_get_string(config, "account", "email", NULL); | ||||
|     password = g_key_file_get_string(config, "account", "password", NULL); | ||||
| 
 | ||||
|     return_if_true(email == NULL || password == NULL, NULL); | ||||
| 
 | ||||
|     ptr = ncdc_account_new2(email, password); | ||||
|     ncdc_account_set_id(ptr, "@me"); | ||||
| 
 | ||||
|     return ptr; | ||||
| } | ||||
| 
 | ||||
| int main(int ac, char **av) | ||||
| { | ||||
|     bool done = false; | ||||
| 
 | ||||
|     atexit(cleanup); | ||||
| 
 | ||||
|     signal(SIGINT, sighandler); | ||||
| 
 | ||||
|     if (getenv("HOME") == NULL) { | ||||
|         fprintf(stderr, "your environment doesn't contain HOME; pls fix\n"); | ||||
|         return 3; | ||||
|     } | ||||
| 
 | ||||
|     asprintf(&ncdc_private_dir, "%s/.ncdc", getenv("HOME")); | ||||
|     if (mkdir(ncdc_private_dir, 0755) < 0) { | ||||
|         if (errno != EEXIST) { | ||||
|             fprintf(stderr, "failed to make %s: %s\n", ncdc_private_dir, | ||||
|                     strerror(errno)); | ||||
|             return 3; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     asprintf(&ncdc_config_file, "%s/config", ncdc_private_dir); | ||||
| 
 | ||||
|     if (!init_everything()) { | ||||
|         return 3; | ||||
|     } | ||||
| 
 | ||||
|     done = false; | ||||
|     if (pthread_create(&looper, NULL, loop_thread, &done)) { | ||||
|         return 3; | ||||
|     } | ||||
| 
 | ||||
|     ncdc_account_t a = account_from_config(); | ||||
|     if (a == NULL) { | ||||
|         fprintf(stderr, "no account specified in config file; sho-sho!\n"); | ||||
|         return 3; | ||||
|     } | ||||
| 
 | ||||
|     if (!ncdc_api_authenticate(api, a)) { | ||||
|         fprintf(stderr, "authentication failed, wrong password?\n"); | ||||
|         return 3; | ||||
|     } | ||||
| 
 | ||||
|     if (!ncdc_api_userinfo(api, a, a)) { | ||||
|         fprintf(stderr, "failed to get user information\n"); | ||||
|         return 3; | ||||
|     } | ||||
| 
 | ||||
|     return 0; | ||||
| } | ||||
							
								
								
									
										25
									
								
								src/refable.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								src/refable.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,25 @@ | ||||
| #include <ncdc/refable.h> | ||||
| 
 | ||||
| void *ncdc_ref(void *arg) | ||||
| { | ||||
|     ncdc_refable_t *ptr = NULL; | ||||
| 
 | ||||
|     return_if_true(arg == NULL,NULL); | ||||
| 
 | ||||
|     ptr = (ncdc_refable_t *)arg; | ||||
|     ++ptr->ref; | ||||
| 
 | ||||
|     return arg; | ||||
| } | ||||
| 
 | ||||
| void ncdc_unref(void *arg) | ||||
| { | ||||
|     ncdc_refable_t *ptr = NULL; | ||||
| 
 | ||||
|     return_if_true(arg == NULL,); | ||||
| 
 | ||||
|     ptr = (ncdc_refable_t *)arg; | ||||
|     if ((--ptr->ref) <= 0 && ptr->cleanup != NULL) { | ||||
|         ptr->cleanup(arg); | ||||
|     } | ||||
| } | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user