소스 검색

Initial commit

master
Alexander Memer 1 년 전
커밋
d9169beb4a
12개의 변경된 파일1545개의 추가작업 그리고 0개의 파일을 삭제
  1. 2
    0
      .gitignore
  2. 51
    0
      CMakeLists.txt
  3. 59
    0
      cmake/FindJansson.cmake
  4. 3
    0
      examples/CMakeLists.txt
  5. 94
    0
      examples/main.c
  6. 246
    0
      include/discord.h
  7. 35
    0
      include/log.h
  8. 63
    0
      include/requests.h
  9. 45
    0
      lib/discord.c
  10. 136
    0
      lib/log.c
  11. 486
    0
      lib/requests.c
  12. 325
    0
      lib/rest.c

+ 2
- 0
.gitignore 파일 보기

@@ -0,0 +1,2 @@
.idea
cmake-build*

+ 51
- 0
CMakeLists.txt 파일 보기

@@ -0,0 +1,51 @@
cmake_minimum_required(VERSION 3.12)
project(libdiscord C)
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake/")

option(EXAMPLES "Build examples" ON)

# Get the current working branch
execute_process(
COMMAND git rev-parse --abbrev-ref HEAD
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
OUTPUT_VARIABLE GIT_BRANCH
OUTPUT_STRIP_TRAILING_WHITESPACE
)

# Get the latest abbreviated commit hash of the working branch
execute_process(
COMMAND git log -1 --format=%h
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
OUTPUT_VARIABLE GIT_COMMIT_HASH
OUTPUT_STRIP_TRAILING_WHITESPACE
)

add_definitions("-DGIT_COMMIT_HASH=${GIT_COMMIT_HASH}")
add_definitions("-DGIT_BRANCH=${GIT_BRANCH}")


include_directories(include)

find_package(CURL)
if(NOT CURL_FOUND)
message(SEND_ERROR "Failed to find CURL")
return()
else()
include_directories(${CURL_INCLUDE_DIR})
endif()

find_package(Jansson)
if (NOT JANSSON_FOUND)
message(SEND_ERROR "Failed to find Jansson")
return()
else()
include_directories(${JANSSON_INCLUDE_DIRS})
endif()
set(CMAKE_C_STANDARD 99)

add_library(discord lib/discord.c include/discord.h lib/rest.c lib/requests.c include/requests.h lib/log.c include/log.h)
target_link_libraries(discord ${JANSSON_LIBRARIES} ${CURL_LIBRARIES})

if (EXAMPLES)
add_subdirectory(examples)
endif()

+ 59
- 0
cmake/FindJansson.cmake 파일 보기

@@ -0,0 +1,59 @@
# - Try to find Jansson
# Once done this will define
#
# JANSSON_FOUND - system has Jansson
# JANSSON_INCLUDE_DIRS - the Jansson include directory
# JANSSON_LIBRARIES - Link these to use Jansson
#
# Copyright (c) 2011 Lee Hambley <lee.hambley@gmail.com>
#
# Redistribution and use is allowed according to the terms of the New
# BSD license.
# For details see the accompanying COPYING-CMAKE-SCRIPTS file.
#

if (JANSSON_LIBRARIES AND JANSSON_INCLUDE_DIRS)
# in cache already
set(JANSSON_FOUND TRUE)
else (JANSSON_LIBRARIES AND JANSSON_INCLUDE_DIRS)
find_path(JANSSON_INCLUDE_DIR
NAMES
jansson.h
PATHS
/usr/include
/usr/local/include
/opt/local/include
/sw/include
)

find_library(JANSSON_LIBRARY
NAMES
jansson
PATHS
/usr/lib
/usr/local/lib
/opt/local/lib
/sw/lib
)

set(JANSSON_INCLUDE_DIRS
${JANSSON_INCLUDE_DIR}
)

if (JANSSON_LIBRARY)
set(JANSSON_LIBRARIES
${JANSSON_LIBRARIES}
${JANSSON_LIBRARY}
)
endif (JANSSON_LIBRARY)

include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(Jansson DEFAULT_MSG
JANSSON_LIBRARIES JANSSON_INCLUDE_DIRS)

# show the JANSSON_INCLUDE_DIRS and JANSSON_LIBRARIES variables only in the advanced view
mark_as_advanced(JANSSON_INCLUDE_DIRS JANSSON_LIBRARIES)

endif (JANSSON_LIBRARIES AND JANSSON_INCLUDE_DIRS)



+ 3
- 0
examples/CMakeLists.txt 파일 보기

@@ -0,0 +1,3 @@
include_directories("${CMAKE_SOURCE_DIR}/include")
add_executable(example main.c)
target_link_libraries(example discord)

+ 94
- 0
examples/main.c 파일 보기

@@ -0,0 +1,94 @@
//
// Created by Memer on 24.08.18.
// Copyright (c) 2018 Alexander Memer. All rights reserved.
//

