qlic

Zoho Cliq but not really

commit 3014f4b315e5e0c40c015499b2e80e1728e1fd31
Author: Bharatvaj <bharatvaj@yahoo.com>
Date: Sun, 17 Apr 2022 03:30:45 +0530

Initial Commit
12 files changed, 290 insertions(+), 0 deletions(-)
A
.gitignore
|
2
++
A
HACKING
|
13
+++++++++++++
A
Makefile
|
7
+++++++
A
README
|
59
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A
cliq_apis.c
|
15
+++++++++++++++
A
cliq_apis.h
|
8
++++++++
A
qlic.c
|
31
+++++++++++++++++++++++++++++++
A
qlic_common.c
|
84
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A
qlic_common.h
|
23
+++++++++++++++++++++++
A
qlic_response_handler.c
|
12
++++++++++++
A
qlic_response_handler.h
|
9
+++++++++
A
qlic_types.h
|
27
+++++++++++++++++++++++++++
diff --git a/.gitignore b/.gitignore
@@ -0,0 +1,2 @@
+.vimrc
+qlic
diff --git a/HACKING b/HACKING
@@ -0,0 +1,13 @@
+HACKING
+=======
+
+qlic is written using c. It works with c99 compilers.
+
+FILES
+-----
+* qlic.c - Controller for qlic, handles argument parsing and delegates control to others parts of the code
+
+* qlic_types.h - Contains the basic types required for cliq. (struct QlicString, etc)
+* cliq_api.h - Contains convenience functions for calling the cliq APIs
+* qlic_common.h - Contains utils and other common function for qlic
+* qlic_response_handler.h - Handles the responses and delegate the control based on the response
diff --git a/Makefile b/Makefile
@@ -0,0 +1,7 @@
+qlic: qlic.c
+	$(CC) qlic.c qlic_response_handler.c qlic_common.c cliq_apis.c -o qlic -I. -lcurl -g
+
+test: qlic
+	./qlic -r
+clean:
+	rm qlic
diff --git a/README b/README
@@ -0,0 +1,59 @@
+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/cliq_apis.c b/cliq_apis.c
@@ -0,0 +1,15 @@
+#include <cliq_apis.h>
+#include <qlic_common.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#define __QLIC_SEND_MESSAGE_STR "https://cliq.zoho.com/api/v2/chats/%s/message"
+QlicString* qlic_send_message_str(QlicString* chat_id) {
+	QlicString* send_message = init_qlic_string();
+	// tenet moment
+	// minus 2 for removing the format specifier :(
+	send_message->len = sizeof(__QLIC_SEND_MESSAGE_STR) + chat_id->len - 2;
+	send_message->string = (char*)malloc(send_message->len);
+	snprintf(send_message->string, send_message->len, __QLIC_SEND_MESSAGE_STR, chat_id->string);
+	return send_message;
+}
diff --git a/cliq_apis.h b/cliq_apis.h
@@ -0,0 +1,8 @@
+#ifndef __CLIQ_API_H
+#define __CLIQ_API_H
+
+#include <qlic_common.h>
+
+QlicString* qlic_send_message_str(QlicString* chat_id);
+
+#endif
diff --git a/qlic.c b/qlic.c
@@ -0,0 +1,31 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <qlic_common.h>
+#include <cliq_apis.h>
+#include <qlic_response_handler.h>
+
+
+// TODO Send error back
+#define __QLIC_ACCESS_TOKEN "Zoho-oauthtoken "
+int main(int argc, char* argv[]) {
+	QlicString* access_token = init_qlic_string();
+	__QLIC_ASSIGN_STRING(access_token, __QLIC_ACCESS_TOKEN);
+	if (argc == 1) {
+		qlic_error("Not enough arguments");
+		return -1;
+	}
+	if (strcmp(argv[1], "-r") == 0) {
+		QlicContext* qlic_context = qlic_context_access_init(access_token);
+		if (qlic_context == NULL) {
+			qlic_error("Cannot init network library");
+			return -1;
+		}
+		QlicString* chat_id = init_qlic_string();
+		__QLIC_ASSIGN_STRING(chat_id, "2243227993181997558");
+		qlic_context->request_url = qlic_send_message_str(chat_id);
+		qlic_request(qlic_context, qlic_handle_send_message, true);
+	}
+	return 0;
+}
+
diff --git a/qlic_common.c b/qlic_common.c
@@ -0,0 +1,84 @@
+#include <qlic_common.h>
+#include <curl/curl.h>
+#include <stdlib.h>
+#include <string.h>
+
+void qlic_error(const char* error_message) {
+	fprintf(stderr, error_message);
+}
+
+static struct curl_slist* __qlic_set_request_headers(QlicContext* context, QlicString* access_token) {
+	CURL* curl = (CURL*)context->context;
+	if(access_token == NULL) {
+		return NULL;
+	}
+	struct curl_slist* list = NULL;
+#define __QLIC_AUTHORIZATION_HEADER "Authorization: "
+	// TODO cleanup authorization_header if curl doesn't handle it?
+	size_t authorization_header_len = sizeof(__QLIC_AUTHORIZATION_HEADER) + access_token->len;
+	char* authorization_header = (char*)malloc(authorization_header_len);
+	strncpy(authorization_header, __QLIC_AUTHORIZATION_HEADER, sizeof(__QLIC_AUTHORIZATION_HEADER));
+	strncat(authorization_header, access_token->string, access_token->len);
+	list = curl_slist_append(list, authorization_header);
+	list = curl_slist_append(list, "Content-Type: application/json");
+	list = curl_slist_append(list, "contentType: application/json");
+	return list;
+}
+
+QlicString* init_qlic_string() {
+	QlicString* qlic_string = (QlicString*)malloc(sizeof(QlicString));
+	qlic_string->string = NULL;
+	qlic_string->len = -1;
+	return qlic_string;
+}
+
+QlicContext* qlic_context_access_init(QlicString* access_token) {
+	QlicContext* qlic_context = qlic_init();
+	if (qlic_context) {
+		CURL* curl = (CURL*)qlic_context->context;
+		struct curl_slist* list = __qlic_set_request_headers(qlic_context, access_token);
+		curl_easy_setopt(curl, CURLOPT_HTTPHEADER, list);
+		return qlic_context;
+	} else {
+		return NULL;
+	}
+}
+
+void destroy_qlic_string(QlicString* qlic_string) {
+	if(qlic_string->string != NULL) {
+		free(qlic_string->string);
+	}
+	free(qlic_string);
+}
+
+QlicContext* qlic_init() {
+	CURL *curl = curl_easy_init();
+	if (curl) {
+		QlicContext* qlic_context = (QlicContext*)malloc(sizeof(QlicContext));
+		qlic_context->context = curl;
+		return qlic_context;
+	} else {
+		return NULL;
+	}
+}
+
+void qlic_request(QlicContext* context, qlic_response_callback callback, bool is_post_request) {
+	if(context) {
+		CURL* curl = (CURL*)context->context;
+		CURLcode res;
+		curl_easy_setopt(curl, CURLOPT_URL, context->request_url->string);
+		/* curl_easy_setopt(curl, CURLOPT_GET, 1); */
+		/* curl_easy_setopt(curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2_0); */
+		curl_easy_setopt(curl, CURLOPT_READFUNCTION, callback);
+		if (is_post_request) {
+			curl_easy_setopt(curl, CURLOPT_POST, 1);
+		}
+		res = curl_easy_perform(curl);
+		if(res != CURLE_OK) {
+			fprintf(stderr, "curl_easy_perform() failed: %s\n",
+								  curl_easy_strerror(res));
+		}
+		curl_easy_cleanup(curl);
+	}
+}
+
diff --git a/qlic_common.h b/qlic_common.h
@@ -0,0 +1,23 @@
+#ifndef __QLIC_COMMON_H
+#define __QLIC_COMMON_H
+
+#include <qlic_types.h>
+#include <stdbool.h>
+
+#define __QLIC_ASSIGN_STRING(X,Y) \
+	X->string = Y; \
+	X->len = sizeof(Y);
+
+void qlic_error(const char* error_message);
+
+QlicString* init_qlic_string();
+
+void destroy_qlic_string(QlicString*);
+
+QlicContext* qlic_context_access_init(QlicString* access_token);
+
+QlicContext* qlic_init();
+
+void qlic_request(QlicContext* context, qlic_response_callback callback, bool is_post_request);
+
+#endif
diff --git a/qlic_response_handler.c b/qlic_response_handler.c
@@ -0,0 +1,12 @@
+#include <qlic_response_handler.h>
+#include <stdio.h>
+
+int qlic_handle_read_chat(char* response, size_t response_size, size_t nmemb, void *userp) {
+	/* printf("helo: %s\n", response); */
+	return 0;
+}
+
+int qlic_handle_send_message(char* response, size_t response_size, size_t nmemb, void *userp) {
+	printf("%s\n", response);
+	return 0;
+}
diff --git a/qlic_response_handler.h b/qlic_response_handler.h
@@ -0,0 +1,9 @@
+#ifndef __QLIC_RESPONSE_HANDLER_H
+#define __QLIC_RESPONSE_HANDLER_H
+
+#include <stddef.h>
+
+int qlic_handle_read_chat(char* response, size_t response_size, size_t nmemb, void *userp);
+int qlic_handle_send_message(char* response, size_t response_size, size_t nmemb, void *userp);
+
+#endif
diff --git a/qlic_types.h b/qlic_types.h
@@ -0,0 +1,27 @@
+#ifndef __QLIC_TYPES_H
+#define __QLIC_TYPES_H
+
+#include <stddef.h>
+
+typedef int (*qlic_response_callback)(char*, size_t, size_t, void*);
+
+typedef struct QlicString {
+	char* string;
+	size_t len;
+} QlicString;
+
+struct QlicCliqAction {
+	char* request_url;
+	size_t request_url_len;
+	qlic_response_callback callback;
+};
+
+/**
+ * Network related information
+ */
+typedef struct QlicContext {
+	void* context;
+	QlicString* request_url;
+} QlicContext;
+
+#endif