Changes in / [fd213fe:12fe5ea]


Ignore:
Files:
19 edited

Legend:

Unmodified
Added
Removed
  • .travis.yml

    rfd213fe r12fe5ea  
    11language: c
    2 script: dpkg-buildpackage -uc -us
     2script: ./configure && make check && dpkg-buildpackage -uc -us
    33before_install:
    44 - sudo apt-get update -qq
    5  - sudo apt-get install --no-install-recommends -qq asciidoc xsltproc xmlto lynx check libevent-dev libpurple-dev
     5 - sudo apt-get install --no-install-recommends -qq asciidoc xsltproc xmlto lynx check libevent-dev libpurple-dev check
    66 - wget http://dump.dequis.org/indexed/bitlbee-travis-libs/libotr5{,-dev}_4.1.0-2~bpo70+1_amd64.deb
    77 - sudo dpkg -i *.deb
  • bitlbee.c

    rfd213fe r12fe5ea  
    329329                        irc_t *irc;
    330330                       
    331                         /* Since we're fork()ing here, let's make sure we won't
    332                            get the same random numbers as the parent/siblings. */
    333                         srand( time( NULL ) ^ getpid() );
    334                        
    335331                        b_main_init();
    336332                       
  • doc/user-guide/commands.xml

    rfd213fe r12fe5ea  
    847847                                <varlistentry><term>rt &lt;screenname|#id&gt;</term><listitem><para>Retweet someone's last Tweet (or one with the given ID)</para></listitem></varlistentry>
    848848                                <varlistentry><term>reply &lt;screenname|#id&gt;</term><listitem><para>Reply to a Tweet (with a reply-to reference)</para></listitem></varlistentry>
     849                                <varlistentry><term>rawreply &lt;screenname|#id&gt;</term><listitem><para>Reply to a Tweet (with no reply-to reference)</para></listitem></varlistentry>
    849850                                <varlistentry><term>report &lt;screenname|#id&gt;</term><listitem><para>Report the given user (or the user who posted the tweet with the given ID) for sending spam. This will also block them.</para></listitem></varlistentry>
    850851                                <varlistentry><term>follow &lt;screenname&gt;</term><listitem><para>Start following a person</para></listitem></varlistentry>
     
    888889                <description>
    889890                        <para>
    890                                 Currently only available for MSN connections. This setting allows you to read and change your "friendly name" for this connection. Since this is a server-side setting, it can't be changed when the account is off-line.
     891                                Currently only available for MSN connections, and for jabber groupchats.
     892                        </para>
     893                        <para>
     894                                For MSN: This setting allows you to read and change your "friendly name" for this connection. Since this is a server-side setting, it can't be changed when the account is off-line.
     895                        </para>
     896                        <para>
     897                                For jabber groupchats: this sets the default value of 'nick' for newly created groupchats. There is no way to set an account-wide nick like MSN.
    891898                        </para>
    892899                </description>
     
    10801087                                With modes "many" and "one", you can post tweets by /msg'ing the twitter_yourusername contact. In mode "chat", messages posted in the Twitter channel will also be posted as tweets.
    10811088                        </para>
     1089                </description>
     1090
     1091        </bitlbee-setting>
     1092
     1093        <bitlbee-setting name="format_string" type="string" scope="account">
     1094                <default>^B[^B%i^B]^B %c</default>
     1095
     1096                <description>
     1097                        <para>
     1098                                This setting controls how tweets are displayed. Below are listed the two replacement attributes you may use in the string. If you wish to use a percent symbol in your string, ensure you escape it with a second percent symbol (i.e. "%%").
     1099                        </para>
     1100
     1101                        <para>
     1102                                The default setting contains ^B as a control code for toggling of the bold property. Other similar properties (including color codes) are also functional in this setting. See the documentation for your IRC client for details on such control codes.
     1103                        </para>
     1104
     1105                        <variablelist>
     1106                                <varlistentry><term>%i</term><listitem><para>The ID of the tweet</para></listitem></varlistentry>
     1107                                <varlistentry><term>%c</term><listitem><para>The text of the tweet</para></listitem></varlistentry>
     1108                        </variablelist>
     1109                </description>
     1110
     1111        </bitlbee-setting>
     1112
     1113        <bitlbee-setting name="reply_format_string" type="string" scope="account">
     1114                <default>^B[^B%i->%t^B]^B %c</default>
     1115
     1116                <description>
     1117                        <para>
     1118                                This setting controls how replies to tweets are displayed. Below are listed the three replacement attributes you may use in the string. If you wish to use a percent symbol in your string, ensure you escape it with a second percent symbol (i.e. "%%").
     1119                        </para>
     1120
     1121                        <para>
     1122                                The default setting contains ^B as a control code for toggling of the bold property. Other similar properties (including color codes) are also functional in this setting. See the documentation for your IRC client for details on such control codes.
     1123                        </para>
     1124
     1125                        <variablelist>
     1126                                <varlistentry><term>%i</term><listitem><para>The ID of this tweet</para></listitem></varlistentry>
     1127                                <varlistentry><term>%r</term><listitem><para>The ID of the tweet being replied to</para></listitem></varlistentry>
     1128                                <varlistentry><term>%c</term><listitem><para>The text of the tweet</para></listitem></varlistentry>
     1129                        </variablelist>
     1130                </description>
     1131
     1132        </bitlbee-setting>
     1133
     1134        <bitlbee-setting name="retweet_format_string" type="string" scope="account">
     1135                <default>^B[^B%i^B]^B RT @%a: %c</default>
     1136
     1137                <description>
     1138                        <para>
     1139                                This setting controls how retweets are displayed. Below are listed the three replacement attributes you may use in the string. If you wish to use a percent symbol in your string, ensure you escape it with a second percent symbol (i.e. "%%").
     1140                        </para>
     1141
     1142                        <para>
     1143                                The default setting contains ^B as a control code for toggling of the bold property. Other similar properties (including color codes) are also functional in this setting. See the documentation for your IRC client for details on such control codes.
     1144                        </para>
     1145
     1146                        <variablelist>
     1147                                <varlistentry><term>%i</term><listitem><para>The ID of the tweet</para></listitem></varlistentry>
     1148                                <varlistentry><term>%a</term><listitem><para>The author of the original tweet</para></listitem></varlistentry>
     1149                                <varlistentry><term>%c</term><listitem><para>The text of the tweet</para></listitem></varlistentry>
     1150                        </variablelist>
    10821151                </description>
    10831152
  • lib/misc.c

    rfd213fe r12fe5ea  
    414414}
    415415
    416 /* A pretty reliable random number generator. Tries to use the /dev/random
    417    devices first, and falls back to the random number generator from libc
    418    when it fails. Opens randomizer devices with O_NONBLOCK to make sure a
    419    lack of entropy won't halt BitlBee. */
     416/* A wrapper for /dev/urandom.
     417 * If /dev/urandom is not present or not usable, it calls abort()
     418 * to prevent bitlbee from working without a decent entropy source */
    420419void random_bytes( unsigned char *buf, int count )
    421420{
    422         static int use_dev = -1;
    423        
    424         /* Actually this probing code isn't really necessary, is it? */
    425         if( use_dev == -1 )
    426         {
    427                 if( access( "/dev/random", R_OK ) == 0 || access( "/dev/urandom", R_OK ) == 0 )
    428                         use_dev = 1;
    429                 else
    430                 {
    431                         use_dev = 0;
    432                         srand( ( getpid() << 16 ) ^ time( NULL ) );
    433                 }
    434         }
    435        
    436         if( use_dev )
    437         {
    438                 int fd;
    439                
    440                 /* At least on Linux, /dev/random can block if there's not
    441                    enough entropy. We really don't want that, so if it can't
    442                    give anything, use /dev/urandom instead. */
    443                 if( ( fd = open( "/dev/random", O_RDONLY | O_NONBLOCK ) ) >= 0 )
    444                         if( read( fd, buf, count ) == count )
    445                         {
    446                                 close( fd );
    447                                 return;
    448                         }
    449                 close( fd );
    450                
    451                 /* urandom isn't supposed to block at all, but just to be
    452                    sure. If it blocks, we'll disable use_dev and use the libc
    453                    randomizer instead. */
    454                 if( ( fd = open( "/dev/urandom", O_RDONLY | O_NONBLOCK ) ) >= 0 )
    455                         if( read( fd, buf, count ) == count )
    456                         {
    457                                 close( fd );
    458                                 return;
    459                         }
    460                 close( fd );
    461                
    462                 /* If /dev/random blocks once, we'll still try to use it
    463                    again next time. If /dev/urandom also fails for some
    464                    reason, stick with libc during this session. */
    465                
    466                 use_dev = 0;
    467                 srand( ( getpid() << 16 ) ^ time( NULL ) );
    468         }
    469        
    470         if( !use_dev )
    471         {
    472                 int i;
    473                
    474                 /* Possibly the LSB of rand() isn't very random on some
    475                    platforms. Seems okay on at least Linux and OSX though. */
    476                 for( i = 0; i < count; i ++ )
    477                         buf[i] = rand() & 0xff;
    478         }
     421        int fd;
     422        if( ( ( fd = open( "/dev/urandom", O_RDONLY ) ) == -1 ) ||
     423            ( read( fd, buf, count ) == -1 ) )
     424        {
     425                log_message( LOGLVL_ERROR, "/dev/urandom not present - aborting" );
     426                abort();
     427        }
     428
     429        close( fd );
    479430}
    480431
  • protocols/account.c

    rfd213fe r12fe5ea  
    2929
    3030static const char* account_protocols_local[] = {
    31         "gg", NULL
     31        "gg", "whatsapp", NULL
    3232};
    3333
     
    351351void account_on( bee_t *bee, account_t *a )
    352352{
    353         GHashTableIter nicks;
    354         gpointer k, v;
    355 
    356353        if( a->ic )
    357354        {
     
    367364        if( a->ic && !( a->ic->flags & ( OPT_SLOW_LOGIN | OPT_LOGGED_IN ) ) )
    368365                a->ic->keepalive = b_timeout_add( 120000, account_on_timeout, a->ic );
    369 
    370         if( a->flags & ACC_FLAG_LOCAL )
    371         {
    372                 g_hash_table_iter_init(&nicks, a->nicks);
    373                 while( g_hash_table_iter_next( &nicks, &k, &v ) )
    374                 {
    375                         a->prpl->add_buddy( a->ic, (char*) k, NULL );
    376                 }
    377         }
    378366}
    379367
  • protocols/bee_chat.c

    rfd213fe r12fe5ea  
    8080}
    8181
     82static gboolean handle_is_self( struct im_connection *ic, const char *handle )
     83{
     84        return ( ic->acc->prpl->handle_is_self ) ?
     85                 ic->acc->prpl->handle_is_self( ic, handle ) :
     86                 ( ic->acc->prpl->handle_cmp( ic->acc->user, handle ) == 0 );
     87}
     88
    8289void imcb_chat_msg( struct groupchat *c, const char *who, char *msg, uint32_t flags, time_t sent_at )
    8390{
     
    8996       
    9097        /* Gaim sends own messages through this too. IRC doesn't want this, so kill them */
    91         if( g_strcasecmp( who, ic->acc->user ) == 0 )
     98        if( handle_is_self( ic, who ) )
    9299                return;
    93100       
     
    139146        if( who == NULL)
    140147                bu = NULL;
    141         else if( g_strcasecmp( who, ic->acc->user ) == 0 )
     148        else if( handle_is_self( ic, who ) )
    142149                bu = bee->user;
    143150        else
     
    161168                imcb_log( c->ic, "User %s added to conversation %p", handle, c );
    162169       
    163         me = ic->acc->prpl->handle_cmp( handle, ic->acc->user ) == 0;
     170        me = handle_is_self( ic, handle );
    164171       
    165172        /* Most protocols allow people to join, even when they're not in
     
    189196       
    190197        /* It might be yourself! */
    191         if( g_strcasecmp( handle, ic->acc->user ) == 0 )
     198        if( handle_is_self( ic, handle ) )
    192199        {
    193200                if( c->joined == 0 )
  • protocols/jabber/iq.c

    rfd213fe r12fe5ea  
    358358                                *s = '\0';
    359359                        jabber_set_me( ic, c->text );
    360                         imcb_log( ic, "Server claims your JID is `%s' instead of `%s'. "
    361                                   "This mismatch may cause problems with groupchats "
    362                                   "and possibly other things.",
    363                                   c->text, ic->acc->user );
    364360                        if( s )
    365361                                *s = '/';
  • protocols/jabber/jabber.c

    rfd213fe r12fe5ea  
    6363        s = set_add( &acc->set, "oauth", "false", set_eval_oauth, acc );
    6464
     65        s = set_add( &acc->set, "display_name", NULL, NULL, acc );
     66       
    6567        g_snprintf( str, sizeof( str ), "%d", jabber_port_list[0] );
    6668        s = set_add( &acc->set, "port", str, set_eval_int, acc );
     
    318320        g_free( jd->oauth2_access_token );
    319321        g_free( jd->away_message );
     322        g_free( jd->internal_jid );
    320323        g_free( jd->username );
    321324        g_free( jd->me );
     
    473476{
    474477        struct jabber_data *jd = ic->proto_data;
    475        
     478        char *final_nick;
     479       
     480        /* Ignore the passed nick parameter if we have our own default */
     481        if ( !( final_nick = set_getstr( sets, "nick" ) ) &&
     482             !( final_nick = set_getstr( &ic->acc->set, "display_name" ) ) ) {
     483                /* Well, whatever, actually use the provided default, then */
     484                final_nick = (char *) nick;
     485        }
     486
    476487        if( strchr( room, '@' ) == NULL )
    477488                imcb_error( ic, "%s is not a valid Jabber room name. Maybe you mean %s@conference.%s?",
     
    480491                imcb_error( ic, "Already present in chat `%s'", room );
    481492        else
    482                 return jabber_chat_join( ic, room, nick, set_getstr( sets, "password" ) );
     493                return jabber_chat_join( ic, room, final_nick, set_getstr( sets, "password" ) );
    483494       
    484495        return NULL;
     
    619630       
    620631        return NULL;
     632}
     633
     634gboolean jabber_handle_is_self( struct im_connection *ic, const char *who ) {
     635        struct jabber_data *jd = ic->proto_data;
     636        return ( ( g_strcasecmp( who, ic->acc->user ) == 0 ) ||
     637                 ( jd->internal_jid &&
     638                   g_strcasecmp( who, jd->internal_jid ) == 0 ) );
    621639}
    622640
     
    648666        ret->send_typing = jabber_send_typing;
    649667        ret->handle_cmp = g_strcasecmp;
     668        ret->handle_is_self = jabber_handle_is_self;
    650669        ret->transfer_request = jabber_si_transfer_request;
    651670        ret->buddy_action_list = jabber_buddy_action_list;
  • protocols/jabber/jabber.h

    rfd213fe r12fe5ea  
    9797        char *server;           /* username@SERVER -=> server/domain, not hostname */
    9898        char *me;               /* bare jid */
     99        char *internal_jid;
    99100       
    100101        const struct oauth2_service *oauth2_service;
  • protocols/jabber/jabber_util.c

    rfd213fe r12fe5ea  
    824824        jd->username = g_strndup( jd->me, jd->server - jd->me );
    825825        jd->server ++;
     826
     827        /* Set the "internal" account username, for groupchats */
     828        g_free( jd->internal_jid );
     829        jd->internal_jid = g_strdup( jd->me );
    826830       
    827831        return TRUE;
  • protocols/nogaim.c

    rfd213fe r12fe5ea  
    294294        if( ic->flags & OPT_LOGGED_IN )
    295295                return;
     296
     297        if( ic->acc->flags & ACC_FLAG_LOCAL )
     298        {
     299                GHashTableIter nicks;
     300                gpointer k, v;
     301                g_hash_table_iter_init( &nicks, ic->acc->nicks );
     302                while( g_hash_table_iter_next( &nicks, &k, &v ) )
     303                {
     304                        ic->acc->prpl->add_buddy( ic, (char*) k, NULL );
     305                }
     306        }
    296307       
    297308        imcb_log( ic, "Logged in" );
  • protocols/nogaim.h

    rfd213fe r12fe5ea  
    263263        void *(* buddy_action) (struct bee_user *bu, const char *action, char * const args[], void *data);
    264264       
     265        /* If null, equivalent to handle_cmp( ic->acc->user, who ) */
     266        gboolean (* handle_is_self) (struct im_connection *, const char *who);
     267
    265268        /* Some placeholders so eventually older plugins may cooperate with newer BitlBees. */
    266269        void *resv1;
  • protocols/oscar/chat.c

    rfd213fe r12fe5ea  
    6767         */
    6868        for (i = 0; i < sizeof(ckstr); i++)
    69                 aimutil_put8(ckstr+i, (guint8) rand());
     69                (void) aimutil_put8(ckstr+i, (guint8) rand());
     70       
    7071
    7172        cookie = aim_mkcookie(ckstr, AIM_COOKIETYPE_CHAT, NULL);
     
    228229         */
    229230        for (i = 0; i < sizeof(ckstr); i++)
    230                 aimutil_put8(ckstr, (guint8) rand());
     231                (void) aimutil_put8(ckstr, (guint8) rand());
    231232
    232233        /* XXX should be uncached by an unwritten 'invite accept' handler */
  • protocols/oscar/tlv.c

    rfd213fe r12fe5ea  
    181181        guint8 v8[1];
    182182
    183         aimutil_put8(v8, v);
     183        (void) aimutil_put8(v8, v);
    184184
    185185        return aim_addtlvtochain_raw(list, t, 1, v8);
     
    199199        guint8 v16[2];
    200200
    201         aimutil_put16(v16, v);
     201        (void) aimutil_put16(v16, v);
    202202
    203203        return aim_addtlvtochain_raw(list, t, 2, v16);
     
    217217        guint8 v32[4];
    218218
    219         aimutil_put32(v32, v);
     219        (void) aimutil_put32(v32, v);
    220220
    221221        return aim_addtlvtochain_raw(list, t, 4, v32);
  • protocols/twitter/twitter.c

    rfd213fe r12fe5ea  
    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
     
    330539
    331540        s = set_add(&acc->set, "strip_newlines", "false", set_eval_bool, acc);
     541
     542        s = set_add(&acc->set, "format_string", "\002[\002%i\002]\002 %c", NULL, acc);
     543        s = set_add(&acc->set, "retweet_format_string", "\002[\002%i\002]\002 RT @%a: %c", NULL, acc);
     544        s = set_add(&acc->set, "reply_format_string", "\002[\002%i->%r\002]\002 %c", NULL, acc);
    332545       
    333546        s = set_add(&acc->set, "_last_tweet", "0", NULL, acc);
     
    436649
    437650        if (td) {
     651                if (td->filter_update_id > 0)
     652                        b_event_remove(td->filter_update_id);
     653
    438654                http_close(td->stream);
     655                twitter_filter_remove_all(ic);
    439656                oauth_info_free(td->oauth_info);
    440657                g_free(td->user);
     
    509726}
    510727
     728static struct groupchat *twitter_chat_join(struct im_connection *ic,
     729                                           const char *room, const char *nick,
     730                                           const char *password, set_t **sets)
     731{
     732        struct groupchat *c = imcb_chat_new(ic, room);
     733        GSList *fs = twitter_filter_parse(c, room);
     734        GString *topic = g_string_new("");
     735        struct twitter_filter *tf;
     736        GSList *l;
     737
     738        fs = g_slist_sort(fs, (GCompareFunc) twitter_filter_cmp);
     739
     740        for (l = fs; l; l = g_slist_next(l)) {
     741                tf = l->data;
     742
     743                if (topic->len > 0)
     744                        g_string_append(topic, ", ");
     745
     746                if (tf->type == TWITTER_FILTER_TYPE_FOLLOW)
     747                        g_string_append_c(topic, '@');
     748
     749                g_string_append(topic, tf->text);
     750        }
     751
     752        if (topic->len > 0)
     753                g_string_prepend(topic, "Twitter Filter: ");
     754
     755        imcb_chat_topic(c, NULL, topic->str, 0);
     756        imcb_chat_add_buddy(c, ic->acc->user);
     757
     758        if (topic->len == 0) {
     759                imcb_error(ic, "Failed to handle any filters");
     760                imcb_chat_free(c);
     761                c = NULL;
     762        }
     763
     764        g_string_free(topic, TRUE);
     765        g_slist_free(fs);
     766
     767        return c;
     768}
     769
    511770static void twitter_chat_leave(struct groupchat *c)
    512771{
    513772        struct twitter_data *td = c->ic->proto_data;
    514773
    515         if (c != td->timeline_gc)
    516                 return;         /* WTF? */
     774        if (c != td->timeline_gc) {
     775                twitter_filter_remove(c);
     776                imcb_chat_free(c);
     777                return;
     778        }
    517779
    518780        /* If the user leaves the channel: Fine. Rejoin him/her once new
     
    674936                in_reply_to = id;
    675937                allow_post = TRUE;
     938        } else if (g_strcasecmp(cmd[0], "rawreply") == 0 && cmd[1] && cmd[2]) {
     939                id = twitter_message_id_from_command_arg(ic, cmd[1], NULL);
     940                if (!id) {
     941                        twitter_log(ic, "Tweet `%s' does not exist", cmd[1]);
     942                        goto eof;
     943                }
     944                message = cmd[2];
     945                in_reply_to = id;
     946                allow_post = TRUE;
    676947        } else if (g_strcasecmp(cmd[0], "post") == 0) {
    677948                message += 5;
     
    7481019        ret->chat_msg = twitter_chat_msg;
    7491020        ret->chat_invite = twitter_chat_invite;
     1021        ret->chat_join = twitter_chat_join;
    7501022        ret->chat_leave = twitter_chat_leave;
    7511023        ret->keepalive = twitter_keepalive;
  • protocols/twitter/twitter.h

    rfd213fe r12fe5ea  
    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

    rfd213fe r12fe5ea  
    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;
     65        struct twitter_xml_status *rt;
    6366};
    6467
     
    8689        g_free(txs->text);
    8790        txu_free(txs->user);
     91        txs_free(txs->rt);
    8892        g_free(txs);
    8993}
     
    392396{
    393397        struct twitter_xml_user *txu;
     398        json_value *jv;
    394399       
    395400        txu = g_new0(struct twitter_xml_user, 1);
    396401        txu->name = g_strdup(json_o_str(node, "name"));
    397402        txu->screen_name = g_strdup(json_o_str(node, "screen_name"));
     403       
     404        jv = json_o_get(node, "id");
     405        txu->uid = jv->u.integer;
    398406       
    399407        return txu;
     
    483491                if (rtxs) {
    484492                        g_free(txs->text);
    485                         txs->text = g_strdup_printf("RT @%s: %s", rtxs->user->screen_name, rtxs->text);
     493                        txs->text = g_strdup(rtxs->text);
    486494                        txs->id = rtxs->id;
    487                         txs_free(rtxs);
     495                        txs->rt = rtxs;
    488496                }
    489497        } else if (entities) {
     
    603611}
    604612
     613/**
     614 * Function to properly format a tweet as per the users configuration.
     615 */
     616static char *twitter_msg_get_text(struct im_connection *ic, int log_id, int reply_to,
     617                                struct twitter_xml_status *txs, const char *prefix) {
     618        gchar * format = set_getstr(&ic->acc->set, "format_string");
     619        GString * text = g_string_new(NULL);
     620
     621        gchar *c;
     622        if (reply_to != -1)
     623                format = set_getstr(&ic->acc->set, "reply_format_string");
     624        if (txs->rt)
     625                format = set_getstr(&ic->acc->set, "retweet_format_string");
     626
     627        for (c = format; *c ; c++) {
     628                if (!(*c == '%' && *(c+1))) {
     629                        text = g_string_append_c(text, *c);
     630                        continue;
     631                }
     632                c++; // Move past the %
     633                switch (*c) {
     634                        case 'i':
     635                                g_string_append_printf(text, "%02x", log_id);
     636                                break;
     637                        case 'r':
     638                                if (reply_to != -1) // In case someone does put %r in the wrong format_string
     639                                g_string_append_printf(text, "%02x", reply_to);
     640                                break;
     641                        case 'a':
     642                                if (txs->rt) // In case someone does put %a in the wrong format_string
     643                                        text = g_string_append(text, txs->rt->user->screen_name);
     644                                break;
     645                        case 'c':
     646                                text = g_string_append(text, txs->text);
     647                                break;
     648                        default:
     649                                text = g_string_append_c(text, *c);
     650                }
     651        }
     652        text = g_string_prepend(text, prefix);
     653        return g_string_free(text, FALSE);
     654}
     655
    605656/* Will log messages either way. Need to keep track of IDs for stream deduping.
    606657   Plus, show_ids is on by default and I don't see why anyone would disable it. */
     
    641692                td->log[td->log_id].id = txs->rt_id;
    642693       
    643         if (set_getbool(&ic->acc->set, "show_ids")) {
    644                 if (reply_to != -1)
    645                         return g_strdup_printf("\002[\002%02x->%02x\002]\002 %s%s",
    646                                                td->log_id, reply_to, prefix, txs->text);
    647                 else
    648                         return g_strdup_printf("\002[\002%02x\002]\002 %s%s",
    649                                                td->log_id, prefix, txs->text);
    650         } else {
    651                 if (*prefix)
    652                         return g_strconcat(prefix, txs->text, NULL);
    653                 else
    654                         return NULL;
    655         }
     694        return twitter_msg_get_text(ic, td->log_id, reply_to, txs, prefix);
     695}
     696
     697/**
     698 * Function that is called to see the filter statuses in groupchat windows.
     699 */
     700static void twitter_status_show_filter(struct im_connection *ic, struct twitter_xml_status *status)
     701{
     702        struct twitter_data *td = ic->proto_data;
     703        char *msg = twitter_msg_add_id(ic, status, "");
     704        struct twitter_filter *tf;
     705        GSList *f;
     706        GSList *l;
     707
     708        for (f = td->filters; f; f = g_slist_next(f)) {
     709                tf = f->data;
     710
     711                switch (tf->type) {
     712                case TWITTER_FILTER_TYPE_FOLLOW:
     713                        if (status->user->uid != tf->uid)
     714                                continue;
     715                        break;
     716
     717                case TWITTER_FILTER_TYPE_TRACK:
     718                        if (strcasestr(status->text, tf->text) == NULL)
     719                                continue;
     720                        break;
     721
     722                default:
     723                        continue;
     724                }
     725
     726                for (l = tf->groupchats; l; l = g_slist_next(l)) {
     727                        imcb_chat_msg(l->data, status->user->screen_name,
     728                                      msg ? msg : status->text, 0, 0);
     729                }
     730        }
     731
     732        g_free(msg);
    656733}
    657734
     
    731808                strip_newlines(status->text);
    732809       
    733         if (td->flags & TWITTER_MODE_CHAT)
     810        if (status->from_filter)
     811                twitter_status_show_filter(ic, status);
     812        else if (td->flags & TWITTER_MODE_CHAT)
    734813                twitter_status_show_chat(ic, status);
    735814        else
     
    745824}
    746825
    747 static gboolean twitter_stream_handle_object(struct im_connection *ic, json_value *o);
     826static gboolean twitter_stream_handle_object(struct im_connection *ic, json_value *o, gboolean from_filter);
    748827
    749828static void twitter_http_stream(struct http_request *req)
     
    754833        int len = 0;
    755834        char c, *nl;
     835        gboolean from_filter;
    756836       
    757837        if (!g_slist_find(twitter_connections, ic))
     
    762842       
    763843        if ((req->flags & HTTPC_EOF) || !req->reply_body) {
    764                 td->stream = NULL;
     844                if (req == td->stream)
     845                        td->stream = NULL;
     846                else if (req == td->filter_stream)
     847                        td->filter_stream = NULL;
     848
    765849                imcb_error(ic, "Stream closed (%s)", req->status_string);
    766850                imc_logout(ic, TRUE);
     
    779863               
    780864                if ((parsed = json_parse(req->reply_body, req->body_size))) {
    781                         twitter_stream_handle_object(ic, parsed);
     865                        from_filter = (req == td->filter_stream);
     866                        twitter_stream_handle_object(ic, parsed, from_filter);
    782867                }
    783868                json_value_free(parsed);
     
    795880static gboolean twitter_stream_handle_status(struct im_connection *ic, struct twitter_xml_status *txs);
    796881
    797 static gboolean twitter_stream_handle_object(struct im_connection *ic, json_value *o)
     882static gboolean twitter_stream_handle_object(struct im_connection *ic, json_value *o, gboolean from_filter)
    798883{
    799884        struct twitter_data *td = ic->proto_data;
     
    802887       
    803888        if ((txs = twitter_xt_get_status(o))) {
     889                txs->from_filter = from_filter;
    804890                gboolean ret = twitter_stream_handle_status(ic, txs);
    805891                txs_free(txs);
     
    899985}
    900986
     987static gboolean twitter_filter_stream(struct im_connection *ic)
     988{
     989        struct twitter_data *td = ic->proto_data;
     990        char *args[4] = {"follow", NULL, "track", NULL};
     991        GString *followstr = g_string_new("");
     992        GString *trackstr = g_string_new("");
     993        gboolean ret = FALSE;
     994        struct twitter_filter *tf;
     995        GSList *l;
     996
     997        for (l = td->filters; l; l = g_slist_next(l)) {
     998                tf = l->data;
     999
     1000                switch (tf->type) {
     1001                case TWITTER_FILTER_TYPE_FOLLOW:
     1002                        if (followstr->len > 0)
     1003                                g_string_append_c(followstr, ',');
     1004
     1005                        g_string_append_printf(followstr, "%" G_GUINT64_FORMAT,
     1006                                               tf->uid);
     1007                        break;
     1008
     1009                case TWITTER_FILTER_TYPE_TRACK:
     1010                        if (trackstr->len > 0)
     1011                                g_string_append_c(trackstr, ',');
     1012
     1013                        g_string_append(trackstr, tf->text);
     1014                        break;
     1015
     1016                default:
     1017                        continue;
     1018                }
     1019        }
     1020
     1021        args[1] = followstr->str;
     1022        args[3] = trackstr->str;
     1023
     1024        if (td->filter_stream)
     1025                http_close(td->filter_stream);
     1026
     1027        if ((td->filter_stream = twitter_http(ic, TWITTER_FILTER_STREAM_URL,
     1028                                              twitter_http_stream, ic, 0,
     1029                                              args, 4))) {
     1030                /* This flag must be enabled or we'll get no data until EOF
     1031                   (which err, kind of, defeats the purpose of a streaming API). */
     1032                td->filter_stream->flags |= HTTPC_STREAMING;
     1033                ret = TRUE;
     1034        }
     1035
     1036        g_string_free(followstr, TRUE);
     1037        g_string_free(trackstr, TRUE);
     1038
     1039        return ret;
     1040}
     1041
     1042static void twitter_filter_users_post(struct http_request *req)
     1043{
     1044        struct im_connection *ic = req->data;
     1045        struct twitter_data *td;
     1046        struct twitter_filter *tf;
     1047        GList *users = NULL;
     1048        json_value *parsed;
     1049        json_value *id;
     1050        const char *name;
     1051        GString *fstr;
     1052        GSList *l;
     1053        GList *u;
     1054        int i;
     1055
     1056        // Check if the connection is still active.
     1057        if (!g_slist_find(twitter_connections, ic))
     1058                return;
     1059
     1060        td = ic->proto_data;
     1061
     1062        if (!(parsed = twitter_parse_response(ic, req)))
     1063                return;
     1064
     1065        for (l = td->filters; l; l = g_slist_next(l)) {
     1066                tf = l->data;
     1067
     1068                if (tf->type == TWITTER_FILTER_TYPE_FOLLOW)
     1069                        users = g_list_prepend(users, tf);
     1070        }
     1071
     1072        if (parsed->type != json_array)
     1073                goto finish;
     1074
     1075        for (i = 0; i < parsed->u.array.length; i++) {
     1076                id = json_o_get(parsed->u.array.values[i], "id");
     1077                name = json_o_str(parsed->u.array.values[i], "screen_name");
     1078
     1079                if (!name || !id || id->type != json_integer)
     1080                        continue;
     1081
     1082                for (u = users; u; u = g_list_next(u)) {
     1083                        tf = u->data;
     1084
     1085                        if (g_strcasecmp(tf->text, name) == 0) {
     1086                                tf->uid = id->u.integer;
     1087                                users = g_list_delete_link(users, u);
     1088                                break;
     1089                        }
     1090                }
     1091        }
     1092
     1093finish:
     1094        json_value_free(parsed);
     1095        twitter_filter_stream(ic);
     1096
     1097        if (!users)
     1098                return;
     1099
     1100        fstr = g_string_new("");
     1101
     1102        for (u = users; u; u = g_list_next(u)) {
     1103                if (fstr->len > 0)
     1104                        g_string_append(fstr, ", ");
     1105
     1106                g_string_append(fstr, tf->text);
     1107        }
     1108
     1109        imcb_error(ic, "Failed UID acquisitions: %s", fstr->str);
     1110
     1111        g_string_free(fstr, TRUE);
     1112        g_list_free(users);
     1113}
     1114
     1115gboolean twitter_open_filter_stream(struct im_connection *ic)
     1116{
     1117        struct twitter_data *td = ic->proto_data;
     1118        char *args[2] = {"screen_name", NULL};
     1119        GString *ustr = g_string_new("");
     1120        struct twitter_filter *tf;
     1121        struct http_request *req;
     1122        GSList *l;
     1123
     1124        for (l = td->filters; l; l = g_slist_next(l)) {
     1125                tf = l->data;
     1126
     1127                if (tf->type != TWITTER_FILTER_TYPE_FOLLOW || tf->uid != 0)
     1128                        continue;
     1129
     1130                if (ustr->len > 0)
     1131                        g_string_append_c(ustr, ',');
     1132
     1133                g_string_append(ustr, tf->text);
     1134        }
     1135
     1136        if (ustr->len == 0) {
     1137                g_string_free(ustr, TRUE);
     1138                return twitter_filter_stream(ic);
     1139        }
     1140
     1141        args[1] = ustr->str;
     1142        req = twitter_http(ic, TWITTER_USERS_LOOKUP_URL,
     1143                           twitter_filter_users_post,
     1144                           ic, 0, args, 2);
     1145
     1146        g_string_free(ustr, TRUE);
     1147        return req != NULL;
     1148}
     1149
    9011150static void twitter_get_home_timeline(struct im_connection *ic, gint64 next_cursor);
    9021151static void twitter_get_mentions(struct im_connection *ic, gint64 next_cursor);
  • protocols/twitter/twitter_lib.h

    rfd213fe r12fe5ea  
    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);
  • unix.c

    rfd213fe r12fe5ea  
    8787#endif
    8888       
    89         srand( time( NULL ) ^ getpid() );
    90        
    9189        global.helpfile = g_strdup( HELP_FILE );
    9290        if( help_init( &global.help, global.helpfile ) == NULL )
Note: See TracChangeset for help on using the changeset viewer.