#include <discord.h>
#include <stdlib.h>
#include <string.h>
#include <log.h>
#include <getopt.h>

int main(int argc, char **argv)
{
struct ld_context_info info;

info.log_level = 0;

while (1)
{
int c;

static struct option long_options[] =
{
{"bot-token", required_argument, 0, 't'},
{"help", no_argument, 0, 'h'},
{"log-level", required_argument, 0, 'l'},
{0, 0, 0, 0}
};

int option_index = 0;
c = getopt_long(argc, argv, "t:hl:", long_options, &option_index);

if (c == -1)
{
break;
}

switch (c) {
case 'h':
printf("%s: [-t bot_token]\n");
return 0;
case 't':
info.bot_token = strdup(optarg);
break;
case 'l':
info.log_level = atoi(optarg);
break;
default:
abort();
}
}

if (!info.bot_token)
{
log_fatal("no bot token specified");
return -1;
}
struct ld_context *ctx;

ctx = ld_create_context(&info);
if (ctx == NULL)
{
fprintf(stderr, "Failed to create libdiscord context");
return -1;
}

printf("%s\n", ld_get_gateway(ctx));

struct ld_gateway_bot_resp *r = ld_get_gateway_bot(ctx);
if (!r)
{
printf("Error (%d): %s", ctx->last_rest_status->what, ctx->last_rest_status->text);
return -1;
}
printf("%s:%d\n", r->url, r->shards);

free(r);


int a = ld_create_message(ctx, "444130990252752898", "test");

if (a)
{
return -1;
}

guild_channel_t channel;

ld_get_channel(ctx, "444130990252752898", &channel);

log_trace(channel.guild_id);

return 0;
}

+ 246
- 0
include/discord.h 파일 보기

@@ -0,0 +1,246 @@
//
// Created by Memer on 24.08.18.
//

#ifndef _DISCORD_H
#define _DISCORD_H

#include <stdbool.h>

#include <curl/curl.h>

#define LD_API_ENDPOINT "https://discordapp.com/api"

enum
{
LD_REST_UNAUTHORIZED = 401,
LD_REST_BAD_REQUEST = 400,
LD_REST_FORBIDDEN = 403,
LD_REST_NOT_FOUND = 404,
LD_REST_METHOD_NOT_ALLOWED = 405,
LD_REST_TOO_MANY_REQUESTS = 429,
LD_REST_GATEWAY_UNAVAILABLE = 502,
LD_REST_SERVER_ERROR = 500,
LD_REST_UNKNOWN = 1
};

struct ld_context
{
// TODO: Websocket stuff
char *bot_token;
char *session_id;

bool hello_triggered;

int heartbeat_interval;
int heartbeat_acks;
int sequence;

struct ld_rest_error *last_rest_status;

CURL *curl_handle;

// ld_user_t self;
};

struct ld_context_info
{
char *bot_token;

int log_level;
};

struct ld_gateway_bot_resp
{
char *url;
int shards;
};

struct ld_rest_error
{
int what;
char *text;
};

enum game_types {
GAME = 0,
STREAMING = 1
};

enum channel_types {
GUILD_TEXT,
DM,
GUILD_VOICE,
GROUP_DM,
GUILD_CATEGORY
};

enum {
DISPATCH,
HEARTBEAT,
IDENTIFY,
PRESENCE,
VOICE_STATE,
VOICE_PING,
RESUME,
RECONNECT,
REQUEST_MEMBERS,
INVALIDATE_SESSION,
HELLO,
HEARTBEAT_ACK
};

typedef struct guild_channel {
char *id;
char *guild_id;
char *name;
int type;
int position;
bool nsfw;
// TODO: permission_overwrites
char *topic;
char *last_message_id;
int bitrate;
int user_limit;
// TODO: recipients
char *icon;
char *owner_id;
char *application_id;
char *parent_id;
char *last_pin_timestamp;
} guild_channel_t;

typedef struct thumbnail {
char* url;
char* proxy_url;
int height;
int width;
} thumbnail_t;

typedef struct video {
char* url;
int height;
int width;
} video_t;

typedef struct image {
char* url;
char* proxy_url;
int height;
int width;
} image_t;

typedef struct provider {
char* name;
char* url;
} provider_t;

typedef struct author {
char* name;
char* url;
char* icon_url;
char* proxy_icon_url;
} author_t;

typedef struct footer {
char* text;
char* icon_url;
char* proxy_icon_url;
} footer_t;

typedef struct field {
char* name;
char* value;
int _inline;
} field_t;

typedef struct embed {
char* title;
char* type;
char* description;
char* url;
char* timestamp;
int color;
footer_t footer;
image_t image;
thumbnail_t thumbnail;
video_t video;
provider_t provider;
author_t author;
field_t fields;
} embed_t;

