├── .gitignore ├── campfire16.png ├── campfire22.png ├── campfire48.png ├── send_and_respond.pdf ├── campfire-libpurple.7z ├── format_code ├── campfire.h ├── README.md ├── Makefile.mingw ├── http.h ├── message.h ├── Makefile ├── campfire.c ├── http.c └── message.c /.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | *.so 3 | -------------------------------------------------------------------------------- /campfire16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jrfoell/campfire-libpurple/HEAD/campfire16.png -------------------------------------------------------------------------------- /campfire22.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jrfoell/campfire-libpurple/HEAD/campfire22.png -------------------------------------------------------------------------------- /campfire48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jrfoell/campfire-libpurple/HEAD/campfire48.png -------------------------------------------------------------------------------- /send_and_respond.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jrfoell/campfire-libpurple/HEAD/send_and_respond.pdf -------------------------------------------------------------------------------- /campfire-libpurple.7z: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jrfoell/campfire-libpurple/HEAD/campfire-libpurple.7z -------------------------------------------------------------------------------- /format_code: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # this usage of the 'indent' program should format code to the specifications 4 | # listed here: http://developer.pidgin.im/wiki/CodingStandards 5 | indent -bap -bbb -bbo -blf -bls -br -ce -i8 -l80 -lp -npcs -nprs -psl -saf \ 6 | -sai -saw -ts8 $* 7 | 8 | -------------------------------------------------------------------------------- /campfire.h: -------------------------------------------------------------------------------- 1 | #ifndef CAMPFIRE_H 2 | #define CAMPFIRE_H 3 | 4 | /*system includes*/ 5 | #include 6 | 7 | /*purple includes*/ 8 | #include 9 | #include 10 | 11 | typedef struct _CampfireConn 12 | { 13 | PurpleAccount *account; 14 | PurpleRoomlist *roomlist; 15 | PurpleConnection *gc; 16 | PurpleSslConnection *gsc; 17 | gchar *hostname; 18 | GHashTable *rooms; 19 | GHashTable *users; 20 | guint message_timer; 21 | GList *queue; 22 | guint num_xaction_malloc; /* valgrind investigation */ 23 | guint num_xaction_free; /* valgrind investigation */ 24 | gboolean needs_join; 25 | gchar *desired_room; 26 | } CampfireConn; 27 | 28 | void campfire_join_chat_after_room_query(CampfireConn * campfire, gchar *room_name); 29 | 30 | #endif /* not CAMPFIRE_H */ 31 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | A campfire plugin for Pidgin, Adium and other libpurple based messengers. 2 | 3 | # Building 4 | $ sudo apt-get install build-essential make libpurple-dev git-core 5 | $ git clone git://github.com/jrfoell/campfire-libpurple.git 6 | $ cd campfire-libpurple 7 | $ sudo make 8 | $ sudo make install 9 | 10 | And now, restart Pidgin. 11 | 12 | # Configuration 13 | Now that it installed, here’s how to configure it for your account: 14 | 15 | * Open “Manage Accounts”. 16 | * Click “New…” 17 | * Select the “Campfire” protocol. 18 | * Enter your username and the hostname of your campfire server. 19 | * Get an API key from the “my info” link on your campfire server’s web interface. (In short: use a web browser to get this info.) 20 | * Enter the API key in the “Advanced” tab. 21 | * Save the account. 22 | 23 | 24 | Theoretically, you should now be able to chat on Campfire using Pidgin. Here’s how to join a room: 25 | 26 | * In the Pidgin Buddy List, select menu item Buddies > “Join a chat…” 27 | * Select the “Campfire” protocol from the dropdown list. 28 | * Click “Room List” to get a list of rooms available on your Campfire server. 29 | * Select the room you wish to join, then click “Join Room”. A new chat window should open up to that room. 30 | * You may have to close the “Join a chat” dialog box. 31 | 32 | # Contributors 33 | * guitarmanvt - this help 34 | * jrfoell 35 | * jfoell 36 | * mtseinart 37 | 38 | -------------------------------------------------------------------------------- /Makefile.mingw: -------------------------------------------------------------------------------- 1 | # 2 | # Makefile.mingw 3 | # 4 | # Description: Makefile for win32 (mingw) version of libcampfire 5 | # 6 | 7 | DEFINES += -D__APPLE_CC__=0 -DPURPLE_PLUGINS 8 | 9 | PIDGIN_TREE_TOP := ../../.. 10 | include $(PIDGIN_TREE_TOP)/libpurple/win32/global.mak 11 | 12 | TARGET = libcampfire 13 | TYPE = PLUGIN 14 | 15 | # Static or Plugin... 16 | ifeq ($(TYPE),STATIC) 17 | DEFINES += -DSTATIC 18 | DLL_INSTALL_DIR = $(PURPLE_INSTALL_DIR) 19 | else 20 | ifeq ($(TYPE),PLUGIN) 21 | DLL_INSTALL_DIR = $(PURPLE_INSTALL_PLUGINS_DIR) 22 | endif 23 | endif 24 | 25 | ## 26 | ## INCLUDE PATHS 27 | ## 28 | INCLUDE_PATHS += -I. \ 29 | -I$(GTK_TOP)/include \ 30 | -I$(GTK_TOP)/include/glib-2.0 \ 31 | -I$(GTK_TOP)/lib/glib-2.0/include \ 32 | -I$(PURPLE_TOP) \ 33 | -I$(PURPLE_TOP)/win32 \ 34 | -I$(PIDGIN_TREE_TOP) 35 | 36 | LIB_PATHS += -L$(GTK_TOP)/lib \ 37 | -L$(PURPLE_TOP) 38 | 39 | ## 40 | ## SOURCES, OBJECTS 41 | ## 42 | C_SRC = campfire.c \ 43 | message.c \ 44 | http.c 45 | 46 | OBJECTS = $(C_SRC:%.c=%.o) 47 | 48 | ## 49 | ## LIBRARIES 50 | ## 51 | LIBS = \ 52 | -lglib-2.0 \ 53 | -lintl \ 54 | -lpurple 55 | 56 | 57 | ifeq ($(CYRUS_SASL), 1) 58 | INCLUDE_PATHS += -I$(CYRUS_SASL_TOP)/include 59 | LIB_PATHS += -L$(CYRUS_SASL_TOP)/bin 60 | LIBS += -llibsasl 61 | endif 62 | 63 | include $(PIDGIN_COMMON_RULES) 64 | 65 | ## 66 | ## TARGET DEFINITIONS 67 | ## 68 | .PHONY: all install clean 69 | 70 | all: $(TARGET).dll 71 | 72 | install: all $(DLL_INSTALL_DIR) 73 | cp $(TARGET).dll $(DLL_INSTALL_DIR) 74 | 75 | $(OBJECTS): $(PURPLE_CONFIG_H) 76 | 77 | ## 78 | ## BUILD DLL 79 | ## 80 | $(TARGET).dll: $(PURPLE_DLL).a $(OBJECTS) 81 | $(CC) -shared $(OBJECTS) $(LIB_PATHS) $(LIBS) $(DLL_LD_FLAGS) -o $(TARGET).dll 82 | 83 | ## 84 | ## CLEAN RULES 85 | ## 86 | 87 | clean: 88 | rm -f $(OBJECTS) 89 | rm -f $(TARGET).dll 90 | 91 | include $(PIDGIN_COMMON_TARGETS) 92 | -------------------------------------------------------------------------------- /http.h: -------------------------------------------------------------------------------- 1 | #ifndef HTTP_H 2 | #define HTTP_H 3 | 4 | /*local includes*/ 5 | #include "campfire.h" 6 | 7 | /*system includes*/ 8 | #include 9 | 10 | /*purple includes*/ 11 | #include 12 | #include 13 | 14 | /* these are not used anymore 15 | * enum http_response_status { 16 | * CAMPFIRE_HTTP_RESPONSE_STATUS_XML_OK, 17 | * CAMPFIRE_HTTP_RESPONSE_STATUS_OK_NO_XML, 18 | * CAMPFIRE_HTTP_RESPONSE_STATUS_TRY_AGAIN, 19 | * CAMPFIRE_HTTP_RESPONSE_STATUS_LOST_CONNECTION, 20 | * CAMPFIRE_HTTP_RESPONSE_STATUS_DISCONNECTED, 21 | * CAMPFIRE_HTTP_RESPONSE_STATUS_NO_CONTENT, 22 | * CAMPFIRE_HTTP_RESPONSE_STATUS_NO_XML, 23 | * CAMPFIRE_HTTP_RESPONSE_STATUS_MORE_CONTENT_NEEDED, 24 | * CAMPFIRE_HTTP_RESPONSE_STATUS_FAIL, 25 | * }; 26 | */ 27 | 28 | typedef enum campfire_http_rx_state 29 | { 30 | CAMPFIRE_HTTP_RX_HEADER, 31 | CAMPFIRE_HTTP_RX_CONTENT, 32 | CAMPFIRE_HTTP_RX_DONE, 33 | } CampfireHttpRxState; 34 | 35 | typedef struct _CampfireHttpResponse 36 | { 37 | GString *response; 38 | GString *header; 39 | GString *content; 40 | gsize content_len; 41 | gsize content_received_len; 42 | gint status; 43 | CampfireHttpRxState rx_state; 44 | } CampfireHttpResponse; 45 | 46 | typedef struct _CampfireSslTransaction 47 | { 48 | CampfireConn *campfire; 49 | GString *http_request; 50 | CampfireHttpResponse http_response; 51 | PurpleSslInputFunction response_cb; 52 | gpointer response_cb_data; 53 | xmlnode *xml_response; 54 | gboolean queued; 55 | //optional 56 | gchar *room_id; 57 | GList *messages; 58 | gboolean first_check; 59 | } CampfireSslTransaction; 60 | 61 | void campfire_http_request(CampfireSslTransaction * xaction, gchar * uri, 62 | gchar * method, xmlnode * postxml); 63 | void campfire_queue_xaction(CampfireConn * campfire, 64 | CampfireSslTransaction * xaction, 65 | PurpleInputCondition cond); 66 | void campfire_message_free(gpointer data, gpointer user_data); 67 | void campfire_xaction_free(CampfireSslTransaction *xaction); 68 | 69 | #endif /* not HTTP_H */ 70 | -------------------------------------------------------------------------------- /message.h: -------------------------------------------------------------------------------- 1 | #ifndef MESSAGE_H 2 | #define MESSAGE_H 3 | 4 | /* local includes */ 5 | #include "http.h" 6 | #include "campfire.h" 7 | 8 | /* system includes */ 9 | #include 10 | #include 11 | 12 | /* purple includes */ 13 | #include 14 | #include 15 | #include 16 | 17 | #define CAMPFIRE_MESSAGE_TEXT "TextMessage" 18 | #define CAMPFIRE_MESSAGE_PASTE "PasteMessage" 19 | #define CAMPFIRE_MESSAGE_SOUND "SoundMessage" 20 | #define CAMPFIRE_MESSAGE_TWEET "TweetMessage" 21 | #define CAMPFIRE_MESSAGE_ENTER "EnterMessage" 22 | #define CAMPFIRE_MESSAGE_LEAVE "LeaveMessage" 23 | #define CAMPFIRE_MESSAGE_TIME "TimestampMessage" 24 | #define CAMPFIRE_MESSAGE_KICK "KickMessage" 25 | #define CAMPFIRE_MESSAGE_UPLOAD "UploadMessage" 26 | #define CAMPFIRE_MESSAGE_TOPIC "TopicChangeMessage" 27 | #define CAMPFIRE_MESSAGE_GUESTALLOW "AllowGuestsMessage" 28 | #define CAMPFIRE_MESSAGE_GUESTDENY "DisallowGuestsMessage" 29 | 30 | #define CAMPFIRE_CMD_ME "me" 31 | #define CAMPFIRE_CMD_PLAY "play" 32 | /* not really commands but we'll implement them to emulate web interface */ 33 | #define CAMPFIRE_CMD_ROOM "room" 34 | #define CAMPFIRE_CMD_TOPIC "topic" 35 | 36 | typedef struct _CampfireMessage 37 | { 38 | gchar *id; 39 | gchar *type; 40 | gchar *message; 41 | time_t time; 42 | gchar *user_id; 43 | } CampfireMessage; 44 | 45 | typedef struct _CampfireRoom 46 | { 47 | gchar *id; 48 | gchar *name; 49 | gchar *last_message_id; 50 | GList *message_id_buffer; 51 | } CampfireRoom; 52 | 53 | void 54 | campfire_message_send(CampfireConn *campfire, int id, const char *message, char *msg_type); 55 | 56 | void 57 | campfire_room_query(CampfireConn *campfire); 58 | 59 | void 60 | campfire_room_join(CampfireConn *campfire, gchar *room_id, gchar *room_name); 61 | 62 | void 63 | campfire_room_leave(CampfireConn *campfire, gint id); 64 | 65 | PurpleCmdRet 66 | campfire_parse_cmd(PurpleConversation *conv, const gchar *cmd, 67 | gchar **args, gchar **error, void *data); 68 | 69 | #endif /* not MESSAGE_H */ 70 | 71 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | LIBNAME = libcampfire.so 2 | 3 | .PHONY: all 4 | all: $(LIBNAME) 5 | 6 | C_SRCS = campfire.c message.c http.c 7 | 8 | # Object file names using 'Substitution Reference' 9 | C_OBJS = $(C_SRCS:.c=.o) 10 | 11 | CC = gcc 12 | LD = $(CC) 13 | CFLAGS_PURPLE = $(shell pkg-config --cflags purple) 14 | CFLAGS = \ 15 | -g \ 16 | -O2 \ 17 | -Wall \ 18 | -Wextra \ 19 | -fPIC \ 20 | -DPURPLE_PLUGINS \ 21 | -DPIC -DENABLE_NLS \ 22 | $(CFLAGS_PURPLE) 23 | 24 | LIBS_PURPLE = $(shell pkg-config --libs purple) 25 | LDFLAGS = -shared 26 | 27 | %.o: %.c 28 | $(V_CC)$(CC) -c $(CFLAGS) -o $@ $< 29 | 30 | $(LIBNAME): $(C_OBJS) 31 | $(V_LINK)$(LD) $(LDFLAGS) -o $@ $^ $(LIBS_PURPLE) 32 | 33 | PLUGIN_DIR_PURPLE:=$(shell pkg-config --variable=plugindir purple) 34 | DATA_ROOT_DIR_PURPLE:=$(shell pkg-config --variable=datarootdir purple) 35 | 36 | .PHONY: install 37 | install: $(LIBNAME) 38 | install -D $(LIBNAME) $(PLUGIN_DIR_PURPLE)/$(LIBNAME) 39 | install --mode=0644 campfire16.png $(DATA_ROOT_DIR_PURPLE)/pixmaps/pidgin/protocols/16/campfire.png 40 | install --mode=0644 campfire22.png $(DATA_ROOT_DIR_PURPLE)/pixmaps/pidgin/protocols/22/campfire.png 41 | install --mode=0644 campfire48.png $(DATA_ROOT_DIR_PURPLE)/pixmaps/pidgin/protocols/48/campfire.png 42 | 43 | .PHONY: uninstall 44 | uninstall: $(LIBNAME) 45 | rm $(PLUGIN_DIR_PURPLE)/$(LIBNAME) 46 | rm $(DATA_ROOT_DIR_PURPLE)/pixmaps/pidgin/protocols/16/campfire.png 47 | rm $(DATA_ROOT_DIR_PURPLE)/pixmaps/pidgin/protocols/22/campfire.png 48 | rm $(DATA_ROOT_DIR_PURPLE)/pixmaps/pidgin/protocols/48/campfire.png 49 | 50 | .PHONY: clean 51 | clean: 52 | -rm *.o 53 | -rm $(LIBNAME) 54 | 55 | 56 | # Quiet by default 57 | VERBOSE ?= 0 58 | 59 | # Define printf macro 60 | v_printf = @printf " %-8s%s\n" 61 | 62 | # Define C verbose macro 63 | V_CC = $(v_CC_$(V)) 64 | v_CC_ = $(v_CC_$(VERBOSE)) 65 | v_CC_0 = $(v_printf) CC $(@F); 66 | 67 | # Define LINK verbose macro 68 | V_LINK = $(v_LINK_$(V)) 69 | v_LINK_ = $(v_LINK_$(VERBOSE)) 70 | v_LINK_0 = $(v_printf) LINK $(@F); 71 | -------------------------------------------------------------------------------- /campfire.c: -------------------------------------------------------------------------------- 1 | 2 | /*local includes*/ 3 | #include "campfire.h" 4 | #include "message.h" 5 | 6 | /*purple includes*/ 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | 13 | static gboolean 14 | plugin_load(G_GNUC_UNUSED PurplePlugin * plugin) 15 | { 16 | return TRUE; 17 | } 18 | 19 | static gboolean 20 | plugin_unload(G_GNUC_UNUSED PurplePlugin * plugin) 21 | { 22 | return TRUE; 23 | } 24 | 25 | static void 26 | campfire_login(PurpleAccount * account) 27 | { 28 | /*don't really login (it's stateless), but init the CampfireConn */ 29 | PurpleConnection *gc = purple_account_get_connection(account); 30 | const char *username = purple_account_get_username(account); 31 | CampfireConn *conn; 32 | char *pos; 33 | PurpleCmdFlag f = PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_PRPL_ONLY; 34 | gchar *prpl_id = "prpl-analog_g-campfire"; /* analog_g = developer.pidgin.im Trac username */ 35 | 36 | conn = g_new0(CampfireConn, 1); 37 | purple_debug_info("campfire", "num_xaction_malloc:%d: num_xaction_free:%d\n", 38 | conn->num_xaction_malloc, 39 | conn->num_xaction_free); 40 | conn->gc = gc; 41 | conn->account = account; 42 | 43 | /* Find the last '@'; usernames can have '@' in them. */ 44 | pos = strrchr(username, '@'); 45 | conn->hostname = g_strdup(pos+1); 46 | pos[0] = 0; 47 | purple_connection_set_display_name(gc, username); 48 | pos[0] = '@'; 49 | 50 | purple_debug_info("campfire", "username: %s\n", username); 51 | purple_debug_info("campfire", "hostname: %s\n", conn->hostname); 52 | 53 | gc->proto_data = conn; 54 | 55 | /*register campfire commands */ 56 | purple_cmd_register(CAMPFIRE_CMD_ME, "s", PURPLE_CMD_P_PRPL, f, prpl_id, 57 | campfire_parse_cmd, 58 | "me <action to perform>: Perform an action.", 59 | conn); 60 | purple_cmd_register(CAMPFIRE_CMD_TOPIC, "s", PURPLE_CMD_P_PRPL, 61 | f | PURPLE_CMD_FLAG_ALLOW_WRONG_ARGS, prpl_id, 62 | campfire_parse_cmd, 63 | "topic <new topic>: Change the room topic.", 64 | conn); 65 | purple_cmd_register(CAMPFIRE_CMD_ROOM, "s", PURPLE_CMD_P_PRPL, f, 66 | prpl_id, campfire_parse_cmd, 67 | "room <new room name>: Change the room name (admin only).", 68 | conn); 69 | purple_cmd_register(CAMPFIRE_CMD_PLAY, "w", PURPLE_CMD_P_PRPL, f, 70 | prpl_id, campfire_parse_cmd, 71 | "play <sound>: Play a sound (trombone, rimshot, crickets, live).", 72 | conn); 73 | 74 | purple_connection_set_state(gc, PURPLE_CONNECTED); 75 | } 76 | 77 | static void 78 | campfire_close(G_GNUC_UNUSED PurpleConnection * gc) 79 | { 80 | } 81 | 82 | static void 83 | campfire_buddy_free(G_GNUC_UNUSED PurpleBuddy * buddy) 84 | { 85 | } 86 | 87 | static gchar * 88 | campfire_status_text(G_GNUC_UNUSED PurpleBuddy * buddy) 89 | { 90 | return NULL; 91 | } 92 | 93 | static void 94 | campfire_set_status(G_GNUC_UNUSED PurpleAccount * acct, 95 | G_GNUC_UNUSED PurpleStatus * status) 96 | { 97 | } 98 | 99 | /* 100 | static GHashTable * campfire_get_account_text_table(PurpleAccount *account) 101 | { 102 | GHashTable *table; 103 | table = g_hash_table_new(g_str_hash, g_str_equal); 104 | g_hash_table_insert(table, "login_label", (gpointer)_("API token")); 105 | return table; 106 | } 107 | */ 108 | 109 | static GList * 110 | campfire_statuses(G_GNUC_UNUSED PurpleAccount * acct) 111 | { 112 | GList *types = NULL; 113 | PurpleStatusType *status; 114 | 115 | /*Online people have a status message and also a date when it was set */ 116 | status = purple_status_type_new_full(PURPLE_STATUS_AVAILABLE, NULL, 117 | _("Online"), TRUE, TRUE, FALSE); 118 | types = g_list_append(types, status); 119 | 120 | /*Offline people dont have messages */ 121 | status = purple_status_type_new_full(PURPLE_STATUS_OFFLINE, NULL, 122 | _("Offline"), TRUE, TRUE, FALSE); 123 | types = g_list_append(types, status); 124 | 125 | return types; 126 | 127 | } 128 | 129 | static GList * 130 | campfire_chat_info(G_GNUC_UNUSED PurpleConnection * gc) 131 | { 132 | GList *m = NULL; 133 | struct proto_chat_entry *pce; 134 | 135 | pce = g_new0(struct proto_chat_entry, 1); 136 | pce->label = _("_Room:"); 137 | pce->identifier = "room"; 138 | pce->required = TRUE; 139 | m = g_list_append(m, pce); 140 | 141 | return m; 142 | } 143 | 144 | static void 145 | campfire_chat_leave(PurpleConnection * gc, int id) 146 | { 147 | purple_debug_info("campfire", "leaving CHAT room id %d\n", id); 148 | 149 | campfire_room_leave(gc->proto_data, id); 150 | } 151 | 152 | static char * 153 | campfire_get_chat_name(GHashTable * data) 154 | { 155 | return g_strdup(g_hash_table_lookup(data, "room")); 156 | } 157 | 158 | 159 | static PurpleRoomlist * 160 | campfire_roomlist_get_list(PurpleConnection * gc) 161 | { 162 | CampfireConn *campfire = gc->proto_data; 163 | GList *fields = NULL; 164 | PurpleRoomlistField *f; 165 | 166 | purple_debug_info("campfire", "initiating ROOMLIST GET LIST\n"); 167 | 168 | if (campfire->roomlist) { 169 | purple_roomlist_unref(campfire->roomlist); 170 | } 171 | 172 | campfire->roomlist = 173 | purple_roomlist_new(purple_connection_get_account(gc)); 174 | 175 | /*f = purple_roomlist_field_new(PURPLE_ROOMLIST_FIELD_STRING, "", "room", TRUE); */ 176 | /*fields = g_list_append(fields, f); */ 177 | 178 | f = purple_roomlist_field_new(PURPLE_ROOMLIST_FIELD_STRING, _("Topic"), 179 | "topic", FALSE); 180 | fields = g_list_append(fields, f); 181 | 182 | f = purple_roomlist_field_new(PURPLE_ROOMLIST_FIELD_STRING, "", "id", 183 | TRUE); 184 | fields = g_list_append(fields, f); 185 | 186 | purple_roomlist_set_fields(campfire->roomlist, fields); 187 | 188 | purple_roomlist_set_in_progress(campfire->roomlist, TRUE); 189 | 190 | campfire_room_query(campfire); 191 | 192 | /*purple_roomlist_set_in_progress(campfire->roomlist, FALSE); */ 193 | /*purple_roomlist_unref(campfire->roomlist); */ 194 | /*campfire->roomlist = NULL; */ 195 | 196 | return campfire->roomlist; 197 | } 198 | 199 | static void 200 | campfire_roomlist_cancel(PurpleRoomlist * list) 201 | { 202 | PurpleConnection *gc = purple_account_get_connection(list->account); 203 | CampfireConn *campfire = NULL; 204 | 205 | if (gc == NULL) 206 | return; 207 | 208 | campfire = gc->proto_data; 209 | 210 | purple_roomlist_set_in_progress(list, FALSE); 211 | 212 | if (campfire->roomlist == list) { 213 | campfire->roomlist = NULL; 214 | purple_roomlist_unref(list); 215 | } 216 | } 217 | 218 | static void 219 | campfire_print_key(gpointer data, G_GNUC_UNUSED gpointer user_data) 220 | { 221 | gchar *key = data; 222 | purple_debug_info("campfire", "key: %s\n", key); 223 | } 224 | 225 | static void 226 | campfire_print_field_name(gpointer data, G_GNUC_UNUSED gpointer user_data) 227 | { 228 | PurpleRoomlistField *field = data; 229 | purple_debug_info("campfire", "field: %s\n", field->name); 230 | } 231 | 232 | void 233 | campfire_join_chat_after_room_query(CampfireConn *campfire, gchar *room_name) 234 | { 235 | /*alternate when not using "Room List" */ 236 | /* @TODO: error checking may be too simple */ 237 | GList *fields; 238 | GList *rooms; 239 | PurpleRoomlistRoom *r; 240 | PurpleRoomlistField *f; 241 | gsize i; 242 | gsize list_size; 243 | gsize id_field_index = 0; 244 | gboolean found = FALSE; 245 | gchar *id = NULL; 246 | gchar *name = NULL; 247 | gboolean room_name_error = FALSE; 248 | 249 | fields = purple_roomlist_get_fields(campfire->roomlist); 250 | g_list_foreach(fields, campfire_print_field_name, NULL); 251 | list_size = g_list_length(fields); 252 | 253 | /* find "id" field */ 254 | for (i = 0; i < list_size; i++) { 255 | f = g_list_nth_data(fields, i); 256 | purple_debug_info("campfire", "field name: %s\n", f->name); 257 | if (strcmp("id", f->name) == 0) { 258 | id_field_index = i; 259 | found = TRUE; 260 | break; 261 | } 262 | } 263 | if (!found) { 264 | room_name_error = TRUE; 265 | purple_notify_message(campfire->gc, PURPLE_NOTIFY_MSG_ERROR, 266 | "campfire error", 267 | "couldn't find 'id' field in roomlist.", 268 | NULL, 269 | NULL, 270 | NULL); 271 | } else { 272 | found = FALSE; 273 | } 274 | rooms = campfire->roomlist->rooms; 275 | list_size = g_list_length(rooms); 276 | purple_debug_info("campfire", "join debug 6\n"); 277 | 278 | /* find typed/chosen room name in available room names */ 279 | for (i = 0; i < list_size; i++) { 280 | r = (PurpleRoomlistRoom *)g_list_nth_data(rooms, i); 281 | if (strcmp(r->name, room_name) == 0) { 282 | purple_debug_info("campfire", "room found\n"); 283 | purple_debug_info("campfire", "room desired: %s\n", room_name); 284 | purple_debug_info("campfire", "room found: %s\n", r->name); 285 | id = g_list_nth_data(r->fields, id_field_index); 286 | name = r->name; 287 | found = TRUE; 288 | break; 289 | } 290 | } 291 | 292 | if (!found) { 293 | room_name_error = TRUE; 294 | purple_notify_message(campfire->gc, PURPLE_NOTIFY_MSG_ERROR, 295 | "campfire error", 296 | "couldn't find room name in roomlist.", 297 | room_name, 298 | NULL, 299 | NULL); 300 | } 301 | if (!room_name_error) { 302 | purple_debug_info("campfire", "trying to JOIN CHAT room id %s\n", id); 303 | campfire_room_join(campfire, id, name); 304 | } 305 | } 306 | 307 | static void 308 | campfire_join_chat(PurpleConnection * gc, GHashTable * data) 309 | { 310 | GList *hash_keys = NULL; 311 | CampfireConn *campfire = gc->proto_data; 312 | gchar *desired_name = NULL; 313 | gchar *id = NULL; 314 | gchar *name = NULL; 315 | gboolean room_name_error = FALSE; 316 | 317 | 318 | purple_debug_info("campfire", "1: %p\n", data); 319 | desired_name = g_hash_table_lookup(data, "room"); 320 | purple_debug_info("campfire", "2\n"); 321 | if (desired_name) { 322 | campfire->desired_room = g_strdup(desired_name); 323 | campfire->needs_join = TRUE; 324 | hash_keys = g_hash_table_get_keys(data); 325 | g_list_foreach(hash_keys, campfire_print_key, NULL); 326 | /* do this if you haven't done a room query yet */ 327 | if (!campfire->roomlist) { 328 | campfire_roomlist_get_list(gc); 329 | } else { 330 | campfire_join_chat_after_room_query(campfire, campfire->desired_room); 331 | } 332 | } else { 333 | id = g_hash_table_lookup(data, "id"); 334 | name = g_hash_table_lookup(data, "name"); 335 | if (!id || !name) { 336 | room_name_error = TRUE; 337 | purple_notify_message(campfire->gc, PURPLE_NOTIFY_MSG_ERROR, 338 | "campfire error", 339 | "hash table error.", 340 | NULL, 341 | NULL, 342 | NULL); 343 | } 344 | if (!room_name_error) { 345 | purple_debug_info("campfire", "trying to JOIN CHAT room id %s\n", id); 346 | campfire_room_join(campfire, id, name); 347 | } 348 | } 349 | } 350 | 351 | 352 | static const char * 353 | campfireim_list_icon(G_GNUC_UNUSED PurpleAccount * account, 354 | G_GNUC_UNUSED PurpleBuddy * buddy) 355 | { 356 | return "campfire"; 357 | } 358 | 359 | static int 360 | campfire_chat_send(PurpleConnection * gc, int id, const char *message, 361 | G_GNUC_UNUSED PurpleMessageFlags flags) 362 | { 363 | campfire_message_send(gc->proto_data, id, message, 364 | CAMPFIRE_MESSAGE_TEXT); 365 | return 1; 366 | } 367 | 368 | static PurplePluginProtocolInfo campfire_protocol_info = { 369 | /* options */ 370 | OPT_PROTO_CHAT_TOPIC | OPT_PROTO_NO_PASSWORD, /*| OPT_PROTO_SLASH_COMMANDS_NATIVE, */ 371 | NULL, /* user_splits */ 372 | NULL, /* protocol_options */ 373 | { /* icon_spec, a PurpleBuddyIconSpec */ 374 | "png,jpg,gif", /* format */ 375 | 0, /* min_width */ 376 | 0, /* min_height */ 377 | 128, /* max_width */ 378 | 128, /* max_height */ 379 | 10000, /* max_filesize */ 380 | PURPLE_ICON_SCALE_DISPLAY, /* scale_rules */ 381 | }, 382 | campfireim_list_icon, /* list_icon */ 383 | NULL, /* list_emblems */ 384 | campfire_status_text, /* status_text */ 385 | NULL, 386 | campfire_statuses, /* status_types */ 387 | NULL, /* blist_node_menu */ 388 | campfire_chat_info, /* chat_info */ 389 | NULL, /* chat_info_defaults */ 390 | campfire_login, /* login */ 391 | campfire_close, /* close */ 392 | NULL, /* send_im */ 393 | NULL, /* set_info */ 394 | NULL, /* send_typing */ 395 | NULL, /* get_info */ 396 | campfire_set_status, /* set_status */ 397 | NULL, /* set_idle */ 398 | NULL, /* change_passwd */ 399 | NULL, /* add_buddy */ 400 | NULL, /* add_buddies */ 401 | NULL, /* remove_buddy */ 402 | NULL, /* remove_buddies */ 403 | NULL, /* add_permit */ 404 | NULL, /* add_deny */ 405 | NULL, /* rem_permit */ 406 | NULL, /* rem_deny */ 407 | NULL, /* set_permit_deny */ 408 | campfire_join_chat, /* join_chat */ 409 | NULL, /* reject chat invite */ 410 | campfire_get_chat_name, /* get_chat_name */ 411 | NULL, /* chat_invite */ 412 | campfire_chat_leave, /* chat_leave */ 413 | NULL, /* chat_whisper */ 414 | campfire_chat_send, /* chat_send */ 415 | NULL, /* keepalive */ 416 | NULL, /* register_user */ 417 | NULL, /* get_cb_info */ 418 | NULL, /* get_cb_away */ 419 | NULL, /* alias_buddy */ 420 | NULL, /* group_buddy */ 421 | NULL, /* rename_group */ 422 | campfire_buddy_free, /* buddy_free */ 423 | NULL, /* convo_closed */ 424 | purple_normalize_nocase, /* normalize */ 425 | NULL, /* set_buddy_icon */ 426 | NULL, /* remove_group */ 427 | NULL, /* get_cb_real_name */ 428 | NULL, /* set_chat_topic */ 429 | NULL, /* find_blist_chat */ 430 | campfire_roomlist_get_list, /* roomlist_get_list */ 431 | campfire_roomlist_cancel, /* roomlist_cancel */ 432 | NULL, /* roomlist_expand_category */ 433 | NULL, /* can_receive_file */ 434 | NULL, /* send_file */ 435 | NULL, /* new_xfer */ 436 | NULL, /* offline_message */ 437 | NULL, /* whiteboard_prpl_ops */ 438 | NULL, /* send_raw */ 439 | NULL, /* roomlist_room_serialize */ 440 | NULL, /* unregister_user */ 441 | NULL, /* send_attention */ 442 | NULL, /* attention_types */ 443 | sizeof(PurplePluginProtocolInfo), /* struct_size */ 444 | NULL, /*campfire_get_account_text_table *//* get_account_text_table */ 445 | NULL, /* initiate_media */ 446 | NULL, /* get_media_caps */ 447 | #if PURPLE_MAJOR_VERSION > 1 448 | #if PURPLE_MINOR_VERSION > 6 449 | NULL, /* get_moods */ 450 | NULL, /* set_public_alias */ 451 | NULL, /* get_public_alias */ 452 | #if PURPLE_MINOR_VERSION > 7 453 | NULL, /* add_buddy_with_invite */ 454 | NULL, /* add_buddies_with_invite */ 455 | #endif /* PURPLE_MINOR_VERSION > 7 */ 456 | #endif /* PURPLE_MINOR_VERSION > 6 */ 457 | #endif /* PURPLE_MAJOR_VERSION > 1 */ 458 | }; 459 | 460 | static PurplePluginInfo info = { 461 | PURPLE_PLUGIN_MAGIC, /* magic */ 462 | PURPLE_MAJOR_VERSION, /* major_version */ 463 | PURPLE_MINOR_VERSION, /* minor_version */ 464 | PURPLE_PLUGIN_PROTOCOL, /* type */ 465 | NULL, /* ui_requirement */ 466 | 0, /* flags */ 467 | NULL, /* dependencies */ 468 | PURPLE_PRIORITY_DEFAULT, /* priority */ 469 | "prpl-analog_g-campfire", /* id */ 470 | "Campfire", /* name */ 471 | "0.1", /* version */ 472 | "Campfire Chat", /* summary */ 473 | "Campfire Chat Protocol Plugin", /* description */ 474 | "Jake Foell ", /* author */ 475 | "https://github.com/jrfoell/campfire-libpurple", /* homepage */ 476 | plugin_load, /* load */ 477 | plugin_unload, /* unload */ 478 | NULL, /* destroy */ 479 | NULL, /* ui_info */ 480 | &campfire_protocol_info, /* extra_info */ 481 | NULL, /* prefs_info */ 482 | NULL, /* actions */ 483 | NULL, /* padding... */ 484 | NULL, 485 | NULL, 486 | NULL 487 | }; 488 | 489 | static void 490 | plugin_init(G_GNUC_UNUSED PurplePlugin * plugin) 491 | { 492 | PurpleAccountUserSplit *split; 493 | PurpleAccountOption *option_token, *option_limit; 494 | 495 | split = purple_account_user_split_new(_("Hostname"), NULL, '@'); 496 | campfire_protocol_info.user_splits = 497 | g_list_append(campfire_protocol_info.user_splits, split); 498 | 499 | option_token = 500 | purple_account_option_string_new(_("API token"), "api_token", 501 | NULL); 502 | campfire_protocol_info.protocol_options = 503 | g_list_append(campfire_protocol_info.protocol_options, 504 | option_token); 505 | 506 | option_limit = 507 | purple_account_option_int_new(_("Retrieve # msgs on join"), 508 | "limit", 10); 509 | campfire_protocol_info.protocol_options = 510 | g_list_append(campfire_protocol_info.protocol_options, 511 | option_limit); 512 | } 513 | 514 | PURPLE_INIT_PLUGIN(campfire, plugin_init, info); 515 | -------------------------------------------------------------------------------- /http.c: -------------------------------------------------------------------------------- 1 | 2 | /*local includes*/ 3 | #include "http.h" 4 | #include "message.h" 5 | 6 | /*system includes*/ 7 | #include 8 | 9 | /*purple includes*/ 10 | #include 11 | 12 | static void 13 | campfire_ssl_failure(G_GNUC_UNUSED PurpleSslConnection * gsc, 14 | G_GNUC_UNUSED PurpleSslErrorType error, 15 | G_GNUC_UNUSED gpointer data) 16 | { 17 | purple_debug_info("campfire", "ssl connect failure\n"); 18 | } 19 | 20 | void 21 | campfire_http_request(CampfireSslTransaction * xaction, gchar * uri, 22 | gchar * method, xmlnode * postxml) 23 | { 24 | CampfireConn *conn = xaction->campfire; 25 | const char *api_token = 26 | purple_account_get_string(conn->account, "api_token", ""); 27 | gchar *xmlstr = NULL, *len = NULL, *encoded = NULL; 28 | gsize auth_len; 29 | 30 | xaction->http_request = g_string_new(method); 31 | g_string_append(xaction->http_request, " "); 32 | g_string_append(xaction->http_request, uri); 33 | g_string_append(xaction->http_request, " HTTP/1.1\r\n"); 34 | 35 | g_string_append(xaction->http_request, 36 | "Content-Type: application/xml\r\n"); 37 | 38 | g_string_append(xaction->http_request, "Authorization: Basic "); 39 | auth_len = strlen(api_token); 40 | encoded = purple_base64_encode((const guchar *) api_token, auth_len); 41 | g_string_append(xaction->http_request, encoded); 42 | g_string_append(xaction->http_request, "\r\n"); 43 | g_free(encoded); 44 | 45 | g_string_append(xaction->http_request, "Host: "); 46 | g_string_append(xaction->http_request, conn->hostname); 47 | g_string_append(xaction->http_request, "\r\n"); 48 | 49 | g_string_append(xaction->http_request, "Accept: */*\r\n"); 50 | 51 | if (postxml) { 52 | xmlstr = xmlnode_to_str(postxml, NULL); 53 | g_string_append(xaction->http_request, "Content-Length: "); 54 | /* len = g_strdup_printf("%lu", strlen(xmlstr)); */ 55 | len = g_strdup_printf("%" G_GSIZE_FORMAT, strlen(xmlstr)); 56 | g_string_append(xaction->http_request, len); 57 | g_free(len); 58 | g_string_append(xaction->http_request, "\r\n\r\n"); 59 | g_string_append(xaction->http_request, xmlstr); 60 | g_string_append(xaction->http_request, "\r\n"); 61 | } 62 | g_string_append(xaction->http_request, "\r\n"); 63 | purple_debug_info("campfire", "Formatted request:\n%s\n", 64 | xaction->http_request->str); 65 | } 66 | 67 | /* return 1 on error, return 0 on success */ 68 | static gint 69 | campfire_get_http_status_from_header(gint * status, GString * header) 70 | { 71 | gchar *str, *found_str, *extra_chars; 72 | gchar prefix[] = "\r\nStatus: "; 73 | gsize prefix_size = strlen(prefix); 74 | gint tmp; 75 | 76 | found_str = g_strstr_len(header->str, header->len, prefix); 77 | if (!found_str) { 78 | return 1; 79 | } 80 | found_str += prefix_size; /* increment pointer to start of status string */ 81 | str = g_malloc0(4); /* status is 3-digits plus NULL */ 82 | g_strlcpy(str, found_str, 4); 83 | tmp = (gint) g_ascii_strtoull(str, &extra_chars, 10); 84 | g_free(str); 85 | if (tmp == 0 && extra_chars == str) { 86 | *status = -1; 87 | return 1; 88 | } 89 | *status = tmp; 90 | return 0; 91 | } 92 | 93 | /* return 1 on error, return 0 on success */ 94 | static gint 95 | campfire_get_content_length_from_header(gsize * cl, GString * header) 96 | { 97 | gchar *str, *found_str, *extra_chars, *eol; 98 | gchar prefix[] = "\r\nContent-Length: "; 99 | gsize prefix_size = strlen(prefix); 100 | gsize rest_of_header_size; 101 | gsize cl_size; 102 | gsize tmp; 103 | 104 | found_str = g_strstr_len(header->str, header->len, prefix); 105 | if (!found_str) { 106 | return 1; 107 | } 108 | found_str += prefix_size; /* increment pointer to start desired string */ 109 | rest_of_header_size = header->len - (found_str - header->str); 110 | eol = g_strstr_len(found_str, rest_of_header_size, "\r\n"); 111 | if (!eol) { 112 | return 1; 113 | } 114 | cl_size = eol - found_str; 115 | purple_debug_info("campfire", "content length str len = %" 116 | G_GSIZE_FORMAT "\n", cl_size); 117 | str = g_malloc0(cl_size + 1); 118 | g_strlcpy(str, found_str, cl_size + 1); 119 | tmp = g_ascii_strtoll(str, &extra_chars, 10); 120 | g_free(str); 121 | if (tmp == 0 && extra_chars == str) { 122 | *cl = 0; 123 | return 1; 124 | } 125 | purple_debug_info("campfire", "content length: %" G_GSIZE_FORMAT "\n", 126 | tmp); 127 | *cl = tmp; 128 | return 0; 129 | } 130 | 131 | static gboolean 132 | ssl_input_consumed(GString * ssl_input) 133 | { 134 | gboolean consumed = FALSE; 135 | if (ssl_input == NULL) { 136 | consumed = TRUE; 137 | } else if (ssl_input->len == 0) { 138 | consumed = TRUE; 139 | } 140 | return consumed; 141 | } 142 | 143 | static void 144 | campfire_consume_http_header(CampfireHttpResponse * response, 145 | GString * ssl_input) 146 | { 147 | gchar blank_line[] = "\r\n\r\n"; 148 | gchar *header_end; 149 | gsize header_len; 150 | 151 | header_end = g_strstr_len(ssl_input->str, ssl_input->len, blank_line); 152 | if (header_end) { 153 | header_end += strlen(blank_line); 154 | header_len = header_end - ssl_input->str; 155 | } else { 156 | header_len = ssl_input->len; 157 | } 158 | g_string_append_len(response->response, ssl_input->str, header_len); 159 | g_string_erase(ssl_input, 0, header_len); 160 | } 161 | 162 | static gboolean 163 | campfire_http_header_received(CampfireHttpResponse * response) 164 | { 165 | gboolean received = FALSE; 166 | gchar blank_line[] = "\r\n\r\n"; 167 | gchar *header_end; 168 | header_end = 169 | g_strstr_len(response->response->str, response->response->len, 170 | blank_line); 171 | if (header_end) { 172 | received = TRUE; 173 | } 174 | return received; 175 | } 176 | 177 | /* this function should be called just after the header (including blank line 178 | * have been copied from ssl_input to the response string. That way this 179 | * operation to save the header string becomes a simple string copy from 180 | * one GString to another. 181 | */ 182 | static void 183 | campfire_process_http_header(CampfireHttpResponse * response) 184 | { 185 | response->header = g_string_new(""); 186 | g_string_append(response->header, response->response->str); 187 | campfire_get_http_status_from_header(&response->status, 188 | response->header); 189 | campfire_get_content_length_from_header(&response->content_len, 190 | response->header); 191 | } 192 | 193 | static void 194 | campfire_consume_http_content(CampfireHttpResponse * response, 195 | GString * ssl_input) 196 | { 197 | g_string_append_len(response->response, ssl_input->str, ssl_input->len); 198 | response->content_received_len += ssl_input->len; 199 | g_string_erase(ssl_input, 0, -1); 200 | } 201 | 202 | static gboolean 203 | campfire_http_content_received(CampfireHttpResponse * response) 204 | { 205 | return response->content_received_len >= response->content_len; 206 | } 207 | 208 | /* this function should be called just after the received bytes match the 209 | * content length prescribe in the http header. To create the 'content' 210 | * GString we must copy the 'response' string starting immediately after 211 | * the blank line 212 | */ 213 | static void 214 | campfire_process_http_content(CampfireHttpResponse * response) 215 | { 216 | gsize content_start_index = response->header->len; 217 | response->content = g_string_new(""); 218 | g_string_append(response->content, 219 | &response->response->str[content_start_index]); 220 | } 221 | 222 | static gint 223 | campfire_http_response(PurpleSslConnection * gsc, 224 | CampfireSslTransaction * xaction, 225 | G_GNUC_UNUSED PurpleInputCondition cond, 226 | G_GNUC_UNUSED xmlnode ** node) 227 | { 228 | gchar buf[1024]; 229 | GString *ssl_input; 230 | gint len, errsv = 0; 231 | gint status; 232 | CampfireHttpResponse *response = &xaction->http_response; 233 | 234 | if (response->rx_state == CAMPFIRE_HTTP_RX_DONE) { 235 | purple_debug_info("campfire", "somefin aint right.\n"); 236 | return -1; 237 | } 238 | 239 | /********************************************************************** 240 | * read input from file descriptor 241 | *********************************************************************/ 242 | ssl_input = g_string_new(""); 243 | errno = 0; 244 | while ((len = purple_ssl_read(gsc, buf, sizeof(buf))) > 0) { 245 | purple_debug_info("campfire", 246 | "read %d bytes from HTTP Response, errno: %i\n", 247 | len, errno); 248 | ssl_input = g_string_append_len(ssl_input, buf, len); 249 | } 250 | errsv = errno; 251 | 252 | /********************************************************************** 253 | * handle return value of ssl input read 254 | *********************************************************************/ 255 | if (len < 0 && errsv == EAGAIN) { 256 | if (ssl_input->len == 0) { 257 | purple_debug_info("campfire", 258 | "TRY AGAIN (returning)\n"); 259 | g_string_free(ssl_input, TRUE); 260 | return 0; 261 | } else { 262 | purple_debug_info("campfire", "EAGAIN (continuing)\n"); 263 | } 264 | } else if (len == 0) { 265 | purple_debug_info("campfire", "SERVER CLOSED CONNECTION\n"); 266 | if (ssl_input->len == 0) { 267 | g_string_free(ssl_input, TRUE); 268 | return -1; 269 | } 270 | } else { 271 | purple_debug_info("campfire", "LOST CONNECTION\n"); 272 | purple_debug_info("campfire", "errno: %d\n", errsv); 273 | g_string_free(ssl_input, TRUE); 274 | return -1; 275 | } 276 | 277 | if (!response->response) { 278 | response->response = g_string_new(""); 279 | } 280 | 281 | purple_debug_info("campfire", "ssl_input:\n%s", ssl_input->str); 282 | 283 | /********************************************************************** 284 | * process input with a simple state machine 285 | *********************************************************************/ 286 | while (!ssl_input_consumed(ssl_input)) { 287 | switch (response->rx_state) { 288 | case CAMPFIRE_HTTP_RX_HEADER: 289 | purple_debug_info("campfire", 290 | "CAMPFIRE_HTTP_RX_HEADER\n"); 291 | campfire_consume_http_header(response, ssl_input); 292 | if (campfire_http_header_received(response)) { 293 | campfire_process_http_header(response); 294 | response->rx_state = CAMPFIRE_HTTP_RX_CONTENT; 295 | } 296 | break; 297 | case CAMPFIRE_HTTP_RX_CONTENT: 298 | purple_debug_info("campfire", 299 | "CAMPFIRE_HTTP_RX_CONTENT\n"); 300 | campfire_consume_http_content(response, ssl_input); 301 | if (campfire_http_content_received(response)) { 302 | campfire_process_http_content(response); 303 | response->rx_state = CAMPFIRE_HTTP_RX_DONE; 304 | } 305 | break; 306 | case CAMPFIRE_HTTP_RX_DONE: 307 | purple_debug_info("campfire", 308 | "CAMPFIRE_HTTP_RX_DONE\n"); 309 | g_string_erase(ssl_input, 0, -1); /* consume input */ 310 | break; 311 | default: 312 | g_string_erase(ssl_input, 0, -1); /* consume input */ 313 | break; 314 | } 315 | } 316 | 317 | /********************************************************************** 318 | * return http status code: -1=error, 0=input_not_received 319 | *********************************************************************/ 320 | if (response->rx_state == CAMPFIRE_HTTP_RX_DONE) { 321 | status = response->status; 322 | } else { 323 | status = 0; 324 | } 325 | g_string_free(ssl_input, TRUE); 326 | return status; 327 | } 328 | 329 | 330 | void 331 | campfire_message_free(gpointer data, G_GNUC_UNUSED gpointer user_data) 332 | { 333 | CampfireMessage *msg = (CampfireMessage *)(data); 334 | if (msg != NULL) { 335 | if (msg->id != NULL) { 336 | g_free(msg->id); 337 | } 338 | if (msg->type != NULL) { 339 | g_free(msg->type); 340 | } 341 | if (msg->message != NULL) { 342 | g_free(msg->message); 343 | } 344 | if (msg->user_id != NULL) { 345 | g_free(msg->user_id); 346 | } 347 | g_free(msg); 348 | } 349 | } 350 | 351 | void 352 | campfire_xaction_free(CampfireSslTransaction *xaction) 353 | { 354 | if (xaction) { 355 | if (xaction->http_request) { 356 | g_string_free(xaction->http_request, TRUE); 357 | } 358 | if (xaction->http_response.response) { 359 | g_string_free(xaction->http_response.response, 360 | TRUE); 361 | } 362 | if (xaction->http_response.header) { 363 | g_string_free(xaction->http_response.header, 364 | TRUE); 365 | } 366 | if (xaction->http_response.content) { 367 | g_string_free(xaction->http_response.content, 368 | TRUE); 369 | } 370 | if (xaction->xml_response) { 371 | xmlnode_free(xaction->xml_response); 372 | } 373 | if (xaction->room_id) { 374 | g_free(xaction->room_id); 375 | } 376 | g_list_foreach(xaction->messages, campfire_message_free, NULL); 377 | g_list_free(xaction->messages); 378 | xaction->campfire->num_xaction_free++; /* valgrind investigation */ 379 | purple_debug_info("campfire", "xaction: %p, num_xaction_malloc:%d: num_xaction_free:%d\n", 380 | xaction, 381 | xaction->campfire->num_xaction_malloc, 382 | xaction->campfire->num_xaction_free); 383 | g_free(xaction); 384 | } 385 | 386 | } 387 | 388 | static void 389 | campfire_ssl_handler(CampfireConn * campfire, 390 | PurpleSslConnection * gsc, PurpleInputCondition cond) 391 | { 392 | GList *first = g_list_first(campfire->queue); 393 | CampfireSslTransaction *xaction = NULL; 394 | gint status; 395 | gboolean close_ssl = FALSE; 396 | gboolean cleanup = TRUE; 397 | 398 | if (first) { 399 | xaction = first->data; 400 | } else { 401 | xaction = g_new0(CampfireSslTransaction, 1); 402 | campfire->num_xaction_malloc++; /* valgrind investigation */ 403 | purple_debug_info("campfire", "%s: xaction: %p, campfire->num_xaction_malloc:%d\n", 404 | __FUNCTION__, xaction, campfire->num_xaction_malloc); 405 | xaction->campfire = campfire; 406 | } 407 | 408 | purple_debug_info("campfire", "%s: first: %p\n", __FUNCTION__, first); 409 | 410 | status = campfire_http_response(gsc, xaction, cond, 411 | &(xaction->xml_response)); 412 | purple_debug_info("campfire", "http status: %d\n", status); 413 | 414 | if (status == 200 || status == 201) { 415 | xaction->xml_response = 416 | xmlnode_from_str(xaction->http_response.content->str, 417 | -1); 418 | if (xaction && xaction->response_cb) { 419 | purple_debug_info("campfire", 420 | "calling response_cb (%p)\n", 421 | xaction->response_cb); 422 | xaction->response_cb(xaction, gsc, cond); 423 | } 424 | cleanup = TRUE; 425 | } else if (status == 0) { /*received partial content */ 426 | cleanup = FALSE; 427 | } else { /*status < 0 or some other http status we don't expect */ 428 | 429 | close_ssl = TRUE; 430 | cleanup = TRUE; 431 | } 432 | 433 | if (close_ssl && campfire->gsc) { 434 | purple_debug_info("campfire", 435 | "closing ssl connection:%p (%p)\n", gsc, 436 | campfire->gsc); 437 | campfire->gsc = NULL; 438 | purple_ssl_close(gsc); 439 | cleanup = TRUE; 440 | } 441 | 442 | if (cleanup) { 443 | campfire_xaction_free(xaction); 444 | 445 | if (first) { 446 | purple_debug_info("campfire", 447 | "removing from queue: length: %d\n", 448 | g_list_length(campfire->queue)); 449 | campfire->queue = 450 | g_list_remove(campfire->queue, xaction); 451 | purple_debug_info("campfire", 452 | "removed from queue: length: %d\n", 453 | g_list_length(campfire->queue)); 454 | } 455 | 456 | first = g_list_first(campfire->queue); 457 | if (first) { 458 | xaction = first->data; 459 | purple_debug_info("campfire", 460 | "writing subsequent request on ssl connection\n"); 461 | purple_ssl_write(gsc, xaction->http_request->str, 462 | xaction->http_request->len); 463 | } 464 | } 465 | } 466 | 467 | /* 468 | * this prototype is needed because the next two functions call each other. 469 | */ 470 | static void campfire_ssl_connect(CampfireConn * campfire, 471 | PurpleInputCondition cond, 472 | gboolean from_connection_callback); 473 | 474 | /* This is just an itermediate function: 475 | * It is called by lower level purple stuff when the ssl connection is 476 | * established. It merely calls 'campfire_ssl_connect' with an extra argument 477 | * to denote that it's coming from the callback. 478 | */ 479 | static void 480 | campfire_ssl_connect_cb(CampfireConn * campfire, PurpleInputCondition cond) 481 | { 482 | campfire_ssl_connect(campfire, cond, TRUE); 483 | } 484 | 485 | static void 486 | campfire_ssl_connect(CampfireConn * campfire, 487 | G_GNUC_UNUSED PurpleInputCondition cond, 488 | gboolean from_connection_callback) 489 | { 490 | GList *first = NULL; 491 | CampfireSslTransaction *xaction = NULL; 492 | 493 | purple_debug_info("campfire", "%s\n", __FUNCTION__); 494 | if (!campfire) { 495 | return; 496 | } else { 497 | first = g_list_first(campfire->queue); 498 | } 499 | 500 | if (!first) { 501 | return; 502 | } else { 503 | xaction = first->data; 504 | } 505 | 506 | if (!xaction) { 507 | return; 508 | } 509 | 510 | if (!campfire->gsc) { 511 | purple_debug_info("campfire", "new ssl connection\n"); 512 | campfire->gsc = purple_ssl_connect(campfire->account, 513 | campfire->hostname, 514 | 443, (PurpleSslInputFunction) 515 | (campfire_ssl_connect_cb), 516 | campfire_ssl_failure, 517 | campfire); 518 | purple_debug_info("campfire", 519 | "new ssl connection kicked off.\n"); 520 | } else { 521 | purple_debug_info("campfire", "previous ssl connection\n"); 522 | /* we want to write our http request to the ssl connection 523 | * WHENEVER this is called from the callback (meaning we've 524 | * JUST NOW established the connection). OR when the first 525 | * transaction is added to the queue on an OPEN ssl connection 526 | */ 527 | if (from_connection_callback 528 | || g_list_length(campfire->queue) == 1) { 529 | /* campfire_ssl_handler is the ONLY input handler we 530 | * EVER use So... if there is already an input handler 531 | * present (inpa > 0), then we DON"T want to add another 532 | * input handler. Quite a few hours spent chasing bugs 533 | * when multiple input handlers were added! 534 | */ 535 | if (campfire->gsc->inpa == 0) { 536 | purple_debug_info("campfire", "adding input\n"); 537 | purple_ssl_input_add(campfire->gsc, 538 | (PurpleSslInputFunction) 539 | (campfire_ssl_handler), 540 | campfire); 541 | } 542 | purple_debug_info("campfire", 543 | "writing first request on ssl connection\n"); 544 | purple_ssl_write(campfire->gsc, 545 | xaction->http_request->str, 546 | xaction->http_request->len); 547 | } 548 | } 549 | return; 550 | } 551 | 552 | void 553 | campfire_queue_xaction(CampfireConn * campfire, 554 | CampfireSslTransaction * xaction, 555 | PurpleInputCondition cond) 556 | { 557 | gboolean from_callback = FALSE; /* this is not the ssl connection callback */ 558 | purple_debug_info("campfire", "%s input condition: %i\n", __FUNCTION__, 559 | cond); 560 | xaction->queued = TRUE; 561 | campfire->queue = g_list_append(campfire->queue, xaction); 562 | purple_debug_info("campfire", "queue length %d\n", 563 | g_list_length(campfire->queue)); 564 | campfire_ssl_connect(campfire, cond, from_callback); 565 | } 566 | -------------------------------------------------------------------------------- /message.c: -------------------------------------------------------------------------------- 1 | 2 | /* local includes */ 3 | #include "campfire.h" 4 | #include "message.h" 5 | #include "http.h" 6 | 7 | /* purple includes */ 8 | #include 9 | 10 | /* internal function prototypes */ 11 | void 12 | campfire_message_handler_callback(CampfireSslTransaction * xaction, 13 | PurpleSslConnection * gsc, 14 | PurpleInputCondition cond); 15 | 16 | static CampfireMessage * 17 | campfire_get_message(xmlnode * xmlmessage) 18 | { 19 | xmlnode *xmlbody = NULL, *xmluser_id = NULL, *xmltime = NULL, 20 | *xmltype = NULL, *xmlid = NULL; 21 | gchar *body = NULL, *user_id = NULL, *msgtype = NULL, *msg_id = NULL; 22 | gchar *xmltime_data; 23 | CampfireMessage *msg = NULL; 24 | GTimeVal timeval; 25 | time_t mtime = 0; 26 | 27 | xmlbody = xmlnode_get_child(xmlmessage, "body"); 28 | body = xmlnode_get_data(xmlbody); /* needs g_free */ 29 | 30 | xmluser_id = xmlnode_get_child(xmlmessage, "user-id"); 31 | user_id = xmlnode_get_data(xmluser_id); /* needs g_free */ 32 | 33 | xmltime = xmlnode_get_child(xmlmessage, "created-at"); 34 | xmltime_data = xmlnode_get_data(xmltime); /* needs g_free */ 35 | if (g_time_val_from_iso8601(xmltime_data, &timeval)) { 36 | mtime = timeval.tv_sec; 37 | } 38 | g_free(xmltime_data); 39 | 40 | xmltype = xmlnode_get_child(xmlmessage, "type"); 41 | msgtype = xmlnode_get_data(xmltype); /* needs g_free */ 42 | 43 | xmlid = xmlnode_get_child(xmlmessage, "id"); 44 | msg_id = xmlnode_get_data(xmlid); /* needs g_free */ 45 | 46 | if (g_strcmp0(msgtype, CAMPFIRE_MESSAGE_TIME) == 0) { 47 | purple_debug_info("campfire", "Skipping message of type: %s\n", 48 | msgtype); 49 | xmlmessage = xmlnode_get_next_twin(xmlmessage); 50 | return NULL; 51 | } 52 | 53 | purple_debug_info("campfire", "got message of type: %s\n", msgtype); 54 | msg = g_new0(CampfireMessage, 1); 55 | msg->id = msg_id; 56 | msg->user_id = user_id; 57 | msg->time = mtime; 58 | msg->type = msgtype; 59 | 60 | if (g_strcmp0(msgtype, CAMPFIRE_MESSAGE_TEXT) == 0 || 61 | g_strcmp0(msgtype, CAMPFIRE_MESSAGE_TWEET) == 0 || 62 | g_strcmp0(msgtype, CAMPFIRE_MESSAGE_PASTE) == 0 || 63 | g_strcmp0(msgtype, CAMPFIRE_MESSAGE_SOUND) == 0 || 64 | g_strcmp0(msgtype, CAMPFIRE_MESSAGE_TOPIC) == 0) { 65 | msg->message = body; 66 | } else { 67 | g_free(body); 68 | } 69 | return msg; 70 | } 71 | 72 | static void 73 | message_g_free(gpointer data, G_GNUC_UNUSED gpointer user_data) 74 | { 75 | g_free(data); 76 | } 77 | 78 | static void 79 | campfire_userlist_callback(CampfireSslTransaction * xaction, 80 | G_GNUC_UNUSED PurpleSslConnection * gsc, 81 | G_GNUC_UNUSED PurpleInputCondition cond) 82 | { 83 | PurpleConversation *convo = NULL; 84 | PurpleConvChat *chat = NULL; 85 | xmlnode *xmlroomname = NULL, *xmltopic = NULL, 86 | *xmlusers = NULL, *xmluser = NULL, *xmlname = NULL; 87 | gchar *room_name = NULL, *topic = NULL, *name = NULL; 88 | GList *users = NULL, *chatusers = NULL, *users_iter = NULL; 89 | PurpleConvChatBuddy *buddy = NULL; 90 | gboolean found; 91 | 92 | xmlroomname = xmlnode_get_child(xaction->xml_response, "name"); 93 | room_name = xmlnode_get_data(xmlroomname); /* needs g_free */ 94 | purple_debug_info("campfire", "locating room: %s\n", room_name); 95 | convo = purple_find_conversation_with_account(PURPLE_CONV_TYPE_ANY, 96 | room_name, 97 | purple_connection_get_account 98 | (xaction->campfire->gc)); 99 | g_free(room_name); 100 | chat = PURPLE_CONV_CHAT(convo); 101 | xmltopic = xmlnode_get_child(xaction->xml_response, "topic"); 102 | topic = xmlnode_get_data(xmltopic); /* needs g_free */ 103 | purple_debug_info("campfire", "setting topic to %s\n", topic); 104 | purple_conv_chat_set_topic(chat, NULL, topic); 105 | g_free(topic); 106 | xmlusers = xmlnode_get_child(xaction->xml_response, "users"); 107 | xmluser = xmlnode_get_child(xmlusers, "user"); 108 | 109 | while (xmluser != NULL) { 110 | xmlname = xmlnode_get_child(xmluser, "name"); 111 | name = xmlnode_get_data(xmlname); /* needs g_free */ 112 | purple_debug_info("campfire", "user in room: %s\n", name); 113 | 114 | if (!purple_conv_chat_find_user(chat, name)) { 115 | purple_debug_info("campfire", 116 | "adding user %s to room\n", name); 117 | purple_conv_chat_add_user(chat, name, 118 | NULL, PURPLE_CBFLAGS_NONE, 119 | TRUE); 120 | } 121 | users = g_list_prepend(users, name); 122 | xmluser = xmlnode_get_next_twin(xmluser); 123 | } 124 | 125 | purple_debug_info("campfire", "Getting all users in room\n"); 126 | chatusers = purple_conv_chat_get_users(chat); 127 | purple_debug_info("campfire", "got all users in room %p\n", chatusers); 128 | 129 | if (users == NULL) { 130 | /** 131 | * Can happen if you're the last user in the room, and you clicked "Leave" through 132 | * the web interface 133 | */ 134 | purple_debug_info("campfire", "removing all users from room\n"); 135 | purple_conv_chat_remove_users(chat, 136 | chatusers, NULL); 137 | } else if (chatusers != NULL) { 138 | GList *remove = NULL; 139 | purple_debug_info("campfire", "iterating chat users\n"); 140 | for (; chatusers != NULL; chatusers = chatusers->next) { 141 | buddy = chatusers->data; 142 | found = FALSE; 143 | purple_debug_info("campfire", 144 | "checking to see if user %s has left\n", 145 | buddy->name); 146 | for (users_iter = users; users_iter; users_iter = users_iter->next) { 147 | if (g_strcmp0(users_iter->data, buddy->name) == 0) { 148 | purple_debug_info("campfire", 149 | "user %s is still here\n", 150 | buddy->name); 151 | found = TRUE; 152 | break; 153 | } 154 | } 155 | 156 | if (!found) { 157 | purple_debug_info("campfire", 158 | "removing user %s that has left\n", 159 | buddy->name); 160 | remove = g_list_prepend(remove, 161 | g_strdup(buddy->name)); 162 | } 163 | } 164 | if (remove) { 165 | purple_conv_chat_remove_users(chat, remove, "left"); 166 | g_list_foreach(remove, message_g_free, NULL); 167 | g_list_free(remove); 168 | } 169 | g_list_foreach(users, message_g_free, NULL); 170 | g_list_free(users); 171 | } 172 | } 173 | 174 | static CampfireSslTransaction * 175 | campfire_new_xaction_copy(CampfireSslTransaction * original) 176 | { 177 | CampfireSslTransaction *xaction = g_new0(CampfireSslTransaction, 1); 178 | GList *msgs = NULL; 179 | CampfireMessage *msg = NULL; 180 | 181 | original->campfire->num_xaction_malloc++; /* valgrind investigation */ 182 | purple_debug_info("campfire", "%s: xaction: %p, campfire->num_xaction_malloc:%d\n", 183 | __FUNCTION__, xaction, original->campfire->num_xaction_malloc); 184 | xaction->campfire = original->campfire; 185 | xaction->response_cb_data = xaction; 186 | 187 | /*xaction->messages = original->messages;*/ /* can't do this safely */ 188 | msgs = original->messages; 189 | for (; msgs != NULL; msgs = msgs->next) { 190 | CampfireMessage *orig_msg = msgs->data; 191 | msg = g_new0(CampfireMessage, 1); 192 | 193 | msg->id = g_strdup(orig_msg->id); 194 | msg->type = g_strdup(orig_msg->type); 195 | msg->message = g_strdup(orig_msg->message); 196 | msg->time = orig_msg->time; 197 | msg->user_id = g_strdup(orig_msg->user_id); 198 | xaction->messages = g_list_append(xaction->messages, msg); 199 | } 200 | 201 | xaction->first_check = original->first_check; 202 | /* make a copy of the room id (if it's set) b/c it might 203 | * get free'd by someone if we just use the pointer 204 | */ 205 | if (original->room_id) 206 | xaction->room_id = g_strdup(original->room_id); 207 | 208 | return xaction; 209 | } 210 | 211 | static void 212 | campfire_message_callback(CampfireSslTransaction * xaction, 213 | G_GNUC_UNUSED PurpleSslConnection * gsc, 214 | G_GNUC_UNUSED PurpleInputCondition cond) 215 | { 216 | xmlnode *xmlmessage = NULL; 217 | GList *msgs = NULL; 218 | CampfireMessage *msg = NULL; 219 | CampfireSslTransaction *xaction2 = NULL; 220 | 221 | purple_debug_info("campfire", "%s\n", __FUNCTION__); 222 | 223 | xmlmessage = xmlnode_get_child(xaction->xml_response, "message"); 224 | 225 | while (xmlmessage != NULL) { 226 | msg = campfire_get_message(xmlmessage); 227 | if (msg) 228 | msgs = g_list_append(msgs, msg); 229 | 230 | xmlmessage = xmlnode_get_next_twin(xmlmessage); 231 | } 232 | 233 | xaction2 = campfire_new_xaction_copy(xaction); 234 | xaction2->response_cb = 235 | (PurpleSslInputFunction) campfire_message_handler_callback; 236 | xaction2->response_cb_data = xaction2; 237 | xaction2->messages = msgs; 238 | 239 | campfire_message_handler_callback(xaction2, xaction2->campfire->gsc, 240 | PURPLE_INPUT_READ); 241 | } 242 | 243 | static gboolean 244 | campfire_room_check(CampfireConn * campfire) 245 | { 246 | GList *rooms = g_hash_table_get_values(campfire->rooms); 247 | CampfireRoom *room = NULL; 248 | CampfireSslTransaction *xaction = NULL, *xaction2 = NULL; 249 | GString *uri = NULL; 250 | 251 | /* cancel the timer if we've left all rooms */ 252 | if (!rooms) { 253 | purple_debug_info("campfire", 254 | "not in any rooms, removing timer\n"); 255 | campfire->message_timer = 0; 256 | return FALSE; 257 | } 258 | 259 | for (; rooms != NULL; rooms = rooms->next) { 260 | room = rooms->data; 261 | 262 | /* first check the room users */ 263 | xaction = g_new0(CampfireSslTransaction, 1); 264 | campfire->num_xaction_malloc++; /* valgrind investigation */ 265 | purple_debug_info("campfire", "%s: xaction: %p, campfire->num_xaction_malloc:%d\n", 266 | __FUNCTION__, xaction, campfire->num_xaction_malloc); 267 | xaction->campfire = campfire; 268 | xaction->response_cb = 269 | (PurpleSslInputFunction) campfire_userlist_callback; 270 | xaction->response_cb_data = xaction; 271 | 272 | purple_debug_info("campfire", 273 | "checking for users in room: %s\n", room->id); 274 | 275 | uri = g_string_new("/room/"); 276 | g_string_append(uri, room->id); 277 | g_string_append(uri, ".xml"); 278 | 279 | campfire_http_request(xaction, uri->str, "GET", NULL); 280 | campfire_queue_xaction(campfire, xaction, 281 | PURPLE_INPUT_READ | PURPLE_INPUT_WRITE); 282 | g_string_free(uri, TRUE); 283 | 284 | /* then get recent messages 285 | * (only if there is nothing in the queue) */ 286 | if (room->last_message_id) { 287 | 288 | xaction2 = campfire_new_xaction_copy(xaction); 289 | xaction2->response_cb = 290 | (PurpleSslInputFunction) 291 | campfire_message_callback; 292 | xaction2->room_id = g_strdup(room->id); 293 | 294 | uri = g_string_new("/room/"); 295 | g_string_append(uri, room->id); 296 | g_string_append(uri, "/recent.xml?since_message_id="); 297 | g_string_append(uri, room->last_message_id); 298 | 299 | purple_debug_info("campfire", 300 | "getting latest messages: %s\n", 301 | uri->str); 302 | 303 | campfire_http_request(xaction2, uri->str, "GET", NULL); 304 | g_string_free(uri, TRUE); 305 | campfire_queue_xaction(campfire, xaction2, 306 | PURPLE_INPUT_READ | 307 | PURPLE_INPUT_WRITE); 308 | } 309 | } 310 | 311 | if (!campfire->message_timer) { 312 | /* call this function again periodically to check for new users */ 313 | campfire->message_timer = 314 | purple_timeout_add_seconds(3, 315 | (GSourceFunc) 316 | campfire_room_check, 317 | campfire); 318 | } 319 | 320 | g_list_free(rooms); 321 | return TRUE; 322 | } 323 | 324 | static void 325 | campfire_request_user(CampfireSslTransaction * xaction, CampfireMessage * msg) 326 | { 327 | CampfireSslTransaction *xaction2 = campfire_new_xaction_copy(xaction); 328 | GString *uri = g_string_new("/users/"); 329 | 330 | g_string_append(uri, msg->user_id); 331 | g_string_append(uri, ".xml"); 332 | 333 | xaction2->response_cb = 334 | (PurpleSslInputFunction) campfire_message_handler_callback; 335 | 336 | campfire_http_request(xaction2, uri->str, "GET", NULL); 337 | campfire_queue_xaction(xaction->campfire, xaction2, PURPLE_INPUT_READ); 338 | g_string_free(uri, TRUE); 339 | } 340 | 341 | static void 342 | campfire_request_upload(CampfireSslTransaction * xaction, CampfireMessage * msg) 343 | { 344 | CampfireSslTransaction *xaction2 = campfire_new_xaction_copy(xaction); 345 | GString *uri = g_string_new("/room/"); 346 | 347 | purple_debug_info("campfire", "Going to fetch upload\n"); 348 | 349 | g_string_append(uri, xaction->room_id); 350 | g_string_append(uri, "/messages/"); 351 | g_string_append(uri, msg->id); 352 | g_string_append(uri, "/upload.xml"); 353 | 354 | xaction2->response_cb = 355 | (PurpleSslInputFunction) campfire_message_handler_callback; 356 | 357 | campfire_http_request(xaction2, uri->str, "GET", NULL); 358 | campfire_queue_xaction(xaction->campfire, xaction2, PURPLE_INPUT_READ); 359 | } 360 | 361 | static void 362 | campfire_print_message(CampfireConn *campfire, CampfireRoom * room, CampfireMessage * msg, 363 | gchar * user_name, gchar * upload_url) 364 | { 365 | PurpleConversation *convo = purple_find_conversation_with_account 366 | (PURPLE_CONV_TYPE_ANY, room->name, 367 | purple_connection_get_account(campfire->gc)); 368 | GString *message = NULL; 369 | 370 | if (g_strcmp0(msg->type, CAMPFIRE_MESSAGE_TEXT) == 0 || 371 | g_strcmp0(msg->type, CAMPFIRE_MESSAGE_TWEET) == 0 || 372 | g_strcmp0(msg->type, CAMPFIRE_MESSAGE_PASTE) == 0) { 373 | purple_debug_info("campfire", 374 | "Writing chat message \"%s\" to %p from name %s\n", 375 | msg->message, convo, 376 | user_name); 377 | purple_conversation_write(convo, user_name, 378 | msg->message, 379 | PURPLE_MESSAGE_RECV, 380 | msg->time); 381 | } else { 382 | message = g_string_new(user_name); 383 | if (g_strcmp0(msg->type, CAMPFIRE_MESSAGE_ENTER) == 0) { 384 | g_string_append(message, 385 | " has entered the room."); 386 | } else if (g_strcmp0(msg->type, CAMPFIRE_MESSAGE_LEAVE) == 0) { 387 | g_string_append(message, 388 | " has left the room."); 389 | } else if (g_strcmp0(msg->type, CAMPFIRE_MESSAGE_KICK) == 0) { 390 | g_string_append(message, " kicked."); 391 | } else if (g_strcmp0(msg->type, CAMPFIRE_MESSAGE_GUESTALLOW) == 0) { 392 | g_string_append(message, 393 | " turned on guest access."); 394 | } else if (g_strcmp0(msg->type, CAMPFIRE_MESSAGE_GUESTDENY) == 0) { 395 | g_string_append(message, 396 | " turned off guest access."); 397 | } else if (g_strcmp0(msg->type, CAMPFIRE_MESSAGE_TOPIC) == 0) { 398 | g_string_append(message, 399 | " changed the room's topic to \""); 400 | g_string_append(message, msg->message); 401 | g_string_append(message, "\""); 402 | } else if (g_strcmp0(msg->type, CAMPFIRE_MESSAGE_UPLOAD) == 0 403 | && upload_url) { 404 | g_string_append(message, " uploaded "); 405 | g_string_append(message, upload_url); 406 | } else if (g_strcmp0(msg->type, CAMPFIRE_MESSAGE_SOUND) == 0) { 407 | g_string_append(message, 408 | " sounded off https://"); 409 | g_string_append(message, 410 | campfire->hostname); 411 | g_string_append(message, "/sounds/"); 412 | g_string_append(message, msg->message); 413 | g_string_append(message, ".mp3"); 414 | } 415 | purple_conversation_write(convo, "", 416 | message->str, 417 | PURPLE_MESSAGE_SYSTEM, 418 | msg->time); 419 | g_string_free(message, TRUE); 420 | } 421 | } 422 | 423 | static void 424 | campfire_message_handler(CampfireSslTransaction * xaction, 425 | CampfireMessage * msg, gchar * upload_url) 426 | { 427 | 428 | CampfireConn *campfire = NULL; 429 | gchar *user_name = NULL; 430 | gchar *msg_id = NULL; 431 | gboolean print = TRUE; 432 | CampfireRoom *room = NULL; 433 | GList *tmp_buf = NULL; 434 | 435 | 436 | purple_debug_info("campfire", "xaction: (%p)\n", xaction); 437 | campfire = xaction->campfire; 438 | purple_debug_info("campfire", "msg: (%p)\n", msg); 439 | purple_debug_info("campfire", "user_id: %s\n", msg->user_id); 440 | user_name = g_hash_table_lookup(campfire->users, msg->user_id); 441 | purple_debug_info("campfire", "Looked up user_id: %s, got %s\n", 442 | msg->user_id, user_name); 443 | 444 | if (!user_name) { 445 | campfire_request_user(xaction, msg); 446 | /* justin, this was your idea. I see 447 | * the need for it now. 448 | */ 449 | if (xaction->queued == FALSE) { 450 | campfire_xaction_free(xaction); 451 | } 452 | } else { 453 | purple_debug_info("campfire", "looking for room %s\n", 454 | xaction->room_id); 455 | room = g_hash_table_lookup(campfire->rooms, xaction->room_id); 456 | purple_debug_info("campfire", "got room %p ID: %s name: %s last message id: %s\n", room, room->id, room->name, room->last_message_id); 457 | 458 | purple_debug_info("campfire", 459 | "Writing message ID \"%s\" type \"%s\" from name \"%s\"\n", 460 | msg->id, msg->type, user_name); 461 | 462 | /* 463 | * In order to not print out your own messages twice, we'll 464 | * keep a buffer of printed messages and check against that. 465 | */ 466 | purple_debug_info("campfire", 467 | "Checking to see if message ID \"%s\" has been written\n", 468 | msg->id); 469 | 470 | tmp_buf = g_list_first(room->message_id_buffer); 471 | 472 | for (; tmp_buf != NULL; 473 | tmp_buf = tmp_buf->next) { 474 | msg_id = tmp_buf->data; 475 | purple_debug_info("campfire", 476 | "Looking at message ID %s from list\n", 477 | msg_id); 478 | 479 | if (g_strcmp0(msg_id, msg->id) == 0) { 480 | purple_debug_info("campfire", 481 | "Won't write message \"%s\" already written\n", 482 | msg_id); 483 | print = FALSE; 484 | break; 485 | } 486 | } 487 | 488 | if (print) { 489 | if (g_strcmp0(msg->type, CAMPFIRE_MESSAGE_UPLOAD) == 0 490 | && !upload_url) { 491 | campfire_request_upload(xaction, msg); 492 | /* return here so we can print this message out 493 | * again once we've retrieved the upload info 494 | */ 495 | return; 496 | } else { 497 | campfire_print_message(campfire, room, msg, user_name, upload_url); 498 | } 499 | /* remember the last message we've written */ 500 | room->last_message_id = g_strdup(msg->id); 501 | purple_debug_info("campfire", "Adding message ID %s to buffer\n", msg->id); 502 | room->message_id_buffer = g_list_append(room->message_id_buffer, g_strdup(msg->id)); 503 | } 504 | 505 | /* only keep 10 messages in the buffer */ 506 | while (g_list_length(room->message_id_buffer) > 10) { 507 | purple_debug_info("campfire", "Removing message ID from buffer\n"); 508 | room->message_id_buffer = g_list_delete_link( 509 | room->message_id_buffer, 510 | g_list_first(room->message_id_buffer)); 511 | } 512 | campfire_message_free(msg, NULL); 513 | xaction->messages = g_list_remove(xaction->messages, msg); 514 | xaction->xml_response = NULL; 515 | /* recurse */ 516 | campfire_message_handler_callback(xaction, campfire->gsc, 517 | PURPLE_INPUT_READ | 518 | PURPLE_INPUT_WRITE); 519 | } 520 | 521 | } 522 | 523 | void 524 | campfire_message_handler_callback(CampfireSslTransaction * xaction, 525 | G_GNUC_UNUSED PurpleSslConnection * gsc, 526 | G_GNUC_UNUSED PurpleInputCondition cond) 527 | { 528 | CampfireConn *campfire = xaction->campfire; 529 | GList *first = NULL; 530 | xmlnode *xmlusername = NULL, *xmluserid = NULL, *xmlurl = NULL; 531 | gchar *user_id = NULL, *username = NULL, *upload_url = NULL; 532 | 533 | purple_debug_info("campfire", "%s first_check:%s\n", 534 | __FUNCTION__, 535 | xaction->first_check ? "true" : "false"); 536 | 537 | /* initialize user list */ 538 | if (!campfire->users) { 539 | campfire->users = g_hash_table_new(g_str_hash, g_str_equal); 540 | } 541 | 542 | /* handle possible response(s) 543 | * (only if used as a callback) 544 | */ 545 | if (xaction->xml_response) { 546 | char *xml_debug = xmlnode_to_str(xaction->xml_response, NULL); 547 | purple_debug_info("campfire", "got xml %s\n", 548 | xml_debug); 549 | g_free(xml_debug); 550 | xmlurl = xmlnode_get_child(xaction->xml_response, "full-url"); 551 | if (xmlurl) { 552 | /* it's an upload response */ 553 | upload_url = xmlnode_get_data(xmlurl); 554 | purple_debug_info("campfire", "got upload URL %s\n", 555 | upload_url); 556 | } else { 557 | /* it's a user response */ 558 | xmlusername = 559 | xmlnode_get_child(xaction->xml_response, 560 | "name"); 561 | xmluserid = 562 | xmlnode_get_child(xaction->xml_response, "id"); 563 | 564 | user_id = xmlnode_get_data(xmluserid); 565 | username = xmlnode_get_data(xmlusername); 566 | purple_debug_info("campfire", 567 | "adding username %s ID %s\n", 568 | username, user_id); 569 | g_hash_table_replace(campfire->users, user_id, 570 | username); 571 | } 572 | } 573 | 574 | purple_debug_info("campfire", 575 | "xaction->messages: (%p)\n", 576 | xaction->messages); 577 | first = g_list_first(xaction->messages); 578 | purple_debug_info("campfire", 579 | "first message: (%p)\n", 580 | first); 581 | /* Do this while there are messages remaining in the GList */ 582 | if (first) { 583 | /* process the next message */ 584 | campfire_message_handler(xaction, first->data, upload_url); 585 | 586 | /* Do this after all messages have been processed */ 587 | } else { 588 | purple_debug_info("campfire", "no more messages to process\n"); 589 | purple_debug_info("campfire", "xaction: %p, xaction->queued: %d\n", 590 | xaction, xaction->queued); 591 | 592 | /* print the user as "in the room" after the previous messages 593 | * have been printed (only if this is the first message check) 594 | */ 595 | if (xaction->first_check) { 596 | campfire_room_check(campfire); 597 | } 598 | /* 'un-queued' cleanup: 599 | * NOTE: any transaction that is 'queued' using 600 | * 'campfire_queue_xaction()' will be cleaned up in 601 | * 'campfire_ssl_handler()'. However, there is a transaction 602 | * allocated in 603 | * 'campfire_message_send_callback()' 604 | * AND 605 | * 'campfire_message_callback()' 606 | * that are used but never 'queued', therefore it must be 607 | * cleaned up here. 608 | */ 609 | if (xaction->queued == FALSE) { 610 | campfire_xaction_free(xaction); 611 | } 612 | } 613 | } 614 | 615 | static void 616 | campfire_message_send_callback(CampfireSslTransaction * xaction, 617 | G_GNUC_UNUSED PurpleSslConnection * gsc, 618 | G_GNUC_UNUSED PurpleInputCondition cond) 619 | { 620 | CampfireMessage *msg = campfire_get_message(xaction->xml_response); 621 | GList *msgs = NULL; 622 | CampfireSslTransaction *xaction2 = NULL; 623 | 624 | /* pretty much the same as campfire_message_callback 625 | * but this one only processes one message 626 | */ 627 | purple_debug_info("campfire", "%s\n", __FUNCTION__); 628 | 629 | msgs = g_list_append(msgs, msg); 630 | 631 | xaction2 = campfire_new_xaction_copy(xaction); 632 | xaction2->response_cb = 633 | (PurpleSslInputFunction) campfire_message_handler_callback; 634 | xaction2->messages = msgs; 635 | 636 | campfire_message_handler_callback(xaction2, xaction->campfire->gsc, 637 | PURPLE_INPUT_READ); 638 | } 639 | 640 | void 641 | campfire_message_send(CampfireConn * campfire, int id, const char *message, 642 | char *msg_type) 643 | { 644 | gchar *room_id = g_strdup_printf("%i", id); 645 | xmlnode *xmlmessage = NULL, *xmlchild = NULL; 646 | gchar *debug_str; 647 | gchar *unescaped; 648 | CampfireSslTransaction *xaction = NULL; 649 | GString *uri = NULL; 650 | 651 | if (!msg_type) 652 | msg_type = CAMPFIRE_MESSAGE_TEXT; 653 | 654 | xmlmessage = xmlnode_new("message"); 655 | xmlnode_set_attrib(xmlmessage, "type", msg_type); 656 | xmlchild = xmlnode_new_child(xmlmessage, "body"); 657 | unescaped = purple_unescape_html(message); 658 | xmlnode_insert_data(xmlchild, unescaped, -1); 659 | g_free(unescaped); 660 | 661 | uri = g_string_new("/room/"); 662 | g_string_append(uri, room_id); 663 | g_string_append(uri, "/speak.xml"); 664 | 665 | xaction = g_new0(CampfireSslTransaction, 1); 666 | campfire->num_xaction_malloc++; /* valgrind investigation */ 667 | purple_debug_info("campfire", "%s: xaction: %p, campfire->num_xaction_malloc:%d\n", 668 | __FUNCTION__, xaction, campfire->num_xaction_malloc); 669 | xaction->campfire = campfire; 670 | xaction->response_cb = 671 | (PurpleSslInputFunction) campfire_message_send_callback; 672 | xaction->response_cb_data = xaction; 673 | xaction->room_id = g_strdup(room_id); 674 | 675 | debug_str = xmlnode_to_str(xmlmessage, NULL); 676 | purple_debug_info("campfire", "Sending message %s\n", 677 | debug_str); 678 | g_free(debug_str); 679 | 680 | campfire_http_request(xaction, uri->str, "POST", xmlmessage); 681 | g_string_free(uri, TRUE); 682 | g_free(room_id); 683 | xmlnode_free(xmlmessage); 684 | campfire_queue_xaction(campfire, xaction, 685 | PURPLE_INPUT_READ | PURPLE_INPUT_WRITE); 686 | } 687 | 688 | static void 689 | campfire_room_query_callback(CampfireSslTransaction * xaction, 690 | G_GNUC_UNUSED PurpleSslConnection * gsc, 691 | G_GNUC_UNUSED PurpleInputCondition cond) 692 | { 693 | xmlnode *xmlroom = NULL, *xmlname = NULL, *xmltopic = NULL, *xmlid = 694 | NULL; 695 | gchar *name = NULL, *topic = NULL, *id = NULL; 696 | PurpleRoomlistRoom *room = NULL; 697 | 698 | purple_debug_info("campfire", "processing xml...\n"); 699 | xmlroom = xmlnode_get_child(xaction->xml_response, "room"); 700 | while (xmlroom != NULL) { 701 | xmlname = xmlnode_get_child(xmlroom, "name"); 702 | name = xmlnode_get_data(xmlname); /* needs g_free */ 703 | xmltopic = xmlnode_get_child(xmlroom, "topic"); 704 | topic = xmlnode_get_data(xmltopic); /* needs g_free */ 705 | xmlid = xmlnode_get_child(xmlroom, "id"); 706 | id = xmlnode_get_data(xmlid); /* needs g_free */ 707 | 708 | room = purple_roomlist_room_new(PURPLE_ROOMLIST_ROOMTYPE_ROOM, 709 | name, NULL); 710 | purple_roomlist_room_add_field(xaction->campfire->roomlist, 711 | room, topic); 712 | purple_roomlist_room_add_field(xaction->campfire->roomlist, 713 | room, id); 714 | purple_roomlist_room_add(xaction->campfire->roomlist, room); 715 | xmlroom = xmlnode_get_next_twin(xmlroom); 716 | g_free(name); 717 | g_free(topic); 718 | g_free(id); 719 | } 720 | purple_roomlist_set_in_progress(xaction->campfire->roomlist, FALSE); 721 | if (xaction->campfire->needs_join) { 722 | campfire_join_chat_after_room_query(xaction->campfire, xaction->campfire->desired_room); 723 | } 724 | } 725 | 726 | void 727 | campfire_room_query(CampfireConn * campfire) 728 | { 729 | CampfireSslTransaction *xaction = g_new0(CampfireSslTransaction, 1); 730 | 731 | campfire->num_xaction_malloc++; /* valgrind investigation */ 732 | purple_debug_info("campfire", "%s: xaction: %p, campfire->num_xaction_malloc:%d\n", 733 | __FUNCTION__, xaction, campfire->num_xaction_malloc); 734 | xaction->campfire = campfire; 735 | xaction->response_cb = 736 | (PurpleSslInputFunction) (campfire_room_query_callback); 737 | xaction->response_cb_data = xaction; 738 | campfire_http_request(xaction, "/rooms.xml", "GET", NULL); 739 | campfire_queue_xaction(campfire, xaction, 740 | PURPLE_INPUT_READ | PURPLE_INPUT_WRITE); 741 | } 742 | 743 | 744 | static void 745 | campfire_room_update_callback(CampfireSslTransaction * xaction, 746 | G_GNUC_UNUSED PurpleSslConnection * gsc, 747 | G_GNUC_UNUSED PurpleInputCondition cond) 748 | { 749 | campfire_room_check(xaction->campfire); 750 | } 751 | 752 | static void 753 | campfire_room_update(CampfireConn * campfire, gint id, gchar * topic, 754 | gchar * room_name) 755 | { 756 | gchar *room_id = g_strdup_printf("%i", id); 757 | xmlnode *xmlroom = NULL, *xmltopic = NULL, *xmlname = NULL; 758 | CampfireSslTransaction *xaction = NULL; 759 | GString *uri = NULL; 760 | 761 | xmlroom = xmlnode_new("room"); 762 | if (topic) { 763 | xmltopic = xmlnode_new_child(xmlroom, "topic"); 764 | xmlnode_insert_data(xmltopic, topic, -1); 765 | } 766 | 767 | if (room_name) { 768 | xmlname = xmlnode_new_child(xmlroom, "name"); 769 | xmlnode_insert_data(xmlname, room_name, -1); 770 | } 771 | 772 | uri = g_string_new("/room/"); 773 | g_string_append(uri, room_id); 774 | g_string_append(uri, ".xml"); 775 | 776 | xaction = g_new0(CampfireSslTransaction, 1); 777 | campfire->num_xaction_malloc++; /* valgrind investigation */ 778 | purple_debug_info("campfire", "%s: xaction: %p, campfire->num_xaction_malloc:%d\n", 779 | __FUNCTION__, xaction, campfire->num_xaction_malloc); 780 | xaction->campfire = campfire; 781 | xaction->response_cb = 782 | (PurpleSslInputFunction) campfire_room_update_callback; 783 | xaction->response_cb_data = xaction; 784 | xaction->room_id = g_strdup(room_id); 785 | 786 | purple_debug_info("campfire", "Sending message %s\n", 787 | xmlnode_to_str(xmlroom, NULL)); 788 | 789 | campfire_http_request(xaction, uri->str, "PUT", xmlroom); 790 | g_string_free(uri, TRUE); 791 | g_free(room_id); 792 | xmlnode_free(xmlroom); 793 | campfire_queue_xaction(campfire, xaction, 794 | PURPLE_INPUT_READ | PURPLE_INPUT_WRITE); 795 | } 796 | 797 | PurpleCmdRet 798 | campfire_parse_cmd(PurpleConversation * conv, const gchar * cmd, 799 | gchar ** args, G_GNUC_UNUSED gchar ** error, void *data) 800 | { 801 | PurpleConnection *gc = purple_conversation_get_gc(conv); 802 | PurpleConvChat *chat = PURPLE_CONV_CHAT(conv); 803 | GString *message = NULL; 804 | 805 | if (!gc) 806 | return PURPLE_CMD_RET_FAILED; 807 | 808 | purple_debug_info("campfire", "cmd %s: args[0]: %s\n", cmd, args[0]); 809 | 810 | if (g_strcmp0(cmd, CAMPFIRE_CMD_ME) == 0) { 811 | /* send a message */ 812 | message = g_string_new("*"); 813 | g_string_append(message, args[0]); 814 | g_string_append(message, "*"); 815 | 816 | campfire_message_send(data, chat->id, message->str, 817 | CAMPFIRE_MESSAGE_TEXT); 818 | } else if (g_strcmp0(cmd, CAMPFIRE_CMD_PLAY) == 0) { 819 | /* send a message */ 820 | campfire_message_send(data, chat->id, args[0], 821 | CAMPFIRE_MESSAGE_SOUND); 822 | } else if (g_strcmp0(cmd, CAMPFIRE_CMD_TOPIC) == 0) { 823 | /* do a room request */ 824 | if (args[0]) 825 | campfire_room_update(data, chat->id, args[0], NULL); 826 | else 827 | campfire_room_update(data, chat->id, "", NULL); 828 | } else if (g_strcmp0(cmd, CAMPFIRE_CMD_ROOM) == 0) { 829 | /* do a room request */ 830 | campfire_room_update(data, chat->id, NULL, args[0]); 831 | } 832 | 833 | return PURPLE_CMD_RET_OK; 834 | } 835 | 836 | static void 837 | campfire_fetch_first_messages(CampfireConn * campfire, gchar * room_id) 838 | { 839 | CampfireSslTransaction *xaction = g_new0(CampfireSslTransaction, 1); 840 | gint limit = purple_account_get_int(campfire->account, "limit", 10); 841 | gchar *limit_str; 842 | GString *uri = NULL; 843 | 844 | campfire->num_xaction_malloc++; /* valgrind investigation */ 845 | purple_debug_info("campfire", "%s: xaction: %p, campfire->num_xaction_malloc:%d\n", 846 | __FUNCTION__, xaction, campfire->num_xaction_malloc); 847 | purple_debug_info("campfire", "%s\n", __FUNCTION__); 848 | 849 | uri = g_string_new("/room/"); 850 | g_string_append(uri, room_id); 851 | g_string_append(uri, "/recent.xml?limit="); 852 | limit_str = g_strdup_printf("%i", limit); 853 | g_string_append(uri, limit_str); 854 | g_free(limit_str); 855 | 856 | 857 | xaction->campfire = campfire; 858 | xaction->response_cb = 859 | (PurpleSslInputFunction) campfire_message_callback; 860 | xaction->response_cb_data = xaction; 861 | xaction->room_id = g_strdup(room_id); 862 | xaction->first_check = TRUE; 863 | 864 | campfire_http_request(xaction, uri->str, "GET", NULL); 865 | g_string_free(uri, TRUE); 866 | campfire_queue_xaction(campfire, xaction, 867 | PURPLE_INPUT_READ | PURPLE_INPUT_WRITE); 868 | } 869 | 870 | static gboolean 871 | hide_buddy_join_cb(G_GNUC_UNUSED PurpleConversation *conv, 872 | G_GNUC_UNUSED const char *name, 873 | G_GNUC_UNUSED PurpleConvChatBuddyFlags flags, 874 | G_GNUC_UNUSED void *data) 875 | { 876 | return TRUE; 877 | } 878 | 879 | static gboolean 880 | hide_buddy_leave_cb(G_GNUC_UNUSED PurpleConversation *conv, 881 | G_GNUC_UNUSED const char *name, 882 | G_GNUC_UNUSED const char *reason, 883 | G_GNUC_UNUSED void *data) 884 | { 885 | return TRUE; 886 | } 887 | 888 | static void 889 | campfire_room_join_callback(CampfireSslTransaction * xaction, 890 | G_GNUC_UNUSED PurpleSslConnection * gsc, 891 | G_GNUC_UNUSED PurpleInputCondition cond) 892 | { 893 | void *conv_handle = purple_conversations_get_handle(); 894 | CampfireRoom *room = 895 | g_hash_table_lookup(xaction->campfire->rooms, xaction->room_id); 896 | 897 | purple_debug_info("campfire", "joining room: %s with id: %s\n", 898 | room->name, room->id); 899 | serv_got_joined_chat(xaction->campfire->gc, 900 | g_ascii_strtoll(xaction->room_id, NULL, 10), 901 | room->name); 902 | 903 | /* hide pidgin's join/part messages (we'll do them ourselves) */ 904 | purple_signal_connect(conv_handle, "chat-buddy-joining", xaction->campfire, 905 | PURPLE_CALLBACK(hide_buddy_join_cb), NULL); 906 | purple_signal_connect(conv_handle, "chat-buddy-leaving", xaction->campfire, 907 | PURPLE_CALLBACK(hide_buddy_leave_cb), NULL); 908 | 909 | campfire_fetch_first_messages(xaction->campfire, xaction->room_id); 910 | } 911 | 912 | void 913 | campfire_room_join(CampfireConn * campfire, gchar * id, gchar * name) 914 | { 915 | CampfireRoom *room = NULL; 916 | CampfireSslTransaction *xaction = g_new0(CampfireSslTransaction, 1); 917 | GString *uri; 918 | 919 | if (!campfire->rooms) { 920 | campfire->rooms = g_hash_table_new(g_str_hash, g_str_equal); 921 | } else { 922 | room = g_hash_table_lookup(campfire->rooms, id); 923 | if ( room ) { 924 | //already joined 925 | purple_debug_info("campfire", "already in room: %s with id: %s\n", name, id); 926 | purple_notify_message(campfire->gc, PURPLE_NOTIFY_MSG_ERROR, 927 | "campfire error", 928 | "already in this room.", 929 | NULL, 930 | NULL, 931 | NULL); 932 | return; 933 | } 934 | } 935 | 936 | uri = g_string_new("/room/"); 937 | 938 | campfire->num_xaction_malloc++; /* valgrind investigation */ 939 | purple_debug_info("campfire", "%s: xaction: %p, campfire->num_xaction_malloc:%d\n", 940 | __FUNCTION__, xaction, campfire->num_xaction_malloc); 941 | g_string_append(uri, id); 942 | g_string_append(uri, "/join.xml"); 943 | 944 | purple_debug_info("campfire", "add room to list %s ID: %s\n", name, id); 945 | room = g_new0(CampfireRoom, 1); 946 | room->name = g_strdup(name); 947 | room->id = g_strdup(id); 948 | room->last_message_id = NULL; 949 | g_hash_table_replace(campfire->rooms, id, room); 950 | 951 | xaction->campfire = campfire; 952 | xaction->response_cb = 953 | (PurpleSslInputFunction) campfire_room_join_callback; 954 | xaction->response_cb_data = xaction; 955 | xaction->room_id = g_strdup(id); 956 | purple_debug_info("campfire", "ID: %s\n", xaction->room_id); 957 | 958 | campfire_http_request(xaction, uri->str, "POST", NULL); 959 | g_string_free(uri, TRUE); 960 | campfire_queue_xaction(campfire, xaction, 961 | PURPLE_INPUT_READ | PURPLE_INPUT_WRITE); 962 | } 963 | 964 | static void 965 | campfire_room_leave_callback(CampfireSslTransaction * xaction, 966 | G_GNUC_UNUSED PurpleSslConnection * gsc, 967 | G_GNUC_UNUSED PurpleInputCondition cond) 968 | { 969 | gboolean left; 970 | 971 | CampfireRoom *room = 972 | g_hash_table_lookup(xaction->campfire->rooms, xaction->room_id); 973 | purple_debug_info("campfire", "leaving room: %s\n", room->name); 974 | serv_got_chat_left(xaction->campfire->gc, 975 | g_ascii_strtoll(xaction->room_id, NULL, 10)); 976 | left = g_hash_table_remove(xaction->campfire->rooms, xaction->room_id); 977 | purple_debug_info("campfire", "left room: %s\n", 978 | left ? "true" : "false"); 979 | } 980 | 981 | void 982 | campfire_room_leave(CampfireConn * campfire, gint id) 983 | { 984 | CampfireSslTransaction *xaction = g_new0(CampfireSslTransaction, 1); 985 | GString *uri = NULL; 986 | 987 | campfire->num_xaction_malloc++; /* valgrind investigation */ 988 | purple_debug_info("campfire", "%s: xaction: %p, campfire->num_xaction_malloc:%d\n", 989 | __FUNCTION__, xaction, campfire->num_xaction_malloc); 990 | xaction->campfire = campfire; 991 | xaction->response_cb = 992 | (PurpleSslInputFunction) campfire_room_leave_callback; 993 | xaction->response_cb_data = xaction; 994 | xaction->room_id = g_strdup_printf("%d", id); 995 | 996 | uri = g_string_new("/room/"); 997 | g_string_append(uri, xaction->room_id); 998 | g_string_append(uri, "/leave.xml"); 999 | 1000 | campfire_http_request(xaction, uri->str, "POST", NULL); 1001 | g_string_free(uri, TRUE); 1002 | campfire_queue_xaction(campfire, xaction, 1003 | PURPLE_INPUT_READ | PURPLE_INPUT_WRITE); 1004 | } 1005 | --------------------------------------------------------------------------------