Changeset 73ee390


Ignore:
Timestamp:
2015-01-26T03:46:03Z (10 years ago)
Author:
jgeboski <jgeboski@…>
Branches:
master
Children:
dfaa46d
Parents:
5eab298f
git-author:
jgeboski <jgeboski@…> (15-12-14 12:17:12)
git-committer:
jgeboski <jgeboski@…> (26-01-15 03:46:03)
Message:

twitter: implemented filter based group chats

Filter group chats allow for the ability to read the tweets of select
users without actually following the users, and/or track keywords or
hashtags. A filter group chat can have multiple users, keywords, or
hashtags. These users, keywords, or hashtags can span multiple group
chats. This allows for rather robust filter organization.

The underlying structure for the filters is based on linked list, as
using the glib hash tables requires >= glib-2.16 for sanity. Since the
glib requirement of bitlbee is only 2.14, linked list are used in order
to prevent an overly complex implementation.

The idea for this patch was inspired by Artem Savkov's "Twitter search
channels" patch.

In order to use the filter group chats, a group chat must be added to
the twitter account. The channel room name is either follow:username,
track:keyword, and/or track:#hashtag. Multiple elements can be used by
separating each element by a semicolon.

Location:
protocols/twitter
Files:
4 edited

Legend:

Unmodified
Added
Removed
  • protocols/twitter/twitter.c

    r5eab298f r73ee390  
    3131
    3232GSList *twitter_connections = NULL;
     33
     34static int twitter_filter_cmp(struct twitter_filter *tf1,
     35                              struct twitter_filter *tf2)
     36{
     37        int i1 = 0;
     38        int i2 = 0;
     39        int i;
     40
     41        static const twitter_filter_type_t types[] = {
     42                /* Order of the types */
     43                TWITTER_FILTER_TYPE_FOLLOW,
     44                TWITTER_FILTER_TYPE_TRACK
     45        };
     46
     47        for (i = 0; i < G_N_ELEMENTS(types); i++) {
     48                if (types[i] == tf1->type) {
     49                        i1 = i + 1;
     50                        break;
     51                }
     52        }
     53
     54        for (i = 0; i < G_N_ELEMENTS(types); i++) {
     55                if (types[i] == tf2->type) {
     56                        i2 = i + 1;
     57                        break;
     58                }
     59        }
     60
     61        if (i1 != i2) {
     62                /* With different types, return their difference */
     63                return i1 - i2;
     64        }
     65
     66        /* With the same type, return the text comparison */
     67        return g_strcasecmp(tf1->text, tf2->text);
     68}
     69
     70static gboolean twitter_filter_update(gpointer data, gint fd,
     71                                      b_input_condition cond)
     72{
     73        struct im_connection *ic = data;
     74        struct twitter_data *td = ic->proto_data;
     75
     76        if (td->filters) {
     77                twitter_open_filter_stream(ic);
     78        } else if (td->filter_stream) {
     79                http_close(td->filter_stream);
     80                td->filter_stream = NULL;
     81        }
     82
     83        td->filter_update_id = 0;
     84        return FALSE;
     85}
     86
     87static struct twitter_filter *twitter_filter_get(struct groupchat *c,
     88                                                 twitter_filter_type_t type,
     89                                                 const char *text)
     90{
     91        struct twitter_data *td = c->ic->proto_data;
     92        struct twitter_filter *tf = NULL;
     93        struct twitter_filter tfc = {type, (char*) text};
     94        GSList *l;
     95
     96        for (l = td->filters; l; l = g_slist_next(l)) {
     97                tf = l->data;
     98
     99                if (twitter_filter_cmp(tf, &tfc) == 0)
     100                        break;
     101
     102                tf = NULL;
     103        }
     104
     105        if (!tf) {
     106                tf = g_new0(struct twitter_filter, 1);
     107                tf->type = type;
     108                tf->text = g_strdup(text);
     109                td->filters = g_slist_prepend(td->filters, tf);
     110        }
     111
     112        if (!g_slist_find(tf->groupchats, c))
     113                tf->groupchats = g_slist_prepend(tf->groupchats, c);
     114
     115        if (td->filter_update_id > 0)
     116                b_event_remove(td->filter_update_id);
     117
     118        /* Wait for other possible filter changes to avoid request spam */
     119        td->filter_update_id = b_timeout_add(TWITTER_FILTER_UPDATE_WAIT,
     120                                             twitter_filter_update, c->ic);
     121        return tf;
     122}
     123
     124static void twitter_filter_free(struct twitter_filter *tf)
     125{
     126        g_slist_free(tf->groupchats);
     127        g_free(tf->text);
     128        g_free(tf);
     129}
     130
     131static void twitter_filter_remove(struct groupchat *c)
     132{
     133        struct twitter_data *td = c->ic->proto_data;
     134        struct twitter_filter *tf;
     135        GSList *l = td->filters;
     136        GSList *p;
     137
     138        while (l != NULL) {
     139                tf = l->data;
     140                tf->groupchats = g_slist_remove(tf->groupchats, c);
     141
     142                p = l;
     143                l = g_slist_next(l);
     144
     145                if (!tf->groupchats) {
     146                        twitter_filter_free(tf);
     147                        td->filters = g_slist_delete_link(td->filters, p);
     148                }
     149        }
     150
     151        if (td->filter_update_id > 0)
     152                b_event_remove(td->filter_update_id);
     153
     154        /* Wait for other possible filter changes to avoid request spam */
     155        td->filter_update_id = b_timeout_add(TWITTER_FILTER_UPDATE_WAIT,
     156                                             twitter_filter_update, c->ic);}
     157
     158static void twitter_filter_remove_all(struct im_connection *ic)
     159{
     160        struct twitter_data *td = ic->proto_data;
     161        GSList *chats = NULL;
     162        struct twitter_filter *tf;
     163        GSList *l = td->filters;
     164        GSList *p;
     165
     166        while (l != NULL) {
     167                tf = l->data;
     168
     169                /* Build up a list of groupchats to be freed */
     170                for (p = tf->groupchats; p; p = g_slist_next(p)) {
     171                        if (!g_slist_find(chats, p->data))
     172                                chats = g_slist_prepend(chats, p->data);
     173                }
     174
     175                p = l;
     176                l = g_slist_next(l);
     177                twitter_filter_free(p->data);
     178                td->filters = g_slist_delete_link(td->filters, p);
     179        }
     180
     181        l = chats;
     182
     183        while (l != NULL) {
     184                p = l;
     185                l = g_slist_next(l);
     186
     187                /* Freed each remaining groupchat */
     188                imcb_chat_free(p->data);
     189                chats = g_slist_delete_link(chats, p);
     190        }
     191
     192        if (td->filter_stream) {
     193                http_close(td->filter_stream);
     194                td->filter_stream = NULL;
     195        }
     196}
     197
     198static GSList *twitter_filter_parse(struct groupchat *c, const char *text)
     199{
     200        char **fs = g_strsplit(text, ";", 0);
     201        GSList *ret = NULL;
     202        struct twitter_filter *tf;
     203        char **f;
     204        char *v;
     205        int i;
     206        int t;
     207
     208        static const twitter_filter_type_t types[] = {
     209                TWITTER_FILTER_TYPE_FOLLOW,
     210                TWITTER_FILTER_TYPE_TRACK
     211        };
     212
     213        static const char *typestrs[] = {
     214                "follow",
     215                "track"
     216        };
     217
     218        for (f = fs; *f; f++) {
     219                if ((v = strchr(*f, ':')) == NULL)
     220                        continue;
     221
     222                *(v++) = 0;
     223
     224                for (t = -1, i = 0; i < G_N_ELEMENTS(types); i++) {
     225                        if (g_strcasecmp(typestrs[i], *f) == 0) {
     226                                t = i;
     227                                break;
     228                        }
     229                }
     230
     231                if (t < 0 || strlen(v) == 0)
     232                        continue;
     233
     234                tf = twitter_filter_get(c, types[t], v);
     235                ret = g_slist_prepend(ret, tf);
     236        }
     237
     238        g_strfreev(fs);
     239        return ret;
     240}
     241
    33242/**
    34243 * Main loop function
     
    436645
    437646        if (td) {
     647                if (td->filter_update_id > 0)
     648                        b_event_remove(td->filter_update_id);
     649
    438650                http_close(td->stream);
     651                twitter_filter_remove_all(ic);
    439652                oauth_info_free(td->oauth_info);
    440653                g_free(td->user);
     
    509722}
    510723
     724static struct groupchat *twitter_chat_join(struct im_connection *ic,
     725                                           const char *room, const char *nick,
     726                                           const char *password, set_t **sets)
     727{
     728        struct groupchat *c = imcb_chat_new(ic, room);
     729        GSList *fs = twitter_filter_parse(c, room);
     730        GString *topic = g_string_new("");
     731        struct twitter_filter *tf;
     732        GSList *l;
     733
     734        fs = g_slist_sort(fs, (GCompareFunc) twitter_filter_cmp);
     735
     736        for (l = fs; l; l = g_slist_next(l)) {
     737                tf = l->data;
     738
     739                if (topic->len > 0)
     740                        g_string_append(topic, ", ");
     741
     742                if (tf->type == TWITTER_FILTER_TYPE_FOLLOW)
     743                        g_string_append_c(topic, '@');
     744
     745                g_string_append(topic, tf->text);
     746        }
     747
     748        if (topic->len > 0)
     749                g_string_prepend(topic, "Twitter Filter: ");
     750
     751        imcb_chat_topic(c, NULL, topic->str, 0);
     752        imcb_chat_add_buddy(c, ic->acc->user);
     753
     754        if (topic->len == 0) {
     755                imcb_error(ic, "Failed to handle any filters");
     756                imcb_chat_free(c);
     757                c = NULL;
     758        }
     759
     760        g_string_free(topic, TRUE);
     761        g_slist_free(fs);
     762
     763        return c;
     764}
     765
    511766static void twitter_chat_leave(struct groupchat *c)
    512767{
    513768        struct twitter_data *td = c->ic->proto_data;
    514769
    515         if (c != td->timeline_gc)
    516                 return;         /* WTF? */
     770        if (c != td->timeline_gc) {
     771                twitter_filter_remove(c);
     772                imcb_chat_free(c);
     773                return;
     774        }
    517775
    518776        /* If the user leaves the channel: Fine. Rejoin him/her once new
     
    7481006        ret->chat_msg = twitter_chat_msg;
    7491007        ret->chat_invite = twitter_chat_invite;
     1008        ret->chat_join = twitter_chat_join;
    7501009        ret->chat_leave = twitter_chat_leave;
    7511010        ret->keepalive = twitter_keepalive;
  • protocols/twitter/twitter.h

    r5eab298f r73ee390  
    4545} twitter_flags_t;
    4646
     47typedef enum
     48{
     49        TWITTER_FILTER_TYPE_FOLLOW = 0,
     50        TWITTER_FILTER_TYPE_TRACK
     51} twitter_filter_type_t;
     52
    4753struct twitter_log_data;
    4854
     
    5864
    5965        GSList *follow_ids;
     66        GSList *filters;
    6067       
    6168        guint64 last_status_id; /* For undo */
    6269        gint main_loop_id;
     70        gint filter_update_id;
    6371        struct http_request *stream;
     72        struct http_request *filter_stream;
    6473        struct groupchat *timeline_gc;
    6574        gint http_fails;
     
    7786        struct twitter_log_data *log;
    7887        int log_id;
     88};
     89
     90#define TWITTER_FILTER_UPDATE_WAIT 3000
     91struct twitter_filter
     92{
     93        twitter_filter_type_t type;
     94        char *text;
     95        guint64 uid;
     96        GSList *groupchats;
    7997};
    8098
  • protocols/twitter/twitter_lib.c

    r5eab298f r73ee390  
    5151
    5252struct twitter_xml_user {
     53        guint64 uid;
    5354        char *name;
    5455        char *screen_name;
     
    6162        guint64 id, rt_id; /* Usually equal, with RTs id == *original* id */
    6263        guint64 reply_to;
     64        gboolean from_filter;
    6365};
    6466
     
    392394{
    393395        struct twitter_xml_user *txu;
     396        json_value *jv;
    394397       
    395398        txu = g_new0(struct twitter_xml_user, 1);
    396399        txu->name = g_strdup(json_o_str(node, "name"));
    397400        txu->screen_name = g_strdup(json_o_str(node, "screen_name"));
     401       
     402        jv = json_o_get(node, "id");
     403        txu->uid = jv->u.integer;
    398404       
    399405        return txu;
     
    657663
    658664/**
     665 * Function that is called to see the filter statuses in groupchat windows.
     666 */
     667static void twitter_status_show_filter(struct im_connection *ic, struct twitter_xml_status *status)
     668{
     669        struct twitter_data *td = ic->proto_data;
     670        char *msg = twitter_msg_add_id(ic, status, "");
     671        struct twitter_filter *tf;
     672        GSList *f;
     673        GSList *l;
     674
     675        for (f = td->filters; f; f = g_slist_next(f)) {
     676                tf = f->data;
     677
     678                switch (tf->type) {
     679                case TWITTER_FILTER_TYPE_FOLLOW:
     680                        if (status->user->uid != tf->uid)
     681                                continue;
     682                        break;
     683
     684                case TWITTER_FILTER_TYPE_TRACK:
     685                        if (strcasestr(status->text, tf->text) == NULL)
     686                                continue;
     687                        break;
     688
     689                default:
     690                        continue;
     691                }
     692
     693                for (l = tf->groupchats; l; l = g_slist_next(l)) {
     694                        imcb_chat_msg(l->data, status->user->screen_name,
     695                                      msg ? msg : status->text, 0, 0);
     696                }
     697        }
     698
     699        g_free(msg);
     700}
     701
     702/**
    659703 * Function that is called to see the statuses in a groupchat window.
    660704 */
     
    731775                strip_newlines(status->text);
    732776       
    733         if (td->flags & TWITTER_MODE_CHAT)
     777        if (status->from_filter)
     778                twitter_status_show_filter(ic, status);
     779        else if (td->flags & TWITTER_MODE_CHAT)
    734780                twitter_status_show_chat(ic, status);
    735781        else
     
    745791}
    746792
    747 static gboolean twitter_stream_handle_object(struct im_connection *ic, json_value *o);
     793static gboolean twitter_stream_handle_object(struct im_connection *ic, json_value *o, gboolean from_filter);
    748794
    749795static void twitter_http_stream(struct http_request *req)
     
    754800        int len = 0;
    755801        char c, *nl;
     802        gboolean from_filter;
    756803       
    757804        if (!g_slist_find(twitter_connections, ic))
     
    762809       
    763810        if ((req->flags & HTTPC_EOF) || !req->reply_body) {
    764                 td->stream = NULL;
     811                if (req == td->stream)
     812                        td->stream = NULL;
     813                else if (req == td->filter_stream)
     814                        td->filter_stream = NULL;
     815
    765816                imcb_error(ic, "Stream closed (%s)", req->status_string);
    766817                imc_logout(ic, TRUE);
     
    779830               
    780831                if ((parsed = json_parse(req->reply_body, req->body_size))) {
    781                         twitter_stream_handle_object(ic, parsed);
     832                        from_filter = (req == td->filter_stream);
     833                        twitter_stream_handle_object(ic, parsed, from_filter);
    782834                }
    783835                json_value_free(parsed);
     
    795847static gboolean twitter_stream_handle_status(struct im_connection *ic, struct twitter_xml_status *txs);
    796848
    797 static gboolean twitter_stream_handle_object(struct im_connection *ic, json_value *o)
     849static gboolean twitter_stream_handle_object(struct im_connection *ic, json_value *o, gboolean from_filter)
    798850{
    799851        struct twitter_data *td = ic->proto_data;
     
    802854       
    803855        if ((txs = twitter_xt_get_status(o))) {
     856                txs->from_filter = from_filter;
    804857                gboolean ret = twitter_stream_handle_status(ic, txs);
    805858                txs_free(txs);
     
    899952}
    900953
     954static gboolean twitter_filter_stream(struct im_connection *ic)
     955{
     956        struct twitter_data *td = ic->proto_data;
     957        char *args[4] = {"follow", NULL, "track", NULL};
     958        GString *followstr = g_string_new("");
     959        GString *trackstr = g_string_new("");
     960        gboolean ret = FALSE;
     961        struct twitter_filter *tf;
     962        GSList *l;
     963
     964        for (l = td->filters; l; l = g_slist_next(l)) {
     965                tf = l->data;
     966
     967                switch (tf->type) {
     968                case TWITTER_FILTER_TYPE_FOLLOW:
     969                        if (followstr->len > 0)
     970                                g_string_append_c(followstr, ',');
     971
     972                        g_string_append_printf(followstr, "%" G_GUINT64_FORMAT,
     973                                               tf->uid);
     974                        break;
     975
     976                case TWITTER_FILTER_TYPE_TRACK:
     977                        if (trackstr->len > 0)
     978                                g_string_append_c(trackstr, ',');
     979
     980                        g_string_append(trackstr, tf->text);
     981                        break;
     982
     983                default:
     984                        continue;
     985                }
     986        }
     987
     988        args[1] = followstr->str;
     989        args[3] = trackstr->str;
     990
     991        if (td->filter_stream)
     992                http_close(td->filter_stream);
     993
     994        if ((td->filter_stream = twitter_http(ic, TWITTER_FILTER_STREAM_URL,
     995                                              twitter_http_stream, ic, 0,
     996                                              args, 4))) {
     997                /* This flag must be enabled or we'll get no data until EOF
     998                   (which err, kind of, defeats the purpose of a streaming API). */
     999                td->filter_stream->flags |= HTTPC_STREAMING;
     1000                ret = TRUE;
     1001        }
     1002
     1003        g_string_free(followstr, TRUE);
     1004        g_string_free(trackstr, TRUE);
     1005
     1006        return ret;
     1007}
     1008
     1009static void twitter_filter_users_post(struct http_request *req)
     1010{
     1011        struct im_connection *ic = req->data;
     1012        struct twitter_data *td;
     1013        struct twitter_filter *tf;
     1014        GList *users = NULL;
     1015        json_value *parsed;
     1016        json_value *id;
     1017        const char *name;
     1018        GString *fstr;
     1019        GSList *l;
     1020        GList *u;
     1021        int i;
     1022
     1023        // Check if the connection is still active.
     1024        if (!g_slist_find(twitter_connections, ic))
     1025                return;
     1026
     1027        td = ic->proto_data;
     1028
     1029        if (!(parsed = twitter_parse_response(ic, req)))
     1030                return;
     1031
     1032        for (l = td->filters; l; l = g_slist_next(l)) {
     1033                tf = l->data;
     1034
     1035                if (tf->type == TWITTER_FILTER_TYPE_FOLLOW)
     1036                        users = g_list_prepend(users, tf);
     1037        }
     1038
     1039        if (parsed->type != json_array)
     1040                goto finish;
     1041
     1042        for (i = 0; i < parsed->u.array.length; i++) {
     1043                id = json_o_get(parsed->u.array.values[i], "id");
     1044                name = json_o_str(parsed->u.array.values[i], "screen_name");
     1045
     1046                if (!name || !id || id->type != json_integer)
     1047                        continue;
     1048
     1049                for (u = users; u; u = g_list_next(u)) {
     1050                        tf = u->data;
     1051
     1052                        if (g_strcasecmp(tf->text, name) == 0) {
     1053                                tf->uid = id->u.integer;
     1054                                users = g_list_delete_link(users, u);
     1055                                break;
     1056                        }
     1057                }
     1058        }
     1059
     1060finish:
     1061        json_value_free(parsed);
     1062        twitter_filter_stream(ic);
     1063
     1064        if (!users)
     1065                return;
     1066
     1067        fstr = g_string_new("");
     1068
     1069        for (u = users; u; u = g_list_next(u)) {
     1070                if (fstr->len > 0)
     1071                        g_string_append(fstr, ", ");
     1072
     1073                g_string_append(fstr, tf->text);
     1074        }
     1075
     1076        imcb_error(ic, "Failed UID acquisitions: %s", fstr->str);
     1077
     1078        g_string_free(fstr, TRUE);
     1079        g_list_free(users);
     1080}
     1081
     1082gboolean twitter_open_filter_stream(struct im_connection *ic)
     1083{
     1084        struct twitter_data *td = ic->proto_data;
     1085        char *args[2] = {"screen_name", NULL};
     1086        GString *ustr = g_string_new("");
     1087        struct twitter_filter *tf;
     1088        struct http_request *req;
     1089        GSList *l;
     1090
     1091        for (l = td->filters; l; l = g_slist_next(l)) {
     1092                tf = l->data;
     1093
     1094                if (tf->type != TWITTER_FILTER_TYPE_FOLLOW || tf->uid != 0)
     1095                        continue;
     1096
     1097                if (ustr->len > 0)
     1098                        g_string_append_c(ustr, ',');
     1099
     1100                g_string_append(ustr, tf->text);
     1101        }
     1102
     1103        if (ustr->len == 0) {
     1104                g_string_free(ustr, TRUE);
     1105                return twitter_filter_stream(ic);
     1106        }
     1107
     1108        args[1] = ustr->str;
     1109        req = twitter_http(ic, TWITTER_USERS_LOOKUP_URL,
     1110                           twitter_filter_users_post,
     1111                           ic, 0, args, 2);
     1112
     1113        g_string_free(ustr, TRUE);
     1114        return req != NULL;
     1115}
     1116
    9011117static void twitter_get_home_timeline(struct im_connection *ic, gint64 next_cursor);
    9021118static void twitter_get_mentions(struct im_connection *ic, gint64 next_cursor);
  • protocols/twitter/twitter_lib.h

    r5eab298f r73ee390  
    7979#define TWITTER_REPORT_SPAM_URL "/users/report_spam.json"
    8080
     81/* Stream URLs */
    8182#define TWITTER_USER_STREAM_URL "https://userstream.twitter.com/1.1/user.json"
     83#define TWITTER_FILTER_STREAM_URL "https://stream.twitter.com/1.1/statuses/filter.json"
    8284
    8385gboolean twitter_open_stream(struct im_connection *ic);
     86gboolean twitter_open_filter_stream(struct im_connection *ic);
    8487gboolean twitter_get_timeline(struct im_connection *ic, gint64 next_cursor);
    8588void twitter_get_friends_ids(struct im_connection *ic, gint64 next_cursor);
Note: See TracChangeset for help on using the changeset viewer.