typedef struct user {
char* id;
char* username;
char* discriminator;
char* avatar;
int bot;
int mfa_enabled;
int verified;
char* email;
} user_t;

typedef struct dm_channel {
char *id;
int is_private;
user_t recipient;
char *last_message_id;
} dm_channel_t;

typedef struct game {
char *name;
int type;
char *url; // Only if type is STREAMING (1)
} game_t;

typedef struct presence_update {
user_t user; // Can be partial
game_t game; // May be null
char *guild_id;
char *status;
char *roles[200];
} presence_update_t;

typedef struct emoji {
char* id;
char* name;
} emoji_t;

typedef struct reaction {
int counter;
int me;
emoji_t _emoji;
} reaction_t;

typedef struct message {
char* id;
char* channel_id;
user_t author;
char* content;
char* timestamp;
char* edited_timestamp;
int tts;
int mention_everyone;
user_t mentions[1024];
/*
TODO:
mention_roles
attachments
embeds
nonce
*/
embed_t embeds[1024];
reaction_t reactions[1024];
int pinned;
char* webhook_id;
} message_t;

struct ld_context *ld_create_context(struct ld_context_info *info);

char *ld_get_gateway(struct ld_context *ctx);
struct ld_gateway_bot_resp *ld_get_gateway_bot(struct ld_context *ctx);
int ld_create_message(struct ld_context *ctx, char *channel_id, char *content);
int ld_get_channel(struct ld_context *ctx, char *channel_id, guild_channel_t *target);

#endif // _DISCORD_H

+ 35
- 0
include/log.h 파일 보기

@@ -0,0 +1,35 @@
/**
* Copyright (c) 2017 rxi
*
* This library is free software; you can redistribute it and/or modify it
* under the terms of the MIT license. See `log.c` for details.
*/

#ifndef LOG_H
#define LOG_H

#include <stdio.h>
#include <stdarg.h>

#define LOG_VERSION "0.1.0"

typedef void (*log_LockFn)(void *udata, int lock);

enum { LOG_TRACE, LOG_DEBUG, LOG_INFO, LOG_WARN, LOG_ERROR, LOG_FATAL };

#define log_trace(...) log_log(LOG_TRACE, __FILE__, __LINE__, __VA_ARGS__)
#define log_debug(...) log_log(LOG_DEBUG, __FILE__, __LINE__, __VA_ARGS__)
#define log_info(...) log_log(LOG_INFO, __FILE__, __LINE__, __VA_ARGS__)
#define log_warn(...) log_log(LOG_WARN, __FILE__, __LINE__, __VA_ARGS__)
#define log_error(...) log_log(LOG_ERROR, __FILE__, __LINE__, __VA_ARGS__)
#define log_fatal(...) log_log(LOG_FATAL, __FILE__, __LINE__, __VA_ARGS__)

void log_set_udata(void *udata);
void log_set_lock(log_LockFn fn);
void log_set_fp(FILE *fp);
void log_set_level(int level);
void log_set_quiet(int enable);

void log_log(int level, const char *file, int line, const char *fmt, ...);

#endif

+ 63
- 0
include/requests.h 파일 보기

@@ -0,0 +1,63 @@
#ifndef REQUESTS_H
#define REQUESTS_H

