summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile3
-rw-r--r--data/post.ui238
-rw-r--r--src/auth.c347
-rw-r--r--src/auth.h7
-rw-r--r--src/config.c46
-rw-r--r--src/config.h8
-rw-r--r--src/http.c14
-rw-r--r--src/log.c4
-rw-r--r--src/login_window.c56
-rw-r--r--src/main.c3
-rw-r--r--src/register.c128
-rw-r--r--src/register.h8
-rw-r--r--src/string-util.c38
-rw-r--r--src/string-util.h1
-rw-r--r--src/timeline.c27
-rw-r--r--src/timeline_window.c81
16 files changed, 674 insertions, 335 deletions
diff --git a/Makefile b/Makefile
index d214f26..0134d5e 100644
--- a/Makefile
+++ b/Makefile
@@ -2,7 +2,7 @@ CC=cc
PKGCONFIG=$(shell which pkg-config)
CFLAGS=-o2 -pipe
-CFLAGS+=-Wall -Wextra -Wno-unused-parameter
+CFLAGS+=-Wall -Wextra -Wno-unused-parameter -std=c99
CFLAGS+=$(shell $(PKGCONFIG) --cflags gtk+-3.0 libcurl jansson) -lpthread
LDFLAGS+=$(shell $(PKGCONFIG) --libs gtk+-3.0 libcurl jansson) -lpthread
DESTDIR=
@@ -17,6 +17,7 @@ OBJECTS=$(OUTDIR)/main.o \
$(OUTDIR)/status.o \
$(OUTDIR)/timeline.o \
$(OUTDIR)/auth.o \
+ $(OUTDIR)/register.o \
$(OUTDIR)/login_window.o \
$(OUTDIR)/timeline_window.o \
$(OUTDIR)/config.o \
diff --git a/data/post.ui b/data/post.ui
new file mode 100644
index 0000000..da222d8
--- /dev/null
+++ b/data/post.ui
@@ -0,0 +1,238 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.20.4
+
+Copyright (C)
+
+This file is part of .
+
+ is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+ is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with . If not, see <http://www.gnu.org/licenses/>.
+
+-->
+<interface>
+ <requires lib="gtk+" version="3.20"/>
+ <!-- interface-license-type gplv3 -->
+ <object class="GtkBox" id="post_box">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="margin_left">4</property>
+ <property name="margin_right">4</property>
+ <property name="margin_top">4</property>
+ <property name="margin_bottom">4</property>
+ <property name="spacing">8</property>
+ <property name="baseline_position">top</property>
+ <child>
+ <object class="GtkImage">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="valign">start</property>
+ <property name="stock">gtk-select-color</property>
+ <property name="icon_size">6</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="valign">start</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">8</property>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Display Name</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">@username</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="content_text">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">A widget’s index in the overlay children list determines which order the children are drawn if they overlap. The first child is drawn at the bottom. It also affects the default focus chain order.</property>
+ <property name="use_markup">True</property>
+ <property name="wrap">True</property>
+ <property name="wrap_mode">char</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="spacing">20</property>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">start</property>
+ <property name="margin_right">10</property>
+ <property name="spacing">5</property>
+ <child>
+ <object class="GtkImage">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="icon_name">mail-reply-sender</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">4</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">start</property>
+ <property name="margin_right">10</property>
+ <property name="spacing">5</property>
+ <child>
+ <object class="GtkImage">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="icon_name">media-playlist-repeat</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">4</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="margin_right">10</property>
+ <property name="spacing">5</property>
+ <child>
+ <object class="GtkImage">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="icon_name">emblem-favorite</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">4</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+</interface>
diff --git a/src/auth.c b/src/auth.c
index 3a2ee17..c903e67 100644
--- a/src/auth.c
+++ b/src/auth.c
@@ -10,328 +10,137 @@
#include "config.h"
#include "log.h"
-#define CLIENT_NAME "ap_client"
-#define PROTOCOL_PREFIX "https://"
+#define LOGIN_URL "https://%s/oauth/token"
-static const char *app_register_url = "/api/v1/apps";
-static const char *login_url = "/oauth/token";
-
-int read_local_credentials()
-{
- if (!(config->access_token) || !*(config->access_token) ||
- !(config->instance_url) || !*(config->instance_url)) {
- log_msg(LOG_WARNING, "read_local_credentials",
- "access_token not found");
- return -1;
- }
- printf("token: %s\niu: %s\n", config->access_token, config->instance_url);
- return 0;
-}
+struct login_response {
+ char *access_token;
+ char *scope;
+};
-bool is_logged_in()
+static char *get_login_url(const char *domain)
{
- if (config->access_token)
- return true;
- return false;
-}
+ char *url;
+ size_t size;
-const char *get_access_token()
-{
- return (const char *)config->access_token;
-}
+ size = strlen(LOGIN_URL) + strlen(domain) - 1;
+ url = malloc(size);
+ if (!url) {
+ err(1, NULL);
+ return NULL;
+ }
-const char *get_instance_url()
-{
- return (const char *)config->instance_url;
+ sprintf(url, LOGIN_URL, domain);
+ return url;
}
-struct register_call_arg {
- char *url;
- char *domain;
- char *post_data;
- void (*callback)(bool);
-};
-
-static void *register_call(void *req_arg)
+static char *get_login_req(const char *email, const char *password)
{
- struct register_call_arg *arg;
- char *resp;
- json_t *root;
+ char *req;
+ json_t *json_root;
+ json_error_t error;
- arg = (struct register_call_arg *)req_arg;
- if (!arg || !(arg->url)) {
- log_msg(LOG_WARNING, "register_call", "invalid arguments");
- goto error;
+ json_root = json_pack_ex(&error, 1, "{s:s, s:s, s:s, s:s, s:s}",
+ "client_id", config->client_id, "client_secret",
+ config->client_secret, "grant_type", "password", "username", email,
+ "password", password);
+ if (!json_root) {
+ return NULL;
}
- resp = post_request(arg->url, arg->post_data);
- if (!resp) {
- log_msg(LOG_WARNING, "register_call", "failed to send http request");
- goto error;
- }
+ req = json_dumps(json_root, 0);
+ json_decref(json_root);
- if (!resp) {
- log_msg(LOG_WARNING, "register_call", "null response");
- goto error;
- }
+ return req;
+}
- root = json_loads(resp, 0, NULL);
- if (!root) {
- log_msg(LOG_WARNING, "register_call", "failed to parse json");
- goto error;
- }
+static struct login_response *get_login_resp(char *data)
+{
+ struct login_response *resp;
+ json_t *json_root;
+ json_t *access_token, *scope;
- if (!json_is_object(root)) {
- log_msg(LOG_WARNING, "register_call", "json root is not object");
- goto error;
+ json_root = json_loads(data, 0, NULL);
+ if (!json_root) {
+ return NULL;
}
- json_t *cid = json_object_get(root, "client_id");
- json_t *csec = json_object_get(root, "client_secret");
- if (!json_is_string(cid) || !json_is_string(csec)) {
- log_msg(LOG_WARNING, "register_call",
- "invalid client_id or client_secret");
- goto error;
+ if (!json_is_object(json_root)) {
+ json_decref(json_root);
+ return NULL;
}
- config_set_client_id(json_string_value(cid));
- config_set_client_secret(json_string_value(csec));
- config_set_instance_url(arg->domain);
- if (!*(config->client_id) || !*(config->client_secret)) {
- log_msg(LOG_WARNING, "register_call",
- "invalid client_id or client_secret");
- goto error;
+ access_token = json_object_get(json_root, "access_token");
+ scope = json_object_get(json_root, "scope");
+ if (!json_is_string(access_token) || !json_is_string(scope)) {
+ json_decref(json_root);
+ return NULL;
}
- free(arg->url);
- if (arg->post_data)
- free(arg->post_data);
- free(arg);
- free(resp);
- json_decref(root);
- (*(arg->callback))(true);
- return NULL;
+ resp = calloc(1, sizeof(struct login_response));
+ resp->access_token = strdup(json_string_value(access_token));
+ resp->scope = strdup(json_string_value(scope));
-error:
- if (arg) {
- if (arg->url)
- free(arg->url);
- if (arg->post_data)
- free(arg->post_data);
- free(arg);
- }
- if (resp)
- free(resp);
- if (root)
- json_decref(root);
- (*(arg->callback))(false);
- return NULL;
+ json_decref(json_root);
+ return resp;
}
-int register_app(const char *instance, void (*callback)(bool success))
+int login(const char *email, const char *password)
{
- size_t size;
- char *instance_prefix;
- char *url, *req_data;
- struct register_call_arg *arg;
- json_t *root;
- json_error_t error;
- pthread_t t;
+ char *url, *req_data, *resp_data;
+ struct login_response *resp;
- if (!instance || strlen(instance) < 1 || !callback) {
- log_msg(LOG_WARNING, "register_app", "invalid argument");
+ if (!email || !*email || !password || !*password) {
+ log_msg(LOG_WARNING, "login", "invalid argument");
return -1;
}
- size = sizeof(PROTOCOL_PREFIX) + sizeof(instance) + 1;
- instance_prefix = malloc(size);
- if (!instance_prefix) {
- err(1, NULL);
+ if (!is_registered()) {
+ log_msg(LOG_WARNING, "login", "app not registred");
return -1;
}
- strlcpy(instance_prefix, PROTOCOL_PREFIX, size);
- strlcat(instance_prefix, instance, size);
-
- root = json_pack_ex(&error, 1, "{s:s, s:s, s:s}", "client_name",
- CLIENT_NAME, "redirect_uris", "urn:ietf:wg:oauth:2.0:oob", "scopes",
- "read write push");
- if (!root) {
- log_msg(LOG_WARNING, "register_app", "json pack error: line %d: %s",
- error.line, error.text);
+ url = get_login_url(config->instance_url);
+ if (!url) {
+ err(1, NULL);
return -1;
}
- req_data = json_dumps(root, 0);
- json_decref(root);
+ req_data = get_login_req(email, password);
if (!req_data) {
- log_msg(LOG_WARNING, "register_app", "failed to dump json");
+ free(url);
return -1;
}
- size = strlen(instance_prefix) + strlen(app_register_url) + 1;
- url = malloc(size);
- if (!url) {
- err(1, NULL);
+ resp_data = post_request(url, req_data);
+ free(url);
+ free(req_data);
+ if (!resp_data) {
+ log_msg(LOG_WARNING, "register_app", "login request failed");
return -1;
}
- strlcpy(url, instance_prefix, size);
- strlcat(url, app_register_url, size);
-
- arg = calloc(1, sizeof(struct register_call_arg));
- if (!arg) {
- err(1, NULL);
+ if (!resp_data) {
+ log_msg(LOG_WARNING, "register_app", "invalid response");
return -1;
}
- arg->url = url;
- arg->domain = instance_prefix;
- arg->post_data = req_data;
- arg->callback = callback;
-
- return pthread_create(&t, NULL, &register_call, arg);
-}
-
-struct login_call_arg {
- char *url;
- char *post_data;
- void (*callback)(bool);
-};
-
-static void *login_call(void *req_arg)
-{
- struct login_call_arg *arg = NULL;
- char *resp = NULL;
- json_t *root = NULL;
-
- arg = (struct login_call_arg *)req_arg;
- if (!arg || !(arg->url)) {
- log_msg(LOG_WARNING, "login_call", "invalid arguments");
- goto error;
- }
-
- resp = post_request(arg->url, arg->post_data);
- if (!resp) {
- log_msg(LOG_WARNING, "login_call", "failed to send http request");
- goto error;
- }
+ resp = get_login_resp(resp_data);
+ free(resp_data);
if (!resp) {
- log_msg(LOG_WARNING, "login_call", "null response");
- goto error;
- }
-
- root = json_loads(resp, 0, NULL);
- if (!root) {
- log_msg(LOG_WARNING, "login_call", "failed to parse json");
- goto error;
- }
-
- if (!json_is_object(root)) {
- log_msg(LOG_WARNING, "login_call", "json root is not object");
- goto error;
- }
-
- json_t *access_token_j = json_object_get(root, "access_token");
- json_t *scope_j = json_object_get(root, "scope");
- if (!json_is_string(access_token_j) || !json_is_string(scope_j) ||
- strlen(json_string_value(access_token_j)) < 1 ||
- strlen(json_string_value(scope_j)) < 1) {
- log_msg(LOG_WARNING, "login_call", "invalid access_token or scope");
- goto error;
+ log_msg(LOG_WARNING, "register_app", "invalid response");
+ return -1;
}
- config_set_access_token(json_string_value(access_token_j));
+ config_set_access_token(resp->access_token);
/* scope = strdup(json_string_value(scope_j)); */
if (config_save()) {
log_msg(LOG_WARNING, "login_call", "failed to save config");
- goto error;
}
- free(arg->url);
- if (arg->post_data)
- free(arg->post_data);
- free(arg);
+ free(resp->access_token);
+ free(resp->scope);
free(resp);
- json_decref(root);
- (*(arg->callback))(true);
-
- printf("access_token: %s", config->access_token);
- return NULL;
-
-error:
- if (arg) {
- if (arg->url)
- free(arg->url);
- if (arg->post_data)
- free(arg->post_data);
- free(arg);
- }
- if (resp)
- free(resp);
- if (root)
- json_decref(root);
- (*(arg->callback))(false);
- return NULL;
-}
-
-int login(
- const char *email, const char *password, void (*callback)(bool success))
-{
- json_t *root;
- json_error_t error;
- pthread_t t;
- size_t size;
- char *url, *req_data;
- struct login_call_arg *arg;
- if (!email || strlen(email) < 1 || !password || strlen(password) < 1) {
- log_msg(LOG_WARNING, "login", "invalid argument");
- return -1;
- }
-
- if (!(config->client_id) || !*(config->client_id) ||
- !(config->client_secret) || !*(config->client_secret) ||
- !(config->instance_url) || !*(config->instance_url)) {
- log_msg(LOG_WARNING, "login", "invalid client_id or client_secret");
- return -1;
- }
-
- root = json_pack_ex(&error, 1, "{s:s, s:s, s:s, s:s, s:s}", "client_id",
- config->client_id, "client_secret", config->client_secret,
- "grant_type", "password", "username", email, "password", password);
-
- if (!root) {
- log_msg(LOG_WARNING, "login", "json pack error: line %d: %s",
- error.line, error.text);
- return -1;
- }
-
- req_data = json_dumps(root, 0);
- json_decref(root);
- if (!req_data) {
- log_msg(LOG_WARNING, "login", "failed to dump json");
- return -1;
- }
-
- size = strlen(config->instance_url) + strlen(login_url) + 1;
- url = malloc(size);
- if (!url) {
- err(1, NULL);
- return -1;
- }
- strlcpy(url, config->instance_url, size);
- strlcat(url, login_url, size);
-
- arg = calloc(1, sizeof(struct login_call_arg));
- if (!arg) {
- err(1, NULL);
- return -1;
- }
- arg->url = url;
- arg->post_data = req_data;
- arg->callback = callback;
-
- return pthread_create(&t, NULL, &login_call, arg);
+ return 0;
}
diff --git a/src/auth.h b/src/auth.h
index bc9c58b..9c3ee03 100644
--- a/src/auth.h
+++ b/src/auth.h
@@ -3,12 +3,7 @@
#include <stdbool.h>
-bool is_logged_in();
-const char* get_access_token();
-const char* get_instance_url();
-int read_local_credentials();
-int register_app(const char *instance, void (*callback) (bool success));
-int login(const char *email, const char *password, void (*callback) (bool success));
+int login(const char *email, const char *password);
#endif
diff --git a/src/config.c b/src/config.c
index ca2d48e..211e953 100644
--- a/src/config.c
+++ b/src/config.c
@@ -18,6 +18,44 @@
struct config _config;
const struct config *config = (const struct config *)&_config;
+int read_local_credentials()
+{
+ if (!(config->access_token) || !*(config->access_token) ||
+ !(config->instance_url) || !*(config->instance_url)) {
+ log_msg(LOG_WARNING, "read_local_credentials",
+ "access_token not found");
+ return -1;
+ }
+ return 0;
+}
+
+bool is_logged_in()
+{
+ if (config->access_token)
+ return true;
+ return false;
+}
+
+const char *get_access_token()
+{
+ return (const char *)config->access_token;
+}
+
+const char *get_instance_url()
+{
+ return (const char *)config->instance_url;
+}
+
+bool is_registered()
+{
+ if (!(_config.client_id) || !*(_config.client_id) ||
+ !(_config.client_secret) || !*(_config.client_secret) ||
+ !(_config.instance_url) || !*(_config.instance_url)) {
+ return false;
+ }
+ return true;
+}
+
char *get_config_path()
{
char *home = getenv("HOME");
@@ -38,7 +76,7 @@ void config_load()
char *path = get_config_path();
root = json_load_file(path, 0, &error);
if (!root) {
- log_msg(LOG_WARNING, "load_config", "json parse error: line %d: %s",
+ log_msg(LOG_WARNING, "config_load", "json parse error: line %d: %s",
error.line, error.text);
free(path);
return;
@@ -46,7 +84,7 @@ void config_load()
free(path);
if (!json_is_object(root)) {
- log_msg(LOG_WARNING, "load_config", "json root is not object");
+ log_msg(LOG_WARNING, "config_load", "json root is not object");
json_decref(root);
return;
}
@@ -96,14 +134,14 @@ int config_save()
if (stat(dir, &st)) {
if (mkdir(dir, S_IRWXU)) {
free(config_path);
- log_msg(LOG_WARNING, "load_config", "failed to create config dir");
+ log_msg(LOG_WARNING, "config_save", "failed to create config dir");
return -1;
}
}
free(config_path);
if (json_dump_file(root, get_config_path(), 0)) {
- log_msg(LOG_WARNING, "load_config", "unable to save json");
+ log_msg(LOG_WARNING, "config_save", "unable to save json");
json_decref(root);
return -1;
}
diff --git a/src/config.h b/src/config.h
index 3875a1b..2456c2b 100644
--- a/src/config.h
+++ b/src/config.h
@@ -1,6 +1,8 @@
#ifndef __CONFIG_H
#define __CONFIG_H
+#include <stdbool.h>
+
struct config {
char *instance_url;
char *client_id;
@@ -9,6 +11,12 @@ struct config {
};
extern const struct config *config;
+
+bool is_logged_in();
+const char* get_access_token();
+const char* get_instance_url();
+int read_local_credentials();
+bool is_registered();
void config_load();
int config_save();
void config_set_client_id(const char *cid);
diff --git a/src/http.c b/src/http.c
index 96a723d..84b62be 100644
--- a/src/http.c
+++ b/src/http.c
@@ -6,6 +6,7 @@
#include <curl/curl.h>
#include "string-util.h"
#include "auth.h"
+#include "config.h"
#include "log.h"
#define BUFFER_SIZE (256 * 1024)
@@ -13,7 +14,11 @@
int http_init()
{
- return curl_global_init(CURL_GLOBAL_ALL);
+ if (curl_global_init(CURL_GLOBAL_ALL)) {
+ log_msg(LOG_WARNING, "http_init", "failed to init curl");
+ return -1;
+ }
+ return 0;
}
void http_cleanup()
@@ -44,7 +49,6 @@ static size_t write_response(void *ptr, size_t size, size_t nmemb, void *stream)
char *get_request(const char *url)
{
-
CURL *curl = NULL;
CURLcode status;
struct curl_slist *headers = NULL;
@@ -61,6 +65,8 @@ char *get_request(const char *url)
struct write_result write_result = {.data = data, .pos = 0};
+ log_msg(LOG_INFO, "get_request", "loggedid: %d, token: %s", is_logged_in(), get_access_token());
+
if (is_logged_in()) {
const char *token = get_access_token();
size_t s = strlen(AUTH_HEADER_STR_PREFIX) + strlen(token) + 1;
@@ -76,7 +82,7 @@ char *get_request(const char *url)
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &write_result);
status = curl_easy_perform(curl);
- if (status != 0) {
+ if (status) {
log_msg(LOG_WARNING, "get_request",
"unable to request data from %s: %s", url,
curl_easy_strerror(status));
@@ -145,7 +151,7 @@ char *post_request(const char *url, char *post_data)
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, post_data);
status = curl_easy_perform(curl);
- if (status != 0) {
+ if (status) {
log_msg(LOG_WARNING, "post_request", "unable to request data from %s: %s",
url, curl_easy_strerror(status));
goto error;
diff --git a/src/log.c b/src/log.c
index 3c78caf..f1cb7d5 100644
--- a/src/log.c
+++ b/src/log.c
@@ -4,7 +4,7 @@
#include "log.h"
static const char *log_levels[] = {
- "info"
+ "info",
"warning",
"error",
"fatal",
@@ -18,7 +18,7 @@ void log_msg(enum log_level level, const char *namespace, const char *format, ..
vfprintf(stderr, format, args);
fprintf(stderr, "\n");
- if (level == LOG_WARNING) {
+ if (level == LOG_ERROR) {
exit(EXIT_FAILURE);
} else if (level == LOG_FATAL) {
abort();
diff --git a/src/login_window.c b/src/login_window.c
index f1823b9..a060d6c 100644
--- a/src/login_window.c
+++ b/src/login_window.c
@@ -5,6 +5,7 @@
#include <stdbool.h>
#include <gtk-3.0/gtk/gtk.h>
#include "auth.h"
+#include "register.h"
#include "http.h"
#include "timeline.h"
#include "timeline_window.h"
@@ -17,54 +18,51 @@ static GtkWidget *instance_name_box, *email_box, *password_box;
static GtkWidget *submit_button;
static GtkWidget *spinner;
-static void window_removed(
- GtkApplication *application, GtkWindow *w, gpointer user_data)
+static gboolean login_completed(gpointer data)
{
- /* gtk_widget_destroy(GTK_WIDGET(w)); */
-}
-
-static void login_callback(bool success)
-{
- if (!success) {
+ int *val_ptr = data;
+ gtk_spinner_stop(GTK_SPINNER(spinner));
+ if (*val_ptr) {
log_msg(LOG_WARNING, "login_callback", "login failed");
- gtk_spinner_stop(GTK_SPINNER(spinner));
- return;
+ } else {
+ create_timeline_window(application, NULL);
}
- gtk_spinner_stop(GTK_SPINNER(spinner));
- create_timeline_window(application, NULL);
/* gtk_window_close(GTK_WINDOW(window)); */
/* gtk_application_remove_window( */
/* GTK_APPLICATION(application), GTK_WINDOW(window)); */
+
+ free(val_ptr);
+ return G_SOURCE_REMOVE;
}
-static void register_callback(bool success)
+static gpointer user_register(gpointer data)
{
- if (!success) {
- gtk_spinner_stop(GTK_SPINNER(spinner));
- log_msg(LOG_WARNING, "register_callback", "register failed");
- return;
- }
+ int *val_ptr;
+ const char *instance_name, *email, *password;
- const char *email, *password;
+ instance_name = gtk_entry_get_text(GTK_ENTRY(instance_name_box));
email = gtk_entry_get_text(GTK_ENTRY(email_box));
password = gtk_entry_get_text(GTK_ENTRY(password_box));
- if (login(email, password, &login_callback)) {
- gtk_spinner_stop(GTK_SPINNER(spinner));
- return;
+ val_ptr = malloc(sizeof(int));
+ *val_ptr = register_app(instance_name);
+ if (*val_ptr) {
+ log_msg(LOG_WARNING, "user_register", "registration failed");
+ }
+ *val_ptr = login(email, password);
+ if (*val_ptr) {
+ log_msg(LOG_WARNING, "user_register", "login failed");
}
+
+ gdk_threads_add_idle(login_completed, val_ptr);
+ return NULL;
}
static void submit_login_form()
{
gtk_spinner_start(GTK_SPINNER(spinner));
- const char *instance_name;
- instance_name = gtk_entry_get_text(GTK_ENTRY(instance_name_box));
- if (register_app(instance_name, &register_callback)) {
- gtk_spinner_stop(GTK_SPINNER(spinner));
- return;
- }
+ g_thread_new("register", user_register, NULL);
return;
}
@@ -104,7 +102,7 @@ void create_login_window(GtkApplication *app, gpointer user_data)
gtk_window_set_title(GTK_WINDOW(window), "ap_client");
gtk_window_set_default_size(GTK_WINDOW(window), 800, 600);
gtk_container_set_border_width(GTK_CONTAINER(window), 10);
- g_signal_connect(app, "window-removed", G_CALLBACK(window_removed), NULL);
+ /* g_signal_connect(app, "window-removed", G_CALLBACK(window_removed), NULL); */
box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 6);
gtk_widget_set_valign(GTK_WIDGET(box), GTK_ALIGN_CENTER);
diff --git a/src/main.c b/src/main.c
index c1c97f4..d95f547 100644
--- a/src/main.c
+++ b/src/main.c
@@ -23,8 +23,7 @@ static void startup(GtkApplication *app, gpointer user_data)
static void activate(GtkApplication *app, gpointer user_data)
{
if (read_local_credentials()) {
- /* create_login_window(app, user_data); */
- log_msg(LOG_WARNING, "activate", "failed to read config files");
+ create_login_window(app, user_data);
return;
}
create_timeline_window(app, NULL);
diff --git a/src/register.c b/src/register.c
new file mode 100644
index 0000000..4885ec5
--- /dev/null
+++ b/src/register.c
@@ -0,0 +1,128 @@
+#define _POSIX_C_SOURCE 200809L
+#include <err.h>
+#include <string.h>
+#include <stdbool.h>
+#include <jansson.h>
+#include "http.h"
+#include "config.h"
+#include "log.h"
+
+#define CLIENT_NAME "ap_client"
+#define REGISTER_URL "https://%s/api/v1/apps"
+
+struct register_response {
+ char *client_id;
+ char *client_secret;
+};
+
+static char *get_register_url(const char *domain)
+{
+ char *url;
+ size_t size;
+
+ size = strlen(REGISTER_URL) + strlen(domain) - 1;
+ url = malloc(size);
+ if (!url) {
+ err(1, NULL);
+ return NULL;
+ }
+
+ sprintf(url, REGISTER_URL, domain);
+ return url;
+}
+
+static char *get_register_req()
+{
+ char *req;
+ json_t *json_root;
+
+ json_root = json_pack_ex(NULL, 1, "{s:s, s:s, s:s}", "client_name",
+ CLIENT_NAME, "redirect_uris", "urn:ietf:wg:oauth:2.0:oob", "scopes",
+ "read write push");
+ if (!json_root) {
+ return NULL;
+ }
+
+ req = json_dumps(json_root, 0);
+ json_decref(json_root);
+
+ return req;
+}
+
+static struct register_response *get_register_resp(const char *data)
+{
+ struct register_response *resp;
+ json_t *json_root;
+ json_t *cid, *csec;
+
+ json_root = json_loads(data, 0, NULL);
+ if (!json_root) {
+ return NULL;
+ }
+
+ if (!json_is_object(json_root)) {
+ json_decref(json_root);
+ return NULL;
+ }
+
+ cid = json_object_get(json_root, "client_id");
+ csec = json_object_get(json_root, "client_secret");
+ if (!json_is_string(cid) || !json_is_string(csec)) {
+ json_decref(json_root);
+ return NULL;
+ }
+
+ resp = calloc(1, sizeof(struct register_response));
+ resp->client_id = strdup(json_string_value(cid));
+ resp->client_secret = strdup(json_string_value(csec));
+
+ json_decref(json_root);
+ return resp;
+}
+
+int register_app(const char *domain)
+{
+ char *url, *req_data, *resp_data;
+ struct register_response *resp;
+
+ if (!domain || !*domain) {
+ log_msg(LOG_WARNING, "register_app", "invalid argument");
+ return -1;
+ }
+
+ url = get_register_url(domain);
+ if (!url) {
+ return -1;
+ }
+
+ req_data = get_register_req();
+ if (!req_data) {
+ free(url);
+ return -1;
+ }
+
+ resp_data = post_request(url, req_data);
+ free(url);
+ free(req_data);
+ if (!resp_data) {
+ log_msg(LOG_WARNING, "register_app", "register request failed");
+ return -1;
+ }
+
+ resp = get_register_resp(resp_data);
+ free(resp_data);
+ if (!resp) {
+ log_msg(LOG_WARNING, "register_app", "invalid response");
+ return -1;
+ }
+
+ config_set_client_id(resp->client_id);
+ config_set_client_secret(resp->client_secret);
+ config_set_instance_url(domain);
+
+ free(resp->client_id);
+ free(resp->client_secret);
+ free(resp);
+
+ return 0;
+}
diff --git a/src/register.h b/src/register.h
new file mode 100644
index 0000000..2122d6b
--- /dev/null
+++ b/src/register.h
@@ -0,0 +1,8 @@
+#ifndef __REGISTER_H
+#define __REGISTER_H
+
+int register_app(const char *domain);
+
+#endif
+
+
diff --git a/src/string-util.c b/src/string-util.c
index e183288..d5a702b 100644
--- a/src/string-util.c
+++ b/src/string-util.c
@@ -19,6 +19,7 @@
#include <sys/types.h>
#include <string.h>
+#include <stdlib.h>
/*
* Copy src to string dst of size siz. At most siz-1 characters
@@ -83,3 +84,40 @@ size_t strlcat(char *dst, const char *src, size_t siz)
return (dlen + (s - src)); /* count does not include NUL */
}
+
+char *str_replace(const char *str, const char *old, const char *new)
+{
+ const char *ins;
+ char *result, *tmp;
+ size_t len_old, len_new, len_front, len_result;
+ int count;
+
+ if (!str || !old || !new)
+ return NULL;
+ len_old = strlen(old);
+ if (len_old == 0)
+ return NULL;
+ len_new = strlen(new);
+
+ ins = str;
+ for (count = 0; (tmp = strstr(ins, old)); ++count) {
+ ins = tmp + len_old;
+ }
+
+ len_result = strlen(str) + (len_new - len_old) * count + 1;
+ tmp = result = malloc(len_result);
+ if (!result)
+ return NULL;
+
+ while (count--) {
+ ins = strstr(str, old);
+ len_front = ins - str;
+ tmp = strncpy(tmp, str, len_front) + len_front;
+ strlcpy(tmp, new, len_result);
+ tmp += len_new;
+ str += len_front + len_old;
+ }
+ strlcpy(tmp, str, len_result);
+ return result;
+}
+
diff --git a/src/string-util.h b/src/string-util.h
index 5bf6f11..2625765 100644
--- a/src/string-util.h
+++ b/src/string-util.h
@@ -5,5 +5,6 @@
size_t strlcpy(char *dst, const char *src, size_t siz);
size_t strlcat(char *dst, const char *src, size_t siz);
+char *str_replace(const char *str, const char *old, const char *new);
#endif
diff --git a/src/timeline.c b/src/timeline.c
index 753f9f1..be1a4c4 100644
--- a/src/timeline.c
+++ b/src/timeline.c
@@ -5,11 +5,28 @@
#include <jansson.h>
#include "timeline.h"
#include "auth.h"
+#include "config.h"
#include "http.h"
#include "string-util.h"
#include "log.h"
-static char *timeline_url = "/api/v1/timelines/home";
+#define TIMELINE_URL "https://%s/api/v1/timelines/home"
+
+static char *get_timeline_url(const char *domain)
+{
+ char *url;
+ size_t size;
+
+ size = strlen(TIMELINE_URL) + strlen(domain) - 1;
+ url = malloc(size);
+ if (!url) {
+ err(1, NULL);
+ return NULL;
+ }
+
+ sprintf(url, TIMELINE_URL, domain);
+ return url;
+}
struct timeline *timeline_from_json(const char *json_data)
{
@@ -77,19 +94,15 @@ struct timeline *get_timeline(const char *max_id, const char *since_id, const ch
int limit)
{
char *url, *resp;
- size_t size;
struct timeline *t;
- size = strlen(get_instance_url()) + strlen(timeline_url) + 1;
- url = malloc(size);
+ url = get_timeline_url(config->instance_url);
if (!url) {
err(1, "get_timeline");
return NULL;
}
- strlcpy(url, get_instance_url(), size);
- strlcat(url, timeline_url, size);
-
+ printf("url: %s\n", url);
resp = get_request(url);
free(url);
if (!resp) {
diff --git a/src/timeline_window.c b/src/timeline_window.c
index 516f2d6..4b17824 100644
--- a/src/timeline_window.c
+++ b/src/timeline_window.c
@@ -8,19 +8,74 @@
#include "http.h"
#include "timeline.h"
#include "log.h"
+#include "string-util.h"
-static GtkWidget *window, *spinner, *list_box;
+static GtkWidget *window, *scrolled, *list_box;
+
+static gchar *html_to_pango(const char *content)
+{
+ char *str1, *str2;
+ gchar *result;
+ GRegex *regex = NULL;
+ GError *error = NULL;
+
+ if (!content) {
+ return NULL;
+ }
+
+ str1 = str_replace(content, "<p>", "");
+ if (!str1) {
+ log_msg(LOG_WARNING, "html_to_pango", "failed to parse html");
+ return NULL;
+ }
+ str2 = str_replace(str1, "</p>", "");
+ free(str1);
+ if (!str2) {
+ log_msg(LOG_WARNING, "html_to_pango", "failed to parse html");
+ return NULL;
+ }
+
+ regex = g_regex_new("(class|target|rel)=\"(.|\n)*?\"", G_REGEX_CASELESS, 0, &error);
+ if (!regex) {
+ log_msg(LOG_WARNING, "html_to_pango", error->message);
+ g_free(error);
+ return NULL;
+ }
+
+ result = g_regex_replace(regex, str2, -1, 0, "", 0, &error);
+ free(str2);
+ if (!result) {
+ log_msg(LOG_WARNING, "html_to_pango", error->message);
+ g_free(error);
+ return NULL;
+ }
+
+ return result;
+}
static gboolean timeline_loaded(gpointer data)
{
struct timeline *t = data;
- GtkWidget *label;
for (size_t i = 0; i < t->size; i++) {
- label = gtk_label_new(t->statuses[i]->content);
- gtk_widget_show(GTK_WIDGET(label));
- gtk_list_box_prepend(GTK_LIST_BOX(list_box), GTK_WIDGET(label));
+ GtkBuilder *post_builder;
+ GObject *post_box, *post_content_label;
+ char *content_markup;
+
+ content_markup = html_to_pango(t->statuses[i]->content);
+ if (!content_markup) {
+ continue;
+ }
+
+ post_builder = gtk_builder_new_from_file("data/post.ui");
+
+ post_box = gtk_builder_get_object(post_builder, "post_box");
+ post_content_label = gtk_builder_get_object(post_builder, "content_text");
+ gtk_label_set_markup(GTK_LABEL(post_content_label), content_markup);
+
+ gtk_list_box_prepend(GTK_LIST_BOX(list_box), GTK_WIDGET(post_box));
+ gtk_widget_show_all(GTK_WIDGET(post_box));
}
- gtk_spinner_stop(GTK_SPINNER(spinner));
+ /* gtk_spinner_stop(GTK_SPINNER(spinner)); */
timeline_free(t);
return G_SOURCE_REMOVE;
}
@@ -44,17 +99,21 @@ void create_timeline_window(GtkApplication *app, gpointer user_data)
gtk_window_set_title(GTK_WINDOW(window), "ap_client");
gtk_window_set_default_size(GTK_WINDOW(window), 800, 600);
gtk_container_set_border_width(GTK_CONTAINER(window), 10);
- gtk_window_set_destroy_with_parent(GTK_WINDOW(window), true);
+
+ scrolled = gtk_scrolled_window_new(NULL, NULL);
+ gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled),
+ GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
list_box = gtk_list_box_new();
gtk_widget_set_valign(GTK_WIDGET(list_box), GTK_ALIGN_CENTER);
gtk_widget_set_halign(GTK_WIDGET(list_box), GTK_ALIGN_CENTER);
- spinner = gtk_spinner_new();
- gtk_spinner_start(GTK_SPINNER(spinner));
- gtk_container_add(GTK_CONTAINER(list_box), spinner);
+ /* spinner = gtk_spinner_new(); */
+ /* gtk_spinner_start(GTK_SPINNER(spinner)); */
+ /* gtk_container_add(GTK_CONTAINER(list_box), spinner); */
- gtk_container_add(GTK_CONTAINER(window), list_box);
+ gtk_container_add(GTK_CONTAINER(window), scrolled);
+ gtk_container_add(GTK_CONTAINER(scrolled), list_box);
gtk_widget_show_all(window);
g_thread_new("load_timeline_thread", &load_timeline, NULL);