qlic

Zoho Cliq but not really
git clone git@getsh.org:qlic.git
Log | Files | Refs | LICENSE

commit ad83f361880eccefef1dd0cc9e08cf18bb61c185
parent dfa6876fe79a49cbd2faaa2fb7250e346057d57f
Author: Bharatvaj <bharatvaj@yahoo.com>
Date:   Thu,  9 Jun 2022 05:11:23 +0530

Parse local config.json with nxjson

Add options '-v' for getting version

Remove defines fields in config.h

Move all defines to qlic_private.h

Diffstat:
M.gitignore | 1+
DREADME | 59-----------------------------------------------------------
AREADME.rst | 65+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mconfig.h | 6++++--
Mqlic.c | 41++++++++++++++++++++++++++---------------
Mqlic_common.c | 6++++--
Mqlic_common.h | 4----
Mqlic_oauth.h | 92+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
Aqlic_private.h | 25+++++++++++++++++++++++++
Mqlic_types.h | 9+++++++++
10 files changed, 224 insertions(+), 84 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -1,2 +1,3 @@ .vimrc qlic +qlic.dSYM/ diff --git a/README b/README @@ -1,59 +0,0 @@ -qlic -==== - -qlic is a cli frontend to the venereal Zoho cliq. It is cliq but unopinionated. - -NOTE: Built with bubble gum and paper clip. Highly unstable, please use at your own risk. - -![cliq-jpeg](images/qlic.jpg) - -Why? ----- -I avoid browsers like the plague. Zoho cliq was my only reason for using a browser. Not anymore. - -What works ----------- -* Authentication via OAuth -* Receive and send text to chat, channels -* Receive and send attachments to chat, channels - -What doesn't work ------------------ -* Cliq call - Apparently this takes more work than I thought. -* Screenshare - Sharing screen is top priority for qlic, but it requires call to work first. -* Widgets - I have no interest in implementing widgets in a cli as it doesn't benefit me, but contributions are welcome as always. - -Unique features ---------------- -* stdin support! - - You can send attachment to qlic's `stdin` -```sh -# NOTE: `nwoo` is the recipient -# qlic tries to match with the closest name, if it matches with multiple names, a prompt will be shown to the user - -# Sending a text -echo "hello fren" | qlic -r nwoo -qlic -r nwooo "henlo fren" - -# Sending an attachment -cat how-to-basic-101.jpg | qlic -r nwoo -b meme.jpg -``` - -Install -------- -```sh -git clone https://github.com/bharatvaj/qlic -cd qlic -make install -``` - -Setup ------ -* Create a config file at `$XDG_DATA_HOME/qlic/data.json` with the following configuration -```json -{ - client_id: 'your_client_id', - client_secret: 'your_client_secret', -} -``` -NOTE: client id and secret can be obtained from [Zoho's API console](https://api-console.zoho.com/) diff --git a/README.rst b/README.rst @@ -0,0 +1,65 @@ +qlic +==== + +qlic is an unecessary fast cli frontend to the venereal Zoho cliq. It is cliq but unopinionated. + +NOTE: Built with bubble gum and paper clip. Highly unstable, please use at your own risk. + +![cliq-jpeg](images/qlic.jpg) + +Why? +---- +I avoid browsers like the plague. Zoho cliq was my only reason for using a browser. Not anymore. + +What works +---------- +* Authentication via OAuth +* Receive and send text to chat, channels +* Receive and send attachments to chat, channels + +What doesn't work +----------------- +* Cliq call - Apparently this takes more work than I thought. +* Screenshare - Sharing screen is top priority for qlic, but it requires call to work first. +* Widgets - I have no interest in implementing widgets in a cli as it doesn't benefit me, but contributions are welcome as always. + +Unique features +--------------- +* stdin support! + - You can send attachment to qlic's `stdin` +```sh +# NOTE: `nwoo` is the recipient +# qlic tries to match with the closest name, if it matches with multiple names, a prompt will be shown to the user + +# Sending a text +echo "hello fren" | qlic -r nwoo +qlic -r nwooo "henlo fren" + +# Sending an attachment +cat how-to-basic-101.jpg | qlic -r nwoo -b meme.jpg +``` + +Install +------- +```sh +git clone https://github.com/bharatvaj/qlic +cd qlic +make install +``` + +Dependencies +------------ +* libcurl +* bharatvaj/OAuth2 +* yarosla/nxjson + +Setup +----- +* Create a config file at `$XDG_DATA_HOME/qlic/data.json` with the following configuration +```json +{ + client_id: 'your_client_id', + client_secret: 'your_client_secret', +} +``` +NOTE: client id and secret can be obtained from [Zoho's API console](https://api-console.zoho.com/) diff --git a/config.h b/config.h @@ -1,10 +1,12 @@ #ifndef __CLIQ_CONFIG_H #define __CLIQ_CONFIG_H + +#define QLIC_VERSION "0.0.1" + #define CLIQ_AUTH_ENDPOINT "https://accounts.zoho.com/oauth/v2/auth" #define CLIQ_TOKEN_ENDPOINT "https://accounts.zoho.com/oauth/v2/token" -#define CLIQ_CLIENT_ID "" -#define CLIQ_CLIENT_SECRET "" + #define CLIQ_REDIRECT_URI "https://127.0.0.1:8443/hello" #define CLIQ_SCOPE "ZohoCliq.Chats.READ,ZohoCliq.Messages.READ,ZohoCliq.Webhooks.CREATE" diff --git a/qlic.c b/qlic.c @@ -5,6 +5,8 @@ #include <cliq_apis.h> #include <qlic_response_handler.h> #include <qlic_oauth.h> +#include <qlic_private.h> +#include "config.h" int qlic_send_text_msg(const char* __access_token, const char* __chat_id) { QlicString* access_token = NULL; @@ -24,23 +26,32 @@ int qlic_send_text_msg(const char* __access_token, const char* __chat_id) { return 0; } +static void qlic_usage() { + fputs("usage: qlic [-va] [-r chat_id]\n", stderr); + exit(1); +} + // TODO Send error back int main(int argc, char* argv[]) { - if (argc == 1) { - qlic_error("Not enough arguments"); - return -1; - } - // TODO Use an argument parsing library - if (strcmp(argv[1], "-r") == 0) { - // TODO read access_token from state.json - char* access_token = "1000.429cf5132d6cc978960bfdd6e0a425cc.80bbd5584b0c35133c4f82143e6811b2"; - // FIXME possible buffer overflow here - qlic_send_text_msg(access_token, argv[2]); - } else if (strcmp(argv[1], "-a") == 0) { - char* access_token = start_oauth_server(); - if (access_token == NULL) { - qlic_error("Access token is empty, authentication failed"); - return -1; + int i; + QlicContext* ctx = qlic_init(); + for (i = 1; i < argc; i++) { + if (!strcmp(argv[i], "-v")) { + fputs("qlic"QLIC_VERSION"\n", stderr); + } else if (!strcmp(argv[i], "-a")) { + char* access_token = start_oauth_server(ctx); + if (access_token == NULL) { + qlic_error("Access token is empty, authentication failed"); + return -1; + } + /* these options take one argument */ + } else if (!strcmp(argv[i], "-r")) { + // TODO read access_token from state.json + char* access_token = "1000.429cf5132d6cc978960bfdd6e0a425cc.80bbd5584b0c35133c4f82143e6811b2"; + // FIXME possible buffer overflow here + qlic_send_text_msg(access_token, argv[++i]); + } else { + qlic_usage(); } } return 0; diff --git a/qlic_common.c b/qlic_common.c @@ -5,9 +5,11 @@ #include <qlic_response_handler.h> void qlic_error(const char* error_message) { - fprintf(stderr, error_message); + fprintf(stderr, "%s\n", error_message); } + + static struct curl_slist* __qlic_set_request_headers(QlicContext* context, QlicString* access_token) { CURL* curl = (CURL*)context->context; if(access_token == NULL) { @@ -55,7 +57,7 @@ void destroy_qlic_string(QlicString* qlic_string) { QlicContext* qlic_init() { CURL *curl = curl_easy_init(); if (curl) { - QlicContext* qlic_context = (QlicContext*)malloc(sizeof(QlicContext)); + QlicContext* qlic_context = (QlicContext*)calloc(1, sizeof(QlicContext)); qlic_context->context = curl; return qlic_context; } else { diff --git a/qlic_common.h b/qlic_common.h @@ -4,10 +4,6 @@ #include <qlic_types.h> #include <stdbool.h> -#define __QLIC_ASSIGN_STRING(X,Y) \ - X->string = Y; \ - X->len = strlen(Y); - void qlic_error(const char* error_message); QlicString* init_qlic_string(); diff --git a/qlic_oauth.h b/qlic_oauth.h @@ -6,6 +6,9 @@ #include <config.h> #include <nxjson.h> +#include "qlic_types.h" +#include "qlic_private.h" + // TODO Choose between DB and text files for saving this information // If using text, choose between formats, yaml or json or other format, which is more suckless // If DB, sliqte3 is a good choice, but don't @@ -35,8 +38,93 @@ char *json_access_code_transformer(char* str) { return NULL; } -char* start_oauth_server() { - oauth2_config* conf = oauth2_init(CLIQ_CLIENT_ID, CLIQ_CLIENT_SECRET); + +/* + * returns >1 if the file is sucessfully read + * returns the err value (<=0) in case of errors + * return -2 if dest is NULL + * returs -3 if dest_len is NULL + */ +uint8_t read_contents(char* dest, size_t* dest_len, FILE* fp) { + if (dest == NULL) { + return -2; + } + if (dest_len == NULL) { + return -3; + } + int err = -1; + size_t res = *dest_len; + while ((res = fread(dest, 1, QLIC_FILE_BUFFER_SIZE, fp)) > 0) { + *dest_len += res; + if (res == 0) { + if((err = ferror(fp)) != 0) { + // error occured + return err; + } else if((err = feof(fp)) != 0) { + // return okay + return err; + } + /// unknown error + return -1; + } else { + // res > QLIC_FILE_READ_SIZE + dest = realloc(dest, *dest_len + QLIC_FILE_BUFFER_SIZE); + } + } + return -1; +} + +QlicErrorCode qlic_read_config_file(QlicContext* ctx) { + /// @todo use xdg paths + FILE* fp = fopen("config.json", "r"); + char* dest = malloc(sizeof(char) * QLIC_FILE_BUFFER_SIZE); + size_t dest_len = QLIC_FILE_BUFFER_SIZE; + read_contents(dest, &dest_len, fp); + const nx_json* json = nx_json_parse(dest, nx_json_unicode_to_utf8); + if (json->type == NX_JSON_OBJECT) { + const nx_json* at_user = nx_json_get(json, "user_id"); + if (at_user == NULL) return QLIC_ERROR; + if (at_user->type == NX_JSON_OBJECT) { + const nx_json* at_client_id = nx_json_get(at_user, "client_id"); + if (at_client_id == NULL) return QLIC_ERROR; + if (at_client_id->type == NX_JSON_STRING) { + qlic_error(at_client_id->text_value); + /// @todo maybe nxjson already string length has it? + __QLIC_ASSIGN_STRING(ctx->client_id, at_client_id->text_value); + qlic_error(ctx->client_id->string); + if (at_client_id->text_value == NULL) { + return QLIC_ERROR; + } + } + const nx_json* at_client_secret = nx_json_get(at_user, "client_secret"); + if (at_client_secret == NULL) return QLIC_ERROR; + if (at_client_secret->type == NX_JSON_STRING) { + qlic_error(at_client_secret->text_value); + /// @todo maybe nxjson already string length has it? + __QLIC_ASSIGN_STRING(ctx->client_secret, at_client_secret->text_value); + qlic_error(ctx->client_secret->string); + if (at_client_secret->text_value == NULL) { + return QLIC_ERROR; + } + } + } + } + return QLIC_ERROR; +} + +char* start_oauth_server(QlicContext* ctx) { + int err = -1; + err = qlic_read_config_file(ctx); + if (err) { + qlic_error("Not able to read config file"); + QLIC_PANIC(); + } + + oauth2_config* conf = oauth2_init(ctx->client_id->string, ctx->client_secret->string); + if (conf == NULL) { + qlic_error("conf is null\n"); + QLIC_PANIC(); + } conf->access_auth_code_transformer = json_access_code_transformer; oauth2_set_redirect_uri(conf, CLIQ_REDIRECT_URI); // TODO generate true state instead of LOL diff --git a/qlic_private.h b/qlic_private.h @@ -0,0 +1,25 @@ +#ifndef __QLIC_PRIVATE_H +#define __QLIC_PRIVATE_H + +#define QLIC_FILE_BUFFER_SIZE 256 +/// @todo this can be optimized based on the cpu + +#define QLIC_PANIC() \ + fprintf(stderr, "qlick panicked at %s:%d", __FILE__, __LINE__); \ + exit(-1); + +#define __QLIC_ASSIGN_STRING(X,Y) \ + if (X == NULL) { \ + if ((X = init_qlic_string()) == NULL) { \ + QLIC_PANIC(); \ + } \ + } \ + X->len = strlen(Y); \ + X->string = (char*)malloc(sizeof(char) * (X->len + 2)); \ + void* __LINE__ptr = strncpy(X->string, Y, X->len); \ + if (__LINE__ptr == NULL) { \ + qlic_error("%d in %s failed\n __LINE__ - 2, __FILE__"); \ + QLIC_PANIC(); \ + } + +#endif diff --git a/qlic_types.h b/qlic_types.h @@ -3,6 +3,8 @@ #include <stddef.h> +#include <qlic_private.h> + typedef size_t (*qlic_response_callback)(char*, size_t, size_t, void*); typedef struct QlicString { @@ -10,6 +12,11 @@ typedef struct QlicString { size_t len; } QlicString; +typedef enum QlicErrorCode { + QLIC_ERROR = 0, + QLIC_OK +} QlicErrorCode; + struct QlicCliqAction { char* request_url; size_t request_url_len; @@ -22,6 +29,8 @@ struct QlicCliqAction { typedef struct QlicContext { void* context; QlicString* request_url; + QlicString* client_id; + QlicString* client_secret; } QlicContext; #endif