/*
* requests.h -- librequests: header file
*
* The MIT License (MIT)
*
* Copyright (c) 2014 Mark Mossberg <mark.mossberg@gmail.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/

#include <assert.h>
#include <string.h>
#include <stdlib.h>
#include <curl/curl.h>
#include <sys/utsname.h>

#define __LIBREQ_VERS__ "v0.2"

typedef struct {
long code;
char *url;
char *text;
size_t size;
char **req_hdrv;
int req_hdrc;
char **resp_hdrv;
int resp_hdrc;
int ok;
} req_t;

CURL *requests_init(req_t *req);
void requests_close(req_t *req);
CURLcode requests_get(CURL *curl, req_t *req, char *url);
CURLcode requests_post(CURL *curl, req_t *req, char *url, char *data);
CURLcode requests_put(CURL *curl, req_t *req, char *url, char *data);
CURLcode requests_get_headers(CURL *curl, req_t *req, char *url,
char **custom_hdrv, int custom_hdrc);
CURLcode requests_post_headers(CURL *curl, req_t *req, char *url, char *data,
char **custom_hdrv, int custom_hdrc);
CURLcode requests_put_headers(CURL *curl, req_t *req, char *url, char *data,
char **custom_hdrv, int custom_hdrc);
char *requests_url_encode(CURL *curl, char **data, int data_size);

#endif

+ 45
- 0
lib/discord.c 파일 보기

@@ -0,0 +1,45 @@
//
// Created by Memer on 24.08.18.
//

#include <stdlib.h>
#include <string.h>

#include <discord.h>
#include <log.h>

struct ld_context *ld_create_context(struct ld_context_info *info)
{
struct ld_context *ctx;

// Initializing context
ctx = malloc(sizeof(struct ld_context));

// Initializing CURL handle
if ((ctx->curl_handle = curl_easy_init()) == NULL)
{
log_fatal("Failed to initialize curl handle");
return NULL;
}

if (!info->bot_token)
{
log_fatal("info->bot_token = NULL");
return NULL;
}

ctx->bot_token = malloc(strlen(info->bot_token) + 1);
strcpy(ctx->bot_token, info->bot_token);

log_trace("ctx->bot_token = %s", ctx->bot_token);

log_set_level(info->log_level);

ctx->hello_triggered = false;

ctx->heartbeat_interval = 0;
ctx->heartbeat_acks = 0;
ctx->sequence = 0;

return ctx;
}

+ 136
- 0
lib/log.c 파일 보기

@@ -0,0 +1,136 @@
/*
* Copyright (c) 2017 rxi
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <time.h>

#include <log.h>

static struct {
void *udata;
log_LockFn lock;
FILE *fp;
int level;
int quiet;
} L;


static const char *level_names[] = {
"TRACE", "DEBUG", "INFO", "WARN", "ERROR", "FATAL"
};

#ifdef LOG_USE_COLOR
static const char *level_colors[] = {
"\x1b[94m", "\x1b[36m", "\x1b[32m", "\x1b[33m", "\x1b[31m", "\x1b[35m"
};
#endif


static void lock(void) {
if (L.lock) {
L.lock(L.udata, 1);
}
}


static void unlock(void) {
if (L.lock) {
L.lock(L.udata, 0);
}
}


void log_set_udata(void *udata) {
L.udata = udata;
}


void log_set_lock(log_LockFn fn) {
L.lock = fn;
}


void log_set_fp(FILE *fp) {
L.fp = fp;
}


void log_set_level(int level) {
L.level = level;
}


void log_set_quiet(int enable) {
L.quiet = enable ? 1 : 0;
}


void log_log(int level, const char *file, int line, const char *fmt, ...) {
if (level < L.level) {
return;
}

/* Acquire lock */
lock();

/* Get current time */
time_t t = time(NULL);
struct tm *lt = localtime(&t);

/* Log to stderr */
if (!L.quiet) {
va_list args;
char buf[16];
buf[strftime(buf, sizeof(buf), "%H:%M:%S", lt)] = '\0';
#ifdef LOG_USE_COLOR
fprintf(
stderr, "%s %s%-5s\x1b[0m \x1b[90m%s:%d:\x1b[0m ",
buf, level_colors[level], level_names[level], file, line);
#else
fprintf(stderr, "%s %-5s %s:%d: ", buf, level_names[level], file, line);
#endif
va_start(args, fmt);
vfprintf(stderr, fmt, args);
va_end(args);
fprintf(stderr, "\n");
fflush(stderr);
}

/* Log to file */
if (L.fp) {
va_list args;
char buf[32];
buf[strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", lt)] = '\0';
fprintf(L.fp, "%s %-5s %s:%d: ", buf, level_names[level], file, line);
va_start(args, fmt);
vfprintf(L.fp, fmt, args);
va_end(args);
fprintf(L.fp, "\n");
fflush(L.fp);
}

/* Release lock */
unlock();
}

+ 486
- 0
lib/requests.c 파일 보기

@@ -0,0 +1,486 @@
/*
* requests.c -- librequests: libcurl wrapper implementation
*
* The MIT License (MIT)
*
* Copyright (c) 2014 Mark Mossberg <mark.mossberg@gmail.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/

#include "requests.h"

static int IS_FIRST = 1;

/*
* Prototypes
*/
static void common_opt(CURL *curl, req_t *req);
static char *user_agent(void);
static int check_ok(long code);
static CURLcode requests_pt(CURL *curl, req_t *req, char *url, char *data,
char **custom_hdrv, int custom_hdrc, int put_flag);
static int hdrv_append(char ***hdrv, int *hdrc, char *new);
static CURLcode process_custom_headers(struct curl_slist **slist,
req_t *req, char **custom_hdrv,
int custom_hdrc);

/*
* requests_init - Initializes requests struct data members
*
* Returns libcurl handle on success, or NULL on failure.
*
* @req: reference to req_t to be initialized
*/
CURL *requests_init(req_t *req)
{

/* if this is not their first request, free previous memory */
if (!IS_FIRST)
requests_close(req);

req->code = 0;
req->url = NULL;
req->size = 0;
req->req_hdrc = 0;
req->resp_hdrc = 0;
req->ok = -1;

req->text = calloc(1, 1);
if (req->text == NULL)
return NULL;

req->req_hdrv = calloc(1, 1);
if (req->req_hdrv == NULL)
return NULL;

req->resp_hdrv = calloc(1, 1);
if (req->resp_hdrv == NULL)
return NULL;

IS_FIRST = 0;
return curl_easy_init();
}

