Changes in / [fd213fe:12fe5ea]
- Files:
-
- 19 edited
Legend:
- Unmodified
- Added
- Removed
-
.travis.yml
rfd213fe r12fe5ea 1 1 language: c 2 script: dpkg-buildpackage -uc -us2 script: ./configure && make check && dpkg-buildpackage -uc -us 3 3 before_install: 4 4 - 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 6 6 - wget http://dump.dequis.org/indexed/bitlbee-travis-libs/libotr5{,-dev}_4.1.0-2~bpo70+1_amd64.deb 7 7 - sudo dpkg -i *.deb -
bitlbee.c
rfd213fe r12fe5ea 329 329 irc_t *irc; 330 330 331 /* Since we're fork()ing here, let's make sure we won't332 get the same random numbers as the parent/siblings. */333 srand( time( NULL ) ^ getpid() );334 335 331 b_main_init(); 336 332 -
doc/user-guide/commands.xml
rfd213fe r12fe5ea 847 847 <varlistentry><term>rt <screenname|#id></term><listitem><para>Retweet someone's last Tweet (or one with the given ID)</para></listitem></varlistentry> 848 848 <varlistentry><term>reply <screenname|#id></term><listitem><para>Reply to a Tweet (with a reply-to reference)</para></listitem></varlistentry> 849 <varlistentry><term>rawreply <screenname|#id></term><listitem><para>Reply to a Tweet (with no reply-to reference)</para></listitem></varlistentry> 849 850 <varlistentry><term>report <screenname|#id></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> 850 851 <varlistentry><term>follow <screenname></term><listitem><para>Start following a person</para></listitem></varlistentry> … … 888 889 <description> 889 890 <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. 891 898 </para> 892 899 </description> … … 1080 1087 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. 1081 1088 </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> 1082 1151 </description> 1083 1152 -
lib/misc.c
rfd213fe r12fe5ea 414 414 } 415 415 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 */ 420 419 void random_bytes( unsigned char *buf, int count ) 421 420 { 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 ); 479 430 } 480 431 -
protocols/account.c
rfd213fe r12fe5ea 29 29 30 30 static const char* account_protocols_local[] = { 31 "gg", NULL31 "gg", "whatsapp", NULL 32 32 }; 33 33 … … 351 351 void account_on( bee_t *bee, account_t *a ) 352 352 { 353 GHashTableIter nicks;354 gpointer k, v;355 356 353 if( a->ic ) 357 354 { … … 367 364 if( a->ic && !( a->ic->flags & ( OPT_SLOW_LOGIN | OPT_LOGGED_IN ) ) ) 368 365 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 }378 366 } 379 367 -
protocols/bee_chat.c
rfd213fe r12fe5ea 80 80 } 81 81 82 static 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 82 89 void imcb_chat_msg( struct groupchat *c, const char *who, char *msg, uint32_t flags, time_t sent_at ) 83 90 { … … 89 96 90 97 /* 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 ) ) 92 99 return; 93 100 … … 139 146 if( who == NULL) 140 147 bu = NULL; 141 else if( g_strcasecmp( who, ic->acc->user ) == 0)148 else if( handle_is_self( ic, who ) ) 142 149 bu = bee->user; 143 150 else … … 161 168 imcb_log( c->ic, "User %s added to conversation %p", handle, c ); 162 169 163 me = ic->acc->prpl->handle_cmp( handle, ic->acc->user ) == 0;170 me = handle_is_self( ic, handle ); 164 171 165 172 /* Most protocols allow people to join, even when they're not in … … 189 196 190 197 /* It might be yourself! */ 191 if( g_strcasecmp( handle, ic->acc->user ) == 0)198 if( handle_is_self( ic, handle ) ) 192 199 { 193 200 if( c->joined == 0 ) -
protocols/jabber/iq.c
rfd213fe r12fe5ea 358 358 *s = '\0'; 359 359 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 );364 360 if( s ) 365 361 *s = '/'; -
protocols/jabber/jabber.c
rfd213fe r12fe5ea 63 63 s = set_add( &acc->set, "oauth", "false", set_eval_oauth, acc ); 64 64 65 s = set_add( &acc->set, "display_name", NULL, NULL, acc ); 66 65 67 g_snprintf( str, sizeof( str ), "%d", jabber_port_list[0] ); 66 68 s = set_add( &acc->set, "port", str, set_eval_int, acc ); … … 318 320 g_free( jd->oauth2_access_token ); 319 321 g_free( jd->away_message ); 322 g_free( jd->internal_jid ); 320 323 g_free( jd->username ); 321 324 g_free( jd->me ); … … 473 476 { 474 477 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 476 487 if( strchr( room, '@' ) == NULL ) 477 488 imcb_error( ic, "%s is not a valid Jabber room name. Maybe you mean %s@conference.%s?", … … 480 491 imcb_error( ic, "Already present in chat `%s'", room ); 481 492 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" ) ); 483 494 484 495 return NULL; … … 619 630 620 631 return NULL; 632 } 633 634 gboolean 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 ) ); 621 639 } 622 640 … … 648 666 ret->send_typing = jabber_send_typing; 649 667 ret->handle_cmp = g_strcasecmp; 668 ret->handle_is_self = jabber_handle_is_self; 650 669 ret->transfer_request = jabber_si_transfer_request; 651 670 ret->buddy_action_list = jabber_buddy_action_list; -
protocols/jabber/jabber.h
rfd213fe r12fe5ea 97 97 char *server; /* username@SERVER -=> server/domain, not hostname */ 98 98 char *me; /* bare jid */ 99 char *internal_jid; 99 100 100 101 const struct oauth2_service *oauth2_service; -
protocols/jabber/jabber_util.c
rfd213fe r12fe5ea 824 824 jd->username = g_strndup( jd->me, jd->server - jd->me ); 825 825 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 ); 826 830 827 831 return TRUE; -
protocols/nogaim.c
rfd213fe r12fe5ea 294 294 if( ic->flags & OPT_LOGGED_IN ) 295 295 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 } 296 307 297 308 imcb_log( ic, "Logged in" ); -
protocols/nogaim.h
rfd213fe r12fe5ea 263 263 void *(* buddy_action) (struct bee_user *bu, const char *action, char * const args[], void *data); 264 264 265 /* If null, equivalent to handle_cmp( ic->acc->user, who ) */ 266 gboolean (* handle_is_self) (struct im_connection *, const char *who); 267 265 268 /* Some placeholders so eventually older plugins may cooperate with newer BitlBees. */ 266 269 void *resv1; -
protocols/oscar/chat.c
rfd213fe r12fe5ea 67 67 */ 68 68 for (i = 0; i < sizeof(ckstr); i++) 69 aimutil_put8(ckstr+i, (guint8) rand()); 69 (void) aimutil_put8(ckstr+i, (guint8) rand()); 70 70 71 71 72 cookie = aim_mkcookie(ckstr, AIM_COOKIETYPE_CHAT, NULL); … … 228 229 */ 229 230 for (i = 0; i < sizeof(ckstr); i++) 230 aimutil_put8(ckstr, (guint8) rand());231 (void) aimutil_put8(ckstr, (guint8) rand()); 231 232 232 233 /* XXX should be uncached by an unwritten 'invite accept' handler */ -
protocols/oscar/tlv.c
rfd213fe r12fe5ea 181 181 guint8 v8[1]; 182 182 183 aimutil_put8(v8, v);183 (void) aimutil_put8(v8, v); 184 184 185 185 return aim_addtlvtochain_raw(list, t, 1, v8); … … 199 199 guint8 v16[2]; 200 200 201 aimutil_put16(v16, v);201 (void) aimutil_put16(v16, v); 202 202 203 203 return aim_addtlvtochain_raw(list, t, 2, v16); … … 217 217 guint8 v32[4]; 218 218 219 aimutil_put32(v32, v);219 (void) aimutil_put32(v32, v); 220 220 221 221 return aim_addtlvtochain_raw(list, t, 4, v32); -
protocols/twitter/twitter.c
rfd213fe r12fe5ea 31 31 32 32 GSList *twitter_connections = NULL; 33 34 static 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 70 static 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 87 static 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 124 static 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 131 static 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 158 static 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 198 static 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 33 242 /** 34 243 * Main loop function … … 330 539 331 540 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); 332 545 333 546 s = set_add(&acc->set, "_last_tweet", "0", NULL, acc); … … 436 649 437 650 if (td) { 651 if (td->filter_update_id > 0) 652 b_event_remove(td->filter_update_id); 653 438 654 http_close(td->stream); 655 twitter_filter_remove_all(ic); 439 656 oauth_info_free(td->oauth_info); 440 657 g_free(td->user); … … 509 726 } 510 727 728 static 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 511 770 static void twitter_chat_leave(struct groupchat *c) 512 771 { 513 772 struct twitter_data *td = c->ic->proto_data; 514 773 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 } 517 779 518 780 /* If the user leaves the channel: Fine. Rejoin him/her once new … … 674 936 in_reply_to = id; 675 937 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; 676 947 } else if (g_strcasecmp(cmd[0], "post") == 0) { 677 948 message += 5; … … 748 1019 ret->chat_msg = twitter_chat_msg; 749 1020 ret->chat_invite = twitter_chat_invite; 1021 ret->chat_join = twitter_chat_join; 750 1022 ret->chat_leave = twitter_chat_leave; 751 1023 ret->keepalive = twitter_keepalive; -
protocols/twitter/twitter.h
rfd213fe r12fe5ea 45 45 } twitter_flags_t; 46 46 47 typedef enum 48 { 49 TWITTER_FILTER_TYPE_FOLLOW = 0, 50 TWITTER_FILTER_TYPE_TRACK 51 } twitter_filter_type_t; 52 47 53 struct twitter_log_data; 48 54 … … 58 64 59 65 GSList *follow_ids; 66 GSList *filters; 60 67 61 68 guint64 last_status_id; /* For undo */ 62 69 gint main_loop_id; 70 gint filter_update_id; 63 71 struct http_request *stream; 72 struct http_request *filter_stream; 64 73 struct groupchat *timeline_gc; 65 74 gint http_fails; … … 77 86 struct twitter_log_data *log; 78 87 int log_id; 88 }; 89 90 #define TWITTER_FILTER_UPDATE_WAIT 3000 91 struct twitter_filter 92 { 93 twitter_filter_type_t type; 94 char *text; 95 guint64 uid; 96 GSList *groupchats; 79 97 }; 80 98 -
protocols/twitter/twitter_lib.c
rfd213fe r12fe5ea 51 51 52 52 struct twitter_xml_user { 53 guint64 uid; 53 54 char *name; 54 55 char *screen_name; … … 61 62 guint64 id, rt_id; /* Usually equal, with RTs id == *original* id */ 62 63 guint64 reply_to; 64 gboolean from_filter; 65 struct twitter_xml_status *rt; 63 66 }; 64 67 … … 86 89 g_free(txs->text); 87 90 txu_free(txs->user); 91 txs_free(txs->rt); 88 92 g_free(txs); 89 93 } … … 392 396 { 393 397 struct twitter_xml_user *txu; 398 json_value *jv; 394 399 395 400 txu = g_new0(struct twitter_xml_user, 1); 396 401 txu->name = g_strdup(json_o_str(node, "name")); 397 402 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; 398 406 399 407 return txu; … … 483 491 if (rtxs) { 484 492 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); 486 494 txs->id = rtxs->id; 487 txs _free(rtxs);495 txs->rt = rtxs; 488 496 } 489 497 } else if (entities) { … … 603 611 } 604 612 613 /** 614 * Function to properly format a tweet as per the users configuration. 615 */ 616 static 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 605 656 /* Will log messages either way. Need to keep track of IDs for stream deduping. 606 657 Plus, show_ids is on by default and I don't see why anyone would disable it. */ … … 641 692 td->log[td->log_id].id = txs->rt_id; 642 693 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 */ 700 static 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); 656 733 } 657 734 … … 731 808 strip_newlines(status->text); 732 809 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) 734 813 twitter_status_show_chat(ic, status); 735 814 else … … 745 824 } 746 825 747 static gboolean twitter_stream_handle_object(struct im_connection *ic, json_value *o );826 static gboolean twitter_stream_handle_object(struct im_connection *ic, json_value *o, gboolean from_filter); 748 827 749 828 static void twitter_http_stream(struct http_request *req) … … 754 833 int len = 0; 755 834 char c, *nl; 835 gboolean from_filter; 756 836 757 837 if (!g_slist_find(twitter_connections, ic)) … … 762 842 763 843 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 765 849 imcb_error(ic, "Stream closed (%s)", req->status_string); 766 850 imc_logout(ic, TRUE); … … 779 863 780 864 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); 782 867 } 783 868 json_value_free(parsed); … … 795 880 static gboolean twitter_stream_handle_status(struct im_connection *ic, struct twitter_xml_status *txs); 796 881 797 static gboolean twitter_stream_handle_object(struct im_connection *ic, json_value *o )882 static gboolean twitter_stream_handle_object(struct im_connection *ic, json_value *o, gboolean from_filter) 798 883 { 799 884 struct twitter_data *td = ic->proto_data; … … 802 887 803 888 if ((txs = twitter_xt_get_status(o))) { 889 txs->from_filter = from_filter; 804 890 gboolean ret = twitter_stream_handle_status(ic, txs); 805 891 txs_free(txs); … … 899 985 } 900 986 987 static 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 1042 static 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 1093 finish: 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 1115 gboolean 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 901 1150 static void twitter_get_home_timeline(struct im_connection *ic, gint64 next_cursor); 902 1151 static void twitter_get_mentions(struct im_connection *ic, gint64 next_cursor); -
protocols/twitter/twitter_lib.h
rfd213fe r12fe5ea 79 79 #define TWITTER_REPORT_SPAM_URL "/users/report_spam.json" 80 80 81 /* Stream URLs */ 81 82 #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" 82 84 83 85 gboolean twitter_open_stream(struct im_connection *ic); 86 gboolean twitter_open_filter_stream(struct im_connection *ic); 84 87 gboolean twitter_get_timeline(struct im_connection *ic, gint64 next_cursor); 85 88 void twitter_get_friends_ids(struct im_connection *ic, gint64 next_cursor); -
unix.c
rfd213fe r12fe5ea 87 87 #endif 88 88 89 srand( time( NULL ) ^ getpid() );90 91 89 global.helpfile = g_strdup( HELP_FILE ); 92 90 if( help_init( &global.help, global.helpfile ) == NULL )
Note: See TracChangeset
for help on using the changeset viewer.