qlic

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

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

Initial Commit

Diffstat:
A.gitignore | 2++
AHACKING | 13+++++++++++++
AMakefile | 7+++++++
AREADME | 59+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acliq_apis.c | 15+++++++++++++++
Acliq_apis.h | 8++++++++
Aqlic.c | 31+++++++++++++++++++++++++++++++
Aqlic_common.c | 84+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aqlic_common.h | 23+++++++++++++++++++++++
Aqlic_response_handler.c | 12++++++++++++
Aqlic_response_handler.h | 9+++++++++
Aqlic_types.h | 27+++++++++++++++++++++++++++
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