/*
* requests_close - Calls curl clean up and free allocated memory
*
* @req: requests struct
*/
void requests_close(req_t *req)
{
for (int i = 0; i < req->resp_hdrc; i++)
free(req->resp_hdrv[i]);

for (int i = 0; i < req->req_hdrc; i++)
free(req->req_hdrv[i]);

free(req->text);
free(req->resp_hdrv);
free(req->req_hdrv);

IS_FIRST = 1;
}

/*
* resp_callback - Callback function for requests, may be called multiple
* times per request. Allocates memory and assembles response data.
*
* Note: `content' will not be NULL terminated.
*/
static size_t resp_callback(char *content, size_t size, size_t nmemb,
req_t *userdata)
{
size_t real_size = size * nmemb;

/* extra 1 is for NULL terminator */
userdata->text = realloc(userdata->text, userdata->size + real_size + 1);
if (userdata->text == NULL)
return -1;

userdata->size += real_size;

/* create NULL terminated version of `content' */
char *responsetext = strndup(content, real_size + 1);
if (responsetext == NULL)
return -1;

strncat(userdata->text, responsetext, real_size);

free(responsetext);
return real_size;
}

/*
* header_callback - Callback function for headers, called once for each
* header. Allocates memory and assembles headers into string array.
*
* Note: `content' will not be NULL terminated.
*/
static size_t header_callback(char *content, size_t size, size_t nmemb,
req_t *userdata)
{
size_t real_size = size * nmemb;

/* the last header is always "\r\n" which we'll intentionally skip */
if (strcmp(content, "\r\n") == 0)
return real_size;

if (hdrv_append(&userdata->resp_hdrv, &userdata->resp_hdrc, content))
return -1;

return real_size;
}

/*
* requests_get - Performs GET request and populates req struct text member
* with request response, code with response code, and size with size of
* response.
*
* Returns the CURLcode return code provided from curl_easy_perform. CURLE_OK
* is returned on success.
*
* @curl: libcurl handle
* @req: request struct
* @url: url to send request to
*/
CURLcode requests_get(CURL *curl, req_t *req, char *url)
{
CURLcode rc;
char *ua = user_agent();
req->url = url;
long code;

common_opt(curl, req);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, resp_callback);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, req);
curl_easy_setopt(curl, CURLOPT_USERAGENT, ua);
rc = curl_easy_perform(curl);
if (rc != CURLE_OK)
return rc;
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &code);

req->code = code;
req->ok = check_ok(code);
curl_easy_cleanup(curl);
free(ua);

return rc;
}

/*
* requests_get_headers - Performs GET request (same as above) but allows
* custom headers.
*
* Returns the CURLcode return code provided from curl_easy_perform. CURLE_OK
* is returned on success.
*
* @curl: libcurl handle
* @req: request struct
* @url: url to send request to
* @custom_hdrv: char* array of custom headers
* @custom_hdrc: length of `custom_hdrv`
*/
CURLcode requests_get_headers(CURL *curl, req_t *req, char *url,
char **custom_hdrv, int custom_hdrc)
{
CURLcode rc;
struct curl_slist *slist = NULL;
char *ua = user_agent();
req->url = url;
long code;

/* headers */
if (custom_hdrv != NULL) {
rc = process_custom_headers(&slist, req, custom_hdrv, custom_hdrc);
if (rc != CURLE_OK)
return rc;
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, slist);
}

common_opt(curl, req);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, resp_callback);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, req);
curl_easy_setopt(curl, CURLOPT_USERAGENT, ua);
rc = curl_easy_perform(curl);
if (rc != CURLE_OK)
return rc;
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &code);

req->code = code;
if (slist != NULL)
curl_slist_free_all(slist);
req->ok = check_ok(code);
curl_easy_cleanup(curl);
free(ua);

return rc;
}

/*
* requests_url_encode - Url encoding function. Takes as input an array of
* char strings and the size of the array. The array should consist of keys
* and the corresponding value immediately after in the array. There must be
* an even number of array elements (one value for every key).
*
* Returns pointer to url encoded string if successful, or NULL if
* unsuccessful.
*
* @curl: libcurl handle
* @data: char* array as described above
* @data-size: length of array
*/
char *requests_url_encode(CURL *curl, char **data, int data_size)
{
char *key, *val, *tmp;
int offset;
size_t term_size;
size_t tmp_len;

if (data_size % 2 != 0)
return NULL;

/* loop through and get total sum of lengths */
size_t total_size = 0;
for (int i = 0; i < data_size; i++) {
tmp = data[i];
tmp_len = strlen(tmp);
total_size += tmp_len;
}

char encoded[total_size]; /* clear junk bytes */
snprintf(encoded, total_size, "%s", "");

/* loop in groups of two, assembling key/val pairs */
for (int i = 0; i < data_size; i+=2) {
key = data[i];
val = data[i+1];
offset = i == 0 ? 2 : 3; /* =, \0 and maybe & */
term_size = strlen(key) + strlen(val) + offset;
char term[term_size];
if (i == 0)
snprintf(term, term_size, "%s=%s", key, val);
else
snprintf(term, term_size, "&%s=%s", key, val);
strncat(encoded, term, strlen(term));
}

char *full_encoded = curl_easy_escape(curl, encoded, strlen(encoded));

return full_encoded;
}

