qlic

Zoho Cliq but not really

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
10 files changed, 224 insertions(+), 84 deletions(-)
M
.gitignore
|
1
+
D
README
|
59
-----------------------------------------------------------
A
README.rst
|
65
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
M
config.h
|
6
++++--
M
qlic.c
|
41
++++++++++++++++++++++++++---------------
M
qlic_common.c
|
6
++++--
M
qlic_common.h
|
4
----
M
qlic_oauth.h
|
92
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
A
qlic_private.h
|
25
+++++++++++++++++++++++++
M
qlic_types.h
|
9
+++++++++
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