grim/purple-spasm

b4552380809e
Parents f9cdd9d6c7e3
Children 81c423d0d699
move the source code to the src directory
--- a/meson.build Sun Apr 19 05:03:55 2020 -0500
+++ b/meson.build Sun Apr 19 05:09:03 2020 -0500
@@ -4,16 +4,6 @@
version: '0.0.1',
)
-SOURCES = [
- 'spasm.c',
- 'spasm-account.c',
- 'spasm-chat.c',
- 'spasm-protocol.c',
- 'spasm-rest.c',
- 'spasm-user.c',
-]
-
-# The code below should not need to be modified for simple use cases.
add_project_arguments(
'-DPREFIX="@0@"'.format(get_option('prefix')),
'-DLIBDIR="@0@"'.format(get_option('libdir')),
@@ -25,13 +15,7 @@
SOUP = dependency('libsoup-2.4')
PURPLE3 = dependency('purple-3', version: '>=3.0.0')
-shared_library(
- meson.project_name() + '3',
- SOURCES,
- name_prefix: '',
- dependencies: [PURPLE3, JSON_GLIB, SOUP],
- install: true,
- install_dir: join_paths(get_option('libdir'), 'purple3'),
-)
subdir('icons')
+subdir('src')
+
--- a/spasm-account.c Sun Apr 19 05:03:55 2020 -0500
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,415 +0,0 @@
-/*
- * Spasm - A Twitch Protocol Plugin
- * Copyright (C) 2017-2019 Gary Kramlich <grim@reaperworld.com>
- *
- * This program 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 2
- * of the License, or (at your option) any later version.
- *
- * This program 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 this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-
-#include <purple.h>
-
-#include "spasm.h"
-#include "spasm-account.h"
-#include "spasm-chat.h"
-#include "spasm-const.h"
-#include "spasm-user.h"
-
-/******************************************************************************
- * Structs
- *****************************************************************************/
-struct _SpasmAccount {
- PurpleAccount *account;
- PurpleConnection *connection;
-
- GCancellable *cancellable;
-
- SoupSession *session;
-
- gchar *access_token;
-
- gchar *display_name;
- gchar *id;
- gchar *name;
- gchar *type;
- gchar *bio;
- gchar *logo;
- gchar *email;
- gboolean email_verified;
- gboolean partnered;
- gboolean twitter_connected;
-
- SpasmChatService *chat;
-};
-
-/******************************************************************************
- * Helpers
- *****************************************************************************/
-static SpasmAccount *
-spasm_account_new(PurpleAccount *account,
- PurpleConnection *connection)
-{
- SpasmAccount *sa = NULL;
-
- sa = g_new0(SpasmAccount, 1);
-
- sa->account = account;
- sa->connection = connection;
-
- sa->session = soup_session_new();
-
- SoupLogger *logger = soup_logger_new(SOUP_LOGGER_LOG_BODY, -1);
- soup_session_add_feature(sa->session, SOUP_SESSION_FEATURE(logger));
-
- return sa;
-}
-
-static void
-spasm_account_free(SpasmAccount *sa) {
- g_return_if_fail(sa);
-
- g_free(sa->access_token);
-
- spasm_chat_service_free(sa->chat);
-
- g_free(sa);
-}
-
-static void
-spasm_account_set_access_token(SpasmAccount *sa,
- const gchar *access_token)
-{
- g_return_if_fail(sa);
-
- sa->access_token = g_strdup(access_token);
-}
-
-static GError *
-spasm_account_update_profile(SpasmAccount *sa, JsonParser *parser) {
- JsonNode *root = NULL;
- JsonObject *obj = NULL;
- const gchar *str_attr = NULL;
-
- if(sa == NULL) {
- return g_error_new(
- SPASM_DOMAIN,
- 0,
- "invalid spasm account"
- );
- }
-
- root = json_parser_get_root(parser);
- obj = json_node_get_object(root);
-
- str_attr = json_object_get_string_member(obj, "display_name");
- sa->display_name = g_strdup(str_attr);
-
- str_attr = json_object_get_string_member(obj, "_id");
- sa->id = g_strdup(str_attr);
-
- str_attr = json_object_get_string_member(obj, "name");
- sa->name = g_strdup(str_attr);
-
- str_attr = json_object_get_string_member(obj, "type");
- sa->type = g_strdup(str_attr);
-
- str_attr = json_object_get_string_member(obj, "bio");
- sa->bio = g_strdup(str_attr);
-
- str_attr = json_object_get_string_member(obj, "logo");
- sa->logo = g_strdup(str_attr);
-
- str_attr = json_object_get_string_member(obj, "email");
- sa->email = g_strdup(str_attr);
-
- sa->email_verified = json_object_get_boolean_member(obj, "email_verified");
- sa->partnered = json_object_get_boolean_member(obj, "partnered");
- sa->twitter_connected = json_object_get_boolean_member(obj, "twitter_connected");
-
- return NULL;
-}
-
-/******************************************************************************
- * Auth
- *****************************************************************************/
-static void spasm_oauth_login(SpasmAccount *sa);
-
-static void
-spasm_login_test_cb(SpasmAccount *sa, JsonParser *parser,
- GError *error, gpointer data)
-{
- PurpleAccount *account = NULL;
- PurpleConnection *connection = NULL;
-
- if(error != NULL) {
- purple_debug_info("spasm", "oauth token invalid, re-authenticating : %s\n", error->message);
-
- spasm_oauth_login(sa);
-
- return;
- }
-
- account = spasm_account_get_account(sa);
- connection = purple_account_get_connection(account);
-
- error = spasm_account_update_profile(sa, parser);
- if(error != NULL) {
- gchar *err_msg = NULL;
-
- err_msg = g_strdup_printf(
- "failed to update profile : %s",
- error->message
- );
-
- g_error_free(error);
-
- purple_connection_error(
- connection,
- PURPLE_CONNECTION_ERROR_OTHER_ERROR,
- err_msg
- );
-
- g_free(err_msg);
-
- return;
- }
-
- sa->chat = spasm_chat_service_new(sa);
- spasm_chat_service_connect(sa->chat);
-}
-
-static void
-spasm_access_token_input_cb(gpointer data, const gchar *access_token) {
- PurpleAccount *account = NULL;
- PurpleConnection *connection = NULL;
- SpasmAccount *sa = SPASM_ACCOUNT(data);
-
- account = spasm_account_get_account(sa);
- purple_account_set_remember_password(account, TRUE);
-
- spasm_account_set_access_token(sa, access_token);
- purple_account_set_password(account, access_token, NULL, NULL);
-
- connection = spasm_account_get_connection(sa);
- purple_connection_update_progress(connection, "Verifying", 2, 3);
-
- spasm_get_user(sa, spasm_login_test_cb, NULL);
-}
-
-static void
-spasm_access_token_cancel_cb(gpointer data) {
- SpasmAccount *sa = SPASM_ACCOUNT(data);
-
- purple_connection_error(
- spasm_account_get_connection(sa),
- PURPLE_CONNECTION_ERROR_OTHER_ERROR,
- "User cancelled authorization"
- );
-
- spasm_account_free(sa);
-}
-
-static void
-spasm_oauth_login(SpasmAccount *sa) {
- PurpleAccount *account = NULL;
- PurpleConnection *connection = NULL;
- PurpleRequestCommonParameters *cpar = NULL;
- gchar *state = NULL, *uri = NULL;
- const gchar *username = NULL;
-
- account = spasm_account_get_account(sa);
- connection = purple_account_get_connection(account);
-
- purple_connection_update_progress(
- connection,
- "Authenticating",
- 0,
- 1
- );
-
- username = purple_account_get_username(account);
- state = g_strdup_printf("%s,%s", SPASM_PLUGIN_ID, username);
-
- uri = g_strdup_printf(
- SPASM_OAUTH2_URI,
- SPASM_OAUTH2_CLIENT_ID,
- SPASM_OAUTH2_REDIRECT_URI,
- SPASM_OAUTH2_SCOPES,
- state
- );
- g_free(state);
-
- /* send off the oauth implicit request */
- purple_notify_uri(connection, uri);
- g_free(uri);
-
- cpar = purple_request_cpar_from_account(account);
-
- purple_request_input(
- connection,
- "Access Token",
- "Enter the access token from https://pidgin.im/oauth.html which should have opened in your web browser",
- NULL,
- "access token",
- FALSE,
- TRUE,
- NULL,
- "OK", G_CALLBACK(spasm_access_token_input_cb),
- "Cancel", G_CALLBACK(spasm_access_token_cancel_cb),
- cpar,
- sa
- );
-}
-
-/******************************************************************************
- * API
- *****************************************************************************/
-PurpleAccount *
-spasm_account_get_account(const SpasmAccount *sa) {
- g_return_val_if_fail(sa, NULL);
-
- return sa->account;
-}
-
-PurpleConnection *
-spasm_account_get_connection(const SpasmAccount *sa) {
- g_return_val_if_fail(sa, NULL);
-
- return sa->connection;
-}
-
-GCancellable *
-spasm_account_get_cancellable(const SpasmAccount *sa) {
- g_return_val_if_fail(sa, NULL);
-
- return sa->cancellable;
-}
-
-SoupSession *
-spasm_account_get_session(const SpasmAccount *sa) {
- g_return_val_if_fail(sa, NULL);
-
- if(sa->session) {
- return SOUP_SESSION(g_object_ref(sa->session));
- }
-
- return NULL;
-}
-
-const gchar *
-spasm_account_get_access_token(const SpasmAccount *sa) {
- g_return_val_if_fail(sa, NULL);
-
- return sa->access_token;
-}
-
-const gchar *
-spasm_account_get_display_name(const SpasmAccount *sa) {
- g_return_val_if_fail(sa, NULL);
-
- return sa->display_name;
-}
-
-const gchar *
-spasm_account_get_id(const SpasmAccount *sa) {
- g_return_val_if_fail(sa, NULL);
-
- return sa->id;
-}
-
-const gchar *
-spasm_account_get_name(const SpasmAccount *sa) {
- g_return_val_if_fail(sa, NULL);
-
- return sa->name;
-}
-
-const gchar *
-spasm_account_get_type(const SpasmAccount *sa) {
- g_return_val_if_fail(sa, NULL);
-
- return sa->type;
-}
-
-const gchar *
-spasm_account_get_bio(const SpasmAccount *sa) {
- g_return_val_if_fail(sa, NULL);
-
- return sa->bio;
-}
-
-const gchar *
-spasm_account_get_logo(const SpasmAccount *sa) {
- g_return_val_if_fail(sa, NULL);
-
- return sa->logo;
-}
-
-const gchar *
-spasm_account_get_email(const SpasmAccount *sa) {
- g_return_val_if_fail(sa, NULL);
-
- return sa->email;
-}
-
-gboolean
-spasm_account_get_email_verified(const SpasmAccount *sa) {
- g_return_val_if_fail(sa, FALSE);
-
- return sa->email_verified;
-}
-
-gboolean
-spasm_account_get_partnered(const SpasmAccount *sa) {
- g_return_val_if_fail(sa, FALSE);
-
- return sa->partnered;
-}
-
-gboolean
-spasm_account_get_twitter_connected(const SpasmAccount *sa) {
- g_return_val_if_fail(sa, FALSE);
-
- return sa->twitter_connected;
-}
-
-static void
-spasm_account_login_got_password(PurpleAccount *account, const gchar *password,
- GError *error, gpointer data)
-{
- SpasmAccount *sa = SPASM_ACCOUNT(data);
-
- if(password != NULL) {
- spasm_account_set_access_token(sa, password);
- spasm_get_user(sa, spasm_login_test_cb, NULL);
- } else {
- spasm_oauth_login(sa);
- }
-}
-
-void
-spasm_account_login(PurpleAccount *account) {
- PurpleConnection *pc = NULL;
- SpasmAccount *sa = NULL;
-
- pc = purple_account_get_connection(account);
-
- sa = spasm_account_new(account, pc);
- purple_connection_set_protocol_data(pc, sa);
-
- purple_connection_set_state(pc, PURPLE_CONNECTION_CONNECTING);
-
- /* try to load the password */
- purple_account_get_password(account, spasm_account_login_got_password, sa);
-}
-
--- a/spasm-account.h Sun Apr 19 05:03:55 2020 -0500
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,66 +0,0 @@
-/*
- * Spasm - A Twitch Protocol Plugin
- * Copyright (C) 2017-2019 Gary Kramlich <grim@reaperworld.com>
- *
- * This program 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 2
- * of the License, or (at your option) any later version.
- *
- * This program 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 this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-
-#ifndef SPASM_ACCOUNT_H
-#define SPASM_ACCOUNT_H
-
-#include <glib.h>
-
-#include <json-glib/json-glib.h>
-
-#include <libsoup/soup.h>
-
-#include <purple.h>
-
-#define SPASM_ACCOUNT(obj) ((SpasmAccount *)(obj))
-
-typedef struct _SpasmAccount SpasmAccount;
-
-#include "spasm-chat.h"
-
-G_BEGIN_DECLS
-
-// SpasmAccount *spasm_account_new(PurpleAccount *account, PurpleConnection *connection);
-// void spasm_account_free(SpasmAccount *sa);
-
-PurpleAccount *spasm_account_get_account(const SpasmAccount *sa);
-PurpleConnection *spasm_account_get_connection(const SpasmAccount *sa);
-
-void spasm_account_login(PurpleAccount *account);
-
-GCancellable *spasm_account_get_cancellable(const SpasmAccount *sa);
-
-SoupSession *spasm_account_get_session(const SpasmAccount *sa);
-
-const gchar *spasm_account_get_access_token(const SpasmAccount *sa);
-
-const gchar *spasm_account_get_display_name(const SpasmAccount *sa);
-const gchar *spasm_account_get_id(const SpasmAccount *sa);
-const gchar *spasm_account_get_name(const SpasmAccount *sa);
-const gchar *spasm_account_get_type(const SpasmAccount *sa);
-const gchar *spasm_account_get_bio(const SpasmAccount *sa);
-const gchar *spasm_account_get_logo(const SpasmAccount *sa);
-const gchar *spasm_account_get_email(const SpasmAccount *sa);
-gboolean spasm_account_get_email_verified(const SpasmAccount *sa);
-gboolean spasm_account_get_partnered(const SpasmAccount *sa);
-gboolean spasm_account_get_twitter_connected(const SpasmAccount *sa);
-
-G_END_DECLS
-
-#endif /* SPASM_ACCOUNT_H */
--- a/spasm-chat.c Sun Apr 19 05:03:55 2020 -0500
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,329 +0,0 @@
-/*
- * Spasm - A Twitch Protocol Plugin
- * Copyright (C) 2017-2019 Gary Kramlich <grim@reaperworld.com>
- *
- * This program 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 2
- * of the License, or (at your option) any later version.
- *
- * This program 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 this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-
-#include "spasm-chat.h"
-#include "spasm-const.h"
-
-#include <stdarg.h>
-
-#include <purple.h>
-
-/******************************************************************************
- * Structs
- *****************************************************************************/
-struct _SpasmChatService {
- SpasmAccount *sa;
-
- GSocketClient *socket_client;
- GSocketConnection *socket_connection;
- GOutputStream *output_stream;
- GDataInputStream *input_stream;
-};
-
-/******************************************************************************
- * sending
- *****************************************************************************/
-static void
-spasm_chat_service_real_send(SpasmChatService *chat,
- const gchar *format, ...)
-{
- GCancellable *cancellable = NULL;
- GError *error = NULL;
- gchar *buffer = NULL;
- gboolean success;
- va_list vargs;
-
- cancellable = spasm_account_get_cancellable(chat->sa);
-
- va_start(vargs, format);
- buffer = g_strdup_vprintf(format, vargs);
- va_end(vargs);
-
- success = g_output_stream_printf(
- chat->output_stream,
- NULL,
- cancellable,
- &error,
- buffer
- );
-
- g_free(buffer);
-
- if(!success) {
- PurpleConnection *purple_connection = spasm_account_get_connection(chat->sa);
-
- if(error) {
- purple_connection_error(
- purple_connection,
- PURPLE_CONNECTION_ERROR_OTHER_ERROR,
- error->message
- );
-
- g_error_free(error);
- } else {
- purple_connection_error(
- purple_connection,
- PURPLE_CONNECTION_ERROR_OTHER_ERROR,
- "unknown error"
- );
- }
-
- return;
- }
- g_output_stream_flush(chat->output_stream, NULL, NULL);
-}
-
-/******************************************************************************
- * read loop
- *****************************************************************************/
-static void
-spasm_chat_service_parse(SpasmChatService *chat,
- const gchar *buffer)
-{
- if(purple_str_has_prefix(buffer, "PING ")) {
- purple_debug_misc("spasm", "PING? PONG!\n");
-
- spasm_chat_service_real_send(chat, "PONG %s\r\n", buffer + 5);
- }
-}
-
-static void spasm_chat_read(SpasmChatService *chat);
-
-static void
-spasm_chat_read_cb(GObject *obj, GAsyncResult *res, gpointer data) {
- GError *error = NULL;
- gchar *buffer = NULL;
- gsize buffer_len;
- SpasmChatService *chat = (SpasmChatService *)data;
-
- buffer = g_data_input_stream_read_line_finish(
- G_DATA_INPUT_STREAM(obj),
- res,
- &buffer_len,
- &error
- );
-
- /* g_data_input_stream_read_line_finish, will return null with error set
- * on connection error. It will also return null with error not set if
- * there is no content to read.
- */
- if(buffer == NULL) {
- gchar *error_msg = NULL;
-
- if(error != NULL) {
- error_msg = g_strdup_printf(
- "spasm lost connection with server : %s",
- error->message
- );
-
- g_error_free(error);
- } else {
- error_msg = g_strdup_printf("spasm server closed connection");
- }
-
- purple_connection_error(
- spasm_account_get_connection(chat->sa),
- PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
- error_msg
- );
-
- g_free(error_msg);
-
- return;
- }
-
- purple_debug_info("spasm", "chat buffer: %s\n", buffer);
-
- spasm_chat_service_parse(chat, buffer);
-
- g_free(buffer);
-
- spasm_chat_read(chat);
-}
-
-static void
-spasm_chat_read(SpasmChatService *chat) {
- g_data_input_stream_read_line_async(
- chat->input_stream,
- G_PRIORITY_DEFAULT,
- spasm_account_get_cancellable(chat->sa),
- spasm_chat_read_cb,
- chat
- );
-}
-
-/******************************************************************************
- * chat login flow
- *****************************************************************************/
-static void
-spasm_chat_login_cb(GObject *obj, GAsyncResult *res, gpointer data) {
- GError *error = NULL;
- PurpleConnection *purple_connection = NULL;
- SpasmChatService *chat = (SpasmChatService *)data;
-
- purple_connection = spasm_account_get_connection(chat->sa);
-
- chat->socket_connection = g_socket_client_connect_to_host_finish(
- G_SOCKET_CLIENT(obj),
- res,
- &error
- );
-
- if(chat->socket_connection == NULL) {
- if(error) {
- g_prefix_error(&error, "failed to connect: ");
-
- purple_connection_error(
- purple_connection,
- PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
- error->message
- );
-
- g_error_free(error);
- } else {
- purple_connection_error(
- purple_connection,
- PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
- "unknown error"
- );
- }
-
- return;
- }
-
- chat->output_stream = g_io_stream_get_output_stream(G_IO_STREAM(chat->socket_connection));
-
- /* now do the login */
- spasm_chat_service_real_send(
- chat,
- "PASS oauth:%s\r\n",
- spasm_account_get_access_token(chat->sa)
- );
-
- /* now try to use our nick */
- spasm_chat_service_real_send(
- chat,
- "NICK %s\r\n",
- spasm_account_get_name(chat->sa)
- );
-
- purple_connection_set_state(purple_connection, PURPLE_CONNECTION_CONNECTED);
-
- chat->input_stream = g_data_input_stream_new(
- g_io_stream_get_input_stream(G_IO_STREAM(chat->socket_connection))
- );
-
- spasm_chat_read(chat);
-}
-
-SpasmChatService *
-spasm_chat_service_new(SpasmAccount *sa) {
- SpasmChatService *chat = NULL;
-
- g_return_val_if_fail(sa, NULL);
-
- chat = g_slice_new0(SpasmChatService);
- chat->sa = sa;
-
- return chat;
-}
-
-void
-spasm_chat_service_free(SpasmChatService *chat) {
- g_object_unref(chat->input_stream);
- g_object_unref(chat->output_stream);
- g_object_unref(chat->socket_client);
- g_object_unref(chat->socket_connection);
-
- g_slice_free(SpasmChatService, chat);
-}
-
-void
-spasm_chat_service_connect(SpasmChatService *chat) {
- g_return_if_fail(chat);
-
- chat->socket_client = g_socket_client_new();
-
- g_socket_client_set_tls(chat->socket_client, TRUE);
-
- g_socket_client_connect_to_host_async(
- chat->socket_client,
- SPASM_CHAT_HOSTNAME,
- SPASM_CHAT_PORT,
- spasm_account_get_cancellable(chat->sa),
- spasm_chat_login_cb,
- chat
- );
-}
-
-GList *_spasm_chat_service_info(PurpleConnection *connection) {
- return NULL;
-}
-
-GHashTable *
-spasm_chat_service_info_default(PurpleConnection *connection,
- const gchar *name)
-{
- GHashTable *defaults = g_hash_table_new_full(
- g_str_hash,
- g_str_equal,
- NULL,
- g_free
- );
-
- if(name != NULL) {
- g_hash_table_insert(defaults, "channel", g_strdup(name));
- }
-
- return defaults;
-}
-
-void
-spasm_chat_service_join(PurpleConnection *connection,
- GHashTable *components)
-{
-
-}
-
-gchar *
-spasm_chat_service_name(GHashTable *components) {
- return g_strdup(g_hash_table_lookup(components, "channel"));
-}
-
-void
-spasm_chat_service_leave(PurpleConnection *connection, gint id) {
-
-}
-
-gint
-spasm_chat_service_send(PurpleConnection *connection,
- gint id,
- const gchar *message,
- PurpleMessageFlags flags)
-{
- return -1;
-}
-
-void
-spasm_chat_service_set_topic(PurpleConnection *connection,
- gint id,
- const gchar *topic)
-{
-
-}
--- a/spasm-chat.h Sun Apr 19 05:03:55 2020 -0500
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,46 +0,0 @@
-/*
- * Spasm - A Twitch Protocol Plugin
- * Copyright (C) 2017-2019 Gary Kramlich <grim@reaperworld.com>
- *
- * This program 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 2
- * of the License, or (at your option) any later version.
- *
- * This program 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 this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-
-#ifndef SPASM_CHAT
-#define SPASM_CHAT
-
-#include <glib.h>
-
-typedef struct _SpasmChatService SpasmChatService;
-
-#include "spasm-account.h"
-
-G_BEGIN_DECLS
-
-SpasmChatService *spasm_chat_service_new(SpasmAccount *sa);
-void spasm_chat_service_free(SpasmChatService *chat);
-
-void spasm_chat_service_connect(SpasmChatService *chat);
-
-GList *spasm_chat_service_info(PurpleConnection *connection);
-GHashTable *spasm_chat_service_info_default(PurpleConnection *connection, const gchar *name);
-void spasm_chat_service_join(PurpleConnection *connection, GHashTable *components);
-gchar *spasm_chat_service_name(GHashTable *components);
-void spasm_chat_service_leave(PurpleConnection *connection, gint id);
-gint spasm_chat_service_send(PurpleConnection *connection, gint id, const gchar *message, PurpleMessageFlags flags);
-void spasm_chat_service_set_topic(PurpleConnection *connection, gint id, const gchar *topic);
-
-G_END_DECLS
-
-#endif /* SPASM_CHAT */
--- a/spasm-const.h Sun Apr 19 05:03:55 2020 -0500
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,42 +0,0 @@
-/*
- * Spasm - A Twitch Protocol Plugin
- * Copyright (C) 2017-2019 Gary Kramlich <grim@reaperworld.com>
- *
- * This program 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 2
- * of the License, or (at your option) any later version.
- *
- * This program 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 this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-
-#ifndef SPASM_CONSTS_H
-#define SPASM_CONSTS_H
-
-#define SPASM_PLUGIN_ID "prpl-grim-spasm"
-
-#define SPASM_CHAT_HOSTNAME "irc.chat.twitch.tv"
-#define SPASM_CHAT_PORT 443
-
-#define SPASM_BASE_URL "https://api.twitch.tv/kraken/"
-#define SPASM_WEBSOCKET_URL "wss://pubsub-edge.twitch.tv"
-#define SPASM_CONTENT_TYPE "application/vnd.twitchtv.v5+json"
-
-#define SPASM_OAUTH2_CLIENT_ID "w7le4wyxwbipv6kf5qmqogwkqskl12"
-#define SPASM_OAUTH2_SCOPES "user_read chat_login"
-#define SPASM_OAUTH2_REDIRECT_URI "https://pidgin.im/oauth.html"
-#define SPASM_OAUTH2_URI SPASM_BASE_URL "oauth2/authorize?" \
- "response_type=token&" \
- "client_id=%s&" \
- "redirect_uri=%s&" \
- "scope=%s&" \
- "state=%s"
-
-#endif /* SPASM_CONSTS_H */
--- a/spasm-protocol.c Sun Apr 19 05:03:55 2020 -0500
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,87 +0,0 @@
-/*
- * Spasm - A Twitch Protocol Plugin
- * Copyright (C) 2017-2019 Gary Kramlich <grim@reaperworld.com>
- *
- * This program 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 2
- * of the License, or (at your option) any later version.
- *
- * This program 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 this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-
-#include "spasm-protocol.h"
-#include "spasm-account.h"
-
-/******************************************************************************
- * PurpleProtocol implementation
- *****************************************************************************/
-static const gchar *
-spasm_protocol_list_icon(PurpleAccount *account, PurpleBuddy *buddy) {
- return "spasm";
-}
-
-static void
-spasm_protocol_close(PurpleConnection *connection) {
-}
-
-static GList *
-spasm_protocol_get_status_types(PurpleAccount *account) {
- GList *types = NULL;
- PurpleStatusType *status;
-
- status = purple_status_type_new_full(PURPLE_STATUS_AVAILABLE, "online", "Online", TRUE, TRUE, FALSE);
- types = g_list_append(types, status);
-
- status = purple_status_type_new_full(PURPLE_STATUS_OFFLINE, NULL, "Offline", TRUE, TRUE, FALSE);
- types = g_list_append(types, status);
-
- return types;
-}
-
-/******************************************************************************
- * GObject Implementation
- *****************************************************************************/
-G_DEFINE_DYNAMIC_TYPE(
- SpasmProtocol,
- spasm_protocol,
- PURPLE_TYPE_PROTOCOL
-)
-
-static void
-spasm_protocol_init(SpasmProtocol *protocol) {
- PurpleProtocol *prpl = PURPLE_PROTOCOL(protocol);
-
- prpl->id = "prpl-grim-spasm";
- prpl->name = "Twitch";
- prpl->options = OPT_PROTO_NO_PASSWORD;
-}
-
-static void
-spasm_protocol_class_init(SpasmProtocolClass *klass) {
- PurpleProtocolClass *protocol_class = PURPLE_PROTOCOL_CLASS(klass);
-
- protocol_class->login = spasm_account_login;
- protocol_class->list_icon = spasm_protocol_list_icon;
- protocol_class->close = spasm_protocol_close;
- protocol_class->status_types = spasm_protocol_get_status_types;
-}
-
-static void
-spasm_protocol_class_finalize(SpasmProtocolClass *klass) {
-}
-
-/******************************************************************************
- * API
- *****************************************************************************/
-void
-spasm_protocol_register(GTypeModule *module) {
- spasm_protocol_register_type(module);
-}
--- a/spasm-protocol.h Sun Apr 19 05:03:55 2020 -0500
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,55 +0,0 @@
-/*
- * Spasm - A Twitch Protocol Plugin
- * Copyright (C) 2017-2019 Gary Kramlich <grim@reaperworld.com>
- *
- * This program 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 2
- * of the License, or (at your option) any later version.
- *
- * This program 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 this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-
-#ifndef SPASM_PROTOCOL_H
-#define SPASM_PROTOCOL_H
-
-#include <purple.h>
-
-#include <glib-object.h>
-
-G_BEGIN_DECLS
-
-#define SPASM_TYPE_PROTOCOL (spasm_protocol_get_type())
-#define SPASM_PROTOCOL(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), SPASM_TYPE_PROTOCOL, SpasmProtocol))
-#define SPASM_PROTOCOL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), SPASM_TYPE_PROTOCOL, SpasmProtocolClass))
-
-typedef struct _SpasmProtocol SpasmProtocol;
-typedef struct _SpasmProtocolClass SpasmProtocolClass;
-
-struct _SpasmProtocol {
- /*< private >*/
- PurpleProtocol parent;
-
- gpointer reserved[4];
-};
-
-struct _SpasmProtocolClass {
- /*< private >*/
- PurpleProtocolClass parent;
-
- gpointer reserved[4];
-};
-
-GType spasm_protocol_get_type(void);
-void spasm_protocol_register(GTypeModule *module);
-
-G_END_DECLS
-
-#endif
--- a/spasm-rest.c Sun Apr 19 05:03:55 2020 -0500
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,182 +0,0 @@
-/*
- * Spasm - A Twitch Protocol Plugin
- * Copyright (C) 2017-2019 Gary Kramlich <grim@reaperworld.com>
- *
- * This program 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 2
- * of the License, or (at your option) any later version.
- *
- * This program 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 this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-
-#include <libsoup/soup.h>
-
-#include <purple.h>
-
-#include "spasm.h"
-#include "spasm-const.h"
-#include "spasm-rest.h"
-
-/******************************************************************************
- * Types
- *****************************************************************************/
-typedef struct {
- SpasmAccount *sa;
- SpasmRestCallback cb;
- gpointer data;
-} SpasmRestCallbackData;
-
-/******************************************************************************
- * Callbacks
- *****************************************************************************/
-static void
-spasm_request_cb(GObject *obj, GAsyncResult *res, gpointer data) {
- GInputStream *istream = NULL;
- GError *error = NULL;
- JsonParser *parser = NULL;
- JsonNode *root = NULL;
- JsonObject *json_obj = NULL;
- SpasmRestCallbackData *cb_data = (SpasmRestCallbackData *)data;
-
- istream = soup_session_send_finish(SOUP_SESSION(obj), res, &error);
- if(error) {
- cb_data->cb(cb_data->sa, NULL, error, cb_data->data);
-
- g_free(cb_data);
-
- return;
- }
-
- parser = json_parser_new();
- json_parser_load_from_stream(parser, istream, NULL, &error);
- if(error) {
- cb_data->cb(cb_data->sa, NULL, error, cb_data->data);
-
- g_free(cb_data);
- g_object_unref(G_OBJECT(parser));
-
- return;
- }
-
- root = json_parser_get_root(parser);
- if(root == NULL || !JSON_NODE_HOLDS_OBJECT(root)) {
- error = g_error_new(
- SPASM_DOMAIN,
- 0,
- "invalid json response"
- );
-
- cb_data->cb(cb_data->sa, NULL, error, cb_data->data);
-
- g_error_free(error);
- g_free(cb_data);
- g_object_unref(G_OBJECT(parser));
-
- return;
- }
-
- json_obj = json_node_get_object(root);
- if(json_obj == NULL) {
- error = g_error_new(
- SPASM_DOMAIN,
- 0,
- "json is not an object"
- );
-
- cb_data->cb(cb_data->sa, NULL, error, cb_data->data);
-
- g_error_free(error);
- g_free(cb_data);
- g_object_unref(G_OBJECT(parser));
-
- return;
- }
-
- if(json_object_has_member(json_obj, "error")) {
- purple_debug_info("spasm", "error: %s\n", json_object_get_string_member(json_obj, "error"));
- error = g_error_new(
- SPASM_DOMAIN,
- 0,
- "error : %s : %" G_GINT64_FORMAT,
- json_object_get_string_member(json_obj, "message"),
- json_object_get_int_member(json_obj, "status")
- );
-
- cb_data->cb(cb_data->sa, NULL, error, cb_data->data);
-
- g_error_free(error);
- g_free(cb_data);
- g_object_unref(G_OBJECT(parser));
-
- return;
- }
-
- cb_data->cb(cb_data->sa, parser, NULL, cb_data->data);
-
- g_object_unref(G_OBJECT(parser));
-}
-
-/******************************************************************************
- * API
- *****************************************************************************/
-void
-spasm_rest_request(SpasmAccount *sa, const gchar *method,
- const gchar *path, const gchar *body,
- SpasmRestCallback callback, gpointer data)
-{
- SpasmRestCallbackData *cb;
- SoupMessage *msg = NULL;
- SoupSession *session = NULL;
- gchar *url = NULL, *auth = NULL;
-
- g_return_if_fail(sa);
-
- url = g_build_filename(SPASM_BASE_URL, path, NULL);
- msg = soup_message_new(method, url);
- g_free(url);
-
- // Set the Client-ID header
- soup_message_headers_replace(
- msg->request_headers,
- "Client-ID",
- SPASM_OAUTH2_CLIENT_ID
- );
-
- // Set the Accept header
- soup_message_headers_replace(
- msg->request_headers,
- "Accept",
- SPASM_CONTENT_TYPE
- );
-
- // Set the authorization header to the access token
- auth = g_strdup_printf("OAuth %s", spasm_account_get_access_token(sa));
- soup_message_headers_replace(
- msg->request_headers,
- "Authorization", auth
- );
- g_free(auth);
-
- cb = g_new0(SpasmRestCallbackData, 1);
- cb->sa = sa;
- cb->cb = callback;
- cb->data = data;
-
- session = spasm_account_get_session(sa);
- soup_session_send_async(
- session,
- msg,
- spasm_account_get_cancellable(sa),
- spasm_request_cb,
- cb
- );
- g_object_unref(G_OBJECT(session));
-}
--- a/spasm-rest.h Sun Apr 19 05:03:55 2020 -0500
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,37 +0,0 @@
-/*
- * Spasm - A Twitch Protocol Plugin
- * Copyright (C) 2017-2019 Gary Kramlich <grim@reaperworld.com>
- *
- * This program 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 2
- * of the License, or (at your option) any later version.
- *
- * This program 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 this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-
-#ifndef SPASM_REST_H
-#define SPASM_REST_H
-
-#include <glib.h>
-
-#include <json-glib/json-glib.h>
-
-#include "spasm-account.h"
-
-G_BEGIN_DECLS
-
-typedef void (*SpasmRestCallback)(SpasmAccount *sa, JsonParser *parser, GError *error, gpointer data);
-
-void spasm_rest_request(SpasmAccount *sa, const gchar *method, const gchar *path, const gchar *body, SpasmRestCallback callback, gpointer data);
-
-G_END_DECLS
-
-#endif /* SPASM_USER_H */
--- a/spasm-user.c Sun Apr 19 05:03:55 2020 -0500
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,40 +0,0 @@
-/*
- * Spasm - A Twitch Protocol Plugin
- * Copyright (C) 2017-2019 Gary Kramlich <grim@reaperworld.com>
- *
- * This program 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 2
- * of the License, or (at your option) any later version.
- *
- * This program 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 this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-
-#include <json-glib/json-glib.h>
-
-#include <purple.h>
-
-#include "spasm-user.h"
-#include "spasm-rest.h"
-
-/******************************************************************************
- * User endpoint
- *****************************************************************************/
-void
-spasm_get_user(SpasmAccount *sa, SpasmRestCallback cb, gpointer data) {
- spasm_rest_request(
- sa,
- "GET",
- "/user",
- NULL,
- cb,
- data
- );
-}
--- a/spasm-user.h Sun Apr 19 05:03:55 2020 -0500
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,34 +0,0 @@
-/*
- * Spasm - A Twitch Protocol Plugin
- * Copyright (C) 2017-2019 Gary Kramlich <grim@reaperworld.com>
- *
- * This program 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 2
- * of the License, or (at your option) any later version.
- *
- * This program 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 this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-
-#ifndef SPASM_USER_H
-#define SPASM_USER_H
-
-#include <glib.h>
-
-#include "spasm-account.h"
-#include "spasm-rest.h"
-
-G_BEGIN_DECLS
-
-void spasm_get_user(SpasmAccount *sa, SpasmRestCallback cb, gpointer data);
-
-G_END_DECLS
-
-#endif /* SPASM_USER_H */
--- a/spasm.c Sun Apr 19 05:03:55 2020 -0500
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,105 +0,0 @@
-/*
- * Spasm - A Twitch Protocol Plugin
- * Copyright (C) 2017-2019 Gary Kramlich <grim@reaperworld.com>
- *
- * This program 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 2
- * of the License, or (at your option) any later version.
- *
- * This program 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 this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-
-#include <glib.h>
-
-#include <purple.h>
-
-#include "spasm-account.h"
-#include "spasm-const.h"
-#include "spasm-protocol.h"
-
-/******************************************************************************
- * Globals
- *****************************************************************************/
-static PurpleProtocol *spasm = NULL;
-
-/******************************************************************************
- * Implementations
- *****************************************************************************/
-
-/******************************************************************************
- * Plugin Stuff
- *****************************************************************************/
-#if 0
-static PurplePluginProtocolInfo prpl_info = {
- .struct_size = sizeof(PurplePluginProtocolInfo),
-
- .options = OPT_PROTO_NO_PASSWORD,
-
- .login = purple_spasm_account_login,
- .close = _purple_spasm_close,
- .list_icon = _purple_spasm_list_icon,
-
- .status_types = _purple_spasm_get_status_types,
- .set_status = _purple_spasm_set_status,
-
- // chat stuff
- .chat_info = purple_spasm_chat_service_info,
- .chat_info_defaults = purple_spasm_chat_service_info_default,
- .join_chat = purple_spasm_chat_service_join,
- .get_chat_name = purple_spasm_chat_service_name,
- .chat_leave = purple_spasm_chat_service_leave,
- .chat_send = purple_spasm_chat_service_send,
- .set_chat_topic = purple_spasm_chat_service_set_topic,
-};
-#endif
-
-/******************************************************************************
- * GPlugin Interface
- *****************************************************************************/
-G_MODULE_EXPORT GPluginPluginInfo *
-gplugin_query(GError **error) {
- const gchar * const authors [] = { "Gary Kramlich <grim@reaperworld.com>", NULL };
-
- return GPLUGIN_PLUGIN_INFO(purple_plugin_info_new(
- "id", SPASM_PLUGIN_ID,
- "name", "Twitch",
- "version", PLUGIN_VERSION,
- "abi-version", PURPLE_ABI_VERSION,
- "flags", PURPLE_PLUGIN_INFO_FLAGS_INTERNAL | PURPLE_PLUGIN_INFO_FLAGS_AUTO_LOAD,
-
- "summary", "Twitch.tv Protocol Plugin",
- "description", "Implements the Twitch.tv chat protocol",
- "authors", authors,
- "website", "https://bitbucket.org/rw_grim/purple-spasm",
-
- NULL
- ));
-}
-
-G_MODULE_EXPORT gboolean
-gplugin_load(GPluginPlugin *plugin, GError **error) {
- spasm_protocol_register(G_TYPE_MODULE(plugin));
-
- spasm = purple_protocols_add(SPASM_TYPE_PROTOCOL, error);
- if(spasm == NULL) {
- g_warning("instance was null");
- return FALSE;
- }
-
- return TRUE;
-}
-
-G_MODULE_EXPORT gboolean
-gplugin_unload(GPluginPlugin *plugin, GError **error) {
- spasm = NULL;
-
- return TRUE;
-}
--- a/spasm.h Sun Apr 19 05:03:55 2020 -0500
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,27 +0,0 @@
-/*
- * Spasm - A Twitch Protocol Plugin
- * Copyright (C) 2017-2019 Gary Kramlich <grim@reaperworld.com>
- *
- * This program 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 2
- * of the License, or (at your option) any later version.
- *
- * This program 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 this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-
-#ifndef SPASM_H
-#define SPASM_H
-
-#include <glib.h>
-
-#define SPASM_DOMAIN (g_quark_from_static_string("purple-spasm"))
-
-#endif /* SPASM_H */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/meson.build Sun Apr 19 05:09:03 2020 -0500
@@ -0,0 +1,18 @@
+SOURCES = [
+ 'spasm.c',
+ 'spasm-account.c',
+ 'spasm-chat.c',
+ 'spasm-protocol.c',
+ 'spasm-rest.c',
+ 'spasm-user.c',
+]
+
+shared_library(
+ meson.project_name() + '3',
+ SOURCES,
+ name_prefix: '',
+ dependencies: [PURPLE3, JSON_GLIB, SOUP],
+ install: true,
+ install_dir: join_paths(get_option('libdir'), 'purple3'),
+)
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/spasm-account.c Sun Apr 19 05:09:03 2020 -0500
@@ -0,0 +1,415 @@
+/*
+ * Spasm - A Twitch Protocol Plugin
+ * Copyright (C) 2017-2019 Gary Kramlich <grim@reaperworld.com>
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include <purple.h>
+
+#include "spasm.h"
+#include "spasm-account.h"
+#include "spasm-chat.h"
+#include "spasm-const.h"
+#include "spasm-user.h"
+
+/******************************************************************************
+ * Structs
+ *****************************************************************************/
+struct _SpasmAccount {
+ PurpleAccount *account;
+ PurpleConnection *connection;
+
+ GCancellable *cancellable;
+
+ SoupSession *session;
+
+ gchar *access_token;
+
+ gchar *display_name;
+ gchar *id;
+ gchar *name;
+ gchar *type;
+ gchar *bio;
+ gchar *logo;
+ gchar *email;
+ gboolean email_verified;
+ gboolean partnered;
+ gboolean twitter_connected;
+
+ SpasmChatService *chat;
+};
+
+/******************************************************************************
+ * Helpers
+ *****************************************************************************/
+static SpasmAccount *
+spasm_account_new(PurpleAccount *account,
+ PurpleConnection *connection)
+{
+ SpasmAccount *sa = NULL;
+
+ sa = g_new0(SpasmAccount, 1);
+
+ sa->account = account;
+ sa->connection = connection;
+
+ sa->session = soup_session_new();
+
+ SoupLogger *logger = soup_logger_new(SOUP_LOGGER_LOG_BODY, -1);
+ soup_session_add_feature(sa->session, SOUP_SESSION_FEATURE(logger));
+
+ return sa;
+}
+
+static void
+spasm_account_free(SpasmAccount *sa) {
+ g_return_if_fail(sa);
+
+ g_free(sa->access_token);
+
+ spasm_chat_service_free(sa->chat);
+
+ g_free(sa);
+}
+
+static void
+spasm_account_set_access_token(SpasmAccount *sa,
+ const gchar *access_token)
+{
+ g_return_if_fail(sa);
+
+ sa->access_token = g_strdup(access_token);
+}
+
+static GError *
+spasm_account_update_profile(SpasmAccount *sa, JsonParser *parser) {
+ JsonNode *root = NULL;
+ JsonObject *obj = NULL;
+ const gchar *str_attr = NULL;
+
+ if(sa == NULL) {
+ return g_error_new(
+ SPASM_DOMAIN,
+ 0,
+ "invalid spasm account"
+ );
+ }
+
+ root = json_parser_get_root(parser);
+ obj = json_node_get_object(root);
+
+ str_attr = json_object_get_string_member(obj, "display_name");
+ sa->display_name = g_strdup(str_attr);
+
+ str_attr = json_object_get_string_member(obj, "_id");
+ sa->id = g_strdup(str_attr);
+
+ str_attr = json_object_get_string_member(obj, "name");
+ sa->name = g_strdup(str_attr);
+
+ str_attr = json_object_get_string_member(obj, "type");
+ sa->type = g_strdup(str_attr);
+
+ str_attr = json_object_get_string_member(obj, "bio");
+ sa->bio = g_strdup(str_attr);
+
+ str_attr = json_object_get_string_member(obj, "logo");
+ sa->logo = g_strdup(str_attr);
+
+ str_attr = json_object_get_string_member(obj, "email");
+ sa->email = g_strdup(str_attr);
+
+ sa->email_verified = json_object_get_boolean_member(obj, "email_verified");
+ sa->partnered = json_object_get_boolean_member(obj, "partnered");
+ sa->twitter_connected = json_object_get_boolean_member(obj, "twitter_connected");
+
+ return NULL;
+}
+
+/******************************************************************************
+ * Auth
+ *****************************************************************************/
+static void spasm_oauth_login(SpasmAccount *sa);
+
+static void
+spasm_login_test_cb(SpasmAccount *sa, JsonParser *parser,
+ GError *error, gpointer data)
+{
+ PurpleAccount *account = NULL;
+ PurpleConnection *connection = NULL;
+
+ if(error != NULL) {
+ purple_debug_info("spasm", "oauth token invalid, re-authenticating : %s\n", error->message);
+
+ spasm_oauth_login(sa);
+
+ return;
+ }
+
+ account = spasm_account_get_account(sa);
+ connection = purple_account_get_connection(account);
+
+ error = spasm_account_update_profile(sa, parser);
+ if(error != NULL) {
+ gchar *err_msg = NULL;
+
+ err_msg = g_strdup_printf(
+ "failed to update profile : %s",
+ error->message
+ );
+
+ g_error_free(error);
+
+ purple_connection_error(
+ connection,
+ PURPLE_CONNECTION_ERROR_OTHER_ERROR,
+ err_msg
+ );
+
+ g_free(err_msg);
+
+ return;
+ }
+
+ sa->chat = spasm_chat_service_new(sa);
+ spasm_chat_service_connect(sa->chat);
+}
+
+static void
+spasm_access_token_input_cb(gpointer data, const gchar *access_token) {
+ PurpleAccount *account = NULL;
+ PurpleConnection *connection = NULL;
+ SpasmAccount *sa = SPASM_ACCOUNT(data);
+
+ account = spasm_account_get_account(sa);
+ purple_account_set_remember_password(account, TRUE);
+
+ spasm_account_set_access_token(sa, access_token);
+ purple_account_set_password(account, access_token, NULL, NULL);
+
+ connection = spasm_account_get_connection(sa);
+ purple_connection_update_progress(connection, "Verifying", 2, 3);
+
+ spasm_get_user(sa, spasm_login_test_cb, NULL);
+}
+
+static void
+spasm_access_token_cancel_cb(gpointer data) {
+ SpasmAccount *sa = SPASM_ACCOUNT(data);
+
+ purple_connection_error(
+ spasm_account_get_connection(sa),
+ PURPLE_CONNECTION_ERROR_OTHER_ERROR,
+ "User cancelled authorization"
+ );
+
+ spasm_account_free(sa);
+}
+
+static void
+spasm_oauth_login(SpasmAccount *sa) {
+ PurpleAccount *account = NULL;
+ PurpleConnection *connection = NULL;
+ PurpleRequestCommonParameters *cpar = NULL;
+ gchar *state = NULL, *uri = NULL;
+ const gchar *username = NULL;
+
+ account = spasm_account_get_account(sa);
+ connection = purple_account_get_connection(account);
+
+ purple_connection_update_progress(
+ connection,
+ "Authenticating",
+ 0,
+ 1
+ );
+
+ username = purple_account_get_username(account);
+ state = g_strdup_printf("%s,%s", SPASM_PLUGIN_ID, username);
+
+ uri = g_strdup_printf(
+ SPASM_OAUTH2_URI,
+ SPASM_OAUTH2_CLIENT_ID,
+ SPASM_OAUTH2_REDIRECT_URI,
+ SPASM_OAUTH2_SCOPES,
+ state
+ );
+ g_free(state);
+
+ /* send off the oauth implicit request */
+ purple_notify_uri(connection, uri);
+ g_free(uri);
+
+ cpar = purple_request_cpar_from_account(account);
+
+ purple_request_input(
+ connection,
+ "Access Token",
+ "Enter the access token from https://pidgin.im/oauth.html which should have opened in your web browser",
+ NULL,
+ "access token",
+ FALSE,
+ TRUE,
+ NULL,
+ "OK", G_CALLBACK(spasm_access_token_input_cb),
+ "Cancel", G_CALLBACK(spasm_access_token_cancel_cb),
+ cpar,
+ sa
+ );
+}
+
+/******************************************************************************
+ * API
+ *****************************************************************************/
+PurpleAccount *
+spasm_account_get_account(const SpasmAccount *sa) {
+ g_return_val_if_fail(sa, NULL);
+
+ return sa->account;
+}
+
+PurpleConnection *
+spasm_account_get_connection(const SpasmAccount *sa) {
+ g_return_val_if_fail(sa, NULL);
+
+ return sa->connection;
+}
+
+GCancellable *
+spasm_account_get_cancellable(const SpasmAccount *sa) {
+ g_return_val_if_fail(sa, NULL);
+
+ return sa->cancellable;
+}
+
+SoupSession *
+spasm_account_get_session(const SpasmAccount *sa) {
+ g_return_val_if_fail(sa, NULL);
+
+ if(sa->session) {
+ return SOUP_SESSION(g_object_ref(sa->session));
+ }
+
+ return NULL;
+}
+
+const gchar *
+spasm_account_get_access_token(const SpasmAccount *sa) {
+ g_return_val_if_fail(sa, NULL);
+
+ return sa->access_token;
+}
+
+const gchar *
+spasm_account_get_display_name(const SpasmAccount *sa) {
+ g_return_val_if_fail(sa, NULL);
+
+ return sa->display_name;
+}
+
+const gchar *
+spasm_account_get_id(const SpasmAccount *sa) {
+ g_return_val_if_fail(sa, NULL);
+
+ return sa->id;
+}
+
+const gchar *
+spasm_account_get_name(const SpasmAccount *sa) {
+ g_return_val_if_fail(sa, NULL);
+
+ return sa->name;
+}
+
+const gchar *
+spasm_account_get_type(const SpasmAccount *sa) {
+ g_return_val_if_fail(sa, NULL);
+
+ return sa->type;
+}
+
+const gchar *
+spasm_account_get_bio(const SpasmAccount *sa) {
+ g_return_val_if_fail(sa, NULL);
+
+ return sa->bio;
+}
+
+const gchar *
+spasm_account_get_logo(const SpasmAccount *sa) {
+ g_return_val_if_fail(sa, NULL);
+
+ return sa->logo;
+}
+
+const gchar *
+spasm_account_get_email(const SpasmAccount *sa) {
+ g_return_val_if_fail(sa, NULL);
+
+ return sa->email;
+}
+
+gboolean
+spasm_account_get_email_verified(const SpasmAccount *sa) {
+ g_return_val_if_fail(sa, FALSE);
+
+ return sa->email_verified;
+}
+
+gboolean
+spasm_account_get_partnered(const SpasmAccount *sa) {
+ g_return_val_if_fail(sa, FALSE);
+
+ return sa->partnered;
+}
+
+gboolean
+spasm_account_get_twitter_connected(const SpasmAccount *sa) {
+ g_return_val_if_fail(sa, FALSE);
+
+ return sa->twitter_connected;
+}
+
+static void
+spasm_account_login_got_password(PurpleAccount *account, const gchar *password,
+ GError *error, gpointer data)
+{
+ SpasmAccount *sa = SPASM_ACCOUNT(data);
+
+ if(password != NULL) {
+ spasm_account_set_access_token(sa, password);
+ spasm_get_user(sa, spasm_login_test_cb, NULL);
+ } else {
+ spasm_oauth_login(sa);
+ }
+}
+
+void
+spasm_account_login(PurpleAccount *account) {
+ PurpleConnection *pc = NULL;
+ SpasmAccount *sa = NULL;
+
+ pc = purple_account_get_connection(account);
+
+ sa = spasm_account_new(account, pc);
+ purple_connection_set_protocol_data(pc, sa);
+
+ purple_connection_set_state(pc, PURPLE_CONNECTION_CONNECTING);
+
+ /* try to load the password */
+ purple_account_get_password(account, spasm_account_login_got_password, sa);
+}
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/spasm-account.h Sun Apr 19 05:09:03 2020 -0500
@@ -0,0 +1,66 @@
+/*
+ * Spasm - A Twitch Protocol Plugin
+ * Copyright (C) 2017-2019 Gary Kramlich <grim@reaperworld.com>
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#ifndef SPASM_ACCOUNT_H
+#define SPASM_ACCOUNT_H
+
+#include <glib.h>
+
+#include <json-glib/json-glib.h>
+
+#include <libsoup/soup.h>
+
+#include <purple.h>
+
+#define SPASM_ACCOUNT(obj) ((SpasmAccount *)(obj))
+
+typedef struct _SpasmAccount SpasmAccount;
+
+#include "spasm-chat.h"
+
+G_BEGIN_DECLS
+
+// SpasmAccount *spasm_account_new(PurpleAccount *account, PurpleConnection *connection);
+// void spasm_account_free(SpasmAccount *sa);
+
+PurpleAccount *spasm_account_get_account(const SpasmAccount *sa);
+PurpleConnection *spasm_account_get_connection(const SpasmAccount *sa);
+
+void spasm_account_login(PurpleAccount *account);
+
+GCancellable *spasm_account_get_cancellable(const SpasmAccount *sa);
+
+SoupSession *spasm_account_get_session(const SpasmAccount *sa);
+
+const gchar *spasm_account_get_access_token(const SpasmAccount *sa);
+
+const gchar *spasm_account_get_display_name(const SpasmAccount *sa);
+const gchar *spasm_account_get_id(const SpasmAccount *sa);
+const gchar *spasm_account_get_name(const SpasmAccount *sa);
+const gchar *spasm_account_get_type(const SpasmAccount *sa);
+const gchar *spasm_account_get_bio(const SpasmAccount *sa);
+const gchar *spasm_account_get_logo(const SpasmAccount *sa);
+const gchar *spasm_account_get_email(const SpasmAccount *sa);
+gboolean spasm_account_get_email_verified(const SpasmAccount *sa);
+gboolean spasm_account_get_partnered(const SpasmAccount *sa);
+gboolean spasm_account_get_twitter_connected(const SpasmAccount *sa);
+
+G_END_DECLS
+
+#endif /* SPASM_ACCOUNT_H */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/spasm-chat.c Sun Apr 19 05:09:03 2020 -0500
@@ -0,0 +1,329 @@
+/*
+ * Spasm - A Twitch Protocol Plugin
+ * Copyright (C) 2017-2019 Gary Kramlich <grim@reaperworld.com>
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include "spasm-chat.h"
+#include "spasm-const.h"
+
+#include <stdarg.h>
+
+#include <purple.h>
+
+/******************************************************************************
+ * Structs
+ *****************************************************************************/
+struct _SpasmChatService {
+ SpasmAccount *sa;
+
+ GSocketClient *socket_client;
+ GSocketConnection *socket_connection;
+ GOutputStream *output_stream;
+ GDataInputStream *input_stream;
+};
+
+/******************************************************************************
+ * sending
+ *****************************************************************************/
+static void
+spasm_chat_service_real_send(SpasmChatService *chat,
+ const gchar *format, ...)
+{
+ GCancellable *cancellable = NULL;
+ GError *error = NULL;
+ gchar *buffer = NULL;
+ gboolean success;
+ va_list vargs;
+
+ cancellable = spasm_account_get_cancellable(chat->sa);
+
+ va_start(vargs, format);
+ buffer = g_strdup_vprintf(format, vargs);
+ va_end(vargs);
+
+ success = g_output_stream_printf(
+ chat->output_stream,
+ NULL,
+ cancellable,
+ &error,
+ buffer
+ );
+
+ g_free(buffer);
+
+ if(!success) {
+ PurpleConnection *purple_connection = spasm_account_get_connection(chat->sa);
+
+ if(error) {
+ purple_connection_error(
+ purple_connection,
+ PURPLE_CONNECTION_ERROR_OTHER_ERROR,
+ error->message
+ );
+
+ g_error_free(error);
+ } else {
+ purple_connection_error(
+ purple_connection,
+ PURPLE_CONNECTION_ERROR_OTHER_ERROR,
+ "unknown error"
+ );
+ }
+
+ return;
+ }
+ g_output_stream_flush(chat->output_stream, NULL, NULL);
+}
+
+/******************************************************************************
+ * read loop
+ *****************************************************************************/
+static void
+spasm_chat_service_parse(SpasmChatService *chat,
+ const gchar *buffer)
+{
+ if(purple_str_has_prefix(buffer, "PING ")) {
+ purple_debug_misc("spasm", "PING? PONG!\n");
+
+ spasm_chat_service_real_send(chat, "PONG %s\r\n", buffer + 5);
+ }
+}
+
+static void spasm_chat_read(SpasmChatService *chat);
+
+static void
+spasm_chat_read_cb(GObject *obj, GAsyncResult *res, gpointer data) {
+ GError *error = NULL;
+ gchar *buffer = NULL;
+ gsize buffer_len;
+ SpasmChatService *chat = (SpasmChatService *)data;
+
+ buffer = g_data_input_stream_read_line_finish(
+ G_DATA_INPUT_STREAM(obj),
+ res,
+ &buffer_len,
+ &error
+ );
+
+ /* g_data_input_stream_read_line_finish, will return null with error set
+ * on connection error. It will also return null with error not set if
+ * there is no content to read.
+ */
+ if(buffer == NULL) {
+ gchar *error_msg = NULL;
+
+ if(error != NULL) {
+ error_msg = g_strdup_printf(
+ "spasm lost connection with server : %s",
+ error->message
+ );
+
+ g_error_free(error);
+ } else {
+ error_msg = g_strdup_printf("spasm server closed connection");
+ }
+
+ purple_connection_error(
+ spasm_account_get_connection(chat->sa),
+ PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
+ error_msg
+ );
+
+ g_free(error_msg);
+
+ return;
+ }
+
+ purple_debug_info("spasm", "chat buffer: %s\n", buffer);
+
+ spasm_chat_service_parse(chat, buffer);
+
+ g_free(buffer);
+
+ spasm_chat_read(chat);
+}
+
+static void
+spasm_chat_read(SpasmChatService *chat) {
+ g_data_input_stream_read_line_async(
+ chat->input_stream,
+ G_PRIORITY_DEFAULT,
+ spasm_account_get_cancellable(chat->sa),
+ spasm_chat_read_cb,
+ chat
+ );
+}
+
+/******************************************************************************
+ * chat login flow
+ *****************************************************************************/
+static void
+spasm_chat_login_cb(GObject *obj, GAsyncResult *res, gpointer data) {
+ GError *error = NULL;
+ PurpleConnection *purple_connection = NULL;
+ SpasmChatService *chat = (SpasmChatService *)data;
+
+ purple_connection = spasm_account_get_connection(chat->sa);
+
+ chat->socket_connection = g_socket_client_connect_to_host_finish(
+ G_SOCKET_CLIENT(obj),
+ res,
+ &error
+ );
+
+ if(chat->socket_connection == NULL) {
+ if(error) {
+ g_prefix_error(&error, "failed to connect: ");
+
+ purple_connection_error(
+ purple_connection,
+ PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
+ error->message
+ );
+
+ g_error_free(error);
+ } else {
+ purple_connection_error(
+ purple_connection,
+ PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
+ "unknown error"
+ );
+ }
+
+ return;
+ }
+
+ chat->output_stream = g_io_stream_get_output_stream(G_IO_STREAM(chat->socket_connection));
+
+ /* now do the login */
+ spasm_chat_service_real_send(
+ chat,
+ "PASS oauth:%s\r\n",
+ spasm_account_get_access_token(chat->sa)
+ );
+
+ /* now try to use our nick */
+ spasm_chat_service_real_send(
+ chat,
+ "NICK %s\r\n",
+ spasm_account_get_name(chat->sa)
+ );
+
+ purple_connection_set_state(purple_connection, PURPLE_CONNECTION_CONNECTED);
+
+ chat->input_stream = g_data_input_stream_new(
+ g_io_stream_get_input_stream(G_IO_STREAM(chat->socket_connection))
+ );
+
+ spasm_chat_read(chat);
+}
+
+SpasmChatService *
+spasm_chat_service_new(SpasmAccount *sa) {
+ SpasmChatService *chat = NULL;
+
+ g_return_val_if_fail(sa, NULL);
+
+ chat = g_slice_new0(SpasmChatService);
+ chat->sa = sa;
+
+ return chat;
+}
+
+void
+spasm_chat_service_free(SpasmChatService *chat) {
+ g_object_unref(chat->input_stream);
+ g_object_unref(chat->output_stream);
+ g_object_unref(chat->socket_client);
+ g_object_unref(chat->socket_connection);
+
+ g_slice_free(SpasmChatService, chat);
+}
+
+void
+spasm_chat_service_connect(SpasmChatService *chat) {
+ g_return_if_fail(chat);
+
+ chat->socket_client = g_socket_client_new();
+
+ g_socket_client_set_tls(chat->socket_client, TRUE);
+
+ g_socket_client_connect_to_host_async(
+ chat->socket_client,
+ SPASM_CHAT_HOSTNAME,
+ SPASM_CHAT_PORT,
+ spasm_account_get_cancellable(chat->sa),
+ spasm_chat_login_cb,
+ chat
+ );
+}
+
+GList *_spasm_chat_service_info(PurpleConnection *connection) {
+ return NULL;
+}
+
+GHashTable *
+spasm_chat_service_info_default(PurpleConnection *connection,
+ const gchar *name)
+{
+ GHashTable *defaults = g_hash_table_new_full(
+ g_str_hash,
+ g_str_equal,
+ NULL,
+ g_free
+ );
+
+ if(name != NULL) {
+ g_hash_table_insert(defaults, "channel", g_strdup(name));
+ }
+
+ return defaults;
+}
+
+void
+spasm_chat_service_join(PurpleConnection *connection,
+ GHashTable *components)
+{
+
+}
+
+gchar *
+spasm_chat_service_name(GHashTable *components) {
+ return g_strdup(g_hash_table_lookup(components, "channel"));
+}
+
+void
+spasm_chat_service_leave(PurpleConnection *connection, gint id) {
+
+}
+
+gint
+spasm_chat_service_send(PurpleConnection *connection,
+ gint id,
+ const gchar *message,
+ PurpleMessageFlags flags)
+{
+ return -1;
+}
+
+void
+spasm_chat_service_set_topic(PurpleConnection *connection,
+ gint id,
+ const gchar *topic)
+{
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/spasm-chat.h Sun Apr 19 05:09:03 2020 -0500
@@ -0,0 +1,46 @@
+/*
+ * Spasm - A Twitch Protocol Plugin
+ * Copyright (C) 2017-2019 Gary Kramlich <grim@reaperworld.com>
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#ifndef SPASM_CHAT
+#define SPASM_CHAT
+
+#include <glib.h>
+
+typedef struct _SpasmChatService SpasmChatService;
+
+#include "spasm-account.h"
+
+G_BEGIN_DECLS
+
+SpasmChatService *spasm_chat_service_new(SpasmAccount *sa);
+void spasm_chat_service_free(SpasmChatService *chat);
+
+void spasm_chat_service_connect(SpasmChatService *chat);
+
+GList *spasm_chat_service_info(PurpleConnection *connection);
+GHashTable *spasm_chat_service_info_default(PurpleConnection *connection, const gchar *name);
+void spasm_chat_service_join(PurpleConnection *connection, GHashTable *components);
+gchar *spasm_chat_service_name(GHashTable *components);
+void spasm_chat_service_leave(PurpleConnection *connection, gint id);
+gint spasm_chat_service_send(PurpleConnection *connection, gint id, const gchar *message, PurpleMessageFlags flags);
+void spasm_chat_service_set_topic(PurpleConnection *connection, gint id, const gchar *topic);
+
+G_END_DECLS
+
+#endif /* SPASM_CHAT */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/spasm-const.h Sun Apr 19 05:09:03 2020 -0500
@@ -0,0 +1,42 @@
+/*
+ * Spasm - A Twitch Protocol Plugin
+ * Copyright (C) 2017-2019 Gary Kramlich <grim@reaperworld.com>
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#ifndef SPASM_CONSTS_H
+#define SPASM_CONSTS_H
+
+#define SPASM_PLUGIN_ID "prpl-grim-spasm"
+
+#define SPASM_CHAT_HOSTNAME "irc.chat.twitch.tv"
+#define SPASM_CHAT_PORT 443
+
+#define SPASM_BASE_URL "https://api.twitch.tv/kraken/"
+#define SPASM_WEBSOCKET_URL "wss://pubsub-edge.twitch.tv"
+#define SPASM_CONTENT_TYPE "application/vnd.twitchtv.v5+json"
+
+#define SPASM_OAUTH2_CLIENT_ID "w7le4wyxwbipv6kf5qmqogwkqskl12"
+#define SPASM_OAUTH2_SCOPES "user_read chat_login"
+#define SPASM_OAUTH2_REDIRECT_URI "https://pidgin.im/oauth.html"
+#define SPASM_OAUTH2_URI SPASM_BASE_URL "oauth2/authorize?" \
+ "response_type=token&" \
+ "client_id=%s&" \
+ "redirect_uri=%s&" \
+ "scope=%s&" \
+ "state=%s"
+
+#endif /* SPASM_CONSTS_H */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/spasm-protocol.c Sun Apr 19 05:09:03 2020 -0500
@@ -0,0 +1,87 @@
+/*
+ * Spasm - A Twitch Protocol Plugin
+ * Copyright (C) 2017-2019 Gary Kramlich <grim@reaperworld.com>
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include "spasm-protocol.h"
+#include "spasm-account.h"
+
+/******************************************************************************
+ * PurpleProtocol implementation
+ *****************************************************************************/
+static const gchar *
+spasm_protocol_list_icon(PurpleAccount *account, PurpleBuddy *buddy) {
+ return "spasm";
+}
+
+static void
+spasm_protocol_close(PurpleConnection *connection) {
+}
+
+static GList *
+spasm_protocol_get_status_types(PurpleAccount *account) {
+ GList *types = NULL;
+ PurpleStatusType *status;
+
+ status = purple_status_type_new_full(PURPLE_STATUS_AVAILABLE, "online", "Online", TRUE, TRUE, FALSE);
+ types = g_list_append(types, status);
+
+ status = purple_status_type_new_full(PURPLE_STATUS_OFFLINE, NULL, "Offline", TRUE, TRUE, FALSE);
+ types = g_list_append(types, status);
+
+ return types;
+}
+
+/******************************************************************************
+ * GObject Implementation
+ *****************************************************************************/
+G_DEFINE_DYNAMIC_TYPE(
+ SpasmProtocol,
+ spasm_protocol,
+ PURPLE_TYPE_PROTOCOL
+)
+
+static void
+spasm_protocol_init(SpasmProtocol *protocol) {
+ PurpleProtocol *prpl = PURPLE_PROTOCOL(protocol);
+
+ prpl->id = "prpl-grim-spasm";
+ prpl->name = "Twitch";
+ prpl->options = OPT_PROTO_NO_PASSWORD;
+}
+
+static void
+spasm_protocol_class_init(SpasmProtocolClass *klass) {
+ PurpleProtocolClass *protocol_class = PURPLE_PROTOCOL_CLASS(klass);
+
+ protocol_class->login = spasm_account_login;
+ protocol_class->list_icon = spasm_protocol_list_icon;
+ protocol_class->close = spasm_protocol_close;
+ protocol_class->status_types = spasm_protocol_get_status_types;
+}
+
+static void
+spasm_protocol_class_finalize(SpasmProtocolClass *klass) {
+}
+
+/******************************************************************************
+ * API
+ *****************************************************************************/
+void
+spasm_protocol_register(GTypeModule *module) {
+ spasm_protocol_register_type(module);
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/spasm-protocol.h Sun Apr 19 05:09:03 2020 -0500
@@ -0,0 +1,55 @@
+/*
+ * Spasm - A Twitch Protocol Plugin
+ * Copyright (C) 2017-2019 Gary Kramlich <grim@reaperworld.com>
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#ifndef SPASM_PROTOCOL_H
+#define SPASM_PROTOCOL_H
+
+#include <purple.h>
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define SPASM_TYPE_PROTOCOL (spasm_protocol_get_type())
+#define SPASM_PROTOCOL(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), SPASM_TYPE_PROTOCOL, SpasmProtocol))
+#define SPASM_PROTOCOL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), SPASM_TYPE_PROTOCOL, SpasmProtocolClass))
+
+typedef struct _SpasmProtocol SpasmProtocol;
+typedef struct _SpasmProtocolClass SpasmProtocolClass;
+
+struct _SpasmProtocol {
+ /*< private >*/
+ PurpleProtocol parent;
+
+ gpointer reserved[4];
+};
+
+struct _SpasmProtocolClass {
+ /*< private >*/
+ PurpleProtocolClass parent;
+
+ gpointer reserved[4];
+};
+
+GType spasm_protocol_get_type(void);
+void spasm_protocol_register(GTypeModule *module);
+
+G_END_DECLS
+
+#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/spasm-rest.c Sun Apr 19 05:09:03 2020 -0500
@@ -0,0 +1,182 @@
+/*
+ * Spasm - A Twitch Protocol Plugin
+ * Copyright (C) 2017-2019 Gary Kramlich <grim@reaperworld.com>
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include <libsoup/soup.h>
+
+#include <purple.h>
+
+#include "spasm.h"
+#include "spasm-const.h"
+#include "spasm-rest.h"
+
+/******************************************************************************
+ * Types
+ *****************************************************************************/
+typedef struct {
+ SpasmAccount *sa;
+ SpasmRestCallback cb;
+ gpointer data;
+} SpasmRestCallbackData;
+
+/******************************************************************************
+ * Callbacks
+ *****************************************************************************/
+static void
+spasm_request_cb(GObject *obj, GAsyncResult *res, gpointer data) {
+ GInputStream *istream = NULL;
+ GError *error = NULL;
+ JsonParser *parser = NULL;
+ JsonNode *root = NULL;
+ JsonObject *json_obj = NULL;
+ SpasmRestCallbackData *cb_data = (SpasmRestCallbackData *)data;
+
+ istream = soup_session_send_finish(SOUP_SESSION(obj), res, &error);
+ if(error) {
+ cb_data->cb(cb_data->sa, NULL, error, cb_data->data);
+
+ g_free(cb_data);
+
+ return;
+ }
+
+ parser = json_parser_new();
+ json_parser_load_from_stream(parser, istream, NULL, &error);
+ if(error) {
+ cb_data->cb(cb_data->sa, NULL, error, cb_data->data);
+
+ g_free(cb_data);
+ g_object_unref(G_OBJECT(parser));
+
+ return;
+ }
+
+ root = json_parser_get_root(parser);
+ if(root == NULL || !JSON_NODE_HOLDS_OBJECT(root)) {
+ error = g_error_new(
+ SPASM_DOMAIN,
+ 0,
+ "invalid json response"
+ );
+
+ cb_data->cb(cb_data->sa, NULL, error, cb_data->data);
+
+ g_error_free(error);
+ g_free(cb_data);
+ g_object_unref(G_OBJECT(parser));
+
+ return;
+ }
+
+ json_obj = json_node_get_object(root);
+ if(json_obj == NULL) {
+ error = g_error_new(
+ SPASM_DOMAIN,
+ 0,
+ "json is not an object"
+ );
+
+ cb_data->cb(cb_data->sa, NULL, error, cb_data->data);
+
+ g_error_free(error);
+ g_free(cb_data);
+ g_object_unref(G_OBJECT(parser));
+
+ return;
+ }
+
+ if(json_object_has_member(json_obj, "error")) {
+ purple_debug_info("spasm", "error: %s\n", json_object_get_string_member(json_obj, "error"));
+ error = g_error_new(
+ SPASM_DOMAIN,
+ 0,
+ "error : %s : %" G_GINT64_FORMAT,
+ json_object_get_string_member(json_obj, "message"),
+ json_object_get_int_member(json_obj, "status")
+ );
+
+ cb_data->cb(cb_data->sa, NULL, error, cb_data->data);
+
+ g_error_free(error);
+ g_free(cb_data);
+ g_object_unref(G_OBJECT(parser));
+
+ return;
+ }
+
+ cb_data->cb(cb_data->sa, parser, NULL, cb_data->data);
+
+ g_object_unref(G_OBJECT(parser));
+}
+
+/******************************************************************************
+ * API
+ *****************************************************************************/
+void
+spasm_rest_request(SpasmAccount *sa, const gchar *method,
+ const gchar *path, const gchar *body,
+ SpasmRestCallback callback, gpointer data)
+{
+ SpasmRestCallbackData *cb;
+ SoupMessage *msg = NULL;
+ SoupSession *session = NULL;
+ gchar *url = NULL, *auth = NULL;
+
+ g_return_if_fail(sa);
+
+ url = g_build_filename(SPASM_BASE_URL, path, NULL);
+ msg = soup_message_new(method, url);
+ g_free(url);
+
+ // Set the Client-ID header
+ soup_message_headers_replace(
+ msg->request_headers,
+ "Client-ID",
+ SPASM_OAUTH2_CLIENT_ID
+ );
+
+ // Set the Accept header
+ soup_message_headers_replace(
+ msg->request_headers,
+ "Accept",
+ SPASM_CONTENT_TYPE
+ );
+
+ // Set the authorization header to the access token
+ auth = g_strdup_printf("OAuth %s", spasm_account_get_access_token(sa));
+ soup_message_headers_replace(
+ msg->request_headers,
+ "Authorization", auth
+ );
+ g_free(auth);
+
+ cb = g_new0(SpasmRestCallbackData, 1);
+ cb->sa = sa;
+ cb->cb = callback;
+ cb->data = data;
+
+ session = spasm_account_get_session(sa);
+ soup_session_send_async(
+ session,
+ msg,
+ spasm_account_get_cancellable(sa),
+ spasm_request_cb,
+ cb
+ );
+ g_object_unref(G_OBJECT(session));
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/spasm-rest.h Sun Apr 19 05:09:03 2020 -0500
@@ -0,0 +1,37 @@
+/*
+ * Spasm - A Twitch Protocol Plugin
+ * Copyright (C) 2017-2019 Gary Kramlich <grim@reaperworld.com>
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#ifndef SPASM_REST_H
+#define SPASM_REST_H
+
+#include <glib.h>
+
+#include <json-glib/json-glib.h>
+
+#include "spasm-account.h"
+
+G_BEGIN_DECLS
+
+typedef void (*SpasmRestCallback)(SpasmAccount *sa, JsonParser *parser, GError *error, gpointer data);
+
+void spasm_rest_request(SpasmAccount *sa, const gchar *method, const gchar *path, const gchar *body, SpasmRestCallback callback, gpointer data);
+
+G_END_DECLS
+
+#endif /* SPASM_USER_H */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/spasm-user.c Sun Apr 19 05:09:03 2020 -0500
@@ -0,0 +1,40 @@
+/*
+ * Spasm - A Twitch Protocol Plugin
+ * Copyright (C) 2017-2019 Gary Kramlich <grim@reaperworld.com>
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include <json-glib/json-glib.h>
+
+#include <purple.h>
+
+#include "spasm-user.h"
+#include "spasm-rest.h"
+
+/******************************************************************************
+ * User endpoint
+ *****************************************************************************/
+void
+spasm_get_user(SpasmAccount *sa, SpasmRestCallback cb, gpointer data) {
+ spasm_rest_request(
+ sa,
+ "GET",
+ "/user",
+ NULL,
+ cb,
+ data
+ );
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/spasm-user.h Sun Apr 19 05:09:03 2020 -0500
@@ -0,0 +1,34 @@
+/*
+ * Spasm - A Twitch Protocol Plugin
+ * Copyright (C) 2017-2019 Gary Kramlich <grim@reaperworld.com>
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#ifndef SPASM_USER_H
+#define SPASM_USER_H
+
+#include <glib.h>
+
+#include "spasm-account.h"
+#include "spasm-rest.h"
+
+G_BEGIN_DECLS
+
+void spasm_get_user(SpasmAccount *sa, SpasmRestCallback cb, gpointer data);
+
+G_END_DECLS
+
+#endif /* SPASM_USER_H */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/spasm.c Sun Apr 19 05:09:03 2020 -0500
@@ -0,0 +1,105 @@
+/*
+ * Spasm - A Twitch Protocol Plugin
+ * Copyright (C) 2017-2019 Gary Kramlich <grim@reaperworld.com>
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include <glib.h>
+
+#include <purple.h>
+
+#include "spasm-account.h"
+#include "spasm-const.h"
+#include "spasm-protocol.h"
+
+/******************************************************************************
+ * Globals
+ *****************************************************************************/
+static PurpleProtocol *spasm = NULL;
+
+/******************************************************************************
+ * Implementations
+ *****************************************************************************/
+
+/******************************************************************************
+ * Plugin Stuff
+ *****************************************************************************/
+#if 0
+static PurplePluginProtocolInfo prpl_info = {
+ .struct_size = sizeof(PurplePluginProtocolInfo),
+
+ .options = OPT_PROTO_NO_PASSWORD,
+
+ .login = purple_spasm_account_login,
+ .close = _purple_spasm_close,
+ .list_icon = _purple_spasm_list_icon,
+
+ .status_types = _purple_spasm_get_status_types,
+ .set_status = _purple_spasm_set_status,
+
+ // chat stuff
+ .chat_info = purple_spasm_chat_service_info,
+ .chat_info_defaults = purple_spasm_chat_service_info_default,
+ .join_chat = purple_spasm_chat_service_join,
+ .get_chat_name = purple_spasm_chat_service_name,
+ .chat_leave = purple_spasm_chat_service_leave,
+ .chat_send = purple_spasm_chat_service_send,
+ .set_chat_topic = purple_spasm_chat_service_set_topic,
+};
+#endif
+
+/******************************************************************************
+ * GPlugin Interface
+ *****************************************************************************/
+G_MODULE_EXPORT GPluginPluginInfo *
+gplugin_query(GError **error) {
+ const gchar * const authors [] = { "Gary Kramlich <grim@reaperworld.com>", NULL };
+
+ return GPLUGIN_PLUGIN_INFO(purple_plugin_info_new(
+ "id", SPASM_PLUGIN_ID,
+ "name", "Twitch",
+ "version", PLUGIN_VERSION,
+ "abi-version", PURPLE_ABI_VERSION,
+ "flags", PURPLE_PLUGIN_INFO_FLAGS_INTERNAL | PURPLE_PLUGIN_INFO_FLAGS_AUTO_LOAD,
+
+ "summary", "Twitch.tv Protocol Plugin",
+ "description", "Implements the Twitch.tv chat protocol",
+ "authors", authors,
+ "website", "https://bitbucket.org/rw_grim/purple-spasm",
+
+ NULL
+ ));
+}
+
+G_MODULE_EXPORT gboolean
+gplugin_load(GPluginPlugin *plugin, GError **error) {
+ spasm_protocol_register(G_TYPE_MODULE(plugin));
+
+ spasm = purple_protocols_add(SPASM_TYPE_PROTOCOL, error);
+ if(spasm == NULL) {
+ g_warning("instance was null");
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+G_MODULE_EXPORT gboolean
+gplugin_unload(GPluginPlugin *plugin, GError **error) {
+ spasm = NULL;
+
+ return TRUE;
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/spasm.h Sun Apr 19 05:09:03 2020 -0500
@@ -0,0 +1,27 @@
+/*
+ * Spasm - A Twitch Protocol Plugin
+ * Copyright (C) 2017-2019 Gary Kramlich <grim@reaperworld.com>
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#ifndef SPASM_H
+#define SPASM_H
+
+#include <glib.h>
+
+#define SPASM_DOMAIN (g_quark_from_static_string("purple-spasm"))
+
+#endif /* SPASM_H */