CURLcode requests_post(CURL *curl, req_t *req, char *url, char *data)
{
return requests_pt(curl, req, url, data, NULL, 0, 0);
}

CURLcode requests_put(CURL *curl, req_t *req, char *url, char *data)
{
return requests_pt(curl, req, url, data, NULL, 0, 1);
}

CURLcode requests_post_headers(CURL *curl, req_t *req, char *url, char *data,
char **custom_hdrv, int custom_hdrc)
{
return requests_pt(curl, req, url, data, custom_hdrv, custom_hdrc, 0);
}

CURLcode requests_put_headers(CURL *curl, req_t *req, char *url, char *data,
char **custom_hdrv, int custom_hdrc)
{
return requests_pt(curl, req, url, data, custom_hdrv, custom_hdrc, 1);
}

/*
* requests_pt - Performs POST or PUT request using supplied data and populates
* req struct text member with request response, code with response code, and
* size with size of response. To submit no data, use NULL for data, and 0 for
* data_size.
*
* Returns CURLcode provided from curl_easy_perform. CURLE_OK is returned on
* success. -1 returned if there are issues with libcurl's internal linked list
* append.
*
* Typically this function isn't used directly, use requests_post() or
* requests_put() instead.
*
* @curl: libcurl handle
* @req: request struct
* @url: url to send request to
* @data: url encoded data to send in request body
* @custom_hdrv: char* array of custom headers
* @custom_hdrc: length of `custom_hdrv`
* @put_flag: if not zero, sends PUT request, otherwise uses POST
*/
static CURLcode requests_pt(CURL *curl, req_t *req, char *url, char *data,
char **custom_hdrv, int custom_hdrc, int put_flag)
{
CURLcode rc;
struct curl_slist *slist = NULL;
req->url = url;

/* body data */
if (data != NULL) {
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, data);
} else {
/* content length header defaults to -1, which causes request to fail
sometimes, so we need to manually set it to 0 */
char *cl_header = "Content-Length: 0";
slist = curl_slist_append(slist, cl_header);
if (slist == NULL)
return -1;
if (custom_hdrv == NULL)
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, slist);

hdrv_append(&req->req_hdrv, &req->req_hdrc, cl_header);
}

/* headers */
if (custom_hdrv != NULL) {
rc = process_custom_headers(&slist, req, custom_hdrv, custom_hdrc);
if (rc != CURLE_OK)
return rc;
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, slist);
}

common_opt(curl, req);
if (put_flag)
/* use custom request instead of dedicated PUT, because dedicated
PUT doesn't work with arbitrary request body data */
curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "PUT");
else
curl_easy_setopt(curl, CURLOPT_POST, 1);
char *ua = user_agent();
curl_easy_setopt(curl, CURLOPT_USERAGENT, ua);
rc = curl_easy_perform(curl);
if (rc != CURLE_OK)
return rc;

long code;
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &code);
req->code = code;
req->ok = check_ok(code);

if (slist != NULL)
curl_slist_free_all(slist);
free(ua);
curl_easy_cleanup(curl);

return rc;
}

/*
* process_custom_headers - Adds custom headers to request and populates the
* req_headerv and req_hdrc fields of the request struct using the supplied
* custom headers.
*
* Returns CURLE_OK on success, CURLE_OUT_OF_MEMORY if realloc failed to
* increase size of req_hdrv, or -1 if libcurl's linked list append fails.
*
* @slist: internal libcurl slist (string linked list)
* @req: request struct
* @custom_hdrv: char* array of custom headers
* @custom_hdrc: length of `custom_hdrv`
*/
static CURLcode process_custom_headers(struct curl_slist **slist, req_t *req,
char **custom_hdrv, int custom_hdrc)
{
for (int i = 0; i < custom_hdrc; i++) {
/* add header to request */
*slist = curl_slist_append(*slist, custom_hdrv[i]);
if (*slist == NULL)
return -1;
if (hdrv_append(&req->req_hdrv, &req->req_hdrc, custom_hdrv[i]))
return CURLE_OUT_OF_MEMORY;
}

return CURLE_OK;
}

