123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177 |
- /*
- * GroupMe plugin for libpurple
- * Copyright (C) 2017-2018 Alyssa Rosenzweig
- * Copyright (C) 2016 Eion Robb
- *
- * 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 3 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, see <https://www.gnu.org/licenses/>.
- */
- #include <stdio.h>
- #include <stdlib.h>
- #include <string.h>
- #ifdef __GNUC__
- #include <unistd.h>
- #endif
- #include <errno.h>
- #ifdef ENABLE_NLS
- # define GETTEXT_PACKAGE "purple-groupme"
- # include <glib/gi18n-lib.h>
- # ifdef _WIN32
- # ifdef LOCALEDIR
- # unset LOCALEDIR
- # endif
- # define LOCALEDIR wpurple_locale_dir()
- # endif
- #else
- # define _(a) (a)
- # define N_(a) (a)
- #endif
- #include "glib_compat.h"
- #include "json_compat.h"
- #include "purple_compat.h"
- #define GROUPME_PLUGIN_ID "prpl-alyssarosenzweig-groupme"
- #ifndef GROUPME_PLUGIN_VERSION
- #define GROUPME_PLUGIN_VERSION "0.1"
- #endif
- #define GROUPME_PLUGIN_WEBSITE "https://notabug.com/alyssa/groupme-purple"
- #define GROUPME_USERAGENT "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.103 Safari/537.36"
- #define GROUPME_API_SERVER "api.groupme.com/v3"
- #define GROUPME_PUSH_SERVER "push.groupme.com/faye?"
- #define GROUPME_GATEWAY_SERVER "push.groupme.com"
- #define GROUPME_GATEWAY_PORT 443
- #define GROUPME_GATEWAY_SERVER_PATH "/faye"
- /* TODO: Websockets */
- #define USE_LONG_POLL
- #ifdef USE_LONG_POLL
- #define GROUPME_PUSH_TYPE "long-polling"
- #else
- #define GROUPME_PUSH_TYPE "websocket"
- #endif
- #define IGNORE_PRINTS
- typedef struct {
- guint64 id;
- gchar *name;
- gchar *icon;
- guint64 owner;
- GArray *members; /* list of member ids */
- GHashTable *nicknames; /* id->nick? */
- GHashTable *nicknames_rev; /* reverse */
- } GroupMeGuild;
- typedef struct {
- guint64 id;
- gchar *nick;
- gboolean is_op;
- } GroupMeGuildMembership;
- typedef struct {
- guint64 id;
- gchar *id_s;
- gchar *name;
- gchar *avatar;
- GHashTable *guild_memberships;
- gboolean bot;
- } GroupMeUser;
- typedef struct {
- PurpleAccount *account;
- PurpleConnection *pc;
- GHashTable *cookie_table;
- gchar *session_token;
- gchar *channel;
- guint64 self_user_id;
- gchar *self_username;
- guint64 last_message_id;
- gint64 last_load_last_message_id;
- gchar *token;
- gchar *session_id;
- gchar *mfa_ticket;
- PurpleSslConnection *websocket;
- gboolean websocket_header_received;
- gboolean sync_complete;
- guchar packet_code;
- gchar *frame;
- guint64 frame_len;
- guint64 frame_len_progress;
- gint64 seq; /* incrementing counter */
- guint heartbeat_timeout;
- GHashTable *one_to_ones; /* A store of known room_id's -> username's */
- GHashTable *one_to_ones_rev; /* A store of known usernames's -> room_id's */
- GHashTable *last_message_id_dm; /* A store of known room_id's -> last_message_id's */
- GHashTable *sent_message_ids; /* A store of message id's that we generated from this instance */
- GHashTable *result_callbacks; /* Result ID -> Callback function */
- GQueue *received_message_queue; /* A store of the last 10 received message id's for de-dup */
- GHashTable *new_users;
- GHashTable *new_guilds;
- GSList *http_conns; /**< PurpleHttpConnection to be cancelled on logout */
- gint frames_since_reconnect;
- GSList *pending_writes;
- gint roomlist_guild_count;
- gchar *client_id;
- int push_id;
- } GroupMeAccount;
- typedef struct {
- GroupMeAccount *account;
- GroupMeGuild *guild;
- } GroupMeAccountGuild;
- static guint64
- to_int(const gchar *id)
- {
- return id ? g_ascii_strtoull(id, NULL, 10) : 0;
- }
- static gchar *
- from_int(guint64 id)
- {
- return g_strdup_printf("%" G_GUINT64_FORMAT, id);
- }
- /** libpurple requires unique chat id's per conversation.
- we use a hash function to convert the 64bit conversation id
- into a platform-dependent chat id (worst case 32bit).
- previously we used g_int64_hash() from glib,
- however libpurple requires positive integers */
- static gint
- groupme_chat_hash(guint64 chat_id)
- {
- return ABS((gint) chat_id);
- }
- static void groupme_free_guild_membership(gpointer data);
- /* creating */
- static GroupMeUser *
- groupme_new_user(JsonObject *json)
- {
- GroupMeUser *user = g_new0(GroupMeUser, 1);
- user->id_s = json_object_get_string_member(json, "user_id");
- if (!user->id_s)
- user->id_s = json_object_get_string_member(json, "id");
- user->id = to_int(user->id_s);
- user->name = json_object_get_string_member(json, "nickname");
- if (!user->name)
- user->name = json_object_get_string_member(json, "name");
- user->avatar = g_strdup(json_object_get_string_member(json, "image_url"));
- user->name = g_strdup(user->name);
- user->id_s = g_strdup(user->id_s);
- user->guild_memberships = g_hash_table_new_full(g_int64_hash, g_int64_equal, NULL, groupme_free_guild_membership);
- return user;
- }
- static GroupMeGuild *
- groupme_new_guild(JsonObject *json)
- {
- GroupMeGuild *guild = g_new0(GroupMeGuild, 1);
- guild->id = to_int(json_object_get_string_member(json, "id"));
- guild->name = g_strdup(json_object_get_string_member(json, "name"));
- guild->icon = g_strdup(json_object_get_string_member(json, "image_url"));
- guild->members = g_array_new(TRUE, TRUE, sizeof(guint64));
- guild->nicknames = g_hash_table_new_full(g_int64_hash, g_int64_equal, NULL, g_free);
- guild->nicknames_rev = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
- return guild;
- }
- static GroupMeGuildMembership *
- groupme_new_guild_membership(guint64 id, JsonObject *json)
- {
- GroupMeGuildMembership *guild_membership = g_new0(GroupMeGuildMembership, 1);
- guild_membership->id = id;
- /* Search for op roles */
- JsonArray *roles = json_object_get_array_member(json, "roles");
- gint i, len = json_array_get_length(roles);
- guild_membership->is_op = FALSE;
- for (i = len - 1; i >= 0; i--) {
- const gchar *role = json_array_get_string_element(roles, i);
- if ((g_strcmp0(role, "admin") == 0) || (g_strcmp0(role, "op") == 0)) {
- guild_membership->is_op = TRUE;
- break;
- }
- }
- return guild_membership;
- }
- /* freeing */
- static void
- groupme_free_guild_membership(gpointer data)
- {
- GroupMeGuildMembership *guild_membership = data;
- g_free(guild_membership->nick);
- g_free(guild_membership);
- }
- static void
- groupme_free_user(gpointer data)
- {
- GroupMeUser *user = data;
- g_free(user->name);
- g_free(user->avatar);
- g_hash_table_unref(user->guild_memberships);
- g_free(user);
- }
- static void
- groupme_free_guild(gpointer data)
- {
- GroupMeGuild *guild = data;
- g_free(guild->name);
- g_free(guild->icon);
- g_array_unref(guild->members);
- g_hash_table_unref(guild->nicknames);
- g_hash_table_unref(guild->nicknames_rev);
- g_free(guild);
- }
- static void groupme_start_socket(GroupMeAccount *ya);
- static void
- groupme_got_subscription(GroupMeAccount *da, JsonNode *node, gpointer user_data)
- {
- /* We're good to go */
- groupme_start_socket(da);
- }
- typedef void (*GroupMeProxyCallbackFunc)(GroupMeAccount *ya, JsonNode *node, gpointer user_data);
- typedef struct {
- GroupMeAccount *ya;
- GroupMeProxyCallbackFunc callback;
- gpointer user_data;
- } GroupMeProxyConnection;
- static void groupme_fetch_url(GroupMeAccount *da, const gchar *url, const gchar *postdata, GroupMeProxyCallbackFunc callback, gpointer user_data);
- static void
- groupme_got_handshake(GroupMeAccount *da, JsonNode *node, gpointer user_data)
- {
- if (node != NULL) {
- JsonArray *responseA = json_node_get_array(node);
- JsonObject *response = json_array_get_object_element(responseA, 0);
-
- if (json_object_has_member(response, "successful")) {
- const gchar *clientId = json_object_get_string_member(response, "clientId");
- da->client_id = g_strdup(clientId);
- /* Subscribe now */
- const gchar *str = g_strdup_printf(
- "{\"channel\": \"/meta/subscribe\", \"clientId\": \"%s\", \"subscription\": \"/user/%" G_GUINT64_FORMAT "\", \"ext\": {\"timestamp\": %" G_GUINT64_FORMAT ", \"access_token\": \"%s\"}, \"id\": 2}",
- clientId,
- da->self_user_id,
- time(NULL),
- da->token);
- da->push_id = 3;
- groupme_fetch_url(da, "https://" GROUPME_PUSH_SERVER, str, groupme_got_subscription, NULL);
- }
- }
- }
- static GroupMeUser *
- groupme_get_user_name(GroupMeAccount *da, int discriminator, gchar *name)
- {
- GHashTableIter iter;
- gpointer key, value;
- g_hash_table_iter_init(&iter, da->new_users);
- while (g_hash_table_iter_next(&iter, &key, &value)) {
- GroupMeUser *user = value;
- if (/*user->discriminator == discriminator && */purple_strequal(user->name, name)) {
- return value;
- }
- }
- return NULL;
- }
- static GroupMeUser *
- groupme_get_user_fullname(GroupMeAccount *da, const gchar *name)
- {
- g_return_val_if_fail(name && *name, NULL);
-
- gchar **split_name = g_strsplit(name, "#", 2);
- GroupMeUser *user = NULL;
-
- if (split_name != NULL) {
- if (split_name[0] && split_name[1]) {
- user = groupme_get_user_name(da, to_int(split_name[1]), split_name[0]);
- }
-
- g_strfreev(split_name);
- }
-
- return user;
- }
- static GroupMeUser *
- groupme_get_user(GroupMeAccount *da, guint64 id)
- {
- return g_hash_table_lookup_int64(da->new_users, id);
- }
- static GroupMeUser *
- groupme_upsert_user(GHashTable *user_table, JsonObject *json)
- {
- const gchar *suid = json_object_get_string_member(json, "user_id");
- if (!suid)
- suid = json_object_get_string_member(json, "id");
- guint64 *key = NULL, user_id = to_int(suid);
- GroupMeUser *user = NULL;
- if (g_hash_table_lookup_extended_int64(user_table, user_id, (gpointer) &key, (gpointer) &user)) {
- return user;
- } else {
- user = groupme_new_user(json);
- g_hash_table_replace_int64(user_table, user->id, user);
- return user;
- }
- }
- static gchar *
- groupme_alloc_nickname(GroupMeUser *user, GroupMeGuild *guild, const gchar *suggested_nick)
- {
- const gchar *base_nick = suggested_nick ? suggested_nick : user->name;
- gchar *nick = NULL;
- if (base_nick == NULL) {
- return NULL;
- }
- guint64 *existing = g_hash_table_lookup(guild->nicknames_rev, base_nick);
-
- if (existing && *existing != user->id) {
- /* Ambiguous; try with the real name */
- nick = g_strdup_printf("%s (%s)", base_nick, user->name);
- existing = g_hash_table_lookup(guild->nicknames_rev, nick);
- if (existing && *existing != user->id) {
- /* Ambiguous; use the UUID */
- g_free(nick);
- nick = g_strdup_printf("%s (%" G_GUINT64_FORMAT ")", base_nick, user->id);
- }
- }
-
- if (!nick) {
- nick = g_strdup(base_nick);
- }
- g_hash_table_replace_int64(guild->nicknames, user->id, g_strdup(nick));
- g_hash_table_replace(guild->nicknames_rev, g_strdup(nick), g_memdup(&user->id, sizeof(user->id)));
- return nick;
- }
- static GroupMeGuild *
- groupme_get_guild(GroupMeAccount *da, guint64 id)
- {
- return g_hash_table_lookup_int64(da->new_guilds, id);
- }
- static GroupMeGuild *
- groupme_upsert_guild(GHashTable *guild_table, JsonObject *json)
- {
- guint64 *key = NULL, guild_id = to_int(json_object_get_string_member(json, "id"));
- GroupMeGuild *guild = NULL;
- if (g_hash_table_lookup_extended_int64(guild_table, guild_id, (gpointer) &key, (gpointer) &guild)) {
- return guild;
- } else {
- guild = groupme_new_guild(json);
- g_hash_table_replace_int64(guild_table, guild->id, guild);
- return guild;
- }
- }
- PurpleChatUserFlags
- groupme_get_user_flags(GroupMeAccount *da, GroupMeGuild *guild, GroupMeUser *user)
- {
- if (user == NULL) {
- return PURPLE_CHAT_USER_NONE;
- }
- guint64 gid = guild->id;
- GroupMeGuildMembership *guild_membership = g_hash_table_lookup_int64(user->guild_memberships, gid);
- PurpleChatUserFlags best_flag = user->bot ? PURPLE_CHAT_USER_VOICE : PURPLE_CHAT_USER_NONE;
- if (guild_membership == NULL)
- return best_flag;
- if (guild_membership->is_op)
- return PURPLE_CHAT_USER_OP;
- return best_flag;
- }
- #if PURPLE_VERSION_CHECK(3, 0, 0)
- static void
- groupme_update_cookies(GroupMeAccount *ya, const GList *cookie_headers)
- {
- const gchar *cookie_start;
- const gchar *cookie_end;
- gchar *cookie_name;
- gchar *cookie_value;
- const GList *cur;
- for (cur = cookie_headers; cur != NULL; cur = g_list_next(cur)) {
- cookie_start = cur->data;
- cookie_end = strchr(cookie_start, '=');
- if (cookie_end != NULL) {
- cookie_name = g_strndup(cookie_start, cookie_end - cookie_start);
- cookie_start = cookie_end + 1;
- cookie_end = strchr(cookie_start, ';');
- if (cookie_end != NULL) {
- cookie_value = g_strndup(cookie_start, cookie_end - cookie_start);
- cookie_start = cookie_end;
- g_hash_table_replace(ya->cookie_table, cookie_name, cookie_value);
- }
- }
- }
- }
- #else
- static void
- groupme_update_cookies(GroupMeAccount *ya, const gchar *headers)
- {
- const gchar *cookie_start;
- const gchar *cookie_end;
- gchar *cookie_name;
- gchar *cookie_value;
- int header_len;
- g_return_if_fail(headers != NULL);
- header_len = strlen(headers);
- /* look for the next "Set-Cookie: " */
- /* grab the data up until ';' */
- cookie_start = headers;
- while ((cookie_start = strstr(cookie_start, "\r\nSet-Cookie: ")) && (cookie_start - headers) < header_len) {
- cookie_start += 14;
- cookie_end = strchr(cookie_start, '=');
- if (cookie_end != NULL) {
- cookie_name = g_strndup(cookie_start, cookie_end - cookie_start);
- cookie_start = cookie_end + 1;
- cookie_end = strchr(cookie_start, ';');
- if (cookie_end != NULL) {
- cookie_value = g_strndup(cookie_start, cookie_end - cookie_start);
- cookie_start = cookie_end;
- g_hash_table_replace(ya->cookie_table, cookie_name, cookie_value);
- }
- }
- }
- }
- #endif
- static void
- groupme_cookie_foreach_cb(gchar *cookie_name, gchar *cookie_value, GString *str)
- {
- g_string_append_printf(str, "%s=%s;", cookie_name, cookie_value);
- }
- static gchar *
- groupme_cookies_to_string(GroupMeAccount *ya)
- {
- GString *str;
- str = g_string_new(NULL);
- g_hash_table_foreach(ya->cookie_table, (GHFunc) groupme_cookie_foreach_cb, str);
- return g_string_free(str, FALSE);
- }
- static void
- groupme_response_callback(PurpleHttpConnection *http_conn,
- #if PURPLE_VERSION_CHECK(3, 0, 0)
- PurpleHttpResponse *response, gpointer user_data)
- {
- gsize len;
- const gchar *url_text = purple_http_response_get_data(response, &len);
- const gchar *error_message = purple_http_response_get_error(response);
- #else
- gpointer user_data, const gchar *url_text, gsize len, const gchar *error_message)
- {
- #endif
- const gchar *body;
- gsize body_len;
- GroupMeProxyConnection *conn = user_data;
- JsonParser *parser = json_parser_new();
- conn->ya->http_conns = g_slist_remove(conn->ya->http_conns, http_conn);
- #if !PURPLE_VERSION_CHECK(3, 0, 0)
- groupme_update_cookies(conn->ya, url_text);
- body = g_strstr_len(url_text, len, "\r\n\r\n");
- body = body ? body + 4 : body;
- body_len = len - (body - url_text);
- #else
- groupme_update_cookies(conn->ya, purple_http_response_get_headers_by_name(response, "Set-Cookie"));
- body = url_text;
- body_len = len;
- #endif
- if (body == NULL && error_message != NULL) {
- /* connection error - unersolvable dns name, non existing server */
- gchar *error_msg_formatted = g_strdup_printf(_("Connection error: %s."), error_message);
- purple_connection_error(conn->ya->pc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, error_msg_formatted);
- g_free(error_msg_formatted);
- g_free(conn);
- return;
- }
- if (body != NULL && !json_parser_load_from_data(parser, body, body_len, NULL)) {
- if (conn->callback) {
- JsonNode *dummy_node = json_node_new(JSON_NODE_OBJECT);
- JsonObject *dummy_object = json_object_new();
- json_node_set_object(dummy_node, dummy_object);
- json_object_set_string_member(dummy_object, "body", body);
- json_object_set_int_member(dummy_object, "len", body_len);
- g_dataset_set_data(dummy_node, "raw_body", (gpointer) body);
- conn->callback(conn->ya, dummy_node, conn->user_data);
- g_dataset_destroy(dummy_node);
- json_node_free(dummy_node);
- json_object_unref(dummy_object);
- }
- } else {
- JsonNode *root = json_parser_get_root(parser);
- purple_debug_misc("groupme", "Got response: %s\n", body);
- if (conn->callback) {
- conn->callback(conn->ya, root, conn->user_data);
- }
- }
- g_object_unref(parser);
- g_free(conn);
- }
- static void
- groupme_fetch_url_with_method(GroupMeAccount *ya, const gchar *method, const gchar *_url, const gchar *postdata, GroupMeProxyCallbackFunc callback, gpointer user_data)
- {
- PurpleAccount *account;
- GroupMeProxyConnection *conn;
- gchar *cookies;
- PurpleHttpConnection *http_conn;
- account = ya->account;
- if (purple_account_is_disconnected(account)) {
- return;
- }
- conn = g_new0(GroupMeProxyConnection, 1);
- conn->ya = ya;
- conn->callback = callback;
- conn->user_data = user_data;
- cookies = groupme_cookies_to_string(ya);
- if (method == NULL) {
- method = "GET";
- }
- /* Attach token to requests */
- gchar *url = g_strdup(_url);
-
- if (ya->token) {
- g_free(url);
- url = g_strdup_printf("%s&token=%s", _url, ya->token);
- }
- purple_debug_info("groupme", "Fetching url %s\n", url);
- #if PURPLE_VERSION_CHECK(3, 0, 0)
- PurpleHttpRequest *request = purple_http_request_new(url);
- purple_http_request_set_method(request, method);
- purple_http_request_header_set(request, "Accept", "*/*");
- purple_http_request_header_set(request, "User-Agent", GROUPME_USERAGENT);
- purple_http_request_header_set(request, "Cookie", cookies);
-
- if (postdata) {
- if (strstr(url, "/login") && strstr(postdata, "password")) {
- purple_debug_info("groupme", "With postdata ###PASSWORD REMOVED###\n");
- } else {
- purple_debug_info("groupme", "With postdata %s\n", postdata);
- }
- if (postdata[0] == '{') {
- purple_http_request_header_set(request, "Content-Type", "application/json");
- } else {
- purple_http_request_header_set(request, "Content-Type", "application/x-www-form-urlencoded");
- }
- purple_http_request_set_contents(request, postdata, -1);
- }
- http_conn = purple_http_request(ya->pc, request, groupme_response_callback, conn);
- purple_http_request_unref(request);
- if (http_conn != NULL) {
- ya->http_conns = g_slist_prepend(ya->http_conns, http_conn);
- }
- #else
- GString *headers;
- gchar *host = NULL, *path = NULL, *user = NULL, *password = NULL;
- int port;
- purple_url_parse(url, &host, &port, &path, &user, &password);
- headers = g_string_new(NULL);
- /* Use the full 'url' until libpurple can handle path's longer than 256 chars */
- g_string_append_printf(headers, "%s /%s HTTP/1.0\r\n", method, path);
- g_string_append_printf(headers, "Connection: close\r\n");
- g_string_append_printf(headers, "Host: %s\r\n", host);
- g_string_append_printf(headers, "Accept: */*\r\n");
- g_string_append_printf(headers, "User-Agent: " GROUPME_USERAGENT "\r\n");
- g_string_append_printf(headers, "Cookie: %s\r\n", cookies);
- if (postdata) {
- if (strstr(url, "/login") && strstr(postdata, "password")) {
- purple_debug_info("groupme", "With postdata ###PASSWORD REMOVED###\n");
- } else {
- purple_debug_info("groupme", "With postdata %s\n", postdata);
- }
- if (postdata[0] == '{') {
- g_string_append(headers, "Content-Type: application/json\r\n");
- } else {
- g_string_append(headers, "Content-Type: application/x-www-form-urlencoded\r\n");
- }
- g_string_append_printf(headers, "Content-Length: %" G_GSIZE_FORMAT "\r\n", strlen(postdata));
- g_string_append(headers, "\r\n");
- g_string_append(headers, postdata);
- } else {
- g_string_append(headers, "\r\n");
- }
- g_free(host);
- g_free(path);
- g_free(user);
- g_free(password);
- http_conn = purple_util_fetch_url_request_len_with_account(ya->account, url, FALSE, GROUPME_USERAGENT, TRUE, headers->str, TRUE, 6553500, groupme_response_callback, conn);
- if (http_conn != NULL) {
- ya->http_conns = g_slist_prepend(ya->http_conns, http_conn);
- }
- g_string_free(headers, TRUE);
- #endif
- g_free(cookies);
- g_free(url);
- }
- static void
- groupme_fetch_url(GroupMeAccount *da, const gchar *url, const gchar *postdata, GroupMeProxyCallbackFunc callback, gpointer user_data)
- {
- groupme_fetch_url_with_method(da, (postdata ? "POST" : "GET"), url, postdata, callback, user_data);
- }
- static void groupme_socket_write_json(GroupMeAccount *ya, JsonObject *data);
- static GHashTable *groupme_chat_info_defaults(PurpleConnection *pc, const char *chatname);
- static void groupme_mark_room_messages_read(GroupMeAccount *ya, guint64 room_id);
- static void groupme_init_push(GroupMeAccount *da);
- static guint64
- groupme_process_message(GroupMeAccount *da, int channel, JsonObject *data, gboolean is_dm);
- static void
- groupme_got_push(GroupMeAccount *da, JsonNode *node, gpointer user_data)
- {
- JsonArray *subscriptions = json_node_get_array(node);
- guint len = json_array_get_length(subscriptions);
- for (int i = len - 1; i >= 0; i--) {
- JsonObject *sub = json_array_get_object_element(subscriptions, i);
- /* Actuate the new response */
- if (!json_object_has_member(sub, "data"))
- continue;
- JsonObject *data = json_object_get_object_member(sub, "data");
- if (!json_object_has_member(data, "subject"))
- continue;
- JsonObject *subj = json_object_get_object_member(data, "subject");
- const gchar *type = json_object_get_string_member(data, "type");
- if (g_strcmp0(type, "line.create") == 0) {
- /* Incoming message */
- int gid = to_int(json_object_get_string_member(subj, "group_id"));
- groupme_process_message(da, gid, subj, FALSE);
- } else if (g_strcmp0(type, "direct_message.create") == 0) {
- /* Direct message: either our sent message or theirs */
- int sid = to_int(json_object_get_string_member(subj, "sender_id"));
- int rid = to_int(json_object_get_string_member(subj, "recipient_id"));
- /* Sometimes we receive our own messages, account for that */
- int channel = sid == da->self_user_id ? rid : sid;
- groupme_process_message(da, channel, subj, TRUE);
- } else {
- printf("Unknown type %s, check debug logs\n", type);
- }
- }
- #ifdef USE_LONG_POLL
- /* Long polling consists of repeated reqeusts to the push server */
- groupme_init_push(da);
- #endif
- }
- static void
- groupme_init_push(GroupMeAccount *da)
- {
- JsonObject *data = json_object_new();
- json_object_set_string_member(data, "channel", "/meta/connect");
- json_object_set_string_member(data, "clientId", da->client_id);
- json_object_set_string_member(data, "connectionType", GROUPME_PUSH_TYPE);
- gchar *id = from_int(da->push_id++);
- json_object_set_string_member(data, "id", id);
- groupme_fetch_url(da, "https://" GROUPME_PUSH_SERVER, json_object_to_string(data), groupme_got_push, NULL);
- //groupme_socket_write_json(da, data);
- json_object_unref(data);
- g_free(id);
- }
- void groupme_handle_add_new_user(GroupMeAccount *ya, JsonObject *obj);
- PurpleGroup *groupme_get_or_create_group(const gchar *name);
- static void groupme_got_history_static(GroupMeAccount *da, JsonNode *node, gpointer user_data);
- static void groupme_got_history_of_room(GroupMeAccount *da, JsonNode *node, gpointer user_data);
- static void groupme_populate_guild(GroupMeAccount *da, JsonObject *guild);
- static void groupme_got_guilds(GroupMeAccount *da, JsonNode *node, gpointer user_data);
- static void groupme_got_chats(GroupMeAccount *da, JsonNode *node, gpointer user_data);
- static void groupme_got_avatar(GroupMeAccount *da, JsonNode *node, gpointer user_data);
- static void groupme_get_avatar(GroupMeAccount *da, GroupMeUser *user);
- static const gchar *groupme_normalise_room_name(const gchar *guild_name, const gchar *name);
- static GroupMeGuild *groupme_open_chat(GroupMeAccount *da, guint64 id, gchar *name, gboolean present);
- static void
- groupme_create_associate(GroupMeAccount *da, guint64 id)
- {
- gchar *id_s = from_int(id);
- /* First, check to see if we already are associated */
- PurpleBuddy *buddy = purple_find_buddy(da->account, id_s);
- /* If not, associate */
- if (!buddy) {
- GroupMeUser *user = groupme_get_user(da, id);
- if (!user) {
- printf("Not associating with unknown user %d\n", id);
- return;
- }
- buddy = purple_buddy_new(da->account, id_s, user->name);
- purple_blist_add_buddy(buddy, NULL, groupme_get_or_create_group("GroupMe"), NULL);
- /* Bring it up */
- purple_protocol_got_user_status(da->account, id_s, "online", NULL);
- groupme_get_avatar(da, user);
- }
- }
- static gchar *
- groupme_get_real_name(PurpleConnection *pc, gint id, const char *who)
- {
- GroupMeAccount *da = purple_connection_get_protocol_data(pc);
- PurpleChatConversation *chatconv;
- chatconv = purple_conversations_find_chat(pc, id);
- guint64 *room_id_ptr = purple_conversation_get_data(PURPLE_CONVERSATION(chatconv), "id");
- if (!room_id_ptr) {
- goto bail;
- }
- guint64 room_id = *room_id_ptr;
- GroupMeGuild *channel = groupme_get_guild(da, room_id);
- if (!channel)
- goto bail;
- guint64 *uid = g_hash_table_lookup(channel->nicknames_rev, who);
- if (uid) {
- groupme_create_associate(da, *uid);
- return from_int(*uid);
- }
- /* Probably a fullname already, bail out */
- bail:
- return g_strdup(who);
- }
- static guint64
- groupme_process_message(GroupMeAccount *da, int channel, JsonObject *data, gboolean is_dm)
- {
- const gchar *guid = json_object_get_string_member(data, "source_guid");
- guint64 author_id = to_int(json_object_get_string_member(data, "sender_id"));
- const gchar *content = json_object_get_string_member(data, "text");
- const gchar *timestamp_str = json_object_get_string_member(data, "created_at");
- time_t timestamp = purple_str_to_time(timestamp_str, FALSE, NULL, NULL, NULL);
- JsonArray *attachments = json_object_get_array_member(data, "attachments");
- PurpleMessageFlags flags;
- gchar *tmp;
- gint i;
- /* Drop our own messages that were pinged back to us */
- if ((author_id == da->self_user_id) && g_hash_table_remove(da->sent_message_ids, guid))
- return;
- if (author_id == da->self_user_id && is_dm) {
- flags = PURPLE_MESSAGE_SEND | PURPLE_MESSAGE_REMOTE_SEND | PURPLE_MESSAGE_DELAYED;
- } else {
- flags = PURPLE_MESSAGE_RECV;
- }
- if (is_dm) {
- /* private message */
- if (author_id == da->self_user_id) {
- PurpleConversation *conv;
- PurpleIMConversation *imconv;
- PurpleMessage *msg;
- gchar *username = groupme_get_user(da, channel)->id_s;
- imconv = purple_conversations_find_im_with_account(username, da->account);
- if (imconv == NULL) {
- imconv = purple_im_conversation_new(da->account, username);
- }
- conv = PURPLE_CONVERSATION(imconv);
- if (content && *content) {
- msg = purple_message_new_outgoing(username, content, flags);
- purple_message_set_time(msg, timestamp);
- purple_conversation_write_message(conv, msg);
- purple_message_destroy(msg);
- }
- if (attachments) {
- for (i = json_array_get_length(attachments) - 1; i >= 0; i--) {
- JsonObject *attachment = json_array_get_object_element(attachments, i);
- const gchar *url = json_object_get_string_member(attachment, "url");
- msg = purple_message_new_outgoing(username, url, flags);
- purple_message_set_time(msg, timestamp);
- purple_conversation_write_message(conv, msg);
- purple_message_destroy(msg);
- }
- }
- } else {
- GroupMeUser *author = groupme_upsert_user(da->new_users, data);
- gchar *merged_username = author->id_s;
- if (content && *content) {
- purple_serv_got_im(da->pc, merged_username, content, flags, timestamp);
- }
- if (attachments) {
- for (i = json_array_get_length(attachments) - 1; i >= 0; i--) {
- JsonObject *attachment = json_array_get_object_element(attachments, i);
- const gchar *url = json_object_get_string_member(attachment, "url");
- purple_serv_got_im(da->pc, merged_username, url, flags, timestamp);
- }
- }
- }
- } else {
- /* Open the buffer if it's not already */
- groupme_open_chat(da, channel, NULL, FALSE);
- gchar *name = json_object_get_string_member(data, "name");
- if (content && *content) {
- purple_serv_got_chat_in(da->pc, groupme_chat_hash(channel), name, flags, content, timestamp);
- }
- if (attachments) {
- for (i = json_array_get_length(attachments) - 1; i >= 0; i--) {
- JsonObject *attachment = json_array_get_object_element(attachments, i);
- if (json_object_has_member(attachment, "url")) {
- const gchar *url = json_object_get_string_member(attachment, "url");
- purple_serv_got_chat_in(da->pc, groupme_chat_hash(channel), name, flags, url, timestamp);
- }
- }
- }
- }
- return 1;
- }
- struct groupme_group_typing_data {
- GroupMeAccount *da;
- guint64 channel_id;
- gchar *username;
- gboolean set;
- gboolean free_me;
- };
- static gboolean
- groupme_set_group_typing(void *_u)
- {
- if (_u == NULL) {
- return FALSE;
- }
- struct groupme_group_typing_data *ctx = _u;
- PurpleChatConversation *chatconv = purple_conversations_find_chat(ctx->da->pc, groupme_chat_hash(ctx->channel_id));
- if (chatconv == NULL) {
- goto release_ctx;
- }
- PurpleChatUser *cb = purple_chat_conversation_find_user(chatconv, ctx->username);
- if (!cb) {
- goto release_ctx;
- }
- PurpleChatUserFlags cbflags;
- cbflags = purple_chat_user_get_flags(cb);
- if (ctx->set) {
- cbflags |= PURPLE_CHAT_USER_TYPING;
- } else {
- cbflags &= ~PURPLE_CHAT_USER_TYPING;
- }
- purple_chat_user_set_flags(cb, cbflags);
- release_ctx:
- if (ctx->free_me) {
- g_free(ctx->username);
- g_free(ctx);
- }
- return FALSE;
- }
- static void
- groupme_got_nick_change(GroupMeAccount *da, GroupMeUser *user, GroupMeGuild *guild, const gchar *new, const gchar *old, gboolean self)
- {
- gchar *old_safe = g_strdup(old);
- if (old) {
- g_hash_table_remove(guild->nicknames_rev, old);
- }
- /* Nick change */
- gchar *nick = groupme_alloc_nickname(user, guild, new);
- /* Propagate through the guild, see e.g. irc_msg_nick */
- GHashTableIter channel_iter;
- gpointer key, value;
- /* TODO: Nick */
- PurpleChatConversation *chat = purple_conversations_find_chat(da->pc, groupme_chat_hash(guild->id));
- if (chat && purple_chat_conversation_has_user(chat, old_safe)) {
- purple_chat_conversation_rename_user(chat, old_safe, nick);
- }
- g_free(nick);
- }
- PurpleChat *
- groupme_bring_up_buddies(PurpleAccount *account)
- {
- PurpleBlistNode *node;
- GSList *lst = purple_find_buddies(account, NULL);
- while (lst) {
- PurpleBuddy *buddy = (PurpleBuddy *) lst->data;
- purple_protocol_got_user_status(account, buddy->name, "online", NULL);
- purple_protocol_got_user_idle(account, buddy->name, 0, 0);
- lst = g_slist_delete_link(lst, lst);
- }
-
- return NULL;
- }
- PurpleChat *
- groupme_find_chat_from_node(PurpleAccount *account, const char *id, PurpleBlistNode *root)
- {
- PurpleBlistNode *node;
-
- for (node = root;
- node != NULL;
- node = purple_blist_node_next(node, TRUE)) {
- if (PURPLE_IS_CHAT(node)) {
- PurpleChat *chat = PURPLE_CHAT(node);
- if (purple_chat_get_account(chat) != account) {
- continue;
- }
-
- GHashTable *components = purple_chat_get_components(chat);
- const gchar *chat_id = g_hash_table_lookup(components, "id");
-
- if (purple_strequal(chat_id, id)) {
- return chat;
- }
- }
- }
-
- return NULL;
- }
- PurpleChat *
- groupme_find_chat(PurpleAccount *account, const char *id)
- {
- return groupme_find_chat_from_node(account, id, purple_blist_get_root());
- }
- PurpleChat *
- groupme_find_chat_in_group(PurpleAccount *account, const char *id, PurpleGroup *group)
- {
- g_return_val_if_fail(group != NULL, NULL);
-
- return groupme_find_chat_from_node(account, id, PURPLE_BLIST_NODE(group));
- }
- static void
- groupme_add_channel_to_blist(GroupMeAccount *da, GroupMeGuild *channel, PurpleGroup *group)
- {
- GHashTable *components = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
- gchar *id = from_int(channel->id);
-
- g_hash_table_replace(components, g_strdup("id"), id);
- g_hash_table_replace(components, g_strdup("name"), g_strdup(channel->name));
- /* Don't re-add the channel to the same group */
-
- if (groupme_find_chat_in_group(da->account, id, group) == NULL) {
- PurpleChat *chat = purple_chat_new(da->account, channel->name, components);
- purple_blist_add_chat(chat, group, NULL);
- } else {
- g_hash_table_unref(components);
- }
- }
- PurpleGroup *
- groupme_get_or_create_group(const gchar *name)
- {
- PurpleGroup *groupme_group = purple_blist_find_group(name);
- if (!groupme_group) {
- groupme_group = purple_group_new(name);
- purple_blist_add_group(groupme_group, NULL);
- }
- return groupme_group;
- }
- static const gchar *
- groupme_normalise_room_name(const gchar *guild_name, const gchar *name)
- {
- gchar *channel_name = g_strconcat(guild_name, "#", name, NULL);
- static gchar *old_name = NULL;
- g_free(old_name);
- old_name = g_ascii_strdown(channel_name, -1);
- purple_util_chrreplace(old_name, ' ', '_');
- g_free(channel_name);
- return old_name;
- }
- static gchar *
- groupme_roomlist_serialize(PurpleRoomlistRoom *room)
- {
- GList *fields = purple_roomlist_room_get_fields(room);
- const gchar *id = (const gchar *) fields->data;
- return g_strdup(id);
- }
- PurpleRoomlist *
- groupme_roomlist_get_list(PurpleConnection *pc)
- {
- GroupMeAccount *da = purple_connection_get_protocol_data(pc);
- PurpleRoomlist *roomlist;
- GList *fields = NULL;
- PurpleRoomlistField *f;
- roomlist = purple_roomlist_new(da->account);
- f = purple_roomlist_field_new(PURPLE_ROOMLIST_FIELD_STRING, _("ID"), "id", TRUE);
- fields = g_list_append(fields, f);
- purple_roomlist_set_fields(roomlist, fields);
- purple_roomlist_set_in_progress(roomlist, TRUE);
- GHashTableIter iter;
- gpointer key, guild;
- g_hash_table_iter_init(&iter, da->new_guilds);
- while (g_hash_table_iter_next(&iter, &key, &guild)) {
- GroupMeGuild *g = (GroupMeGuild *) guild;
- PurpleRoomlistRoom *room = purple_roomlist_room_new(PURPLE_ROOMLIST_ROOMTYPE_ROOM, g->name, NULL);
- gchar *channel_id = from_int(g->id);
-
- purple_roomlist_room_add_field(roomlist, room, channel_id);
- purple_roomlist_room_add(roomlist, room);
- g_free(channel_id);
- }
- purple_roomlist_set_in_progress(roomlist, FALSE);
- return roomlist;
- }
- static void
- groupme_restart_channel(GroupMeAccount *da)
- {
- purple_connection_set_state(da->pc, PURPLE_CONNECTION_CONNECTING);
- groupme_start_socket(da);
- }
- static guint groupme_conv_send_typing(PurpleConversation *conv, PurpleIMTypingState state, GroupMeAccount *ya);
- static gulong chat_conversation_typing_signal = 0;
- static void groupme_mark_conv_seen(PurpleConversation *conv, PurpleConversationUpdateType type);
- static gulong conversation_updated_signal = 0;
- typedef struct {
- GroupMeAccount *da;
- GroupMeUser *user;
- } GroupMeUserInviteResponseStore;
- static void
- groupme_populate_guild(GroupMeAccount *da, JsonObject *guild)
- {
- GroupMeGuild *g = groupme_upsert_guild(da->new_guilds, guild);
- gchar *name = json_object_get_string_member(guild, "name");
- /* Add chat to blist */
- PurpleGroup *group = groupme_get_or_create_group("GroupMe Chats");
- groupme_add_channel_to_blist(da, g, group);
- JsonArray *members = json_object_get_array_member(guild, "members");
- /* Populate members */
- for (int j = json_array_get_length(members) - 1; j >= 0; j--) {
- JsonObject *member = json_array_get_object_element(members, j);
- GroupMeUser *u = groupme_upsert_user(da->new_users, member);
- g_array_append_val(g->members, u->id);
- GroupMeGuildMembership *membership = groupme_new_guild_membership(g->id, member);
- g_hash_table_replace_int64(u->guild_memberships, g->id, membership);
- membership->nick = groupme_alloc_nickname(u, g, json_object_get_string_member(member, "nickname"));
- }
- }
- static void
- groupme_got_guilds(GroupMeAccount *da, JsonNode *node, gpointer user_data)
- {
- JsonObject *container = json_node_get_object(node);
- JsonArray *guilds = json_object_get_array_member(container, "response");
- guint len = json_array_get_length(guilds);
- for (int i = len - 1; i >= 0; i--) {
- JsonObject *guild = json_array_get_object_element(guilds, i);
- groupme_populate_guild(da, guild);
- }
- }
- static void
- groupme_got_chats(GroupMeAccount *da, JsonNode *node, gpointer user_data)
- {
- JsonObject *container = json_node_get_object(node);
- JsonArray *chats = json_object_get_array_member(container, "response");
- guint len = json_array_get_length(chats);
- for (int i = len - 1; i >= 0; i--) {
- JsonObject *chat = json_array_get_object_element(chats, i);
- JsonObject *msg = json_object_get_object_member(chat, "last_message");
- JsonObject *other = json_object_get_object_member(chat, "other_user");
- const gchar *chan = json_object_get_string_member(other, "id");
- /* TODO: Actually fetch history, rolling, save counts, etc */
- groupme_upsert_user(da->new_users, other);
- groupme_process_message(da, to_int(chan), msg, TRUE);
- }
- }
- static void
- groupme_got_self(GroupMeAccount *da, JsonNode *node, gpointer user_data)
- {
- JsonObject *container = json_node_get_object(node);
- JsonObject *resp = json_object_get_object_member(container, "response");
- da->self_user_id = to_int(json_object_get_string_member(resp, "id"));
- da->self_username = g_strdup(json_object_get_string_member(resp, "name"));
- /* Now that we have the user ID, we can start the websocket handshake */
- {
- const gchar *str = "{\"channel\": \"/meta/handshake\", \"version\": \"1.0\", \"supportedConnectionTypes\": [\"" GROUPME_PUSH_TYPE "\"], \"id\": 1}";
- groupme_fetch_url(da, "https://" GROUPME_PUSH_SERVER, str, groupme_got_handshake, NULL);
- }
- }
- static void groupme_login_response(GroupMeAccount *da, JsonNode *node, gpointer user_data);
- static void
- groupme_mfa_text_entry(gpointer user_data, const gchar *code)
- {
- GroupMeAccount *da = user_data;
- JsonObject *data = json_object_new();
- gchar *str;
- json_object_set_string_member(data, "code", code);
- json_object_set_string_member(data, "ticket", da->mfa_ticket);
- str = json_object_to_string(data);
- groupme_fetch_url(da, "https://" GROUPME_API_SERVER "/api/v6/auth/mfa/totp", str, groupme_login_response, NULL);
- g_free(str);
- json_object_unref(data);
- g_free(da->mfa_ticket);
- da->mfa_ticket = NULL;
- }
- static void
- groupme_mfa_cancel(gpointer user_data)
- {
- GroupMeAccount *da = user_data;
- purple_connection_error(da->pc, PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED, _("Cancelled 2FA auth"));
- }
- static void
- groupme_login_response(GroupMeAccount *da, JsonNode *node, gpointer user_data)
- {
- if (node != NULL) {
- JsonObject *response = json_node_get_object(node);
- da->token = g_strdup(json_object_get_string_member(response, "token"));
- purple_account_set_string(da->account, "token", da->token);
- if (da->token) {
- groupme_start_socket(da);
- return;
- }
- if (json_object_get_boolean_member(response, "mfa")) {
- g_free(da->mfa_ticket);
- da->mfa_ticket = g_strdup(json_object_get_string_member(response, "ticket"));
- purple_request_input(da->pc, _("Two-factor authentication"),
- _("Enter GroupMe auth code"),
- _("You can get this token from your two-factor authentication mobile app."),
- NULL, FALSE, FALSE, "",
- _("_Login"), G_CALLBACK(groupme_mfa_text_entry),
- _("_Cancel"), G_CALLBACK(groupme_mfa_cancel),
- purple_request_cpar_from_connection(da->pc),
- da);
- return;
- }
- if (json_object_has_member(response, "email")) {
- /* Probably an error about new location */
- purple_connection_error(da->pc, PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED, json_object_get_string_member(response, "email"));
- return;
- }
- if (json_object_has_member(response, "password")) {
- /* Probably an error about bad password */
- purple_connection_error(da->pc, PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED, json_object_get_string_member(response, "password"));
- return;
- }
- }
- purple_connection_error(da->pc, PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED, _("Bad username/password"));
- }
- void
- groupme_login(PurpleAccount *account)
- {
- GroupMeAccount *da;
- PurpleConnection *pc = purple_account_get_connection(account);
- PurpleConnectionFlags pc_flags;
- pc_flags = purple_connection_get_flags(pc);
- pc_flags |= PURPLE_CONNECTION_FLAG_NO_FONTSIZE;
- pc_flags |= PURPLE_CONNECTION_FLAG_NO_BGCOLOR;
- pc_flags |= PURPLE_CONNECTION_FLAG_NO_IMAGES;
- purple_connection_set_flags(pc, pc_flags);
- da = g_new0(GroupMeAccount, 1);
- purple_connection_set_protocol_data(pc, da);
- da->account = account;
- da->pc = pc;
- da->cookie_table = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
- da->last_load_last_message_id = purple_account_get_int(account, "last_message_id_high", 0);
- if (da->last_load_last_message_id != 0) {
- da->last_load_last_message_id = (da->last_load_last_message_id << 32) | ((guint64) purple_account_get_int(account, "last_message_id_low", 0) & 0xFFFFFFFF);
- }
- da->one_to_ones = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
- da->one_to_ones_rev = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
- da->last_message_id_dm = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
- da->sent_message_ids = g_hash_table_new_full(g_str_insensitive_hash, g_str_insensitive_equal, g_free, NULL);
- da->result_callbacks = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
- da->received_message_queue = g_queue_new();
- /* TODO make these the roots of all groupme data */
- da->new_users = g_hash_table_new_full(g_int64_hash, g_int64_equal, NULL, groupme_free_user);
- da->new_guilds = g_hash_table_new_full(g_int64_hash, g_int64_equal, NULL, groupme_free_guild);
- purple_connection_set_state(pc, PURPLE_CONNECTION_CONNECTING);
- const gchar *dev_token = purple_connection_get_password(da->pc);
- da->token = g_strdup(dev_token);
- /* Test the REST API */
- groupme_fetch_url(da, "https://" GROUPME_API_SERVER "/users/me?", NULL, groupme_got_self, NULL);
- groupme_fetch_url(da, "https://" GROUPME_API_SERVER "/groups?", NULL, groupme_got_guilds, NULL);
- groupme_fetch_url(da, "https://" GROUPME_API_SERVER "/chats?", NULL, groupme_got_chats, NULL);
- /* XXX: Authenticate good */
- purple_connection_set_state(da->pc, PURPLE_CONNECTION_CONNECTED);
- groupme_bring_up_buddies(da->account);
- if (!chat_conversation_typing_signal) {
- chat_conversation_typing_signal = purple_signal_connect(purple_conversations_get_handle(), "chat-conversation-typing", purple_connection_get_protocol(pc), PURPLE_CALLBACK(groupme_conv_send_typing), NULL);
- }
- if (!conversation_updated_signal) {
- conversation_updated_signal = purple_signal_connect(purple_conversations_get_handle(), "conversation-updated", purple_connection_get_protocol(pc), PURPLE_CALLBACK(groupme_mark_conv_seen), NULL);
- }
- }
- static void
- groupme_close(PurpleConnection *pc)
- {
- GroupMeAccount *da = purple_connection_get_protocol_data(pc);
- g_return_if_fail(da != NULL);
- if (da->heartbeat_timeout) {
- g_source_remove(da->heartbeat_timeout);
- }
- if (da->websocket != NULL) {
- purple_ssl_close(da->websocket);
- da->websocket = NULL;
- }
- g_hash_table_unref(da->one_to_ones);
- da->one_to_ones = NULL;
- g_hash_table_unref(da->one_to_ones_rev);
- da->one_to_ones_rev = NULL;
- g_hash_table_unref(da->last_message_id_dm);
- da->last_message_id_dm = NULL;
- g_hash_table_unref(da->sent_message_ids);
- da->sent_message_ids = NULL;
- g_hash_table_unref(da->result_callbacks);
- da->result_callbacks = NULL;
- g_hash_table_unref(da->new_users);
- da->new_users = NULL;
- g_hash_table_unref(da->new_guilds);
- da->new_guilds = NULL;
- g_queue_free(da->received_message_queue);
- da->received_message_queue = NULL;
- while (da->http_conns) {
- #if !PURPLE_VERSION_CHECK(3, 0, 0)
- purple_util_fetch_url_cancel(da->http_conns->data);
- #else
- purple_http_conn_cancel(da->http_conns->data);
- #endif
- da->http_conns = g_slist_delete_link(da->http_conns, da->http_conns);
- }
- while (da->pending_writes) {
- json_object_unref(da->pending_writes->data);
- da->pending_writes = g_slist_delete_link(da->pending_writes, da->pending_writes);
- }
- g_hash_table_destroy(da->cookie_table);
- da->cookie_table = NULL;
- g_free(da->frame);
- da->frame = NULL;
- g_free(da->token);
- da->token = NULL;
- g_free(da->session_id);
- da->session_id = NULL;
- g_free(da->self_username);
- da->self_username = NULL;
- g_free(da);
- }
- /* static void groupme_start_polling(GroupMeAccount *ya); */
- static gboolean
- groupme_process_frame(GroupMeAccount *da, const gchar *frame)
- {
- JsonParser *parser = json_parser_new();
- JsonNode *root;
- gint64 opcode;
- printf("process frame!\n");
- purple_debug_info("groupme", "got frame data: %s\n", frame);
- if (!json_parser_load_from_data(parser, frame, -1, NULL)) {
- purple_debug_error("groupme", "Error parsing response: %s\n", frame);
- return TRUE;
- }
- root = json_parser_get_root(parser);
- if (root != NULL) {
- JsonObject *obj = json_node_get_object(root);
- printf("TODO: Wire up websocket\n");
- }
- g_object_unref(parser);
- return TRUE;
- }
- static guchar *
- groupme_websocket_mask(guchar key[4], const guchar *pload, guint64 psize)
- {
- guint64 i;
- guchar *ret = g_new0(guchar, psize);
- for (i = 0; i < psize; i++) {
- ret[i] = pload[i] ^ key[i % 4];
- }
- return ret;
- }
- static void
- groupme_socket_write_data(GroupMeAccount *ya, guchar *data, gsize data_len, guchar type)
- {
- guchar *full_data;
- guint len_size = 1;
- guchar mkey[4] = { 0x12, 0x34, 0x56, 0x78 };
- if (data_len) {
- purple_debug_info("groupme", "sending frame: %*s\n", (int) data_len, data);
- }
- data = groupme_websocket_mask(mkey, data, data_len);
- if (data_len > 125) {
- if (data_len <= G_MAXUINT16) {
- len_size += 2;
- } else {
- len_size += 8;
- }
- }
- full_data = g_new0(guchar, 1 + data_len + len_size + 4);
- if (type == 0) {
- type = 129;
- }
- full_data[0] = type;
- if (data_len <= 125) {
- full_data[1] = data_len | 0x80;
- } else if (data_len <= G_MAXUINT16) {
- guint16 be_len = GUINT16_TO_BE(data_len);
- full_data[1] = 126 | 0x80;
- memmove(full_data + 2, &be_len, 2);
- } else {
- guint64 be_len = GUINT64_TO_BE(data_len);
- full_data[1] = 127 | 0x80;
- memmove(full_data + 2, &be_len, 8);
- }
- memmove(full_data + (1 + len_size), &mkey, 4);
- memmove(full_data + (1 + len_size + 4), data, data_len);
- purple_ssl_write(ya->websocket, full_data, 1 + data_len + len_size + 4);
- g_free(full_data);
- g_free(data);
- }
- /* takes ownership of data parameter */
- static void
- groupme_socket_write_json(GroupMeAccount *rca, JsonObject *data)
- {
- JsonNode *node;
- gchar *str;
- gsize len;
- JsonGenerator *generator;
- if (rca->websocket == NULL) {
- if (data != NULL) {
- rca->pending_writes = g_slist_append(rca->pending_writes, data);
- }
- return;
- }
- node = json_node_new(JSON_NODE_OBJECT);
- json_node_set_object(node, data);
- generator = json_generator_new();
- json_generator_set_root(generator, node);
- str = json_generator_to_data(generator, &len);
- g_object_unref(generator);
- json_node_free(node);
- groupme_socket_write_data(rca, (guchar *) str, len, 0);
- g_free(str);
- }
- static void
- groupme_socket_got_data(gpointer userdata, PurpleSslConnection *conn, PurpleInputCondition cond)
- {
- GroupMeAccount *ya = userdata;
- guchar length_code;
- int read_len = 0;
- gboolean done_some_reads = FALSE;
- printf("ws got data\n");
- if (G_UNLIKELY(!ya->websocket_header_received)) {
- gint nlbr_count = 0;
- gchar nextchar;
- while (nlbr_count < 4 && (read_len = purple_ssl_read(conn, &nextchar, 1)) == 1) {
- if (nextchar == '\r' || nextchar == '\n') {
- nlbr_count++;
- } else {
- nlbr_count = 0;
- }
- }
- if (nlbr_count == 4) {
- ya->websocket_header_received = TRUE;
- done_some_reads = TRUE;
- /* flush stuff that we attempted to send before the websocket was ready */
- while (ya->pending_writes) {
- groupme_socket_write_json(ya, ya->pending_writes->data);
- ya->pending_writes = g_slist_delete_link(ya->pending_writes, ya->pending_writes);
- }
- }
- }
- while (ya->frame || (read_len = purple_ssl_read(conn, &ya->packet_code, 1)) == 1) {
- if (!ya->frame) {
- if (ya->packet_code != 129) {
- if (ya->packet_code == 136) {
- purple_debug_error("groupme", "websocket closed\n");
- length_code = 0;
- purple_ssl_read(conn, &length_code, 1);
- if (length_code > 0 && length_code <= 125) {
- guchar error_buf[2];
- if (purple_ssl_read(conn, &error_buf, 2) == 2) {
- gint error_code = (error_buf[0] << 8) + error_buf[1];
- purple_debug_error("groupme", "error code %d\n", error_code);
- if (error_code == 4004) {
- /* bad auth token, clear and reset */
- purple_account_set_string(ya->account, "token", NULL);
- purple_connection_error(ya->pc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, _("Reauthentication required"));
- return;
- }
- }
- }
- /* Try reconnect */
- groupme_start_socket(ya);
- return;
- } else if (ya->packet_code == 137) {
- /* Ping */
- gint ping_frame_len = 0;
- length_code = 0;
- purple_ssl_read(conn, &length_code, 1);
- if (length_code <= 125) {
- ping_frame_len = length_code;
- } else if (length_code == 126) {
- guchar len_buf[2];
- purple_ssl_read(conn, len_buf, 2);
- ping_frame_len = (len_buf[0] << 8) + len_buf[1];
- } else if (length_code == 127) {
- purple_ssl_read(conn, &ping_frame_len, 8);
- ping_frame_len = GUINT64_FROM_BE(ping_frame_len);
- }
- if (ping_frame_len) {
- guchar *pong_data = g_new0(guchar, ping_frame_len);
- purple_ssl_read(conn, pong_data, ping_frame_len);
- groupme_socket_write_data(ya, pong_data, ping_frame_len, 138);
- g_free(pong_data);
- } else {
- groupme_socket_write_data(ya, (guchar *) "", 0, 138);
- }
- return;
- } else if (ya->packet_code == 138) {
- /* Ignore pong */
- return;
- }
- purple_debug_error("groupme", "unknown websocket error %d\n", ya->packet_code);
- return;
- }
- length_code = 0;
- purple_ssl_read(conn, &length_code, 1);
- if (length_code <= 125) {
- ya->frame_len = length_code;
- } else if (length_code == 126) {
- guchar len_buf[2];
- purple_ssl_read(conn, len_buf, 2);
- ya->frame_len = (len_buf[0] << 8) + len_buf[1];
- } else if (length_code == 127) {
- purple_ssl_read(conn, &ya->frame_len, 8);
- ya->frame_len = GUINT64_FROM_BE(ya->frame_len);
- }
- ya->frame = g_new0(gchar, ya->frame_len + 1);
- ya->frame_len_progress = 0;
- }
- do {
- read_len = purple_ssl_read(conn, ya->frame + ya->frame_len_progress, ya->frame_len - ya->frame_len_progress);
- if (read_len > 0) {
- ya->frame_len_progress += read_len;
- }
- } while (read_len > 0 && ya->frame_len_progress < ya->frame_len);
- done_some_reads = TRUE;
- if (ya->frame_len_progress == ya->frame_len) {
- gboolean success = groupme_process_frame(ya, ya->frame);
- g_free(ya->frame);
- ya->frame = NULL;
- ya->packet_code = 0;
- ya->frame_len = 0;
- ya->frames_since_reconnect++;
- if (G_UNLIKELY(ya->websocket == NULL || success == FALSE)) {
- return;
- }
- } else {
- return;
- }
- }
- if (done_some_reads == FALSE && read_len <= 0) {
- if (read_len < 0 && errno == EAGAIN) {
- return;
- }
- purple_debug_error("groupme", "got errno %d, read_len %d from websocket thread\n", errno, read_len);
- if (ya->frames_since_reconnect < 2) {
- purple_connection_error(ya->pc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, _("Lost connection to server"));
- } else {
- /* Try reconnect */
- groupme_start_socket(ya);
- }
- }
- }
- static void
- groupme_socket_connected(gpointer userdata, PurpleSslConnection *conn, PurpleInputCondition cond)
- {
- GroupMeAccount *da = userdata;
- gchar *websocket_header;
- const gchar *websocket_key = "15XF+ptKDhYVERXoGcdHTA=="; /* TODO don't be lazy */
- purple_ssl_input_add(da->websocket, groupme_socket_got_data, da);
- websocket_header = g_strdup_printf("GET %s HTTP/1.1\r\n"
- "Host: %s\r\n"
- "Connection: Upgrade\r\n"
- "Pragma: no-cache\r\n"
- "Cache-Control: no-cache\r\n"
- "Upgrade: websocket\r\n"
- "Sec-WebSocket-Version: 13\r\n"
- "Sec-WebSocket-Key: %s\r\n"
- "User-Agent: " GROUPME_USERAGENT "\r\n"
- "\r\n",
- GROUPME_GATEWAY_SERVER_PATH, GROUPME_GATEWAY_SERVER,
- websocket_key);
- purple_ssl_write(da->websocket, websocket_header, strlen(websocket_header));
- g_free(websocket_header);
- groupme_init_push(da);
- }
- static void
- groupme_socket_failed(PurpleSslConnection *conn, PurpleSslErrorType errortype, gpointer userdata)
- {
- GroupMeAccount *da = userdata;
- da->websocket = NULL;
- da->websocket_header_received = FALSE;
- if (da->frames_since_reconnect < 1) {
- purple_connection_error(da->pc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, _("Couldn't connect to gateway"));
- } else {
- groupme_restart_channel(da);
- }
- }
- static void
- groupme_start_socket(GroupMeAccount *da)
- {
- #ifdef USE_LONG_POLL
- groupme_init_push(da);
- return;
- #endif
- if (da->heartbeat_timeout) {
- g_source_remove(da->heartbeat_timeout);
- }
- /* Reset all the old stuff */
- if (da->websocket != NULL) {
- purple_ssl_close(da->websocket);
- }
- da->websocket = NULL;
- da->websocket_header_received = FALSE;
- g_free(da->frame);
- da->frame = NULL;
- da->packet_code = 0;
- da->frame_len = 0;
- da->frames_since_reconnect = 0;
- da->websocket = purple_ssl_connect(da->account, GROUPME_GATEWAY_SERVER, GROUPME_GATEWAY_PORT, groupme_socket_connected, groupme_socket_failed, da);
- }
- static void
- groupme_chat_leave_by_room_id(PurpleConnection *pc, guint64 room_id)
- {
- /*GroupMeAccount *ya = purple_connection_get_protocol_data(pc);
- JsonObject *data = json_object_new();
- JsonArray *params = json_array_new();
- json_array_add_string_element(params, room_id);
- json_object_set_string_member(data, "msg", "method");
- json_object_set_string_member(data, "method", "leaveRoom");
- json_object_set_array_member(data, "params", params);
- json_object_set_string_member(data, "id", groupme_get_next_id_str(ya));
- groupme_socket_write_json(ya, data);*/
- }
- static void
- groupme_chat_leave(PurpleConnection *pc, int id)
- {
- PurpleChatConversation *chatconv;
- /* TODO check source */
- chatconv = purple_conversations_find_chat(pc, id);
- guint64 room_id = *(guint64 *) purple_conversation_get_data(PURPLE_CONVERSATION(chatconv), "id");
- if (!room_id) {
- /* TODO FIXME? */
- room_id = to_int(purple_conversation_get_name(PURPLE_CONVERSATION(chatconv)));
- }
- groupme_chat_leave_by_room_id(pc, room_id);
- }
- /* Invite to a _group DM_
- * The API for inviting to a guild is different, TODO implement that one too */
- static void
- groupme_chat_invite(PurpleConnection *pc, int id, const char *message, const char *who)
- {
- GroupMeAccount *ya;
- guint64 room_id;
- PurpleChatConversation *chatconv;
- GroupMeUser *user;
- JsonObject *data;
- ya = purple_connection_get_protocol_data(pc);
- chatconv = purple_conversations_find_chat(pc, id);
- guint64 *room_id_ptr = purple_conversation_get_data(PURPLE_CONVERSATION(chatconv), "id");
- if(!room_id_ptr) {
- return;
- }
- room_id = *room_id_ptr;
- user = groupme_get_user_fullname(ya, who);
- if (!user) {
- purple_debug_info("groupme", "Missing user in invitation for %s", who);
- return;
- }
- data = json_object_new();
- json_object_set_string_member(data, "recipient", from_int(user->id));
- gchar *postdata = json_object_to_string(data);
- gchar *url = g_strdup_printf("https://" GROUPME_API_SERVER "/api/v6/channels/%" G_GUINT64_FORMAT "/recipients/%" G_GUINT64_FORMAT, room_id, user->id);
- groupme_fetch_url_with_method(ya, "PUT", url, postdata, NULL, NULL);
- g_free(url);
- g_free(postdata);
- json_object_unref(data);
- }
- static const gchar *
- groupme_resolve_nick(GroupMeAccount *da, guint64 id, guint64 channel)
- {
- GroupMeGuild *g = groupme_get_guild(da, channel);
- const gchar *nick = g_hash_table_lookup_int64(g->nicknames, id);
- if (nick)
- return nick;
- GroupMeUser *u = groupme_get_user(da, id);
- return u->name;
- }
- static void
- groupme_chat_nick(PurpleConnection *pc, int id, gchar *new_nick)
- {
- PurpleChatConversation *chatconv;
- /* TODO check source */
- chatconv = purple_conversations_find_chat(pc, id);
- guint64 room_id = *(guint64 *) purple_conversation_get_data(PURPLE_CONVERSATION(chatconv), "id");
- if (!room_id) {
- /* TODO FIXME? */
- room_id = to_int(purple_conversation_get_name(PURPLE_CONVERSATION(chatconv)));
- }
- GroupMeAccount *da = purple_connection_get_protocol_data(pc);
- GroupMeGuild *guild = groupme_get_guild(da, room_id);
- JsonObject *container = json_object_new();
- JsonObject *data = json_object_new();
- json_object_set_string_member(data, "nickname", new_nick);
- json_object_set_object_member(container, "membership", data);
- gchar *postdata = json_object_to_string(container);
- gchar *url = g_strdup_printf("https://" GROUPME_API_SERVER "/groups/%" G_GUINT64_FORMAT "/memberships/update?", guild->id);
- groupme_fetch_url(da, url, postdata, NULL, NULL);
- g_free(url);
- g_free(postdata);
- json_object_unref(container);
- /* Propragate locally as well */
- const gchar *old_nick = g_hash_table_lookup_int64(guild->nicknames, da->self_user_id);
- groupme_got_nick_change(da, groupme_get_user(da, da->self_user_id), guild, new_nick, old_nick, TRUE);
- }
- static GList *
- groupme_chat_info(PurpleConnection *pc)
- {
- GList *m = NULL;
- PurpleProtocolChatEntry *pce;
- pce = g_new0(PurpleProtocolChatEntry, 1);
- pce->label = _("ID");
- pce->identifier = "id";
- m = g_list_append(m, pce);
- pce = g_new0(PurpleProtocolChatEntry, 1);
- pce->label = _("Name");
- pce->identifier = "name";
- m = g_list_append(m, pce);
- return m;
- }
- static gboolean
- str_is_number(const gchar *str)
- {
- gint i = strlen(str) - 1;
- for (; i >= 0; i--) {
- if (!g_ascii_isdigit(str[i])) {
- return FALSE;
- }
- }
- return TRUE;
- }
- static __attribute__((optimize("O0"))) GHashTable *
- groupme_chat_info_defaults(PurpleConnection *pc, const char *chatname)
- {
- GroupMeAccount *da = purple_connection_get_protocol_data(pc);
- GHashTable *defaults = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, g_free);
- if (chatname != NULL) {
- if (str_is_number(chatname)) {
- printf("Bad case 1\n");
- //GroupMeChannel *channel = groupme_get_channel_global(da, chatname);
- //if (channel != NULL) {
- //g_hash_table_insert(defaults, "name", g_strdup(channel->name));
- //}
- g_hash_table_insert(defaults, "id", g_strdup(chatname));
- } else {
- printf("XXXX bad\n");
- /*GroupMeChannel *channel = groupme_get_channel_global_name(da, chatname);
- if (channel != NULL) {
- g_hash_table_insert(defaults, "name", g_strdup(channel->name));
- g_hash_table_insert(defaults, "id", from_int(channel->id));
- }*/
- }
- }
- return defaults;
- }
- static gchar *
- groupme_get_chat_name(GHashTable *data)
- {
- gchar *temp;
- if (data == NULL) {
- return NULL;
- }
- temp = g_hash_table_lookup(data, "name");
- if (temp == NULL) {
- temp = g_hash_table_lookup(data, "id");
- }
- if (temp == NULL) {
- return NULL;
- }
- return g_strdup(temp);
- }
- static void groupme_set_room_last_id(GroupMeAccount *da, guint64 channel_id, guint64 last_id);
- static void
- groupme_got_history_of_room(GroupMeAccount *da, JsonNode *node, gpointer user_data)
- {
- JsonObject *container = json_node_get_object(node);
- JsonObject *resp = json_object_get_object_member(container, "response");
- JsonArray *messages = json_object_get_array_member(resp, "messages");
- GroupMeGuild *channel = user_data;
- gint i, len = json_array_get_length(messages);
- guint64 last_message = /* channel->last_message_id */ 0 /* XXX */;
- guint64 rolling_last_message_id = 0;
- /* latest are first */
- for (i = len - 1; i >= 0; i--) {
- JsonObject *message = json_array_get_object_element(messages, i);
- #if 0
- guint64 id = to_int(json_object_get_string_member(message, "id"));
- if (id >= last_message) {
- break;
- }
- #endif
- rolling_last_message_id = groupme_process_message(da, channel->id, message, FALSE);
- }
- if (rolling_last_message_id != 0) {
- /* ACK HISTORY ACK */
- #if 0
- groupme_set_room_last_id(da, channel->id, rolling_last_message_id);
- if (rolling_last_message_id < last_message) {
- /* Request the next 100 messages */
- gchar *url = g_strdup_printf("https://" GROUPME_API_SERVER "/api/v6/channels/%" G_GUINT64_FORMAT "/messages?limit=100&after=%" G_GUINT64_FORMAT, channel->id, rolling_last_message_id);
- groupme_fetch_url(da, url, NULL, groupme_got_history_of_room, channel);
- g_free(url);
- }
- #endif
- }
- }
- /* identical endpoint as above, but not rolling */
- static void
- groupme_got_history_static(GroupMeAccount *da, JsonNode *node, gpointer user_data)
- {
- JsonArray *messages = json_node_get_array(node);
- gint i, len = json_array_get_length(messages);
- for (i = len - 1; i >= 0; i--) {
- JsonObject *message = json_array_get_object_element(messages, i);
- //groupme_process_message(da, message, FALSE);
- }
- }
- /* libpurple can't store a 64bit int on a 32bit machine, so convert to
- * something more usable instead (puke). also needs to work cross platform, in
- * case the accounts.xml is being shared (double puke)
- */
- static guint64
- groupme_get_room_last_id(GroupMeAccount *da, guint64 id)
- {
- guint64 last_message_id = da->last_load_last_message_id;
- PurpleBlistNode *blistnode = NULL;
- gchar *channel_id = from_int(id);
- if (g_hash_table_contains(da->one_to_ones, channel_id)) {
- /* is a direct message */
- blistnode = PURPLE_BLIST_NODE(purple_blist_find_buddy(da->account, g_hash_table_lookup(da->one_to_ones, channel_id)));
- } else {
- /* twas a group chat */
- blistnode = PURPLE_BLIST_NODE(purple_blist_find_chat(da->account, channel_id));
- }
- if (blistnode != NULL) {
- guint64 last_room_id = purple_blist_node_get_int(blistnode, "last_message_id_high");
- if (last_room_id != 0) {
- last_room_id = (last_room_id << 32) | ((guint64) purple_blist_node_get_int(blistnode, "last_message_id_low") & 0xFFFFFFFF);
- last_message_id = MAX(da->last_message_id, last_room_id);
- }
- }
- g_free(channel_id);
- return last_message_id;
- }
- static void
- groupme_set_room_last_id(GroupMeAccount *da, guint64 id, guint64 last_id)
- {
- PurpleBlistNode *blistnode = NULL;
- gchar *channel_id = from_int(id);
- if (g_hash_table_contains(da->one_to_ones, channel_id)) {
- /* is a direct message */
- blistnode = PURPLE_BLIST_NODE(purple_blist_find_buddy(da->account, g_hash_table_lookup(da->one_to_ones, channel_id)));
- } else {
- /* twas a group chat */
- blistnode = PURPLE_BLIST_NODE(purple_blist_find_chat(da->account, channel_id));
- }
- if (blistnode != NULL) {
- purple_blist_node_set_int(blistnode, "last_message_id_high", last_id >> 32);
- purple_blist_node_set_int(blistnode, "last_message_id_low", last_id & 0xFFFFFFFF);
- }
- da->last_message_id = MAX(da->last_message_id, last_id);
- purple_account_set_int(da->account, "last_message_id_high", last_id >> 32);
- purple_account_set_int(da->account, "last_message_id_low", last_id & 0xFFFFFFFF);
- g_free(channel_id);
- }
- static void groupme_join_chat(PurpleConnection *pc, GHashTable *chatdata);
- static GroupMeGuild *
- groupme_open_chat(GroupMeAccount *da, guint64 id, gchar *name, gboolean present)
- {
- PurpleChatConversation *chatconv = NULL;
- GroupMeGuild *channel = groupme_get_guild(da, id);
- if (channel == NULL) {
- return NULL;
- }
- if (name == NULL) {
- name = channel->name;
- }
- gchar *id_str = from_int(id);
- chatconv = purple_conversations_find_chat_with_account(id_str, da->account);
- if (chatconv != NULL && !purple_chat_conversation_has_left(chatconv)) {
- g_free(id_str);
- if (present) {
- purple_conversation_present(PURPLE_CONVERSATION(chatconv));
- }
- return NULL;
- }
- chatconv = purple_serv_got_joined_chat(da->pc, groupme_chat_hash(id), id_str);
- g_free(id_str);
- purple_conversation_set_data(PURPLE_CONVERSATION(chatconv), "id", g_memdup(&(id), sizeof(guint64)));
- purple_conversation_present(PURPLE_CONVERSATION(chatconv));
- /* Adds members */
- GList *users = NULL, *flags = NULL;
- for (int j = channel->members->len - 1; j >= 0; j--) {
- int uid = g_array_index(channel->members, guint64, j);
- GroupMeUser *u = groupme_get_user(da, uid);
- PurpleChatUserFlags cbflags = groupme_get_user_flags(da, channel, u);
- users = g_list_prepend(users, g_strdup(groupme_resolve_nick(da, uid, channel->id)));
- flags = g_list_prepend(flags, GINT_TO_POINTER(cbflags));
- }
- purple_chat_conversation_clear_users(chatconv);
- purple_chat_conversation_add_users(chatconv, users, NULL, flags, FALSE);
- while (users != NULL) {
- g_free(users->data);
- users = g_list_delete_link(users, users);
- }
- g_list_free(users);
- g_list_free(flags);
- return channel;
- }
- static void
- groupme_join_chat(PurpleConnection *pc, GHashTable *chatdata)
- {
- GroupMeAccount *da = purple_connection_get_protocol_data(pc);
- guint64 id = to_int(g_hash_table_lookup(chatdata, "id"));
- gchar *name = (gchar *) g_hash_table_lookup(chatdata, "name");
- GroupMeGuild *channel = groupme_open_chat(da, id, name, TRUE);
- if (!channel) {
- return;
- }
- /* TODO: HISTORY */
- #if 0
- /* Get any missing messages */
- guint64 last_message_id = groupme_get_room_last_id(da, id);
- if (last_message_id != 0 && channel->last_message_id > last_message_id) {
- gchar *url = g_strdup_printf("https://" GROUPME_API_SERVER "/api/v6/channels/%" G_GUINT64_FORMAT "/messages?limit=100&after=%" G_GUINT64_FORMAT, id, last_message_id);
- groupme_fetch_url(da, url, NULL, groupme_got_history_of_room, channel);
- g_free(url);
- }
- #endif
- gchar *url = g_strdup_printf("https://" GROUPME_API_SERVER "/groups/%" G_GUINT64_FORMAT "/messages?limit=100", id);
- groupme_fetch_url(da, url, NULL, groupme_got_history_of_room, channel);
- }
- static void
- groupme_mark_room_messages_read(GroupMeAccount *da, guint64 channel_id)
- {
- if (!channel_id) {
- return;
- }
- printf("XXX: TODO mark room messages\n");
- #if 0
- GroupMeChannel *channel = groupme_get_channel_global_int(da, channel_id);
- guint64 last_message_id;
- if (channel) {
- last_message_id = channel->last_message_id;
- } else {
- gchar *channel = from_int(channel_id);
- gchar *msg = g_hash_table_lookup(da->last_message_id_dm, channel);
- g_free(channel);
- if (msg) {
- last_message_id = to_int(msg);
- } else {
- purple_debug_info("groupme", "Unknown acked channel %" G_GUINT64_FORMAT, channel_id);
- return;
- }
- }
- if (last_message_id == 0) {
- purple_debug_info("groupme", "Won't ack message ID == 0");
- }
- guint64 known_message_id = groupme_get_room_last_id(da, channel_id);
- if (last_message_id == known_message_id) {
- /* Up to date */
- return;
- }
- groupme_set_room_last_id(da, channel_id, last_message_id);
- gchar *url;
- url = g_strdup_printf("https://" GROUPME_API_SERVER "/api/v6/channels/%" G_GUINT64_FORMAT "/messages/%" G_GUINT64_FORMAT "/ack", channel_id, last_message_id);
- groupme_fetch_url(da, url, "{\"token\":null}", NULL, NULL);
- g_free(url);
- #endif
- }
- static void
- groupme_mark_conv_seen(PurpleConversation *conv, PurpleConversationUpdateType type)
- {
- PurpleConnection *pc;
- GroupMeAccount *ya;
- if (type != PURPLE_CONVERSATION_UPDATE_UNSEEN) {
- return;
- }
- pc = purple_conversation_get_connection(conv);
- if (!PURPLE_CONNECTION_IS_CONNECTED(pc)) {
- return;
- }
- if (!purple_strequal(purple_protocol_get_id(purple_connection_get_protocol(pc)), GROUPME_PLUGIN_ID)) {
- return;
- }
- ya = purple_connection_get_protocol_data(pc);
- guint64 *room_id_ptr = purple_conversation_get_data(conv, "id");
- guint64 room_id = 0;
- if (room_id_ptr) {
- room_id = *room_id_ptr;
- } else {
- room_id = to_int(g_hash_table_lookup(ya->one_to_ones_rev, purple_conversation_get_name(conv)));
- }
- groupme_mark_room_messages_read(ya, room_id);
- }
- static guint
- groupme_conv_send_typing(PurpleConversation *conv, PurpleIMTypingState state, GroupMeAccount *ya)
- {
- PurpleConnection *pc;
- gchar *url;
- if (state != PURPLE_IM_TYPING) {
- return 0;
- }
- pc = ya ? ya->pc : purple_conversation_get_connection(conv);
- if (!PURPLE_CONNECTION_IS_CONNECTED(pc)) {
- return 0;
- }
- if (!purple_strequal(purple_protocol_get_id(purple_connection_get_protocol(pc)), GROUPME_PLUGIN_ID)) {
- return 0;
- }
- if (ya == NULL) {
- ya = purple_connection_get_protocol_data(pc);
- }
- printf("Send typing\n");
- #if 0
- guint64 *room_id_ptr = purple_conversation_get_data(conv, "id");
- guint64 room_id = 0;
- if (room_id_ptr) {
- room_id = *room_id_ptr;
- } else {
- room_id = to_int(g_hash_table_lookup(ya->one_to_ones_rev, purple_conversation_get_name(conv)));
- }
- url = g_strdup_printf("https://" GROUPME_API_SERVER "/api/v6/channels/%" G_GUINT64_FORMAT "/typing", room_id);
- groupme_fetch_url(ya, url, "", NULL, NULL);
- g_free(url);
- #endif
- return 10;
- }
- static guint
- groupme_send_typing(PurpleConnection *pc, const gchar *who, PurpleIMTypingState state)
- {
- PurpleConversation *conv;
- conv = PURPLE_CONVERSATION(purple_conversations_find_im_with_account(who, purple_connection_get_account(pc)));
- g_return_val_if_fail(conv, -1);
- return groupme_conv_send_typing(conv, state, NULL);
- }
- static gint
- groupme_conversation_send_message(GroupMeAccount *da, guint64 room_id, const gchar *message, gboolean is_dm)
- {
- JsonObject *data = json_object_new();
- JsonObject *msg = json_object_new();
- gchar *url;
- gchar *postdata;
- gchar *stripped = purple_markup_strip_html(message);
- gchar *rid = from_int(room_id);
- gchar *uuid = g_uuid_string_random();
- gchar *guid = g_strdup_printf("groupme-min-%s", uuid);
- g_free(uuid);
- /* Remember we sent it so we don't double display */
- g_hash_table_replace(da->sent_message_ids, guid, TRUE);
- json_object_set_string_member(msg, "text", stripped);
- json_object_set_string_member(msg, "source_guid", guid);
- json_object_set_object_member(data, is_dm ? "direct_message" : "message", msg);
- if (is_dm) {
- json_object_set_string_member(msg, "recipient_id", rid);
- }
- /* DMs use a different endpoint than group messages */
- if (is_dm) {
- url = g_strdup("https://" GROUPME_API_SERVER "/direct_messages?");
- } else {
- url = g_strdup_printf("https://" GROUPME_API_SERVER "/groups/%" G_GUINT64_FORMAT "/messages?", room_id);
- }
- postdata = json_object_to_string(data);
- groupme_fetch_url(da, url, postdata, NULL, NULL);
- g_free(stripped);
- g_free(url);
- g_free(postdata);
- g_free(rid);
- json_object_unref(data);
- return 1;
- }
- static gint
- groupme_chat_send(PurpleConnection *pc, gint id,
- #if PURPLE_VERSION_CHECK(3, 0, 0)
- PurpleMessage *msg)
- {
- const gchar *message = purple_message_get_contents(msg);
- #else
- const gchar *message, PurpleMessageFlags flags)
- {
- #endif
- GroupMeAccount *da;
- PurpleChatConversation *chatconv;
- gint ret;
- da = purple_connection_get_protocol_data(pc);
- chatconv = purple_conversations_find_chat(pc, id);
- guint64 *room_id_ptr = purple_conversation_get_data(PURPLE_CONVERSATION(chatconv), "id");
- g_return_val_if_fail(room_id_ptr, -1);
- guint64 room_id = *room_id_ptr;
- ret = groupme_conversation_send_message(da, room_id, message, FALSE);
- if (ret > 0) {
- purple_serv_got_chat_in(pc, groupme_chat_hash(room_id), groupme_resolve_nick(da, da->self_user_id, room_id), PURPLE_MESSAGE_SEND, message, time(NULL));
- }
- return ret;
- }
- static int
- groupme_send_im(PurpleConnection *pc,
- #if PURPLE_VERSION_CHECK(3, 0, 0)
- PurpleMessage *msg)
- {
- const gchar *who = purple_message_get_recipient(msg);
- const gchar *message = purple_message_get_contents(msg);
- #else
- const gchar *who, const gchar *message, PurpleMessageFlags flags)
- {
- #endif
- GroupMeAccount *da = purple_connection_get_protocol_data(pc);
- gchar *room_id = g_hash_table_lookup(da->one_to_ones_rev, who);
- int room_id_i;
- /* Create DM if there isn't one */
- if (room_id == NULL) {
- #if !PURPLE_VERSION_CHECK(3, 0, 0)
- PurpleMessage *msg = purple_message_new_outgoing(who, message, flags);
- #endif
- guint64 uid = to_int(who);
- GroupMeUser *user = groupme_get_user(da, uid);
-
- if (!user) {
- purple_debug_error("groupme", "Bad user: %s\n", who);
- return 1;
- }
-
- /* Cache it */
- g_hash_table_replace(da->one_to_ones, from_int(uid), g_strdup(who));
- g_hash_table_replace(da->one_to_ones_rev, g_strdup(who), from_int(uid));
- room_id_i = user->id;
- } else {
- room_id_i = to_int(room_id);
- }
- return groupme_conversation_send_message(da, room_id_i, message, TRUE);
- }
- static void
- groupme_chat_set_topic(PurpleConnection *pc, int id, const char *topic)
- {
- /* PATCH https:// GROUPME_API_SERVER /api/v6/channels/%s channel */
- /*{ "name" : "test", "position" : 1, "topic" : "new topic", "bitrate" : 64000, "user_limit" : 0 } */
- }
- static void
- groupme_got_avatar(GroupMeAccount *ya, JsonNode *node, gpointer user_data)
- {
- GroupMeUser *user = user_data;
- gchar *username = user->id_s;
- if (node != NULL) {
- JsonObject *response = json_node_get_object(node);
- const gchar *response_str;
- gsize response_len;
- gpointer response_dup;
- response_str = g_dataset_get_data(node, "raw_body");
- response_len = json_object_get_int_member(response, "len");
- response_dup = g_memdup(response_str, response_len);
- purple_buddy_icons_set_for_user(ya->account, username, response_dup, response_len, user->avatar);
- }
- }
- static void
- groupme_get_avatar(GroupMeAccount *da, GroupMeUser *user)
- {
- if (!user) {
- return;
- }
- gchar *username = from_int(user->id);
- const gchar *checksum = purple_buddy_icons_get_checksum_for_user(purple_blist_find_buddy(da->account, username));
- g_free(username);
- if (purple_strequal(checksum, user->avatar)) {
- return;
- }
- /* Disable token for image requests */
- gchar *token = da->token;
- da->token = NULL;
- groupme_fetch_url(da, user->avatar, NULL, groupme_got_avatar, user);
- da->token = token;
- }
- static void
- groupme_get_info(PurpleConnection *pc, const gchar *username)
- {
- GroupMeAccount *da = purple_connection_get_protocol_data(pc);
- gchar *url;
- GroupMeUser *user = groupme_get_user(da, to_int(username));
- if (!user) {
- return;
- }
- PurpleNotifyUserInfo *user_info;
- user_info = purple_notify_user_info_new();
- purple_notify_user_info_add_pair_html(user_info, _("ID"), username);
- purple_notify_user_info_add_pair_html(user_info, _("Name"), user->name);
- purple_notify_user_info_add_pair_html(user_info, _("Avatar"), user->avatar);
- purple_notify_user_info_add_section_break(user_info);
- purple_notify_user_info_add_pair_html(user_info, _("Mutual Groups"), "");
- GHashTableIter iter;
- gpointer key, value;
- g_hash_table_iter_init(&iter, user->guild_memberships);
- while (g_hash_table_iter_next(&iter, &key, &value)) {
- GroupMeGuildMembership *membership = value;
- GroupMeGuild *guild = groupme_get_guild(da, membership->id);
-
- gchar *name = membership->nick;
- gchar *str = g_strdup_printf("%s%s", name, membership->is_op ? "*" : "");
- purple_notify_user_info_add_pair_html(user_info, guild->name, str);
- g_free(str);
- }
- purple_notify_userinfo(da->pc, username, user_info, NULL, NULL);
- }
- static const char *
- groupme_list_icon(PurpleAccount *account, PurpleBuddy *buddy)
- {
- return "groupme";
- }
- static GList *
- groupme_status_types(PurpleAccount *account)
- {
- PurpleStatusType *status = purple_status_type_new_full(PURPLE_STATUS_AVAILABLE, "online", _("Online"), TRUE, FALSE, FALSE);
- return g_list_append(NULL, status);
- }
- static gchar *
- groupme_status_text(PurpleBuddy *buddy)
- {
- return NULL;
- }
- static void
- groupme_block_user(PurpleConnection *pc, const char *who)
- {
- GroupMeAccount *da = purple_connection_get_protocol_data(pc);
- gchar *url;
- GroupMeUser *user = groupme_get_user_fullname(da, who);
- if (!user) {
- return;
- }
- url = g_strdup_printf("https://" GROUPME_API_SERVER "/api/v6/users/@me/relationships/%" G_GUINT64_FORMAT, user->id);
- groupme_fetch_url_with_method(da, "PUT", url, "{\"type\":2}", NULL, NULL);
- g_free(url);
- }
- static void
- groupme_unblock_user(PurpleConnection *pc, const char *who)
- {
- GroupMeAccount *da = purple_connection_get_protocol_data(pc);
- gchar *url;
- GroupMeUser *user = groupme_get_user_fullname(da, who);
- if (!user) {
- return;
- }
- url = g_strdup_printf("https://" GROUPME_API_SERVER "/api/v6/users/@me/relationships/%" G_GUINT64_FORMAT, user->id);
- groupme_fetch_url_with_method(da, "DELETE", url, NULL, NULL, NULL);
- g_free(url);
- }
- const gchar *
- groupme_list_emblem(PurpleBuddy *buddy)
- {
- PurpleAccount *account = purple_buddy_get_account(buddy);
- if (purple_account_is_connected(account)) {
- PurpleConnection *pc = purple_account_get_connection(account);
- GroupMeAccount *da = purple_connection_get_protocol_data(pc);
- GroupMeUser *user = groupme_get_user_fullname(da, purple_buddy_get_name(buddy));
- if (user != NULL) {
- if (user->bot) {
- return "bot";
- }
- }
- }
- return NULL;
- }
- void
- groupme_tooltip_text(PurpleBuddy *buddy, PurpleNotifyUserInfo *user_info, gboolean full)
- {
- PurplePresence *presence = purple_buddy_get_presence(buddy);
- PurpleStatus *status = purple_presence_get_active_status(presence);
- const gchar *message = purple_status_get_attr_string(status, "message");
- purple_notify_user_info_add_pair_html(user_info, _("Status"), purple_status_get_name(status));
- if (message != NULL) {
- gchar *escaped = g_markup_printf_escaped("%s", message);
- purple_notify_user_info_add_pair_html(user_info, _("Playing"), escaped);
- g_free(escaped);
- }
- }
- static GHashTable *
- groupme_get_account_text_table(PurpleAccount *unused)
- {
- GHashTable *table;
- table = g_hash_table_new(g_str_hash, g_str_equal);
- g_hash_table_insert(table, "login_label", (gpointer) _("Email address..."));
- return table;
- }
- static GList *
- groupme_add_account_options(GList *account_options)
- {
- PurpleAccountOption *option;
- option = purple_account_option_bool_new(_("Use status message as in-game info"), "use-status-as-game", FALSE);
- account_options = g_list_append(account_options, option);
- option = purple_account_option_bool_new(_("Auto-create rooms on buddy list"), "populate-blist", TRUE);
- account_options = g_list_append(account_options, option);
- option = purple_account_option_int_new(_("Number of users in a large channel"), "large-channel-count", 20);
- account_options = g_list_append(account_options, option);
- return account_options;
- }
- void
- groupme_join_server_text(gpointer user_data, const gchar *text)
- {
- GroupMeAccount *da = user_data;
- gchar *url;
- const gchar *invite_code;
- invite_code = strrchr(text, '/');
- if (invite_code == NULL) {
- invite_code = text;
- } else {
- invite_code += 1;
- }
- url = g_strdup_printf("https://" GROUPME_API_SERVER "/api/v6/invite/%s", purple_url_encode(invite_code));
- groupme_fetch_url(da, url, "", NULL, NULL);
- g_free(url);
- }
- void
- groupme_join_server(PurpleProtocolAction *action)
- {
- PurpleConnection *pc = purple_protocol_action_get_connection(action);
- GroupMeAccount *da = purple_connection_get_protocol_data(pc);
- purple_request_input(pc, _("Join a server"),
- _("Join a server"),
- _("Enter the join URL here"),
- NULL, FALSE, FALSE, "https://groupme.gg/ABC123",
- _("_Join"), G_CALLBACK(groupme_join_server_text),
- _("_Cancel"), NULL,
- purple_request_cpar_from_connection(pc),
- da);
- }
- static GList *
- groupme_actions(
- #if !PURPLE_VERSION_CHECK(3, 0, 0)
- PurplePlugin *plugin, gpointer context
- #else
- PurpleConnection *pc
- #endif
- )
- {
- GList *m = NULL;
- PurpleProtocolAction *act;
- act = purple_protocol_action_new(_("Join a server..."), groupme_join_server);
- m = g_list_append(m, act);
- return m;
- }
- static PurpleCmdRet
- groupme_cmd_leave(PurpleConversation *conv, const gchar *cmd, gchar **args, gchar **error, gpointer data)
- {
- PurpleConnection *pc = purple_conversation_get_connection(conv);
- int id = purple_chat_conversation_get_id(PURPLE_CHAT_CONVERSATION(conv));
- if (pc == NULL || id == -1) {
- return PURPLE_CMD_RET_FAILED;
- }
- groupme_chat_leave(pc, id);
- return PURPLE_CMD_RET_OK;
- }
- static PurpleCmdRet
- groupme_cmd_nick(PurpleConversation *conv, const gchar *cmd, gchar **args, gchar **error, gpointer data)
- {
- PurpleConnection *pc = purple_conversation_get_connection(conv);
- int id = purple_chat_conversation_get_id(PURPLE_CHAT_CONVERSATION(conv));
- if (pc == NULL || id == -1) {
- return PURPLE_CMD_RET_FAILED;
- }
- groupme_chat_nick(pc, id, args[0]);
- return PURPLE_CMD_RET_OK;
- }
- static gboolean
- plugin_load(PurplePlugin *plugin, GError **error)
- {
- purple_cmd_register("nick", "s", PURPLE_CMD_P_PLUGIN, PURPLE_CMD_FLAG_CHAT |
- PURPLE_CMD_FLAG_PROTOCOL_ONLY | PURPLE_CMD_FLAG_ALLOW_WRONG_ARGS,
- GROUPME_PLUGIN_ID, groupme_cmd_nick,
- _("nick <new nickname>: Changes nickname on a server"), NULL);
- #if 0
- purple_cmd_register("kick", "s", PURPLE_CMD_P_PLUGIN, PURPLE_CMD_FLAG_CHAT |
- PURPLE_CMD_FLAG_PROTOCOL_ONLY | PURPLE_CMD_FLAG_ALLOW_WRONG_ARGS,
- GROUPME_PLUGIN_ID, groupme_slash_command,
- _("kick <username>: Remove someone from channel"), NULL);
- #endif
- purple_cmd_register("leave", "", PURPLE_CMD_P_PLUGIN, PURPLE_CMD_FLAG_CHAT |
- PURPLE_CMD_FLAG_PROTOCOL_ONLY | PURPLE_CMD_FLAG_ALLOW_WRONG_ARGS,
- GROUPME_PLUGIN_ID, groupme_cmd_leave,
- _("leave: Leave the channel"), NULL);
- purple_cmd_register("part", "", PURPLE_CMD_P_PLUGIN, PURPLE_CMD_FLAG_CHAT |
- PURPLE_CMD_FLAG_PROTOCOL_ONLY | PURPLE_CMD_FLAG_ALLOW_WRONG_ARGS,
- GROUPME_PLUGIN_ID, groupme_cmd_leave,
- _("part: Leave the channel"), NULL);
- #if 0
- purple_cmd_register("mute", "s", PURPLE_CMD_P_PLUGIN, PURPLE_CMD_FLAG_CHAT |
- PURPLE_CMD_FLAG_PROTOCOL_ONLY | PURPLE_CMD_FLAG_ALLOW_WRONG_ARGS,
- GROUPME_PLUGIN_ID, groupme_slash_command,
- _("mute <username>: Mute someone in channel"), NULL);
- purple_cmd_register("unmute", "s", PURPLE_CMD_P_PLUGIN, PURPLE_CMD_FLAG_CHAT |
- PURPLE_CMD_FLAG_PROTOCOL_ONLY | PURPLE_CMD_FLAG_ALLOW_WRONG_ARGS,
- GROUPME_PLUGIN_ID, groupme_slash_command,
- _("unmute <username>: Un-mute someone in channel"), NULL);
- purple_cmd_register("topic", "s", PURPLE_CMD_P_PLUGIN, PURPLE_CMD_FLAG_CHAT |
- PURPLE_CMD_FLAG_PROTOCOL_ONLY | PURPLE_CMD_FLAG_ALLOW_WRONG_ARGS,
- GROUPME_PLUGIN_ID, groupme_slash_command,
- _("topic <description>: Set the channel topic description"), NULL);
- #endif
- return TRUE;
- }
- static gboolean
- plugin_unload(PurplePlugin *plugin, GError **error)
- {
- purple_signals_disconnect_by_handle(plugin);
- return TRUE;
- }
- /* Purple2 Plugin Load Functions */
- #if !PURPLE_VERSION_CHECK(3, 0, 0)
- static gboolean
- libpurple2_plugin_load(PurplePlugin *plugin)
- {
- return plugin_load(plugin, NULL);
- }
- static gboolean
- libpurple2_plugin_unload(PurplePlugin *plugin)
- {
- return plugin_unload(plugin, NULL);
- }
- static void
- plugin_init(PurplePlugin *plugin)
- {
- #ifdef ENABLE_NLS
- bindtextdomain(GETTEXT_PACKAGE, LOCALEDIR);
- bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8");
- #endif
- PurplePluginInfo *info;
- PurplePluginProtocolInfo *prpl_info = g_new0(PurplePluginProtocolInfo, 1);
- info = plugin->info;
- if (info == NULL) {
- plugin->info = info = g_new0(PurplePluginInfo, 1);
- }
- info->extra_info = prpl_info;
- #if PURPLE_MINOR_VERSION >= 5
- prpl_info->struct_size = sizeof(PurplePluginProtocolInfo);
- #endif
- prpl_info->options = OPT_PROTO_CHAT_TOPIC | OPT_PROTO_SLASH_COMMANDS_NATIVE | OPT_PROTO_UNIQUE_CHATNAME;
- prpl_info->protocol_options = groupme_add_account_options(prpl_info->protocol_options);
- prpl_info->icon_spec.format = "png,gif,jpeg";
- prpl_info->icon_spec.min_width = 0;
- prpl_info->icon_spec.min_height = 0;
- prpl_info->icon_spec.max_width = 96;
- prpl_info->icon_spec.max_height = 96;
- prpl_info->icon_spec.max_filesize = 0;
- prpl_info->icon_spec.scale_rules = PURPLE_ICON_SCALE_DISPLAY;
- prpl_info->get_account_text_table = groupme_get_account_text_table;
- prpl_info->list_emblem = groupme_list_emblem;
- prpl_info->status_text = groupme_status_text;
- prpl_info->tooltip_text = groupme_tooltip_text;
- prpl_info->list_icon = groupme_list_icon;
- prpl_info->status_types = groupme_status_types;
- prpl_info->chat_info = groupme_chat_info;
- prpl_info->chat_info_defaults = groupme_chat_info_defaults;
- prpl_info->login = groupme_login;
- prpl_info->close = groupme_close;
- prpl_info->send_im = groupme_send_im;
- prpl_info->send_typing = groupme_send_typing;
- prpl_info->join_chat = groupme_join_chat;
- prpl_info->get_chat_name = groupme_get_chat_name;
- prpl_info->find_blist_chat = groupme_find_chat;
- prpl_info->chat_invite = groupme_chat_invite;
- prpl_info->chat_send = groupme_chat_send;
- prpl_info->set_chat_topic = groupme_chat_set_topic;
- prpl_info->get_cb_real_name = groupme_get_real_name;
- prpl_info->get_info = groupme_get_info;
- prpl_info->add_deny = groupme_block_user;
- prpl_info->rem_deny = groupme_unblock_user;
- prpl_info->roomlist_get_list = groupme_roomlist_get_list;
- prpl_info->roomlist_room_serialize = groupme_roomlist_serialize;
- }
- static PurplePluginInfo info = {
- PURPLE_PLUGIN_MAGIC,
- /* PURPLE_MAJOR_VERSION,
- PURPLE_MINOR_VERSION,
- */
- 2, 1,
- PURPLE_PLUGIN_PROTOCOL, /* type */
- NULL, /* ui_requirement */
- 0, /* flags */
- NULL, /* dependencies */
- PURPLE_PRIORITY_DEFAULT, /* priority */
- GROUPME_PLUGIN_ID, /* id */
- "GroupMe", /* name */
- GROUPME_PLUGIN_VERSION, /* version */
- "", /* summary */
- "", /* description */
- "Alyssa Rosenzweig <alyssa@rosenzweig.io>", /* author */
- GROUPME_PLUGIN_WEBSITE, /* homepage */
- libpurple2_plugin_load, /* load */
- libpurple2_plugin_unload, /* unload */
- NULL, /* destroy */
- NULL, /* ui_info */
- NULL, /* extra_info */
- NULL, /* prefs_info */
- groupme_actions, /* actions */
- NULL, /* padding */
- NULL,
- NULL,
- NULL
- };
- PURPLE_INIT_PLUGIN(groupme, plugin_init, info);
- #else
- /* Purple 3 plugin load functions */
- G_MODULE_EXPORT GType groupme_protocol_get_type(void);
- #define GROUPME_TYPE_PROTOCOL (groupme_protocol_get_type())
- #define GROUPME_PROTOCOL(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), GROUPME_TYPE_PROTOCOL, GroupMeProtocol))
- #define GROUPME_PROTOCOL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), GROUPME_TYPE_PROTOCOL, GroupMeProtocolClass))
- #define GROUPME_IS_PROTOCOL(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), GROUPME_TYPE_PROTOCOL))
- #define GROUPME_IS_PROTOCOL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), GROUPME_TYPE_PROTOCOL))
- #define GROUPME_PROTOCOL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), GROUPME_TYPE_PROTOCOL, GroupMeProtocolClass))
- typedef struct _GroupMeProtocol {
- PurpleProtocol parent;
- } GroupMeProtocol;
- typedef struct _GroupMeProtocolClass {
- PurpleProtocolClass parent_class;
- } GroupMeProtocolClass;
- static void
- groupme_protocol_init(PurpleProtocol *prpl_info)
- {
- PurpleProtocol *info = prpl_info;
- info->id = GROUPME_PLUGIN_ID;
- info->name = "GroupMe";
- info->options = OPT_PROTO_CHAT_TOPIC | OPT_PROTO_SLASH_COMMANDS_NATIVE | OPT_PROTO_UNIQUE_CHATNAME;
- info->account_options = groupme_add_account_options(info->account_options);
- }
- static void
- groupme_protocol_class_init(PurpleProtocolClass *prpl_info)
- {
- prpl_info->login = groupme_login;
- prpl_info->close = groupme_close;
- prpl_info->status_types = groupme_status_types;
- prpl_info->list_icon = groupme_list_icon;
- }
- static void
- groupme_protocol_im_iface_init(PurpleProtocolIMIface *prpl_info)
- {
- prpl_info->send = groupme_send_im;
- prpl_info->send_typing = groupme_send_typing;
- }
- static void
- groupme_protocol_chat_iface_init(PurpleProtocolChatIface *prpl_info)
- {
- prpl_info->send = groupme_chat_send;
- prpl_info->info = groupme_chat_info;
- prpl_info->info_defaults = groupme_chat_info_defaults;
- prpl_info->join = groupme_join_chat;
- prpl_info->get_name = groupme_get_chat_name;
- prpl_info->invite = groupme_chat_invite;
- prpl_info->set_topic = groupme_chat_set_topic;
- prpl_info->get_user_real_name = groupme_get_real_name;
- }
- static void
- groupme_protocol_server_iface_init(PurpleProtocolServerIface *prpl_info)
- {
- prpl_info->get_info = groupme_get_info;
- }
- static void
- groupme_protocol_client_iface_init(PurpleProtocolClientIface *prpl_info)
- {
- prpl_info->get_account_text_table = groupme_get_account_text_table;
- prpl_info->status_text = groupme_status_text;
- prpl_info->get_actions = groupme_actions;
- prpl_info->list_emblem = groupme_list_emblem;
- prpl_info->tooltip_text = groupme_tooltip_text;
- prpl_info->find_blist_chat = groupme_find_chat;
- }
- static void
- groupme_protocol_privacy_iface_init(PurpleProtocolPrivacyIface *prpl_info)
- {
- prpl_info->add_deny = groupme_block_user;
- prpl_info->rem_deny = groupme_unblock_user;
- }
- static void
- groupme_protocol_roomlist_iface_init(PurpleProtocolRoomlistIface *prpl_info)
- {
- prpl_info->get_list = groupme_roomlist_get_list;
- prpl_info->room_serialize = groupme_roomlist_serialize;
- }
- static PurpleProtocol *groupme_protocol;
- PURPLE_DEFINE_TYPE_EXTENDED(
- GroupMeProtocol, groupme_protocol, PURPLE_TYPE_PROTOCOL, 0,
- PURPLE_IMPLEMENT_INTERFACE_STATIC(PURPLE_TYPE_PROTOCOL_IM_IFACE,
- groupme_protocol_im_iface_init)
- PURPLE_IMPLEMENT_INTERFACE_STATIC(PURPLE_TYPE_PROTOCOL_CHAT_IFACE,
- groupme_protocol_chat_iface_init)
- PURPLE_IMPLEMENT_INTERFACE_STATIC(PURPLE_TYPE_PROTOCOL_SERVER_IFACE,
- groupme_protocol_server_iface_init)
- PURPLE_IMPLEMENT_INTERFACE_STATIC(PURPLE_TYPE_PROTOCOL_CLIENT_IFACE,
- groupme_protocol_client_iface_init)
-
- PURPLE_IMPLEMENT_INTERFACE_STATIC(PURPLE_TYPE_PROTOCOL_PRIVACY_IFACE,
- groupme_protocol_privacy_iface_init)
- PURPLE_IMPLEMENT_INTERFACE_STATIC(PURPLE_TYPE_PROTOCOL_ROOMLIST_IFACE,
- groupme_protocol_roomlist_iface_init)
- );
- static gboolean
- libpurple3_plugin_load(PurplePlugin *plugin, GError **error)
- {
- groupme_protocol_register_type(plugin);
- groupme_protocol = purple_protocols_add(GROUPME_TYPE_PROTOCOL, error);
- if (!groupme_protocol) {
- return FALSE;
- }
- return plugin_load(plugin, error);
- }
- static gboolean
- libpurple3_plugin_unload(PurplePlugin *plugin, GError **error)
- {
- if (!plugin_unload(plugin, error)) {
- return FALSE;
- }
- if (!purple_protocols_remove(groupme_protocol, error)) {
- return FALSE;
- }
- return TRUE;
- }
- static PurplePluginInfo *
- plugin_query(GError **error)
- {
- #ifdef ENABLE_NLS
- bindtextdomain(GETTEXT_PACKAGE, LOCALEDIR);
- bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8");
- #endif
-
- return purple_plugin_info_new(
- "id", GROUPME_PLUGIN_ID,
- "name", "GroupMe",
- "version", GROUPME_PLUGIN_VERSION,
- "category", _("Protocol"),
- "summary", _("GroupMe Protocol Plugins."),
- "description", _("Adds GroupMe protocol support to libpurple."),
- "website", GROUPME_PLUGIN_WEBSITE,
- "abi-version", PURPLE_ABI_VERSION,
- "flags", PURPLE_PLUGIN_INFO_FLAGS_INTERNAL |
- PURPLE_PLUGIN_INFO_FLAGS_AUTO_LOAD,
- NULL);
- }
- PURPLE_PLUGIN_INIT(groupme, plugin_query, libpurple3_plugin_load, libpurple3_plugin_unload);
- #endif
|