commit 3014f4b315e5e0c40c015499b2e80e1728e1fd31
Author: Bharatvaj <bharatvaj@yahoo.com>
Date: Sun, 17 Apr 2022 03:30:45 +0530
Initial Commit
Diffstat:
12 files changed, 290 insertions(+), 0 deletions(-)
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