/*
* hdrv_append - Appends to an arbitrary char* array and increments the given
* array length.
*
* Returns 0 on success and -1 on memory error.
*
* @hdrv: pointer to the char* array
* @hdrc: length of `hdrv' (NOTE: this value gets updated)
* @new: char* to append
*/
static int hdrv_append(char ***hdrv, int *hdrc, char *new)
{
/* current array size in bytes */
size_t current_size = *hdrc * sizeof(char*);
char *newdup = strndup(new, strlen(new));
if (newdup == NULL)
return -1;

*hdrv = realloc(*hdrv, current_size + sizeof(char*));
if (*hdrv == NULL)
return -1;
(*hdrc)++;
(*hdrv)[*hdrc - 1] = newdup;
return 0;
}

/*
* common_opt - Sets common libcurl options.
*
* @curl: libcurl handle
* @req: request struct
*/
static void common_opt(CURL *curl, req_t *req)
{
curl_easy_setopt(curl, CURLOPT_URL, req->url);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, resp_callback);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, req);
curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, header_callback);
curl_easy_setopt(curl, CURLOPT_HEADERDATA, req);
}

/*
* user_agent - Creates custom user agent.
*
* Returns a char* containing the user agent, or NULL on failure.
*/
static char *user_agent(void)
{
struct utsname name;
uname(&name);
char *kernel = name.sysname;
char *version = name.release;
char *ua;
asprintf(&ua, "librequests/%s %s/%s", __LIBREQ_VERS__, kernel, version);
return ua;
}

/*
* check_ok - Utility function for setting "ok" struct field. Response codes
* of 400+ are considered "not ok".
*
* @req: request struct
*/
static int check_ok(long code)
{
if (code >= 400 || code == 0)
return 0;
else
return 1;
}

+ 325
- 0
lib/rest.c 파일 보기

@@ -0,0 +1,325 @@
//
// Created by Memer on 24.08.18.
// Copyright (c) 2018 Alexander Memer. All rights reserved.
//

#include <discord.h>
#include <requests.h>
#include <jansson.h>
#include <log.h>

void _ld_set_last_rest_error(struct ld_context *ctx, req_t *req)
{
log_warn("_ld_set_last_rest_error triggered");

ctx->last_rest_status = malloc(sizeof(struct ld_rest_error));

ctx->last_rest_status->what = (int)req->code;

json_error_t err;
json_t *res = json_loads(req->text, 0, &err);

if (!res)
{
log_error("failed to parse json at line %d: %s", err.line, err.text);
ctx->last_rest_status->text = "";
json_decref(res);
return;
}

json_t *message = json_object_get(res, "message");
if (!json_is_string(message))
{
log_error("message is not a string");
ctx->last_rest_status->text = "";
json_decref(res);
return;
}
char *_message = (char*)json_string_value(message);

ctx->last_rest_status->text = malloc(strlen(_message) + 1);
strcpy(ctx->last_rest_status->text, _message);
}

json_t *ld_get_request(struct ld_context *ctx, char *url)
{
req_t req;
ctx->curl_handle = requests_init(&req);

char *target = malloc(strlen(LD_API_ENDPOINT) + strlen(url) + 1);
char *auth = malloc(strlen("Authorization: Bot ") + strlen(ctx->bot_token) + 1);

sprintf(target, "%s%s", LD_API_ENDPOINT, url);
sprintf(auth, "%s%s", "Authorization: Bot ", ctx->bot_token);

requests_get_headers(ctx->curl_handle, &req, target, &auth, 1);

free(target);
free(auth);

if(!req.ok)
{
_ld_set_last_rest_error(ctx, &req);
return NULL;
}

json_t *res;
json_error_t err;

res = json_loads(req.text, 0, &err);

requests_close(&req);

if (!res)
{
log_error("failed to parse json on line %d: %s", err.line, err.text);
return NULL;
}

return res;

}

json_t *ld_post_request(struct ld_context *ctx, char *url, json_t *contents)
{
req_t req;
ctx->curl_handle = requests_init(&req);

char *target = malloc(strlen(LD_API_ENDPOINT) + strlen(url) + 1);
char *auth = malloc(strlen("Authorization: Bot ") + strlen(ctx->bot_token) + 1);

sprintf(target, "%s%s", LD_API_ENDPOINT, url);
sprintf(auth, "%s%s", "Authorization: Bot ", ctx->bot_token);

if (!contents)
{
requests_post_headers(ctx->curl_handle, &req, target, "", &auth, 1);
}
else
{
requests_post_headers(ctx->curl_handle, &req, target, json_dumps(contents, 0), &auth, 1);
}

free(target);
free(auth);

if(!req.ok)
{
_ld_set_last_rest_error(ctx, &req);
return NULL;
}

json_t *res;
json_error_t err;

res = json_loads(req.text, 0, &err);

requests_close(&req);

if (!res)
{
log_error("failed to parse json on line %d: %s", err.line, err.text);
return NULL;
}

return res;
}

