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.

File:
1 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;
Note: See TracChangeset for help on using the changeset viewer.