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 @@
-# The code below should not need to be modified for simple use cases.
'-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')
- meson.project_name() + '3',
- dependencies: [PURPLE3, JSON_GLIB, SOUP],
- install_dir: join_paths(get_option('libdir'), 'purple3'),
--- 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 "spasm-account.h"
-#include "spasm-const.h"
-/******************************************************************************
- *****************************************************************************/
- PurpleAccount *account;
- PurpleConnection *connection;
- GCancellable *cancellable;
- gboolean email_verified;
- gboolean twitter_connected;
- SpasmChatService *chat;
-/******************************************************************************
- *****************************************************************************/
-spasm_account_new(PurpleAccount *account,
- PurpleConnection *connection)
- SpasmAccount *sa = NULL;
- sa = g_new0(SpasmAccount, 1);
- 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));
-spasm_account_free(SpasmAccount *sa) {
- g_free(sa->access_token);
- spasm_chat_service_free(sa->chat);
-spasm_account_set_access_token(SpasmAccount *sa,
- const gchar *access_token)
- sa->access_token = g_strdup(access_token);
-spasm_account_update_profile(SpasmAccount *sa, JsonParser *parser) {
- JsonObject *obj = NULL;
- const gchar *str_attr = NULL;
- "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");
-/******************************************************************************
- *****************************************************************************/
-static void spasm_oauth_login(SpasmAccount *sa);
-spasm_login_test_cb(SpasmAccount *sa, JsonParser *parser,
- GError *error, gpointer data)
- PurpleAccount *account = NULL;
- PurpleConnection *connection = NULL;
- purple_debug_info("spasm", "oauth token invalid, re-authenticating : %s\n", error->message);
- account = spasm_account_get_account(sa);
- connection = purple_account_get_connection(account);
- error = spasm_account_update_profile(sa, parser);
- err_msg = g_strdup_printf(
- "failed to update profile : %s",
- purple_connection_error(
- PURPLE_CONNECTION_ERROR_OTHER_ERROR,
- sa->chat = spasm_chat_service_new(sa);
- spasm_chat_service_connect(sa->chat);
-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);
-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);
-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(
- username = purple_account_get_username(account);
- state = g_strdup_printf("%s,%s", SPASM_PLUGIN_ID, username);
- SPASM_OAUTH2_CLIENT_ID,
- SPASM_OAUTH2_REDIRECT_URI,
- /* send off the oauth implicit request */
- purple_notify_uri(connection, uri);
- cpar = purple_request_cpar_from_account(account);
- "Enter the access token from https://pidgin.im/oauth.html which should have opened in your web browser",
- "OK", G_CALLBACK(spasm_access_token_input_cb),
- "Cancel", G_CALLBACK(spasm_access_token_cancel_cb),
-/******************************************************************************
- *****************************************************************************/
-spasm_account_get_account(const SpasmAccount *sa) {
- g_return_val_if_fail(sa, NULL);
-spasm_account_get_connection(const SpasmAccount *sa) {
- g_return_val_if_fail(sa, NULL);
-spasm_account_get_cancellable(const SpasmAccount *sa) {
- g_return_val_if_fail(sa, NULL);
- return sa->cancellable;
-spasm_account_get_session(const SpasmAccount *sa) {
- g_return_val_if_fail(sa, NULL);
- return SOUP_SESSION(g_object_ref(sa->session));
-spasm_account_get_access_token(const SpasmAccount *sa) {
- g_return_val_if_fail(sa, NULL);
- return sa->access_token;
-spasm_account_get_display_name(const SpasmAccount *sa) {
- g_return_val_if_fail(sa, NULL);
- return sa->display_name;
-spasm_account_get_id(const SpasmAccount *sa) {
- g_return_val_if_fail(sa, NULL);
-spasm_account_get_name(const SpasmAccount *sa) {
- g_return_val_if_fail(sa, NULL);
-spasm_account_get_type(const SpasmAccount *sa) {
- g_return_val_if_fail(sa, NULL);
-spasm_account_get_bio(const SpasmAccount *sa) {
- g_return_val_if_fail(sa, NULL);
-spasm_account_get_logo(const SpasmAccount *sa) {
- g_return_val_if_fail(sa, NULL);
-spasm_account_get_email(const SpasmAccount *sa) {
- g_return_val_if_fail(sa, NULL);
-spasm_account_get_email_verified(const SpasmAccount *sa) {
- g_return_val_if_fail(sa, FALSE);
- return sa->email_verified;
-spasm_account_get_partnered(const SpasmAccount *sa) {
- g_return_val_if_fail(sa, FALSE);
-spasm_account_get_twitter_connected(const SpasmAccount *sa) {
- g_return_val_if_fail(sa, FALSE);
- return sa->twitter_connected;
-spasm_account_login_got_password(PurpleAccount *account, const gchar *password,
- GError *error, gpointer data)
- SpasmAccount *sa = SPASM_ACCOUNT(data);
- spasm_account_set_access_token(sa, password);
- spasm_get_user(sa, spasm_login_test_cb, NULL);
-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.
-#include <json-glib/json-glib.h>
-#include <libsoup/soup.h>
-#define SPASM_ACCOUNT(obj) ((SpasmAccount *)(obj))
-typedef struct _SpasmAccount SpasmAccount;
-// 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);
-#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-const.h"
-/******************************************************************************
- *****************************************************************************/
-struct _SpasmChatService {
- GSocketClient *socket_client;
- GSocketConnection *socket_connection;
- GOutputStream *output_stream;
- GDataInputStream *input_stream;
-/******************************************************************************
- *****************************************************************************/
-spasm_chat_service_real_send(SpasmChatService *chat,
- const gchar *format, ...)
- GCancellable *cancellable = NULL;
- cancellable = spasm_account_get_cancellable(chat->sa);
- va_start(vargs, format);
- buffer = g_strdup_vprintf(format, vargs);
- success = g_output_stream_printf(
- PurpleConnection *purple_connection = spasm_account_get_connection(chat->sa);
- purple_connection_error(
- PURPLE_CONNECTION_ERROR_OTHER_ERROR,
- purple_connection_error(
- PURPLE_CONNECTION_ERROR_OTHER_ERROR,
- g_output_stream_flush(chat->output_stream, NULL, NULL);
-/******************************************************************************
- *****************************************************************************/
-spasm_chat_service_parse(SpasmChatService *chat,
- 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);
-spasm_chat_read_cb(GObject *obj, GAsyncResult *res, gpointer data) {
- SpasmChatService *chat = (SpasmChatService *)data;
- buffer = g_data_input_stream_read_line_finish(
- G_DATA_INPUT_STREAM(obj),
- /* 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.
- gchar *error_msg = NULL;
- error_msg = g_strdup_printf(
- "spasm lost connection with server : %s",
- error_msg = g_strdup_printf("spasm server closed connection");
- purple_connection_error(
- spasm_account_get_connection(chat->sa),
- PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
- purple_debug_info("spasm", "chat buffer: %s\n", buffer);
- spasm_chat_service_parse(chat, buffer);
-spasm_chat_read(SpasmChatService *chat) {
- g_data_input_stream_read_line_async(
- spasm_account_get_cancellable(chat->sa),
-/******************************************************************************
- *****************************************************************************/
-spasm_chat_login_cb(GObject *obj, GAsyncResult *res, gpointer data) {
- 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(
- if(chat->socket_connection == NULL) {
- g_prefix_error(&error, "failed to connect: ");
- purple_connection_error(
- PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
- purple_connection_error(
- PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
- chat->output_stream = g_io_stream_get_output_stream(G_IO_STREAM(chat->socket_connection));
- spasm_chat_service_real_send(
- spasm_account_get_access_token(chat->sa)
- /* now try to use our nick */
- spasm_chat_service_real_send(
- 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_service_new(SpasmAccount *sa) {
- SpasmChatService *chat = NULL;
- g_return_val_if_fail(sa, NULL);
- chat = g_slice_new0(SpasmChatService);
-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);
-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(
- spasm_account_get_cancellable(chat->sa),
-GList *_spasm_chat_service_info(PurpleConnection *connection) {
-spasm_chat_service_info_default(PurpleConnection *connection,
- GHashTable *defaults = g_hash_table_new_full(
- g_hash_table_insert(defaults, "channel", g_strdup(name));
-spasm_chat_service_join(PurpleConnection *connection,
- GHashTable *components)
-spasm_chat_service_name(GHashTable *components) {
- return g_strdup(g_hash_table_lookup(components, "channel"));
-spasm_chat_service_leave(PurpleConnection *connection, gint id) {
-spasm_chat_service_send(PurpleConnection *connection,
- PurpleMessageFlags flags)
-spasm_chat_service_set_topic(PurpleConnection *connection,
--- 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.
-typedef struct _SpasmChatService SpasmChatService;
-#include "spasm-account.h"
-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);
--- 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.
-#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&" \
-#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
- *****************************************************************************/
-spasm_protocol_list_icon(PurpleAccount *account, PurpleBuddy *buddy) {
-spasm_protocol_close(PurpleConnection *connection) {
-spasm_protocol_get_status_types(PurpleAccount *account) {
- 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);
-/******************************************************************************
- * GObject Implementation
- *****************************************************************************/
-spasm_protocol_init(SpasmProtocol *protocol) {
- PurpleProtocol *prpl = PURPLE_PROTOCOL(protocol);
- prpl->id = "prpl-grim-spasm";
- prpl->options = OPT_PROTO_NO_PASSWORD;
-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;
-spasm_protocol_class_finalize(SpasmProtocolClass *klass) {
-/******************************************************************************
- *****************************************************************************/
-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 <glib-object.h>
-#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 _SpasmProtocolClass {
- PurpleProtocolClass parent;
-GType spasm_protocol_get_type(void);
-void spasm_protocol_register(GTypeModule *module);
--- 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 "spasm-const.h"
-/******************************************************************************
- *****************************************************************************/
-} SpasmRestCallbackData;
-/******************************************************************************
- *****************************************************************************/
-spasm_request_cb(GObject *obj, GAsyncResult *res, gpointer data) {
- GInputStream *istream = NULL;
- JsonParser *parser = NULL;
- JsonObject *json_obj = NULL;
- SpasmRestCallbackData *cb_data = (SpasmRestCallbackData *)data;
- istream = soup_session_send_finish(SOUP_SESSION(obj), res, &error);
- cb_data->cb(cb_data->sa, NULL, error, cb_data->data);
- parser = json_parser_new();
- json_parser_load_from_stream(parser, istream, NULL, &error);
- cb_data->cb(cb_data->sa, NULL, error, cb_data->data);
- g_object_unref(G_OBJECT(parser));
- root = json_parser_get_root(parser);
- if(root == NULL || !JSON_NODE_HOLDS_OBJECT(root)) {
- "invalid json response"
- cb_data->cb(cb_data->sa, NULL, error, cb_data->data);
- g_object_unref(G_OBJECT(parser));
- json_obj = json_node_get_object(root);
- "json is not an object"
- cb_data->cb(cb_data->sa, NULL, error, cb_data->data);
- g_object_unref(G_OBJECT(parser));
- if(json_object_has_member(json_obj, "error")) {
- purple_debug_info("spasm", "error: %s\n", json_object_get_string_member(json_obj, "error"));
- "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_object_unref(G_OBJECT(parser));
- cb_data->cb(cb_data->sa, parser, NULL, cb_data->data);
- g_object_unref(G_OBJECT(parser));
-/******************************************************************************
- *****************************************************************************/
-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;
- url = g_build_filename(SPASM_BASE_URL, path, NULL);
- msg = soup_message_new(method, url);
- // Set the Client-ID header
- soup_message_headers_replace(
- // Set the Accept header
- soup_message_headers_replace(
- // Set the authorization header to the access token
- auth = g_strdup_printf("OAuth %s", spasm_account_get_access_token(sa));
- soup_message_headers_replace(
- cb = g_new0(SpasmRestCallbackData, 1);
- session = spasm_account_get_session(sa);
- soup_session_send_async(
- spasm_account_get_cancellable(sa),
- 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.
-#include <json-glib/json-glib.h>
-#include "spasm-account.h"
-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);
-#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>
-/******************************************************************************
- *****************************************************************************/
-spasm_get_user(SpasmAccount *sa, SpasmRestCallback cb, gpointer 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.
-#include "spasm-account.h"
-void spasm_get_user(SpasmAccount *sa, SpasmRestCallback cb, gpointer data);
-#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 "spasm-account.h"
-#include "spasm-const.h"
-#include "spasm-protocol.h"
-/******************************************************************************
- *****************************************************************************/
-static PurpleProtocol *spasm = NULL;
-/******************************************************************************
- *****************************************************************************/
-/******************************************************************************
- *****************************************************************************/
-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_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,
-/******************************************************************************
- *****************************************************************************/
-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(
- "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",
- "website", "https://bitbucket.org/rw_grim/purple-spasm",
-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);
- g_warning("instance was null");
-G_MODULE_EXPORT gboolean
-gplugin_unload(GPluginPlugin *plugin, GError **error) {
--- 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.
-#define SPASM_DOMAIN (g_quark_from_static_string("purple-spasm"))
--- /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 @@
+ meson.project_name() + '3', + dependencies: [PURPLE3, JSON_GLIB, SOUP], + 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 "spasm-account.h" +#include "spasm-const.h" +/****************************************************************************** + *****************************************************************************/ + PurpleAccount *account; + PurpleConnection *connection; + GCancellable *cancellable; + gboolean email_verified; + gboolean twitter_connected; + SpasmChatService *chat; +/****************************************************************************** + *****************************************************************************/ +spasm_account_new(PurpleAccount *account, + PurpleConnection *connection) + SpasmAccount *sa = NULL; + sa = g_new0(SpasmAccount, 1); + 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)); +spasm_account_free(SpasmAccount *sa) { + g_free(sa->access_token); + spasm_chat_service_free(sa->chat); +spasm_account_set_access_token(SpasmAccount *sa, + const gchar *access_token) + sa->access_token = g_strdup(access_token); +spasm_account_update_profile(SpasmAccount *sa, JsonParser *parser) { + JsonObject *obj = NULL; + const gchar *str_attr = NULL; + "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"); +/****************************************************************************** + *****************************************************************************/ +static void spasm_oauth_login(SpasmAccount *sa); +spasm_login_test_cb(SpasmAccount *sa, JsonParser *parser, + GError *error, gpointer data) + PurpleAccount *account = NULL; + PurpleConnection *connection = NULL; + purple_debug_info("spasm", "oauth token invalid, re-authenticating : %s\n", error->message); + account = spasm_account_get_account(sa); + connection = purple_account_get_connection(account); + error = spasm_account_update_profile(sa, parser); + err_msg = g_strdup_printf( + "failed to update profile : %s", + purple_connection_error( + PURPLE_CONNECTION_ERROR_OTHER_ERROR, + sa->chat = spasm_chat_service_new(sa); + spasm_chat_service_connect(sa->chat); +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); +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); +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( + username = purple_account_get_username(account); + state = g_strdup_printf("%s,%s", SPASM_PLUGIN_ID, username); + SPASM_OAUTH2_CLIENT_ID, + SPASM_OAUTH2_REDIRECT_URI, + /* send off the oauth implicit request */ + purple_notify_uri(connection, uri); + cpar = purple_request_cpar_from_account(account); + "Enter the access token from https://pidgin.im/oauth.html which should have opened in your web browser", + "OK", G_CALLBACK(spasm_access_token_input_cb), + "Cancel", G_CALLBACK(spasm_access_token_cancel_cb), +/****************************************************************************** + *****************************************************************************/ +spasm_account_get_account(const SpasmAccount *sa) { + g_return_val_if_fail(sa, NULL); +spasm_account_get_connection(const SpasmAccount *sa) { + g_return_val_if_fail(sa, NULL); +spasm_account_get_cancellable(const SpasmAccount *sa) { + g_return_val_if_fail(sa, NULL); + return sa->cancellable; +spasm_account_get_session(const SpasmAccount *sa) { + g_return_val_if_fail(sa, NULL); + return SOUP_SESSION(g_object_ref(sa->session)); +spasm_account_get_access_token(const SpasmAccount *sa) { + g_return_val_if_fail(sa, NULL); + return sa->access_token; +spasm_account_get_display_name(const SpasmAccount *sa) { + g_return_val_if_fail(sa, NULL); + return sa->display_name; +spasm_account_get_id(const SpasmAccount *sa) { + g_return_val_if_fail(sa, NULL); +spasm_account_get_name(const SpasmAccount *sa) { + g_return_val_if_fail(sa, NULL); +spasm_account_get_type(const SpasmAccount *sa) { + g_return_val_if_fail(sa, NULL); +spasm_account_get_bio(const SpasmAccount *sa) { + g_return_val_if_fail(sa, NULL); +spasm_account_get_logo(const SpasmAccount *sa) { + g_return_val_if_fail(sa, NULL); +spasm_account_get_email(const SpasmAccount *sa) { + g_return_val_if_fail(sa, NULL); +spasm_account_get_email_verified(const SpasmAccount *sa) { + g_return_val_if_fail(sa, FALSE); + return sa->email_verified; +spasm_account_get_partnered(const SpasmAccount *sa) { + g_return_val_if_fail(sa, FALSE); +spasm_account_get_twitter_connected(const SpasmAccount *sa) { + g_return_val_if_fail(sa, FALSE); + return sa->twitter_connected; +spasm_account_login_got_password(PurpleAccount *account, const gchar *password, + GError *error, gpointer data) + SpasmAccount *sa = SPASM_ACCOUNT(data); + spasm_account_set_access_token(sa, password); + spasm_get_user(sa, spasm_login_test_cb, NULL); +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. +#include <json-glib/json-glib.h> +#include <libsoup/soup.h> +#define SPASM_ACCOUNT(obj) ((SpasmAccount *)(obj)) +typedef struct _SpasmAccount SpasmAccount; +// 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); +#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-const.h" +/****************************************************************************** + *****************************************************************************/ +struct _SpasmChatService { + GSocketClient *socket_client; + GSocketConnection *socket_connection; + GOutputStream *output_stream; + GDataInputStream *input_stream; +/****************************************************************************** + *****************************************************************************/ +spasm_chat_service_real_send(SpasmChatService *chat, + const gchar *format, ...) + GCancellable *cancellable = NULL; + cancellable = spasm_account_get_cancellable(chat->sa); + va_start(vargs, format); + buffer = g_strdup_vprintf(format, vargs); + success = g_output_stream_printf( + PurpleConnection *purple_connection = spasm_account_get_connection(chat->sa); + purple_connection_error( + PURPLE_CONNECTION_ERROR_OTHER_ERROR, + purple_connection_error( + PURPLE_CONNECTION_ERROR_OTHER_ERROR, + g_output_stream_flush(chat->output_stream, NULL, NULL); +/****************************************************************************** + *****************************************************************************/ +spasm_chat_service_parse(SpasmChatService *chat, + 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); +spasm_chat_read_cb(GObject *obj, GAsyncResult *res, gpointer data) { + SpasmChatService *chat = (SpasmChatService *)data; + buffer = g_data_input_stream_read_line_finish( + G_DATA_INPUT_STREAM(obj), + /* 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. + gchar *error_msg = NULL; + error_msg = g_strdup_printf( + "spasm lost connection with server : %s", + error_msg = g_strdup_printf("spasm server closed connection"); + purple_connection_error( + spasm_account_get_connection(chat->sa), + PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + purple_debug_info("spasm", "chat buffer: %s\n", buffer); + spasm_chat_service_parse(chat, buffer); +spasm_chat_read(SpasmChatService *chat) { + g_data_input_stream_read_line_async( + spasm_account_get_cancellable(chat->sa), +/****************************************************************************** + *****************************************************************************/ +spasm_chat_login_cb(GObject *obj, GAsyncResult *res, gpointer data) { + 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( + if(chat->socket_connection == NULL) { + g_prefix_error(&error, "failed to connect: "); + purple_connection_error( + PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + purple_connection_error( + PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + chat->output_stream = g_io_stream_get_output_stream(G_IO_STREAM(chat->socket_connection)); + spasm_chat_service_real_send( + spasm_account_get_access_token(chat->sa) + /* now try to use our nick */ + spasm_chat_service_real_send( + 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_service_new(SpasmAccount *sa) { + SpasmChatService *chat = NULL; + g_return_val_if_fail(sa, NULL); + chat = g_slice_new0(SpasmChatService); +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); +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( + spasm_account_get_cancellable(chat->sa), +GList *_spasm_chat_service_info(PurpleConnection *connection) { +spasm_chat_service_info_default(PurpleConnection *connection, + GHashTable *defaults = g_hash_table_new_full( + g_hash_table_insert(defaults, "channel", g_strdup(name)); +spasm_chat_service_join(PurpleConnection *connection, + GHashTable *components) +spasm_chat_service_name(GHashTable *components) { + return g_strdup(g_hash_table_lookup(components, "channel")); +spasm_chat_service_leave(PurpleConnection *connection, gint id) { +spasm_chat_service_send(PurpleConnection *connection, + PurpleMessageFlags flags) +spasm_chat_service_set_topic(PurpleConnection *connection, --- /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. +typedef struct _SpasmChatService SpasmChatService; +#include "spasm-account.h" +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); --- /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. +#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&" \ +#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 + *****************************************************************************/ +spasm_protocol_list_icon(PurpleAccount *account, PurpleBuddy *buddy) { +spasm_protocol_close(PurpleConnection *connection) { +spasm_protocol_get_status_types(PurpleAccount *account) { + 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); +/****************************************************************************** + * GObject Implementation + *****************************************************************************/ +spasm_protocol_init(SpasmProtocol *protocol) { + PurpleProtocol *prpl = PURPLE_PROTOCOL(protocol); + prpl->id = "prpl-grim-spasm"; + prpl->options = OPT_PROTO_NO_PASSWORD; +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; +spasm_protocol_class_finalize(SpasmProtocolClass *klass) { +/****************************************************************************** + *****************************************************************************/ +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 <glib-object.h> +#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 _SpasmProtocolClass { + PurpleProtocolClass parent; +GType spasm_protocol_get_type(void); +void spasm_protocol_register(GTypeModule *module); --- /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 "spasm-const.h" +/****************************************************************************** + *****************************************************************************/ +} SpasmRestCallbackData; +/****************************************************************************** + *****************************************************************************/ +spasm_request_cb(GObject *obj, GAsyncResult *res, gpointer data) { + GInputStream *istream = NULL; + JsonParser *parser = NULL; + JsonObject *json_obj = NULL; + SpasmRestCallbackData *cb_data = (SpasmRestCallbackData *)data; + istream = soup_session_send_finish(SOUP_SESSION(obj), res, &error); + cb_data->cb(cb_data->sa, NULL, error, cb_data->data); + parser = json_parser_new(); + json_parser_load_from_stream(parser, istream, NULL, &error); + cb_data->cb(cb_data->sa, NULL, error, cb_data->data); + g_object_unref(G_OBJECT(parser)); + root = json_parser_get_root(parser); + if(root == NULL || !JSON_NODE_HOLDS_OBJECT(root)) { + "invalid json response" + cb_data->cb(cb_data->sa, NULL, error, cb_data->data); + g_object_unref(G_OBJECT(parser)); + json_obj = json_node_get_object(root); + "json is not an object" + cb_data->cb(cb_data->sa, NULL, error, cb_data->data); + g_object_unref(G_OBJECT(parser)); + if(json_object_has_member(json_obj, "error")) { + purple_debug_info("spasm", "error: %s\n", json_object_get_string_member(json_obj, "error")); + "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_object_unref(G_OBJECT(parser)); + cb_data->cb(cb_data->sa, parser, NULL, cb_data->data); + g_object_unref(G_OBJECT(parser)); +/****************************************************************************** + *****************************************************************************/ +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; + url = g_build_filename(SPASM_BASE_URL, path, NULL); + msg = soup_message_new(method, url); + // Set the Client-ID header + soup_message_headers_replace( + // Set the Accept header + soup_message_headers_replace( + // Set the authorization header to the access token + auth = g_strdup_printf("OAuth %s", spasm_account_get_access_token(sa)); + soup_message_headers_replace( + cb = g_new0(SpasmRestCallbackData, 1); + session = spasm_account_get_session(sa); + soup_session_send_async( + spasm_account_get_cancellable(sa), + 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. +#include <json-glib/json-glib.h> +#include "spasm-account.h" +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); +#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> +/****************************************************************************** + *****************************************************************************/ +spasm_get_user(SpasmAccount *sa, SpasmRestCallback cb, gpointer 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. +#include "spasm-account.h" +void spasm_get_user(SpasmAccount *sa, SpasmRestCallback cb, gpointer data); +#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 "spasm-account.h" +#include "spasm-const.h" +#include "spasm-protocol.h" +/****************************************************************************** + *****************************************************************************/ +static PurpleProtocol *spasm = NULL; +/****************************************************************************** + *****************************************************************************/ +/****************************************************************************** + *****************************************************************************/ +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_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, +/****************************************************************************** + *****************************************************************************/ +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( + "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", + "website", "https://bitbucket.org/rw_grim/purple-spasm", +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); + g_warning("instance was null"); +G_MODULE_EXPORT gboolean +gplugin_unload(GPluginPlugin *plugin, GError **error) { --- /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. +#define SPASM_DOMAIN (g_quark_from_static_string("purple-spasm"))