char *ld_get_gateway(struct ld_context *ctx)
{
json_t *res = ld_get_request(ctx, "/gateway");

if (!res)
{
log_error("failed to get gateway url");
return NULL;
}

json_t *gateway = json_object_get(res, "url");
if (!json_is_string(gateway))
{
log_error("failed to parse url of gateway");
json_decref(res);
return NULL;
}

char *url = strdup(json_string_value(gateway));

json_decref(gateway);
json_decref(res);

return url;
}

struct ld_gateway_bot_resp *ld_get_gateway_bot(struct ld_context *ctx)
{
json_t *res = ld_get_request(ctx, "/gateway/bot");

if (!res)
{
log_error("failed to get gateway url");
return NULL;
}

struct ld_gateway_bot_resp *r;
r = malloc(sizeof(struct ld_gateway_bot_resp));

json_t *gateway = json_object_get(res, "url");
if (!json_is_string(gateway))
{
log_error("failed to parse json");
json_decref(res);
return NULL;
}

json_t *shards = json_object_get(res, "shards");
if (!json_is_integer(shards))
{
log_error("failed to parse shards (not integer?)");
json_decref(res);
return NULL;
}

r->url = strdup(json_string_value(gateway));
r->shards = (int)json_integer_value(shards);

json_decref(gateway);
json_decref(shards);
json_decref(res);

return r;
}

int ld_create_message(struct ld_context *ctx, char *channel_id, char *content)
{
char *target = malloc(39);

sprintf(target, "/channels/%s/messages", channel_id);

json_t *contents = json_object();
json_t *_content = json_string(content);

json_object_set_new(contents, "content", _content);

json_t *res = ld_post_request(ctx, target, contents);

free(target);

if (json_object_size(res) <= 0)
{
log_warn("probably failed to create message");
return -1;
}

json_decref(res);

return 0;
}

int ld_get_channel(struct ld_context *ctx, char *channel_id, guild_channel_t *channel)
{
char *target = malloc(28);

sprintf(target, "/channels/%s", channel_id);

json_t *res = ld_get_request(ctx, target);

free(target);

if (json_object_size(res) <= 0)
{
log_warn("probably failed to get channel");
return -1;
}

log_trace("%s", json_dumps(res, 0));

json_t *id = json_object_get(res, "id");
channel->id = strdup(json_string_value(id));
json_decref(id);

json_t *type = json_object_get(res, "type");
channel->type = (int)json_integer_value(type);
json_decref(type);

if (channel->type == GUILD_TEXT || channel->type == GUILD_CATEGORY)
{
json_t *guild_id = json_object_get(res, "guild_id");
channel->guild_id = strdup(json_string_value(guild_id));
json_decref(guild_id);

// TODO: permission_overwrites

json_t *name = json_object_get(res, "name");
channel->name = strdup(json_string_value(name));
json_decref(name);
if (channel->type == GUILD_TEXT)
{
// Topic is nullable
json_t *topic = json_object_get(res, "topic");
if (json_is_string(topic)) {
channel->topic = strdup(json_string_value(topic));
}
json_decref(topic);

json_t *nsfw = json_object_get(res, "nsfw");
channel->nsfw = json_boolean_value(nsfw);
json_decref(nsfw);

json_t *last_message_id = json_object_get(res, "last_message_id");
if (last_message_id) {
if (json_is_string(last_message_id)) {
channel->last_message_id = strdup(json_string_value(last_message_id));
}
json_decref(last_message_id);
}
}

json_t *parent_id = json_object_get(res, "parent_id");
if (json_is_string(parent_id))
{
channel->parent_id = strdup(json_string_value(parent_id));
}
json_decref(parent_id);

json_t *position = json_object_get(res, "position");
channel->position = (int)json_integer_value(position);
json_decref(position);
}
else if (channel->type == GUILD_VOICE)
{
json_t *bitrate = json_object_get(res, "bitrate");
channel->bitrate = (int)json_integer_value(bitrate);
json_decref(bitrate);

json_t *user_limit = json_object_get(res, "user_limit");
channel->user_limit = (int)json_integer_value(user_limit);
json_decref(user_limit);
}
else if (channel->type == DM || channel->type == GROUP_DM)
{
// TODO: recipients
json_t *icon = json_object_get(res, "icon");
if (icon)
{
if (json_is_string(icon))
{
channel->icon = strdup(json_string_value(icon));
}
json_decref(icon);
}

json_t *owner_id = json_object_get(res, "owner_id");
channel->owner_id = strdup(json_string_value(owner_id));
json_decref(owner_id);

json_t *application_id = json_object_get(res, "application_id");
if (application_id)
{
channel->application_id = strdup(json_string_value(application_id));
json_decref(application_id);
}
}

return 0;
}

Loading…
취소
저장