Changes in / [cc6fdf8:92d3044]
- Files:
-
- 4 deleted
- 18 edited
Legend:
- Unmodified
- Added
- Removed
-
debian/copyright
rcc6fdf8 r92d3044 8 8 9 9 Mainly Copyright 2002-2012 Wilmer van der Gaast. 10 11 12 Bits of third party code, also GPLed: 13 * Some parts (mostly protocols/oscar/) are borrowed from Gaim (version 14 0.58), now known as Pidgin <http://www.pidgin.im/>. 15 * protocols/yahoo/ is libyahoo2 <http://libyahoo2.sf.net/>. 16 17 Other license (but GPL-compatible): 18 * lib/json.[ch] is from <https://github.com/udp/json-parser> and licensed 19 under the modified BSD license. 20 10 Some parts are borrowed from Gaim (version 0.58) <http://gaim.sf.net/>. 11 For the copyrights on those parts, please read the Gaim source code. 21 12 22 13 BitlBee License: … … 42 33 43 34 The SGML-formatted documentation is written by Jelmer Vernooij 44 <jelmer@ samba.org> under the GNU Free Documentation License:35 <jelmer@nl.linux.org> under the GNU Free Documentation License: 45 36 46 37 ============================================================================ 47 Copyright (c) 2002-2012 Jelmer Vernooij, Wilmer van der Gaast. 48 49 Permission is granted to copy, distribute and/or modify this document 50 under the terms of the GNU Free Documentation License, Version 1.1 or 51 any later version published by the Free Software Foundation; with no 52 Invariant Sections, with no Front-Cover Texts, and with no Back-Cover 53 Texts. A copy of the license is included in the section entitled `GNU 54 Free Documentation License''. 38 GNU Free Documentation License 39 Version 1.1, March 2000 40 41 Copyright (C) 2000 Free Software Foundation, Inc. 42 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. 43 Everyone is permitted to copy and distribute verbatim copies 44 of this license document, but changing it is not allowed. 45 46 47 0. PREAMBLE 48 49 The purpose of this License is to make a manual, textbook, or other 50 written document "free" in the sense of freedom: to assure everyone 51 the effective freedom to copy and redistribute it, with or without 52 modifying it, either commercially or noncommercially. Secondarily, 53 this License preserves for the author and publisher a way to get 54 credit for their work, while not being considered responsible for 55 modifications made by others. 56 57 This License is a kind of "copyleft", which means that derivative 58 works of the document must themselves be free in the same sense. It 59 complements the GNU General Public License, which is a copyleft 60 license designed for free software. 61 62 We have designed this License in order to use it for manuals for free 63 software, because free software needs free documentation: a free 64 program should come with manuals providing the same freedoms that the 65 software does. But this License is not limited to software manuals; 66 it can be used for any textual work, regardless of subject matter or 67 whether it is published as a printed book. We recommend this License 68 principally for works whose purpose is instruction or reference. 69 70 71 1. APPLICABILITY AND DEFINITIONS 72 73 This License applies to any manual or other work that contains a 74 notice placed by the copyright holder saying it can be distributed 75 under the terms of this License. The "Document", below, refers to any 76 such manual or work. Any member of the public is a licensee, and is 77 addressed as "you". 78 79 A "Modified Version" of the Document means any work containing the 80 Document or a portion of it, either copied verbatim, or with 81 modifications and/or translated into another language. 82 83 A "Secondary Section" is a named appendix or a front-matter section of 84 the Document that deals exclusively with the relationship of the 85 publishers or authors of the Document to the Document's overall subject 86 (or to related matters) and contains nothing that could fall directly 87 within that overall subject. (For example, if the Document is in part a 88 textbook of mathematics, a Secondary Section may not explain any 89 mathematics.) The relationship could be a matter of historical 90 connection with the subject or with related matters, or of legal, 91 commercial, philosophical, ethical or political position regarding 92 them. 93 94 The "Invariant Sections" are certain Secondary Sections whose titles 95 are designated, as being those of Invariant Sections, in the notice 96 that says that the Document is released under this License. 97 98 The "Cover Texts" are certain short passages of text that are listed, 99 as Front-Cover Texts or Back-Cover Texts, in the notice that says that 100 the Document is released under this License. 101 102 A "Transparent" copy of the Document means a machine-readable copy, 103 represented in a format whose specification is available to the 104 general public, whose contents can be viewed and edited directly and 105 straightforwardly with generic text editors or (for images composed of 106 pixels) generic paint programs or (for drawings) some widely available 107 drawing editor, and that is suitable for input to text formatters or 108 for automatic translation to a variety of formats suitable for input 109 to text formatters. A copy made in an otherwise Transparent file 110 format whose markup has been designed to thwart or discourage 111 subsequent modification by readers is not Transparent. A copy that is 112 not "Transparent" is called "Opaque". 113 114 Examples of suitable formats for Transparent copies include plain 115 ASCII without markup, Texinfo input format, LaTeX input format, SGML 116 or XML using a publicly available DTD, and standard-conforming simple 117 HTML designed for human modification. Opaque formats include 118 PostScript, PDF, proprietary formats that can be read and edited only 119 by proprietary word processors, SGML or XML for which the DTD and/or 120 processing tools are not generally available, and the 121 machine-generated HTML produced by some word processors for output 122 purposes only. 123 124 The "Title Page" means, for a printed book, the title page itself, 125 plus such following pages as are needed to hold, legibly, the material 126 this License requires to appear in the title page. For works in 127 formats which do not have any title page as such, "Title Page" means 128 the text near the most prominent appearance of the work's title, 129 preceding the beginning of the body of the text. 130 131 132 2. VERBATIM COPYING 133 134 You may copy and distribute the Document in any medium, either 135 commercially or noncommercially, provided that this License, the 136 copyright notices, and the license notice saying this License applies 137 to the Document are reproduced in all copies, and that you add no other 138 conditions whatsoever to those of this License. You may not use 139 technical measures to obstruct or control the reading or further 140 copying of the copies you make or distribute. However, you may accept 141 compensation in exchange for copies. If you distribute a large enough 142 number of copies you must also follow the conditions in section 3. 143 144 You may also lend copies, under the same conditions stated above, and 145 you may publicly display copies. 146 147 148 3. COPYING IN QUANTITY 149 150 If you publish printed copies of the Document numbering more than 100, 151 and the Document's license notice requires Cover Texts, you must enclose 152 the copies in covers that carry, clearly and legibly, all these Cover 153 Texts: Front-Cover Texts on the front cover, and Back-Cover Texts on 154 the back cover. Both covers must also clearly and legibly identify 155 you as the publisher of these copies. The front cover must present 156 the full title with all words of the title equally prominent and 157 visible. You may add other material on the covers in addition. 158 Copying with changes limited to the covers, as long as they preserve 159 the title of the Document and satisfy these conditions, can be treated 160 as verbatim copying in other respects. 161 162 If the required texts for either cover are too voluminous to fit 163 legibly, you should put the first ones listed (as many as fit 164 reasonably) on the actual cover, and continue the rest onto adjacent 165 pages. 166 167 If you publish or distribute Opaque copies of the Document numbering 168 more than 100, you must either include a machine-readable Transparent 169 copy along with each Opaque copy, or state in or with each Opaque copy 170 a publicly-accessible computer-network location containing a complete 171 Transparent copy of the Document, free of added material, which the 172 general network-using public has access to download anonymously at no 173 charge using public-standard network protocols. If you use the latter 174 option, you must take reasonably prudent steps, when you begin 175 distribution of Opaque copies in quantity, to ensure that this 176 Transparent copy will remain thus accessible at the stated location 177 until at least one year after the last time you distribute an Opaque 178 copy (directly or through your agents or retailers) of that edition to 179 the public. 180 181 It is requested, but not required, that you contact the authors of the 182 Document well before redistributing any large number of copies, to give 183 them a chance to provide you with an updated version of the Document. 184 185 186 4. MODIFICATIONS 187 188 You may copy and distribute a Modified Version of the Document under 189 the conditions of sections 2 and 3 above, provided that you release 190 the Modified Version under precisely this License, with the Modified 191 Version filling the role of the Document, thus licensing distribution 192 and modification of the Modified Version to whoever possesses a copy 193 of it. In addition, you must do these things in the Modified Version: 194 195 A. Use in the Title Page (and on the covers, if any) a title distinct 196 from that of the Document, and from those of previous versions 197 (which should, if there were any, be listed in the History section 198 of the Document). You may use the same title as a previous version 199 if the original publisher of that version gives permission. 200 B. List on the Title Page, as authors, one or more persons or entities 201 responsible for authorship of the modifications in the Modified 202 Version, together with at least five of the principal authors of the 203 Document (all of its principal authors, if it has less than five). 204 C. State on the Title page the name of the publisher of the 205 Modified Version, as the publisher. 206 D. Preserve all the copyright notices of the Document. 207 E. Add an appropriate copyright notice for your modifications 208 adjacent to the other copyright notices. 209 F. Include, immediately after the copyright notices, a license notice 210 giving the public permission to use the Modified Version under the 211 terms of this License, in the form shown in the Addendum below. 212 G. Preserve in that license notice the full lists of Invariant Sections 213 and required Cover Texts given in the Document's license notice. 214 H. Include an unaltered copy of this License. 215 I. Preserve the section entitled "History", and its title, and add to 216 it an item stating at least the title, year, new authors, and 217 publisher of the Modified Version as given on the Title Page. If 218 there is no section entitled "History" in the Document, create one 219 stating the title, year, authors, and publisher of the Document as 220 given on its Title Page, then add an item describing the Modified 221 Version as stated in the previous sentence. 222 J. Preserve the network location, if any, given in the Document for 223 public access to a Transparent copy of the Document, and likewise 224 the network locations given in the Document for previous versions 225 it was based on. These may be placed in the "History" section. 226 You may omit a network location for a work that was published at 227 least four years before the Document itself, or if the original 228 publisher of the version it refers to gives permission. 229 K. In any section entitled "Acknowledgements" or "Dedications", 230 preserve the section's title, and preserve in the section all the 231 substance and tone of each of the contributor acknowledgements 232 and/or dedications given therein. 233 L. Preserve all the Invariant Sections of the Document, 234 unaltered in their text and in their titles. Section numbers 235 or the equivalent are not considered part of the section titles. 236 M. Delete any section entitled "Endorsements". Such a section 237 may not be included in the Modified Version. 238 N. Do not retitle any existing section as "Endorsements" 239 or to conflict in title with any Invariant Section. 240 241 If the Modified Version includes new front-matter sections or 242 appendices that qualify as Secondary Sections and contain no material 243 copied from the Document, you may at your option designate some or all 244 of these sections as invariant. To do this, add their titles to the 245 list of Invariant Sections in the Modified Version's license notice. 246 These titles must be distinct from any other section titles. 247 248 You may add a section entitled "Endorsements", provided it contains 249 nothing but endorsements of your Modified Version by various 250 parties--for example, statements of peer review or that the text has 251 been approved by an organization as the authoritative definition of a 252 standard. 253 254 You may add a passage of up to five words as a Front-Cover Text, and a 255 passage of up to 25 words as a Back-Cover Text, to the end of the list 256 of Cover Texts in the Modified Version. Only one passage of 257 Front-Cover Text and one of Back-Cover Text may be added by (or 258 through arrangements made by) any one entity. If the Document already 259 includes a cover text for the same cover, previously added by you or 260 by arrangement made by the same entity you are acting on behalf of, 261 you may not add another; but you may replace the old one, on explicit 262 permission from the previous publisher that added the old one. 263 264 The author(s) and publisher(s) of the Document do not by this License 265 give permission to use their names for publicity for or to assert or 266 imply endorsement of any Modified Version. 267 268 269 5. COMBINING DOCUMENTS 270 271 You may combine the Document with other documents released under this 272 License, under the terms defined in section 4 above for modified 273 versions, provided that you include in the combination all of the 274 Invariant Sections of all of the original documents, unmodified, and 275 list them all as Invariant Sections of your combined work in its 276 license notice. 277 278 The combined work need only contain one copy of this License, and 279 multiple identical Invariant Sections may be replaced with a single 280 copy. If there are multiple Invariant Sections with the same name but 281 different contents, make the title of each such section unique by 282 adding at the end of it, in parentheses, the name of the original 283 author or publisher of that section if known, or else a unique number. 284 Make the same adjustment to the section titles in the list of 285 Invariant Sections in the license notice of the combined work. 286 287 In the combination, you must combine any sections entitled "History" 288 in the various original documents, forming one section entitled 289 "History"; likewise combine any sections entitled "Acknowledgements", 290 and any sections entitled "Dedications". You must delete all sections 291 entitled "Endorsements." 292 293 294 6. COLLECTIONS OF DOCUMENTS 295 296 You may make a collection consisting of the Document and other documents 297 released under this License, and replace the individual copies of this 298 License in the various documents with a single copy that is included in 299 the collection, provided that you follow the rules of this License for 300 verbatim copying of each of the documents in all other respects. 301 302 You may extract a single document from such a collection, and distribute 303 it individually under this License, provided you insert a copy of this 304 License into the extracted document, and follow this License in all 305 other respects regarding verbatim copying of that document. 306 307 308 7. AGGREGATION WITH INDEPENDENT WORKS 309 310 A compilation of the Document or its derivatives with other separate 311 and independent documents or works, in or on a volume of a storage or 312 distribution medium, does not as a whole count as a Modified Version 313 of the Document, provided no compilation copyright is claimed for the 314 compilation. Such a compilation is called an "aggregate", and this 315 License does not apply to the other self-contained works thus compiled 316 with the Document, on account of their being thus compiled, if they 317 are not themselves derivative works of the Document. 318 319 If the Cover Text requirement of section 3 is applicable to these 320 copies of the Document, then if the Document is less than one quarter 321 of the entire aggregate, the Document's Cover Texts may be placed on 322 covers that surround only the Document within the aggregate. 323 Otherwise they must appear on covers around the whole aggregate. 324 325 326 8. TRANSLATION 327 328 Translation is considered a kind of modification, so you may 329 distribute translations of the Document under the terms of section 4. 330 Replacing Invariant Sections with translations requires special 331 permission from their copyright holders, but you may include 332 translations of some or all Invariant Sections in addition to the 333 original versions of these Invariant Sections. You may include a 334 translation of this License provided that you also include the 335 original English version of this License. In case of a disagreement 336 between the translation and the original English version of this 337 License, the original English version will prevail. 338 339 340 9. TERMINATION 341 342 You may not copy, modify, sublicense, or distribute the Document except 343 as expressly provided for under this License. Any other attempt to 344 copy, modify, sublicense or distribute the Document is void, and will 345 automatically terminate your rights under this License. However, 346 parties who have received copies, or rights, from you under this 347 License will not have their licenses terminated so long as such 348 parties remain in full compliance. 349 350 351 10. FUTURE REVISIONS OF THIS LICENSE 352 353 The Free Software Foundation may publish new, revised versions 354 of the GNU Free Documentation License from time to time. Such new 355 versions will be similar in spirit to the present version, but may 356 differ in detail to address new problems or concerns. See 357 http://www.gnu.org/copyleft/. 358 359 Each version of the License is given a distinguishing version number. 360 If the Document specifies that a particular numbered version of this 361 License "or any later version" applies to it, you have the option of 362 following the terms and conditions either of that specified version or 363 of any later version that has been published (not as a draft) by the 364 Free Software Foundation. If the Document does not specify a version 365 number of this License, you may choose any version ever published (not 366 as a draft) by the Free Software Foundation. 367 368 369 ADDENDUM: How to use this License for your documents 370 371 To use this License in a document you have written, include a copy of 372 the License in the document and put the following copyright and 373 license notices just after the title page: 374 375 Copyright (c) YEAR YOUR NAME. 376 Permission is granted to copy, distribute and/or modify this document 377 under the terms of the GNU Free Documentation License, Version 1.1 378 or any later version published by the Free Software Foundation; 379 with the Invariant Sections being LIST THEIR TITLES, with the 380 Front-Cover Texts being LIST, and with the Back-Cover Texts being LIST. 381 A copy of the license is included in the section entitled "GNU 382 Free Documentation License". 383 384 If you have no Invariant Sections, write "with no Invariant Sections" 385 instead of saying which ones are invariant. If you have no 386 Front-Cover Texts, write "no Front-Cover Texts" instead of 387 "Front-Cover Texts being LIST"; likewise for Back-Cover Texts. 388 389 If your document contains nontrivial examples of program code, we 390 recommend releasing these examples in parallel under your choice of 391 free software license, such as the GNU General Public License, 392 to permit their use in free software. 55 393 ============================================================================ -
doc/user-guide/commands.xml
rcc6fdf8 r92d3044 835 835 <bitlbee-setting name="commands" type="boolean" scope="account"> 836 836 <default>true</default> 837 <possible-values>true, false, strict</possible-values>838 837 839 838 <description> … … 854 853 855 854 <para> 856 Anything that doesn't look like a command will be treated as a tweet. Watch out for typos , or to avoid this behaviour, you can set this setting to <emphasis>strict</emphasis>, which causes the <emphasis>post</emphasis> command to become mandatory for posting a tweet.855 Anything that doesn't look like a command will be treated as a tweet. Watch out for typos! :-) 857 856 </para> 858 857 </description> … … 1024 1023 1025 1024 </bitlbee-setting> 1026 1027 <bitlbee-setting name="stream" type="boolean" scope="account">1028 <default>true</default>1029 1030 <description>1031 <para>1032 For Twitter accounts, this setting enables use of the Streaming API. This automatically gives you incoming DMs as well.1033 </para>1034 1035 <para>1036 For other Twitter-like services, this setting is not supported.1037 </para>1038 </description>1039 1040 </bitlbee-setting>1041 1025 1042 1026 <bitlbee-setting name="target_url_length" type="integer" scope="account"> … … 1354 1338 1355 1339 <bitlbee-setting name="show_ids" type="boolean" scope="account"> 1356 <default> true</default>1340 <default>false</default> 1357 1341 1358 1342 <description> -
lib/Makefile
rcc6fdf8 r92d3044 13 13 14 14 # [SH] Program variables 15 objects = arc.o base64.o $(EVENT_HANDLER) ftutil.o http_client.o ini.o json.o json_util.omd5.o misc.o oauth.o oauth2.o proxy.o sha1.o $(SSL_CLIENT) url.o xmltree.o15 objects = arc.o base64.o $(EVENT_HANDLER) ftutil.o http_client.o ini.o md5.o misc.o oauth.o oauth2.o proxy.o sha1.o $(SSL_CLIENT) url.o xmltree.o 16 16 17 17 LFLAGS += -r -
lib/http_client.c
rcc6fdf8 r92d3044 193 193 } 194 194 195 static gboolean http_handle_headers( struct http_request *req );196 197 195 static gboolean http_incoming_data( gpointer data, int source, b_input_condition cond ) 198 196 { 199 197 struct http_request *req = data; 200 char buffer[4096]; 201 char *s; 198 int evil_server = 0; 199 char buffer[2048]; 200 char *end1, *end2, *s; 202 201 size_t content_length; 203 202 int st; … … 219 218 packets that abort connections! \o/ */ 220 219 221 goto eof;220 goto got_reply; 222 221 } 223 222 } 224 223 else if( st == 0 ) 225 224 { 226 goto eof;225 goto got_reply; 227 226 } 228 227 } … … 240 239 else if( st == 0 ) 241 240 { 242 goto eof;243 } 244 } 245 246 if( st > 0 && !req->sbuf)241 goto got_reply; 242 } 243 } 244 245 if( st > 0 ) 247 246 { 248 247 req->reply_headers = g_realloc( req->reply_headers, req->bytes_read + st + 1 ); 249 248 memcpy( req->reply_headers + req->bytes_read, buffer, st ); 250 249 req->bytes_read += st; 251 252 st = 0; 253 } 254 255 if( st >= 0 && ( req->flags & HTTPC_STREAMING ) ) 256 { 257 if( !req->reply_body && 258 ( strstr( req->reply_headers, "\r\n\r\n" ) || 259 strstr( req->reply_headers, "\n\n" ) ) ) 260 { 261 size_t hlen; 262 263 /* We've now received all headers, so process them once 264 before we start feeding back data. */ 265 if( !http_handle_headers( req ) ) 266 return FALSE; 267 268 hlen = req->reply_body - req->reply_headers; 269 270 req->sblen = req->bytes_read - hlen; 271 req->sbuf = g_memdup( req->reply_body, req->sblen + 1 ); 272 req->reply_headers = g_realloc( req->reply_headers, hlen + 1 ); 273 274 req->reply_body = req->sbuf; 275 } 276 277 if( st > 0 ) 278 { 279 int pos = req->reply_body - req->sbuf; 280 req->sbuf = g_realloc( req->sbuf, req->sblen + st + 1 ); 281 memcpy( req->sbuf + req->sblen, buffer, st ); 282 req->bytes_read += st; 283 req->sblen += st; 284 req->sbuf[req->sblen] = '\0'; 285 req->reply_body = req->sbuf + pos; 286 req->body_size = req->sblen - pos; 287 } 288 289 if( req->reply_body ) 290 req->func( req ); 291 } 292 293 if( ssl_pending( req->ssl ) ) 294 return http_incoming_data( data, source, cond ); 250 } 295 251 296 252 /* There will be more! */ … … 299 255 http_incoming_data, req ); 300 256 301 return FALSE; 302 303 eof: 304 req->flags |= HTTPC_EOF; 305 257 if( ssl_pending( req->ssl ) ) 258 return http_incoming_data( data, source, cond ); 259 else 260 return FALSE; 261 262 got_reply: 306 263 /* Maybe if the webserver is overloaded, or when there's bad SSL 307 264 support... */ … … 312 269 } 313 270 314 if( !( req->flags & HTTPC_STREAMING ) )315 {316 /* Returns FALSE if we were redirected, in which case we should abort317 and not run any callback yet. */318 if( !http_handle_headers( req ) )319 return FALSE;320 }321 322 cleanup:323 if( req->ssl )324 ssl_disconnect( req->ssl );325 else326 closesocket( req->fd );327 328 if( ( s = get_rfc822_header( req->reply_headers, "Content-Length", 0 ) ) &&329 sscanf( s, "%zd", &content_length ) == 1 )330 {331 if( content_length < req->body_size )332 {333 req->status_code = -1;334 g_free( req->status_string );335 req->status_string = g_strdup( "Response truncated" );336 }337 }338 g_free( s );339 340 if( getenv( "BITLBEE_DEBUG" ) && req )341 printf( "Finishing HTTP request with status: %s\n",342 req->status_string ? req->status_string : "NULL" );343 344 req->func( req );345 http_free( req );346 return FALSE;347 }348 349 /* Splits headers and body. Checks result code, in case of 300s it'll handle350 redirects. If this returns FALSE, don't call any callbacks! */351 static gboolean http_handle_headers( struct http_request *req )352 {353 char *end1, *end2;354 int evil_server = 0;355 356 271 /* Zero termination is very convenient. */ 357 req->reply_headers[req->bytes_read] = '\0';272 req->reply_headers[req->bytes_read] = 0; 358 273 359 274 /* Find the separation between headers and body, and keep stupid … … 374 289 { 375 290 req->status_string = g_strdup( "Malformed HTTP reply" ); 376 return TRUE;291 goto cleanup; 377 292 } 378 293 … … 391 306 if( ( end1 = strchr( req->reply_headers, ' ' ) ) != NULL ) 392 307 { 393 if( sscanf( end1 + 1, "% hd", &req->status_code ) != 1 )308 if( sscanf( end1 + 1, "%d", &req->status_code ) != 1 ) 394 309 { 395 310 req->status_string = g_strdup( "Can't parse status code" ); … … 434 349 { 435 350 req->status_string = g_strdup( "Can't locate Location: header" ); 436 return TRUE;351 goto cleanup; 437 352 } 438 353 … … 454 369 req->status_string = g_strdup( "Can't handle recursive redirects" ); 455 370 456 return TRUE;371 goto cleanup; 457 372 } 458 373 else … … 465 380 s = strstr( loc, "\r\n" ); 466 381 if( s == NULL ) 467 return TRUE;382 goto cleanup; 468 383 469 384 url = g_new0( url_t, 1 ); … … 474 389 req->status_string = g_strdup( "Malformed redirect URL" ); 475 390 g_free( url ); 476 return TRUE;391 goto cleanup; 477 392 } 478 393 … … 486 401 req->status_string = g_strdup( "Error while rebuilding request string" ); 487 402 g_free( url ); 488 return TRUE;403 goto cleanup; 489 404 } 490 405 … … 552 467 req->status_string = g_strdup( "Connection problem during redirect" ); 553 468 g_free( new_request ); 554 return TRUE;469 goto cleanup; 555 470 } 556 471 … … 565 480 } 566 481 567 return TRUE; 568 } 569 570 void http_flush_bytes( struct http_request *req, size_t len ) 571 { 572 if( len <= 0 || len > req->body_size || !( req->flags & HTTPC_STREAMING ) ) 573 return; 574 575 req->reply_body += len; 576 req->body_size -= len; 577 578 if( req->reply_body - req->sbuf >= 512 ) 579 { 580 printf( "Wasting %ld bytes, cleaning up stream buffer\n", req->reply_body - req->sbuf ); 581 char *new = g_memdup( req->reply_body, req->body_size + 1 ); 582 g_free( req->sbuf ); 583 req->reply_body = req->sbuf = new; 584 req->sblen = req->body_size; 585 } 586 } 587 588 void http_close( struct http_request *req ) 589 { 590 if( !req ) 591 return; 592 593 if( req->inpa > 0 ) 594 b_event_remove( req->inpa ); 595 482 /* Assume that a closed connection means we're finished, this indeed 483 breaks with keep-alive connections and faulty connections. */ 484 /* req->finished = 1; */ 485 486 cleanup: 596 487 if( req->ssl ) 597 488 ssl_disconnect( req->ssl ); … … 599 490 closesocket( req->fd ); 600 491 492 if( ( s = get_rfc822_header( req->reply_headers, "Content-Length", 0 ) ) && 493 sscanf( s, "%zd", &content_length ) == 1 ) 494 { 495 if( content_length < req->body_size ) 496 { 497 req->status_code = -1; 498 g_free( req->status_string ); 499 req->status_string = g_strdup( "Response truncated" ); 500 } 501 } 502 g_free( s ); 503 504 if( getenv( "BITLBEE_DEBUG" ) && req ) 505 printf( "Finishing HTTP request with status: %s\n", 506 req->status_string ? req->status_string : "NULL" ); 507 508 req->func( req ); 601 509 http_free( req ); 510 return FALSE; 602 511 } 603 512 … … 607 516 g_free( req->reply_headers ); 608 517 g_free( req->status_string ); 609 g_free( req->sbuf );610 518 g_free( req ); 611 519 } 520 -
lib/http_client.h
rcc6fdf8 r92d3044 26 26 /* http_client allows you to talk (asynchronously, again) to HTTP servers. 27 27 In the "background" it will send the whole query and wait for a complete 28 response to come back. Initially written for MS Passport authentication, 29 but used for many other things now like OAuth and Twitter. 28 response to come back. Right now it's only used by the MSN Passport 29 authentication code, but it might be useful for other things too (for 30 example the AIM usericon patch uses this so icons can be stored on 31 webservers instead of the local filesystem). 30 32 31 It's very useful for doing quick requests without blocking the whole 32 program. Unfortunately it doesn't support fancy stuff like HTTP keep- 33 alives. */ 33 Didn't test this too much, but it seems to work well. Just don't look 34 at the code that handles HTTP 30x redirects. ;-) The function is 35 probably not very useful for downloading lots of data since it keeps 36 everything in a memory buffer until the download is completed (and 37 can't pass any data or whatever before then). It's very useful for 38 doing quick requests without blocking the whole program, though. */ 34 39 35 40 #include <glib.h> … … 37 42 38 43 struct http_request; 39 40 typedef enum http_client_flags41 {42 HTTPC_STREAMING = 1,43 HTTPC_EOF = 2,44 45 /* Let's reserve 0x1000000+ for lib users. */46 } http_client_flags_t;47 44 48 45 /* Your callback function should look like this: */ … … 56 53 char *request; /* The request to send to the server. */ 57 54 int request_length; /* Its size. */ 58 short status_code;/* The numeric HTTP status code. (Or -155 int status_code; /* The numeric HTTP status code. (Or -1 59 56 if something really went wrong) */ 60 57 char *status_string; /* The error text. */ … … 62 59 char *reply_body; 63 60 int body_size; /* The number of bytes in reply_body. */ 64 short redir_ttl; /* You can set it to 0 if you don't want 61 /* int finished; Set to non-0 if the request was completed 62 successfully. */ 63 int redir_ttl; /* You can set it to 0 if you don't want 65 64 http_client to follow them. */ 66 67 http_client_flags_t flags;68 65 69 66 http_input_function func; … … 71 68 72 69 /* Please don't touch the things down here, you shouldn't need them. */ 70 73 71 void *ssl; 74 72 int fd; … … 77 75 int bytes_written; 78 76 int bytes_read; 79 80 /* Used in streaming mode. Caller should read from reply_body. */81 char *sbuf;82 size_t sblen;83 77 }; 84 78 … … 89 83 struct http_request *http_dorequest( char *host, int port, int ssl, char *request, http_input_function func, gpointer data ); 90 84 struct http_request *http_dorequest_url( char *url_string, http_input_function func, gpointer data ); 91 92 /* For streaming connections only; flushes len bytes at the start of the buffer. */93 void http_flush_bytes( struct http_request *req, size_t len );94 void http_close( struct http_request *req ); -
lib/oauth2.c
rcc6fdf8 r92d3044 4 4 * Simple OAuth client (consumer) implementation. * 5 5 * * 6 * Copyright 2010-201 2Wilmer van der Gaast <wilmer@gaast.net> *6 * Copyright 2010-2011 Wilmer van der Gaast <wilmer@gaast.net> * 7 7 * * 8 8 * This program is free software; you can redistribute it and/or modify * … … 26 26 #include "oauth2.h" 27 27 #include "oauth.h" 28 #include "json.h"29 28 #include "url.h" 30 29 … … 45 44 }; 46 45 46 static char *oauth2_json_dumb_get( const char *json, const char *key ); 47 47 static void oauth2_access_token_done( struct http_request *req ); 48 48 … … 115 115 else if( content_type && strstr( content_type, "application/json" ) ) 116 116 { 117 json_value *js = json_parse( req->reply_body ); 118 if( js && js->type == json_object ) 119 { 120 int i; 121 122 for( i = 0; i < js->u.object.length; i ++ ) 123 { 124 if( js->u.object.values[i].value->type != json_string ) 125 continue; 126 if( strcmp( js->u.object.values[i].name, "access_token" ) == 0 ) 127 atoken = g_strdup( js->u.object.values[i].value->u.string.ptr ); 128 if( strcmp( js->u.object.values[i].name, "refresh_token" ) == 0 ) 129 rtoken = g_strdup( js->u.object.values[i].value->u.string.ptr ); 130 } 131 } 132 json_value_free( js ); 117 atoken = oauth2_json_dumb_get( req->reply_body, "access_token" ); 118 rtoken = oauth2_json_dumb_get( req->reply_body, "refresh_token" ); 133 119 } 134 120 else … … 151 137 g_free( cb_data ); 152 138 } 139 140 /* Super dumb. I absolutely refuse to use/add a complete json parser library 141 (adding a new dependency to BitlBee for the first time in.. 6 years?) just 142 to parse 100 bytes of data. So I have to do my own parsing because OAuth2 143 dropped support for XML. (GRRR!) This is very dumb and for example won't 144 work for integer values, nor will it strip/handle backslashes. */ 145 static char *oauth2_json_dumb_get( const char *json, const char *key ) 146 { 147 int is_key = 0; /* 1 == reading key, 0 == reading value */ 148 int found_key = 0; 149 150 while( json && *json ) 151 { 152 /* Grab strings and see if they're what we're looking for. */ 153 if( *json == '"' || *json == '\'' ) 154 { 155 char q = *json; 156 const char *str_start; 157 json ++; 158 str_start = json; 159 160 while( *json ) 161 { 162 /* \' and \" are not string terminators. */ 163 if( *json == '\\' && json[1] == q ) 164 json ++; 165 /* But without a \ it is. */ 166 else if( *json == q ) 167 break; 168 json ++; 169 } 170 if( *json == '\0' ) 171 return NULL; 172 173 if( is_key && strncmp( str_start, key, strlen( key ) ) == 0 ) 174 { 175 found_key = 1; 176 } 177 else if( !is_key && found_key ) 178 { 179 char *ret = g_memdup( str_start, json - str_start + 1 ); 180 ret[json-str_start] = '\0'; 181 return ret; 182 } 183 184 } 185 else if( *json == '{' || *json == ',' ) 186 { 187 found_key = 0; 188 is_key = 1; 189 } 190 else if( *json == ':' ) 191 is_key = 0; 192 193 json ++; 194 } 195 196 return NULL; 197 } -
lib/ssl_gnutls.c
rcc6fdf8 r92d3044 38 38 39 39 static gboolean initialized = FALSE; 40 gnutls_certificate_credentials _txcred;40 gnutls_certificate_credentials xcred; 41 41 42 42 #include <limits.h> … … 60 60 gboolean verify; 61 61 62 gnutls_session _tsession;62 gnutls_session session; 63 63 }; 64 64 … … 134 134 conn->data = data; 135 135 conn->inpa = -1; 136 conn->hostname = g_strdup( hostname );136 conn->hostname = hostname; 137 137 138 138 /* For now, SSL verification is globally enabled by setting the cafile … … 171 171 int verifyret = 0; 172 172 gnutls_x509_crt_t cert; 173 struct scd *conn;174 175 conn= gnutls_session_get_ptr( session );173 const char *hostname; 174 175 hostname = gnutls_session_get_ptr( session ); 176 176 177 177 gnutlsret = gnutls_certificate_verify_peers2( session, &status ); … … 211 211 return VERIFY_CERT_ERROR; 212 212 213 if( !gnutls_x509_crt_check_hostname( cert, conn->hostname ) )213 if( !gnutls_x509_crt_check_hostname( cert, hostname ) ) 214 214 { 215 215 verifyret |= VERIFY_CERT_INVALID; … … 267 267 268 268 gnutls_init( &conn->session, GNUTLS_CLIENT ); 269 gnutls_session_set_ptr( conn->session, (void *) conn ); 269 if( conn->verify ) 270 gnutls_session_set_ptr( conn->session, (void *) conn->hostname ); 270 271 #if GNUTLS_VERSION_NUMBER < 0x020c00 271 272 gnutls_transport_set_lowat( conn->session, 0 ); … … 275 276 276 277 sock_make_nonblocking( conn->fd ); 277 gnutls_transport_set_ptr( conn->session, (gnutls_transport_ptr _t) GNUTLS_STUPID_CAST conn->fd );278 gnutls_transport_set_ptr( conn->session, (gnutls_transport_ptr) GNUTLS_STUPID_CAST conn->fd ); 278 279 279 280 return ssl_handshake( data, source, cond ); … … 401 402 if( conn->session ) 402 403 gnutls_deinit( conn->session ); 403 g_free( conn->hostname );404 404 g_free( conn ); 405 405 } -
nick.c
rcc6fdf8 r92d3044 134 134 chop = fmt[1]; 135 135 if( chop == '\0' ) 136 {137 g_string_free( ret, TRUE );138 136 return NULL; 139 }140 137 fmt += 2; 141 138 } … … 190 187 else 191 188 { 192 g_string_free( ret, TRUE );193 189 return NULL; 194 190 } -
protocols/msn/msn.c
rcc6fdf8 r92d3044 53 53 54 54 ic->proto_data = md; 55 ic->flags |= OPT_PONGS | OPT_PONGED;56 55 57 56 if( strchr( acc->user, '@' ) == NULL ) -
protocols/msn/ns.c
rcc6fdf8 r92d3044 576 576 if( num_parts >= 7 ) 577 577 handler->msglen = atoi( cmd[6] ); 578 }579 else if( strcmp( cmd[0], "QNG" ) == 0 )580 {581 ic->flags |= OPT_PONGED;582 578 } 583 579 else if( isdigit( cmd[0][0] ) ) -
protocols/nogaim.c
rcc6fdf8 r92d3044 261 261 struct im_connection *ic = d; 262 262 263 if( ( ic->flags & OPT_PONGS ) && !( ic->flags & OPT_PONGED ) )264 {265 /* This protocol is expected to ack keepalives and hasn't266 since the last time we were here. */267 imcb_error( ic, "Connection timeout" );268 imc_logout( ic, TRUE );269 return FALSE;270 }271 ic->flags &= ~OPT_PONGED;272 273 263 if( ic->acc->prpl->keepalive ) 274 264 ic->acc->prpl->keepalive( ic ); 275 265 276 266 return TRUE; 277 }278 279 void start_keepalives( struct im_connection *ic, int interval )280 {281 b_event_remove( ic->keepalive );282 ic->keepalive = b_timeout_add( interval, send_keepalive, ic );283 284 /* Connecting successfully counts as a first successful pong. */285 if( ic->flags & OPT_PONGS )286 ic->flags |= OPT_PONGED;287 267 } 288 268 … … 297 277 imcb_log( ic, "Logged in" ); 298 278 279 b_event_remove( ic->keepalive ); 280 ic->keepalive = b_timeout_add( 60000, send_keepalive, ic ); 299 281 ic->flags |= OPT_LOGGED_IN; 300 start_keepalives( ic, 60000 );301 282 302 283 /* Necessary to send initial presence status, even if we're not away. */ -
protocols/nogaim.h
rcc6fdf8 r92d3044 68 68 #define OPT_THINKING 0x00000200 /* about these values... Stupid me! */ 69 69 #define OPT_NOOTR 0x00001000 /* protocol not suitable for OTR */ 70 #define OPT_PONGS 0x00010000 /* Service sends us keep-alives */71 #define OPT_PONGED 0x00020000 /* Received a keep-alive during last interval */72 70 73 71 /* ok. now the fun begins. first we create a connection structure */ -
protocols/twitter/twitter.c
rcc6fdf8 r92d3044 4 4 * Simple module to facilitate twitter functionality. * 5 5 * * 6 * Copyright 2009-2010 Geert Mulders <g.c.w.m.mulders@gmail.com> * 7 * Copyright 2010-2012 Wilmer van der Gaast <wilmer@gaast.net> * 6 * Copyright 2009 Geert Mulders <g.c.w.m.mulders@gmail.com> * 8 7 * * 9 8 * This library is free software; you can redistribute it and/or * … … 30 29 #include "url.h" 31 30 31 #define twitter_msg( ic, fmt... ) \ 32 do { \ 33 struct twitter_data *td = ic->proto_data; \ 34 if( td->timeline_gc ) \ 35 imcb_chat_log( td->timeline_gc, fmt ); \ 36 else \ 37 imcb_log( ic, fmt ); \ 38 } while( 0 ); 39 32 40 GSList *twitter_connections = NULL; 41 33 42 /** 34 43 * Main loop function … … 53 62 struct twitter_data *td = ic->proto_data; 54 63 55 /* Create the room now that we "logged in". */56 if (td->flags & TWITTER_MODE_CHAT)57 twitter_groupchat_init(ic);58 59 64 imcb_log(ic, "Getting initial statuses"); 60 65 61 // Run this once. After this queue the main loop function (or open the 62 // stream if available). 66 // Run this once. After this queue the main loop function. 63 67 twitter_main_loop(ic, -1, 0); 64 65 if (set_getbool(&ic->acc->set, "stream")) { 66 /* That fetch was just to get backlog, the stream will give 67 us the rest. \o/ */ 68 twitter_open_stream(ic); 69 70 /* Stream sends keepalives (empty lines) or actual data at 71 least twice a minute. Disconnect if this stops. */ 72 ic->flags |= OPT_PONGS; 73 } else { 74 /* Not using the streaming API, so keep polling the old- 75 fashioned way. :-( */ 76 td->main_loop_id = 77 b_timeout_add(set_getint(&ic->acc->set, "fetch_interval") * 1000, 78 twitter_main_loop, ic); 79 } 80 } 81 82 struct groupchat *twitter_groupchat_init(struct im_connection *ic) 83 { 84 char *name_hint; 85 struct groupchat *gc; 86 struct twitter_data *td = ic->proto_data; 87 GSList *l; 88 89 if (td->timeline_gc) 90 return td->timeline_gc; 91 92 td->timeline_gc = gc = imcb_chat_new(ic, "twitter/timeline"); 93 94 name_hint = g_strdup_printf("%s_%s", td->prefix, ic->acc->user); 95 imcb_chat_name_hint(gc, name_hint); 96 g_free(name_hint); 97 98 for (l = ic->bee->users; l; l = l->next) { 99 bee_user_t *bu = l->data; 100 if (bu->ic == ic) 101 imcb_chat_add_buddy(gc, bu->handle); 102 } 103 imcb_chat_add_buddy(gc, ic->acc->user); 104 105 return gc; 68 69 // Queue the main_loop 70 // Save the return value, so we can remove the timeout on logout. 71 td->main_loop_id = 72 b_timeout_add(set_getint(&ic->acc->set, "fetch_interval") * 1000, twitter_main_loop, ic); 106 73 } 107 74 … … 116 83 if (set_getbool(&ic->acc->set, "oauth") && !td->oauth_info) 117 84 twitter_oauth_start(ic); 118 else if ( !(td->flags & TWITTER_MODE_ONE)&&119 85 else if (g_strcasecmp(set_getstr(&ic->acc->set, "mode"), "one") != 0 && 86 !(td->flags & TWITTER_HAVE_FRIENDS)) { 120 87 imcb_log(ic, "Getting contact list"); 121 88 twitter_get_friends_ids(ic, -1); 89 //twitter_get_statuses_friends(ic, -1); 122 90 } else 123 91 twitter_main_loop_start(ic); … … 219 187 } 220 188 189 190 static char *set_eval_mode(set_t * set, char *value) 191 { 192 if (g_strcasecmp(value, "one") == 0 || 193 g_strcasecmp(value, "many") == 0 || g_strcasecmp(value, "chat") == 0) 194 return value; 195 else 196 return NULL; 197 } 198 221 199 int twitter_url_len_diff(gchar *msg, unsigned int target_len) 222 200 { … … 255 233 return TRUE; 256 234 257 twitter_log(ic, "Maximum message length exceeded: %d > %d", len, max);235 imcb_error(ic, "Maximum message length exceeded: %d > %d", len, max); 258 236 259 237 return FALSE; 260 }261 262 static char *set_eval_commands(set_t * set, char *value)263 {264 if (g_strcasecmp(value, "strict") == 0 )265 return value;266 else267 return set_eval_bool(set, value);268 }269 270 static char *set_eval_mode(set_t * set, char *value)271 {272 if (g_strcasecmp(value, "one") == 0 ||273 g_strcasecmp(value, "many") == 0 || g_strcasecmp(value, "chat") == 0)274 return value;275 else276 return NULL;277 238 } 278 239 … … 296 257 s->flags |= ACC_SET_OFFLINE_ONLY; 297 258 298 s = set_add(&acc->set, "commands", "true", set_eval_ commands, acc);259 s = set_add(&acc->set, "commands", "true", set_eval_bool, acc); 299 260 300 261 s = set_add(&acc->set, "fetch_interval", "60", set_eval_int, acc); … … 313 274 314 275 s = set_add(&acc->set, "show_ids", "true", set_eval_bool, acc); 276 s->flags |= ACC_SET_OFFLINE_ONLY; 315 277 316 278 s = set_add(&acc->set, "show_old_mentions", "20", set_eval_int, acc); 317 279 318 280 s = set_add(&acc->set, "strip_newlines", "false", set_eval_bool, acc); 319 320 if (strcmp(acc->prpl->name, "twitter") == 0) {321 s = set_add(&acc->set, "stream", "true", set_eval_bool, acc);322 s->flags |= ACC_SET_OFFLINE_ONLY;323 }324 281 } 325 282 326 283 /** 327 * Login method. Since the twitter API works with sep arate HTTP request we284 * Login method. Since the twitter API works with seperate HTTP request we 328 285 * only save the user and pass to the twitter_data object. 329 286 */ … … 341 298 imc_logout(ic, FALSE); 342 299 return; 343 }344 345 if (!strstr(url.host, "twitter.com") &&346 set_getbool(&ic->acc->set, "stream")) {347 imcb_error(ic, "Warning: The streaming API is only supported by Twitter, "348 "and you seem to be connecting to a different service.");349 300 } 350 301 … … 389 340 imcb_buddy_status(ic, name, OPT_LOGGED_IN, NULL, NULL); 390 341 391 td->log = g_new0(struct twitter_log_data, TWITTER_LOG_LENGTH); 392 td->log_id = -1; 393 394 s = set_getstr(&ic->acc->set, "mode"); 395 if (g_strcasecmp(s, "one") == 0) 396 td->flags |= TWITTER_MODE_ONE; 397 else if (g_strcasecmp(s, "many") == 0) 398 td->flags |= TWITTER_MODE_MANY; 399 else 400 td->flags |= TWITTER_MODE_CHAT; 342 if (set_getbool(&acc->set, "show_ids")) 343 td->log = g_new0(struct twitter_log_data, TWITTER_LOG_LENGTH); 401 344 402 345 twitter_login_finish(ic); … … 420 363 421 364 if (td) { 422 http_close(td->stream);423 365 oauth_info_free(td->oauth_info); 424 366 g_free(td->user); … … 553 495 * Returns 0 if the user provides garbage. 554 496 */ 555 static guint64 twitter_message_id_from_command_arg(struct im_connection *ic, char *arg, bee_user_t **bu_) { 556 struct twitter_data *td = ic->proto_data; 497 static guint64 twitter_message_id_from_command_arg(struct im_connection *ic, struct twitter_data *td, char *arg) { 557 498 struct twitter_user_data *tud; 558 bee_user_t *bu = NULL;499 bee_user_t *bu; 559 500 guint64 id = 0; 560 561 if (bu_) 562 *bu_ = NULL; 563 if (!arg || !arg[0]) 564 return 0; 565 566 if (arg[0] != '#' && (bu = bee_user_by_handle(ic->bee, ic, arg))) { 567 if ((tud = bu->data)) 568 id = tud->last_id; 569 } else { 570 if (arg[0] == '#') 571 arg++; 572 if (sscanf(arg, "%" G_GINT64_MODIFIER "x", &id) == 1 && 573 id < TWITTER_LOG_LENGTH) { 574 bu = td->log[id].bu; 501 if (g_str_has_prefix(arg, "#") && 502 sscanf(arg + 1, "%" G_GUINT64_FORMAT, &id) == 1) { 503 if (id < TWITTER_LOG_LENGTH && td->log) 575 504 id = td->log[id].id; 576 /* Beware of dangling pointers! */ 577 if (!g_slist_find(ic->bee->users, bu)) 578 bu = NULL; 579 } else if (sscanf(arg, "%" G_GINT64_MODIFIER "d", &id) == 1) { 580 /* Allow normal tweet IDs as well; not a very useful 581 feature but it's always been there. Just ignore 582 very low IDs to avoid accidents. */ 583 if (id < 1000000) 584 id = 0; 585 } 586 } 587 if (bu_) 588 *bu_ = bu; 505 } else if ((bu = bee_user_by_handle(ic->bee, ic, arg)) && 506 (tud = bu->data) && tud->last_id) 507 id = tud->last_id; 508 else if (sscanf(arg, "%" G_GUINT64_FORMAT, &id) == 1){ 509 if (id < TWITTER_LOG_LENGTH && td->log) 510 id = td->log[id].id; 511 } 589 512 return id; 590 513 } … … 594 517 struct twitter_data *td = ic->proto_data; 595 518 char *cmds, **cmd, *new = NULL; 596 guint64 in_reply_to = 0, id; 597 gboolean allow_post = 598 g_strcasecmp(set_getstr(&ic->acc->set, "commands"), "strict") != 0; 599 bee_user_t *bu = NULL; 519 guint64 in_reply_to = 0; 600 520 601 521 cmds = g_strdup(message); … … 603 523 604 524 if (cmd[0] == NULL) { 605 goto eof; 606 } else if (!set_getbool(&ic->acc->set, "commands") && allow_post) { 607 /* Not supporting commands if "commands" is set to true/strict. */ 525 g_free(cmds); 526 return; 527 } else if (!set_getbool(&ic->acc->set, "commands")) { 528 /* Not supporting commands. */ 608 529 } else if (g_strcasecmp(cmd[0], "undo") == 0) { 530 guint64 id; 531 609 532 if (cmd[1] == NULL) 610 533 twitter_status_destroy(ic, td->last_status_id); 611 else if ((id = twitter_message_id_from_command_arg(ic, cmd[1], NULL))) 534 else if (sscanf(cmd[1], "%" G_GUINT64_FORMAT, &id) == 1) { 535 if (id < TWITTER_LOG_LENGTH && td->log) 536 id = td->log[id].id; 537 612 538 twitter_status_destroy(ic, id); 613 else 614 twitter_log(ic, "Could not undo last action"); 615 616 goto eof; 539 } else 540 twitter_msg(ic, "Could not undo last action"); 541 542 g_free(cmds); 543 return; 617 544 } else if (g_strcasecmp(cmd[0], "favourite") == 0 && cmd[1]) { 618 if ((id = twitter_message_id_from_command_arg(ic, cmd[1], NULL))) { 545 guint64 id; 546 if ((id = twitter_message_id_from_command_arg(ic, td, cmd[1]))) { 619 547 twitter_favourite_tweet(ic, id); 620 548 } else { 621 twitter_log(ic, "Please provide a message ID or username."); 622 } 623 goto eof; 549 twitter_msg(ic, "Please provide a message ID or username."); 550 } 551 g_free(cmds); 552 return; 624 553 } else if (g_strcasecmp(cmd[0], "follow") == 0 && cmd[1]) { 625 554 twitter_add_buddy(ic, cmd[1], NULL); 626 goto eof; 555 g_free(cmds); 556 return; 627 557 } else if (g_strcasecmp(cmd[0], "unfollow") == 0 && cmd[1]) { 628 558 twitter_remove_buddy(ic, cmd[1], NULL); 629 goto eof; 559 g_free(cmds); 560 return; 630 561 } else if ((g_strcasecmp(cmd[0], "report") == 0 || 631 562 g_strcasecmp(cmd[0], "spam") == 0) && cmd[1]) { 632 char *screen_name; 633 563 char * screen_name; 564 guint64 id; 565 screen_name = cmd[1]; 634 566 /* Report nominally works on users but look up the user who 635 567 posted the given ID if the user wants to do it that way */ 636 twitter_message_id_from_command_arg(ic, cmd[1], &bu); 637 if (bu) 638 screen_name = bu->handle; 639 else 640 screen_name = cmd[1]; 641 568 if (g_str_has_prefix(cmd[1], "#") && 569 sscanf(cmd[1] + 1, "%" G_GUINT64_FORMAT, &id) == 1) { 570 if (id < TWITTER_LOG_LENGTH && td->log) { 571 if (g_slist_find(ic->bee->users, td->log[id].bu)) { 572 screen_name = td->log[id].bu->handle; 573 } 574 } 575 } 642 576 twitter_report_spam(ic, screen_name); 643 goto eof; 577 g_free(cmds); 578 return; 644 579 } else if (g_strcasecmp(cmd[0], "rt") == 0 && cmd[1]) { 645 id = twitter_message_id_from_command_arg(ic, cmd[1], NULL);580 guint64 id = twitter_message_id_from_command_arg(ic, td, cmd[1]); 646 581 647 582 td->last_status_id = 0; … … 649 584 twitter_status_retweet(ic, id); 650 585 else 651 twitter_ log(ic, "User `%s' does not exist or didn't "586 twitter_msg(ic, "User `%s' does not exist or didn't " 652 587 "post any statuses recently", cmd[1]); 653 588 654 goto eof; 589 g_free(cmds); 590 return; 655 591 } else if (g_strcasecmp(cmd[0], "reply") == 0 && cmd[1] && cmd[2]) { 656 id = twitter_message_id_from_command_arg(ic, cmd[1], &bu); 592 struct twitter_user_data *tud; 593 bee_user_t *bu = NULL; 594 guint64 id = 0; 595 596 if (g_str_has_prefix(cmd[1], "#") && 597 sscanf(cmd[1] + 1, "%" G_GUINT64_FORMAT, &id) == 1 && 598 (id < TWITTER_LOG_LENGTH) && td->log) { 599 bu = td->log[id].bu; 600 if (g_slist_find(ic->bee->users, bu)) 601 id = td->log[id].id; 602 else 603 bu = NULL; 604 } else if ((bu = bee_user_by_handle(ic->bee, ic, cmd[1])) && 605 (tud = bu->data) && tud->last_id) { 606 id = tud->last_id; 607 } else if (sscanf(cmd[1], "%" G_GUINT64_FORMAT, &id) == 1 && 608 (id < TWITTER_LOG_LENGTH) && td->log) { 609 bu = td->log[id].bu; 610 if (g_slist_find(ic->bee->users, bu)) 611 id = td->log[id].id; 612 else 613 bu = NULL; 614 } 615 657 616 if (!id || !bu) { 658 twitter_ log(ic, "User `%s' does not exist or didn't "617 twitter_msg(ic, "User `%s' does not exist or didn't " 659 618 "post any statuses recently", cmd[1]); 660 goto eof; 619 g_free(cmds); 620 return; 661 621 } 662 622 message = new = g_strdup_printf("@%s %s", bu->handle, message + (cmd[2] - cmd[0])); 663 623 in_reply_to = id; 664 allow_post = TRUE;665 624 } else if (g_strcasecmp(cmd[0], "post") == 0) { 666 625 message += 5; 667 allow_post = TRUE; 668 } 669 670 if (allow_post) { 626 } 627 628 { 671 629 char *s; 672 673 if (!twitter_length_check(ic, message)) 674 goto eof; 630 bee_user_t *bu; 631 632 if (!twitter_length_check(ic, message)) { 633 g_free(new); 634 g_free(cmds); 635 return; 636 } 675 637 676 638 s = cmd[0] + strlen(cmd[0]) - 1; … … 695 657 td->last_status_id = 0; 696 658 twitter_post_status(ic, message, in_reply_to); 697 } else { 698 twitter_log(ic, "Unknown command: %s", cmd[0]); 699 } 700 eof: 701 g_free(new); 659 g_free(new); 660 } 702 661 g_free(cmds); 703 662 } 704 705 void twitter_log(struct im_connection *ic, char *format, ... )706 {707 struct twitter_data *td = ic->proto_data;708 va_list params;709 char *text;710 711 va_start(params, format);712 text = g_strdup_vprintf(format, params);713 va_end(params);714 715 if (td->timeline_gc)716 imcb_chat_log(td->timeline_gc, "%s", text);717 else718 imcb_log(ic, "%s", text);719 720 g_free(text);721 }722 723 663 724 664 void twitter_initmodule() -
protocols/twitter/twitter.h
rcc6fdf8 r92d3044 4 4 * Simple module to facilitate twitter functionality. * 5 5 * * 6 * Copyright 2009-2010 Geert Mulders <g.c.w.m.mulders@gmail.com> * 7 * Copyright 2010-2012 Wilmer van der Gaast <wilmer@gaast.net> * 6 * Copyright 2009 Geert Mulders <g.c.w.m.mulders@gmail.com> * 8 7 * * 9 8 * This library is free software; you can redistribute it and/or * … … 36 35 typedef enum 37 36 { 38 TWITTER_HAVE_FRIENDS = 0x00001, 39 TWITTER_MODE_ONE = 0x00002, 40 TWITTER_MODE_MANY = 0x00004, 41 TWITTER_MODE_CHAT = 0x00008, 37 TWITTER_HAVE_FRIENDS = 1, 42 38 TWITTER_DOING_TIMELINE = 0x10000, 43 TWITTER_GOT_TIMELINE 44 TWITTER_GOT_MENTIONS 39 TWITTER_GOT_TIMELINE = 0x20000, 40 TWITTER_GOT_MENTIONS = 0x40000, 45 41 } twitter_flags_t; 46 42 … … 61 57 guint64 last_status_id; /* For undo */ 62 58 gint main_loop_id; 63 struct http_request *stream;64 59 struct groupchat *timeline_gc; 65 60 gint http_fails; … … 85 80 }; 86 81 87 #define TWITTER_LOG_LENGTH 25682 #define TWITTER_LOG_LENGTH 100 88 83 struct twitter_log_data 89 84 { … … 104 99 char *twitter_parse_error( struct http_request *req ); 105 100 106 void twitter_log(struct im_connection *ic, char *format, ... );107 struct groupchat *twitter_groupchat_init(struct im_connection *ic);108 109 101 #endif //_TWITTER_H -
protocols/twitter/twitter_http.c
rcc6fdf8 r92d3044 47 47 * This is actually pretty generic function... Perhaps it should move to the lib/http_client.c 48 48 */ 49 struct http_request*twitter_http(struct im_connection *ic, char *url_string, http_input_function func,50 49 void *twitter_http(struct im_connection *ic, char *url_string, http_input_function func, 50 gpointer data, int is_post, char **arguments, int arguments_len) 51 51 { 52 52 struct twitter_data *td = ic->proto_data; … … 55 55 void *ret; 56 56 char *url_arguments; 57 url_t *base_url = NULL;58 57 59 58 url_arguments = g_strdup(""); … … 68 67 } 69 68 } 70 71 if (strstr(url_string, "://")) {72 base_url = g_new0(url_t, 1);73 if (!url_set(base_url, url_string)) {74 g_free(base_url);75 return NULL;76 }77 }78 79 69 // Make the request. 80 70 g_string_printf(request, "%s %s%s%s%s HTTP/1.0\r\n" … … 82 72 "User-Agent: BitlBee " BITLBEE_VERSION " " ARCH "/" CPU "\r\n", 83 73 is_post ? "POST" : "GET", 84 base_url ? base_url->file : td->url_path, 85 base_url ? "" : url_string, 86 is_post ? "" : "?", is_post ? "" : url_arguments, 87 base_url ? base_url->host : td->url_host); 74 td->url_path, url_string, 75 is_post ? "" : "?", is_post ? "" : url_arguments, td->url_host); 88 76 89 77 // If a pass and user are given we append them to the request. … … 92 80 char *full_url; 93 81 94 if (base_url) 95 full_url = g_strdup(url_string); 96 else 97 full_url = g_strconcat(set_getstr(&ic->acc->set, "base_url"), url_string, NULL); 82 full_url = g_strconcat(set_getstr(&ic->acc->set, "base_url"), url_string, NULL); 98 83 full_header = oauth_http_header(td->oauth_info, is_post ? "POST" : "GET", 99 84 full_url, url_arguments); … … 124 109 } 125 110 126 if (base_url) 127 ret = http_dorequest(base_url->host, base_url->port, base_url->proto == PROTO_HTTPS, request->str, func, data); 128 else 129 ret = http_dorequest(td->url_host, td->url_port, td->url_ssl, request->str, func, data); 111 ret = http_dorequest(td->url_host, td->url_port, td->url_ssl, request->str, func, data); 130 112 131 113 g_free(url_arguments); 132 114 g_string_free(request, TRUE); 133 g_free(base_url);134 return ret;135 }136 137 struct http_request *twitter_http_f(struct im_connection *ic, char *url_string, http_input_function func,138 gpointer data, int is_post, char **arguments, int arguments_len, twitter_http_flags_t flags)139 {140 struct http_request *ret = twitter_http(ic, url_string, func, data, is_post, arguments, arguments_len);141 if (ret)142 ret->flags |= flags;143 115 return ret; 144 116 } -
protocols/twitter/twitter_http.h
rcc6fdf8 r92d3044 28 28 #include "http_client.h" 29 29 30 typedef enum {31 /* With this set, twitter_http_post() will post a generic confirmation32 message to the user. */33 TWITTER_HTTP_USER_ACK = 0x1000000,34 } twitter_http_flags_t;35 36 30 struct oauth_info; 37 31 38 struct http_request *twitter_http(struct im_connection *ic, char *url_string, http_input_function func, 39 gpointer data, int is_post, char** arguments, int arguments_len); 40 struct http_request *twitter_http_f(struct im_connection *ic, char *url_string, http_input_function func, 41 gpointer data, int is_post, char** arguments, int arguments_len, twitter_http_flags_t flags); 32 void *twitter_http(struct im_connection *ic, char *url_string, http_input_function func, 33 gpointer data, int is_post, char** arguments, int arguments_len); 42 34 43 35 #endif //_TWITTER_HTTP_H -
protocols/twitter/twitter_lib.c
rcc6fdf8 r92d3044 35 35 #include "misc.h" 36 36 #include "base64.h" 37 #include "xmltree.h" 37 38 #include "twitter_lib.h" 38 #include "json_util.h"39 39 #include <ctype.h> 40 40 #include <errno.h> … … 67 67 char *text; 68 68 struct twitter_xml_user *user; 69 guint64 id, rt_id; /* Usually equal, with RTs id == *original* id */ 70 guint64 reply_to; 69 guint64 id, reply_to; 71 70 }; 71 72 static void twitter_groupchat_init(struct im_connection *ic); 72 73 73 74 /** … … 147 148 // Check if the buddy is already in the buddy list. 148 149 if (!bee_user_by_handle(ic->bee, ic, name)) { 150 char *mode = set_getstr(&ic->acc->set, "mode"); 151 149 152 // The buddy is not in the list, add the buddy and set the status to logged in. 150 153 imcb_add_buddy(ic, name, NULL); 151 154 imcb_rename_buddy(ic, name, fullname); 152 if ( td->flags & TWITTER_MODE_CHAT) {155 if (g_strcasecmp(mode, "chat") == 0) { 153 156 /* Necessary so that nicks always get translated to the 154 157 exact Twitter username. */ 155 158 imcb_buddy_nick_hint(ic, name, name); 156 if (td->timeline_gc) 157 imcb_chat_add_buddy(td->timeline_gc, name); 158 } else if (td->flags & TWITTER_MODE_MANY) 159 imcb_chat_add_buddy(td->timeline_gc, name); 160 } else if (g_strcasecmp(mode, "many") == 0) 159 161 imcb_buddy_status(ic, name, OPT_LOGGED_IN, NULL, NULL); 160 162 } … … 166 168 { 167 169 static char *ret = NULL; 168 json_value *root, *err;170 struct xt_node *root, *node, *err; 169 171 170 172 g_free(ret); … … 172 174 173 175 if (req->body_size > 0) { 174 root = json_parse(req->reply_body);175 err = json_o_get(root, "errors");176 if (err && err->type == json_array && (err = err->u.array.values[0]) &&177 err->type == json_object) {178 const char *msg = json_o_str(err, "message");179 if (msg)180 ret = g_strdup_printf("%s (%s)", req->status_string, msg);181 } 182 json_value_free(root);176 root = xt_from_string(req->reply_body, req->body_size); 177 178 for (node = root; node; node = node->next) 179 if ((err = xt_find_node(node->children, "error")) && err->text_len > 0) { 180 ret = g_strdup_printf("%s (%s)", req->status_string, err->text); 181 break; 182 } 183 184 xt_free_node(root); 183 185 } 184 186 … … 186 188 } 187 189 188 /* WATCH OUT: This function might or might not destroy your connection. 189 Sub-optimal indeed, but just be careful when this returns NULL! */ 190 static json_value *twitter_parse_response(struct im_connection *ic, struct http_request *req) 190 static struct xt_node *twitter_parse_response(struct im_connection *ic, struct http_request *req) 191 191 { 192 192 gboolean logging_in = !(ic->flags & OPT_LOGGED_IN); 193 193 gboolean periodic; 194 194 struct twitter_data *td = ic->proto_data; 195 json_value *ret;195 struct xt_node *ret; 196 196 char path[64] = "", *s; 197 197 … … 211 211 throwing 401s so I'll keep treating this one as fatal 212 212 only during login. */ 213 imcb_error(ic, "Authentication failure (%s)", 214 twitter_parse_error(req)); 213 imcb_error(ic, "Authentication failure"); 215 214 imc_logout(ic, FALSE); 216 215 return NULL; … … 218 217 // It didn't go well, output the error and return. 219 218 if (!periodic || logging_in || ++td->http_fails >= 5) 220 twitter_log(ic, "Error:Could not retrieve %s: %s",221 219 imcb_error(ic, "Could not retrieve %s: %s", 220 path, twitter_parse_error(req)); 222 221 223 222 if (logging_in) … … 228 227 } 229 228 230 if ((ret = json_parse(req->reply_body)) == NULL) {229 if ((ret = xt_from_string(req->reply_body, req->body_size)) == NULL) { 231 230 imcb_error(ic, "Could not retrieve %s: %s", 232 231 path, "XML parse error"); … … 252 251 253 252 /** 253 * Function to help fill a list. 254 */ 255 static xt_status twitter_xt_next_cursor(struct xt_node *node, struct twitter_xml_list *txl) 256 { 257 char *end = NULL; 258 259 if (node->text) 260 txl->next_cursor = g_ascii_strtoll(node->text, &end, 10); 261 if (end == NULL) 262 txl->next_cursor = -1; 263 264 return XT_HANDLED; 265 } 266 267 /** 254 268 * Fill a list of ids. 255 269 */ 256 static gboolean twitter_xt_get_friends_id_list(json_value *node, struct twitter_xml_list *txl) 257 { 258 json_value *c; 259 int i; 270 static xt_status twitter_xt_get_friends_id_list(struct xt_node *node, struct twitter_xml_list *txl) 271 { 272 struct xt_node *child; 260 273 261 274 // Set the list type. 262 275 txl->type = TXL_ID; 263 276 264 c = json_o_get(node, "ids"); 265 if (!c || c->type != json_array) 266 return FALSE; 267 268 for (i = 0; i < c->u.array.length; i ++) { 269 if (c->u.array.values[i]->type != json_integer) 270 continue; 271 272 txl->list = g_slist_prepend(txl->list, 273 g_strdup_printf("%lld", c->u.array.values[i]->u.integer)); 274 } 275 276 c = json_o_get(node, "next_cursor"); 277 if (c && c->type == json_integer) 278 txl->next_cursor = c->u.integer; 279 else 280 txl->next_cursor = -1; 281 282 return TRUE; 277 // The root <statuses> node should hold the list of statuses <status> 278 // Walk over the nodes children. 279 for (child = node->children; child; child = child->next) { 280 if (g_strcasecmp("ids", child->name) == 0) { 281 struct xt_node *idc; 282 for (idc = child->children; idc; idc = idc->next) 283 if (g_strcasecmp(idc->name, "id") == 0) 284 txl->list = g_slist_prepend(txl->list, 285 g_memdup(idc->text, idc->text_len + 1)); 286 } else if (g_strcasecmp("next_cursor", child->name) == 0) { 287 twitter_xt_next_cursor(child, txl); 288 } 289 } 290 291 return XT_HANDLED; 283 292 } 284 293 … … 291 300 { 292 301 struct im_connection *ic; 293 json_value *parsed;302 struct xt_node *parsed; 294 303 struct twitter_xml_list *txl; 295 304 struct twitter_data *td; … … 303 312 td = ic->proto_data; 304 313 314 /* Create the room now that we "logged in". */ 315 if (!td->timeline_gc && g_strcasecmp(set_getstr(&ic->acc->set, "mode"), "chat") == 0) 316 twitter_groupchat_init(ic); 317 305 318 txl = g_new0(struct twitter_xml_list, 1); 306 319 txl->list = td->follow_ids; … … 309 322 if (!(parsed = twitter_parse_response(ic, req))) 310 323 return; 311 312 324 twitter_xt_get_friends_id_list(parsed, txl); 313 json_value_free(parsed);325 xt_free_node(parsed); 314 326 315 327 td->follow_ids = txl->list; … … 326 338 } 327 339 328 static gboolean twitter_xt_get_users(json_value *node, struct twitter_xml_list *txl);340 static xt_status twitter_xt_get_users(struct xt_node *node, struct twitter_xml_list *txl); 329 341 static void twitter_http_get_users_lookup(struct http_request *req); 330 342 … … 367 379 { 368 380 struct im_connection *ic = req->data; 369 json_value *parsed;381 struct xt_node *parsed; 370 382 struct twitter_xml_list *txl; 371 383 GSList *l = NULL; … … 383 395 return; 384 396 twitter_xt_get_users(parsed, txl); 385 json_value_free(parsed);397 xt_free_node(parsed); 386 398 387 399 // Add the users as buddies. … … 397 409 } 398 410 399 struct twitter_xml_user *twitter_xt_get_user(const json_value *node) 400 { 401 struct twitter_xml_user *txu; 402 403 txu = g_new0(struct twitter_xml_user, 1); 404 txu->name = g_strdup(json_o_str(node, "name")); 405 txu->screen_name = g_strdup(json_o_str(node, "screen_name")); 406 407 return txu; 411 /** 412 * Function to fill a twitter_xml_user struct. 413 * It sets: 414 * - the name and 415 * - the screen_name. 416 */ 417 static xt_status twitter_xt_get_user(struct xt_node *node, struct twitter_xml_user *txu) 418 { 419 struct xt_node *child; 420 421 // Walk over the nodes children. 422 for (child = node->children; child; child = child->next) { 423 if (g_strcasecmp("name", child->name) == 0) { 424 txu->name = g_memdup(child->text, child->text_len + 1); 425 } else if (g_strcasecmp("screen_name", child->name) == 0) { 426 txu->screen_name = g_memdup(child->text, child->text_len + 1); 427 } 428 } 429 return XT_HANDLED; 408 430 } 409 431 … … 413 435 * - all <user>s from the <users> element. 414 436 */ 415 static gboolean twitter_xt_get_users(json_value *node, struct twitter_xml_list *txl)437 static xt_status twitter_xt_get_users(struct xt_node *node, struct twitter_xml_list *txl) 416 438 { 417 439 struct twitter_xml_user *txu; 418 int i;440 struct xt_node *child; 419 441 420 442 // Set the type of the list. 421 443 txl->type = TXL_USER; 422 444 423 if (!node || node->type != json_array)424 return FALSE;425 426 445 // The root <users> node should hold the list of users <user> 427 446 // Walk over the nodes children. 428 for (i = 0; i < node->u.array.length; i ++) { 429 txu = twitter_xt_get_user(node->u.array.values[i]); 430 if (txu) 447 for (child = node->children; child; child = child->next) { 448 if (g_strcasecmp("user", child->name) == 0) { 449 txu = g_new0(struct twitter_xml_user, 1); 450 twitter_xt_get_user(child, txu); 451 // Put the item in the front of the list. 431 452 txl->list = g_slist_prepend(txl->list, txu); 432 } 433 434 return TRUE; 453 } 454 } 455 456 return XT_HANDLED; 435 457 } 436 458 … … 440 462 #define TWITTER_TIME_FORMAT "%a %b %d %H:%M:%S +0000 %Y" 441 463 #endif 442 443 static char* expand_entities(char* text, const json_value *entities);444 464 445 465 /** … … 451 471 * - the user in a twitter_xml_user struct. 452 472 */ 453 static struct twitter_xml_status *twitter_xt_get_status(const json_value *node) 454 { 455 struct twitter_xml_status *txs; 456 const json_value *rt = NULL, *entities = NULL; 457 458 if (node->type != json_object) 459 return FALSE; 460 txs = g_new0(struct twitter_xml_status, 1); 461 462 JSON_O_FOREACH (node, k, v) { 463 if (strcmp("text", k) == 0 && v->type == json_string) { 464 txs->text = g_memdup(v->u.string.ptr, v->u.string.length + 1); 465 strip_html(txs->text); 466 } else if (strcmp("retweeted_status", k) == 0 && v->type == json_object) { 467 rt = v; 468 } else if (strcmp("created_at", k) == 0 && v->type == json_string) { 473 static xt_status twitter_xt_get_status(struct xt_node *node, struct twitter_xml_status *txs) 474 { 475 struct xt_node *child, *rt = NULL; 476 477 // Walk over the nodes children. 478 for (child = node->children; child; child = child->next) { 479 if (g_strcasecmp("text", child->name) == 0) { 480 txs->text = g_memdup(child->text, child->text_len + 1); 481 } else if (g_strcasecmp("retweeted_status", child->name) == 0) { 482 rt = child; 483 } else if (g_strcasecmp("created_at", child->name) == 0) { 469 484 struct tm parsed; 470 485 … … 472 487 this field. :-( Also assumes the timezone used 473 488 is UTC since C time handling functions suck. */ 474 if (strptime( v->u.string.ptr, TWITTER_TIME_FORMAT, &parsed) != NULL)489 if (strptime(child->text, TWITTER_TIME_FORMAT, &parsed) != NULL) 475 490 txs->created_at = mktime_utc(&parsed); 476 } else if (strcmp("user", k) == 0 && v->type == json_object) { 477 txs->user = twitter_xt_get_user(v); 478 } else if (strcmp("id", k) == 0 && v->type == json_integer) { 479 txs->rt_id = txs->id = v->u.integer; 480 } else if (strcmp("in_reply_to_status_id", k) == 0 && v->type == json_integer) { 481 txs->reply_to = v->u.integer; 482 } else if (strcmp("entities", k) == 0 && v->type == json_object) { 483 entities = v; 491 } else if (g_strcasecmp("user", child->name) == 0) { 492 txs->user = g_new0(struct twitter_xml_user, 1); 493 twitter_xt_get_user(child, txs->user); 494 } else if (g_strcasecmp("id", child->name) == 0) { 495 txs->id = g_ascii_strtoull(child->text, NULL, 10); 496 } else if (g_strcasecmp("in_reply_to_status_id", child->name) == 0) { 497 txs->reply_to = g_ascii_strtoull(child->text, NULL, 10); 484 498 } 485 499 } … … 488 502 wasn't truncated because it may be lying. */ 489 503 if (rt) { 490 struct twitter_xml_status *rtxs = twitter_xt_get_status(rt); 491 if (rtxs) { 492 g_free(txs->text); 493 txs->text = g_strdup_printf("RT @%s: %s", rtxs->user->screen_name, rtxs->text); 494 txs->id = rtxs->id; 504 struct twitter_xml_status *rtxs = g_new0(struct twitter_xml_status, 1); 505 if (twitter_xt_get_status(rt, rtxs) != XT_HANDLED) { 495 506 txs_free(rtxs); 496 } 497 } else if (entities) { 498 txs->text = expand_entities(txs->text, entities); 499 } 500 501 if (txs->text && txs->user && txs->id) 502 return txs; 503 504 txs_free(txs); 505 return NULL; 506 } 507 508 /** 509 * Function to fill a twitter_xml_status struct (DM variant). 510 */ 511 static struct twitter_xml_status *twitter_xt_get_dm(const json_value *node) 512 { 513 struct twitter_xml_status *txs; 514 const json_value *entities = NULL; 515 516 if (node->type != json_object) 517 return FALSE; 518 txs = g_new0(struct twitter_xml_status, 1); 519 520 JSON_O_FOREACH (node, k, v) { 521 if (strcmp("text", k) == 0 && v->type == json_string) { 522 txs->text = g_memdup(v->u.string.ptr, v->u.string.length + 1); 523 strip_html(txs->text); 524 } else if (strcmp("created_at", k) == 0 && v->type == json_string) { 525 struct tm parsed; 526 527 /* Very sensitive to changes to the formatting of 528 this field. :-( Also assumes the timezone used 529 is UTC since C time handling functions suck. */ 530 if (strptime(v->u.string.ptr, TWITTER_TIME_FORMAT, &parsed) != NULL) 531 txs->created_at = mktime_utc(&parsed); 532 } else if (strcmp("sender", k) == 0 && v->type == json_object) { 533 txs->user = twitter_xt_get_user(v); 534 } else if (strcmp("id", k) == 0 && v->type == json_integer) { 535 txs->id = v->u.integer; 536 } 537 } 538 539 if (entities) { 540 txs->text = expand_entities(txs->text, entities); 541 } 542 543 if (txs->text && txs->user && txs->id) 544 return txs; 545 546 txs_free(txs); 547 return NULL; 548 } 549 550 static char* expand_entities(char* text, const json_value *entities) { 551 JSON_O_FOREACH (entities, k, v) { 552 int i; 507 return XT_HANDLED; 508 } 509 510 g_free(txs->text); 511 txs->text = g_strdup_printf("RT @%s: %s", rtxs->user->screen_name, rtxs->text); 512 txs_free(rtxs); 513 } else { 514 struct xt_node *urls, *url; 553 515 554 if (v->type != json_array) 555 continue; 556 if (strcmp(k, "urls") != 0 && strcmp(k, "media") != 0) 557 continue; 558 559 for (i = 0; i < v->u.array.length; i ++) { 560 if (v->u.array.values[i]->type != json_object) 516 urls = xt_find_path(node, "entities"); 517 if (urls != NULL) 518 urls = urls->children; 519 for (; urls; urls = urls->next) { 520 if (strcmp(urls->name, "urls") != 0 && strcmp(urls->name, "media") != 0) 561 521 continue; 562 522 563 const char *kort = json_o_str(v->u.array.values[i], "url"); 564 const char *disp = json_o_str(v->u.array.values[i], "display_url"); 565 char *pos, *new; 566 567 if (!kort || !disp || !(pos = strstr(text, kort))) 568 continue; 569 570 *pos = '\0'; 571 new = g_strdup_printf("%s%s <%s>%s", text, kort, 572 disp, pos + strlen(kort)); 573 574 g_free(text); 575 text = new; 576 } 577 } 578 579 return text; 523 for (url = urls ? urls->children : NULL; url; url = url->next) { 524 /* "short" is a reserved word. :-P */ 525 struct xt_node *kort = xt_find_node(url->children, "url"); 526 struct xt_node *disp = xt_find_node(url->children, "display_url"); 527 char *pos, *new; 528 529 if (!kort || !kort->text || !disp || !disp->text || 530 !(pos = strstr(txs->text, kort->text))) 531 continue; 532 533 *pos = '\0'; 534 new = g_strdup_printf("%s%s <%s>%s", txs->text, kort->text, 535 disp->text, pos + strlen(kort->text)); 536 537 g_free(txs->text); 538 txs->text = new; 539 } 540 } 541 } 542 543 return XT_HANDLED; 580 544 } 581 545 … … 586 550 * - the next_cursor. 587 551 */ 588 static gboolean twitter_xt_get_status_list(struct im_connection *ic, const json_value *node,589 552 static xt_status twitter_xt_get_status_list(struct im_connection *ic, struct xt_node *node, 553 struct twitter_xml_list *txl) 590 554 { 591 555 struct twitter_xml_status *txs; 592 int i; 556 struct xt_node *child; 557 bee_user_t *bu; 593 558 594 559 // Set the type of the list. 595 560 txl->type = TXL_STATUS; 596 597 if (node->type != json_array)598 return FALSE;599 561 600 562 // The root <statuses> node should hold the list of statuses <status> 601 563 // Walk over the nodes children. 602 for (i = 0; i < node->u.array.length; i ++) { 603 txs = twitter_xt_get_status(node->u.array.values[i]); 604 if (!txs) 605 continue; 606 607 txl->list = g_slist_prepend(txl->list, txs); 608 } 609 610 return TRUE; 611 } 612 613 /* Will log messages either way. Need to keep track of IDs for stream deduping. 614 Plus, show_ids is on by default and I don't see why anyone would disable it. */ 564 for (child = node->children; child; child = child->next) { 565 if (g_strcasecmp("status", child->name) == 0) { 566 txs = g_new0(struct twitter_xml_status, 1); 567 twitter_xt_get_status(child, txs); 568 // Put the item in the front of the list. 569 txl->list = g_slist_prepend(txl->list, txs); 570 571 if (txs->user && txs->user->screen_name && 572 (bu = bee_user_by_handle(ic->bee, ic, txs->user->screen_name))) { 573 struct twitter_user_data *tud = bu->data; 574 575 if (txs->id > tud->last_id) { 576 tud->last_id = txs->id; 577 tud->last_time = txs->created_at; 578 } 579 } 580 } else if (g_strcasecmp("next_cursor", child->name) == 0) { 581 twitter_xt_next_cursor(child, txl); 582 } 583 } 584 585 return XT_HANDLED; 586 } 587 615 588 static char *twitter_msg_add_id(struct im_connection *ic, 616 589 struct twitter_xml_status *txs, const char *prefix) 617 590 { 618 591 struct twitter_data *td = ic->proto_data; 619 int reply_to = -1; 620 bee_user_t *bu; 621 592 char *ret = NULL; 593 594 if (!set_getbool(&ic->acc->set, "show_ids")) { 595 if (*prefix) 596 return g_strconcat(prefix, txs->text, NULL); 597 else 598 return NULL; 599 } 600 601 td->log[td->log_id].id = txs->id; 602 td->log[td->log_id].bu = bee_user_by_handle(ic->bee, ic, txs->user->screen_name); 622 603 if (txs->reply_to) { 623 604 int i; 624 605 for (i = 0; i < TWITTER_LOG_LENGTH; i++) 625 606 if (td->log[i].id == txs->reply_to) { 626 reply_to = i; 607 ret = g_strdup_printf("\002[\002%02d->%02d\002]\002 %s%s", 608 td->log_id, i, prefix, txs->text); 627 609 break; 628 610 } 629 611 } 630 631 if (txs->user && txs->user->screen_name && 632 (bu = bee_user_by_handle(ic->bee, ic, txs->user->screen_name))) { 633 struct twitter_user_data *tud = bu->data; 634 635 if (txs->id > tud->last_id) { 636 tud->last_id = txs->id; 637 tud->last_time = txs->created_at; 638 } 639 } 640 612 if (ret == NULL) 613 ret = g_strdup_printf("\002[\002%02d\002]\002 %s%s", td->log_id, prefix, txs->text); 641 614 td->log_id = (td->log_id + 1) % TWITTER_LOG_LENGTH; 642 td->log[td->log_id].id = txs->id; 643 td->log[td->log_id].bu = bee_user_by_handle(ic->bee, ic, txs->user->screen_name);644 645 /* This is all getting hairy. :-( If we RT'ed something ourselves, 646 remember OUR id instead so undo will work. In other cases, the 647 original tweet's id should be remembered for deduplicating. */ 648 if (strcmp(txs->user->screen_name, td->user) == 0)649 td->log[td->log_id].id = txs->rt_id;650 651 if (set_getbool(&ic->acc->set, "show_ids")) {652 if (reply_to != -1) 653 return g_strdup_printf("\002[\002%02x->%02x\002]\002 %s%s",654 td->log_id, reply_to, prefix, txs->text); 655 else656 return g_strdup_printf("\002[\002%02x\002]\002 %s%s",657 td->log_id, prefix, txs->text);658 } else { 659 if (*prefix)660 return g_strconcat(prefix, txs->text, NULL);661 else662 return NULL;615 616 return ret; 617 } 618 619 static void twitter_groupchat_init(struct im_connection *ic) 620 { 621 char *name_hint; 622 struct groupchat *gc; 623 struct twitter_data *td = ic->proto_data; 624 GSList *l; 625 626 td->timeline_gc = gc = imcb_chat_new(ic, "twitter/timeline"); 627 628 name_hint = g_strdup_printf("%s_%s", td->prefix, ic->acc->user); 629 imcb_chat_name_hint(gc, name_hint); 630 g_free(name_hint); 631 632 for (l = ic->bee->users; l; l = l->next) { 633 bee_user_t *bu = l->data; 634 if (bu->ic == ic) 635 imcb_chat_add_buddy(td->timeline_gc, bu->handle); 663 636 } 664 637 } … … 667 640 * Function that is called to see the statuses in a groupchat window. 668 641 */ 669 static void twitter_ status_show_chat(struct im_connection *ic, struct twitter_xml_status *status)642 static void twitter_groupchat(struct im_connection *ic, GSList * list) 670 643 { 671 644 struct twitter_data *td = ic->proto_data; 645 GSList *l = NULL; 646 struct twitter_xml_status *status; 672 647 struct groupchat *gc; 673 gboolean me = g_strcasecmp(td->user, status->user->screen_name) == 0; 674 char *msg; 648 guint64 last_id = 0; 675 649 676 650 // Create a new groupchat if it does not exsist. 677 gc = twitter_groupchat_init(ic); 678 679 if (!me) 680 /* MUST be done before twitter_msg_add_id() to avoid #872. */ 681 twitter_add_buddy(ic, status->user->screen_name, status->user->name); 682 msg = twitter_msg_add_id(ic, status, ""); 683 684 // Say it! 685 if (me) { 686 imcb_chat_log(gc, "You: %s", msg ? msg : status->text); 687 } else { 688 imcb_chat_msg(gc, status->user->screen_name, 689 msg ? msg : status->text, 0, status->created_at); 690 } 691 692 g_free(msg); 651 if (!td->timeline_gc) 652 twitter_groupchat_init(ic); 653 654 gc = td->timeline_gc; 655 if (!gc->joined) 656 imcb_chat_add_buddy(gc, ic->acc->user); 657 658 for (l = list; l; l = g_slist_next(l)) { 659 char *msg; 660 661 status = l->data; 662 if (status->user == NULL || status->text == NULL || last_id == status->id) 663 continue; 664 665 last_id = status->id; 666 667 strip_html(status->text); 668 669 if (set_getbool(&ic->acc->set, "strip_newlines")) 670 strip_newlines(status->text); 671 672 msg = twitter_msg_add_id(ic, status, ""); 673 674 // Say it! 675 if (g_strcasecmp(td->user, status->user->screen_name) == 0) { 676 imcb_chat_log(gc, "You: %s", msg ? msg : status->text); 677 } else { 678 twitter_add_buddy(ic, status->user->screen_name, status->user->name); 679 680 imcb_chat_msg(gc, status->user->screen_name, 681 msg ? msg : status->text, 0, status->created_at); 682 } 683 684 g_free(msg); 685 686 // Update the timeline_id to hold the highest id, so that by the next request 687 // we won't pick up the updates already in the list. 688 td->timeline_id = MAX(td->timeline_id, status->id); 689 } 693 690 } 694 691 … … 696 693 * Function that is called to see statuses as private messages. 697 694 */ 698 static void twitter_ status_show_msg(struct im_connection *ic, struct twitter_xml_status *status)695 static void twitter_private_message_chat(struct im_connection *ic, GSList * list) 699 696 { 700 697 struct twitter_data *td = ic->proto_data; 701 char from[MAX_STRING] = ""; 702 char *prefix = NULL, *text = NULL; 703 gboolean me = g_strcasecmp(td->user, status->user->screen_name) == 0; 704 705 if (td->flags & TWITTER_MODE_ONE) { 698 GSList *l = NULL; 699 struct twitter_xml_status *status; 700 char from[MAX_STRING]; 701 gboolean mode_one; 702 guint64 last_id = 0; 703 704 mode_one = g_strcasecmp(set_getstr(&ic->acc->set, "mode"), "one") == 0; 705 706 if (mode_one) { 706 707 g_snprintf(from, sizeof(from) - 1, "%s_%s", td->prefix, ic->acc->user); 707 708 from[MAX_STRING - 1] = '\0'; 708 709 } 709 710 710 if (td->flags & TWITTER_MODE_ONE) 711 prefix = g_strdup_printf("\002<\002%s\002>\002 ", 712 status->user->screen_name); 713 else if (!me) 714 twitter_add_buddy(ic, status->user->screen_name, status->user->name); 715 else 716 prefix = g_strdup("You: "); 717 718 text = twitter_msg_add_id(ic, status, prefix ? prefix : ""); 719 720 imcb_buddy_msg(ic, 721 *from ? from : status->user->screen_name, 722 text ? text : status->text, 0, status->created_at); 723 724 g_free(text); 725 g_free(prefix); 726 } 727 728 static void twitter_status_show(struct im_connection *ic, struct twitter_xml_status *status) 729 { 730 struct twitter_data *td = ic->proto_data; 731 732 if (status->user == NULL || status->text == NULL) 733 return; 734 735 /* Grrrr. Would like to do this during parsing, but can't access 736 settings from there. */ 737 if (set_getbool(&ic->acc->set, "strip_newlines")) 738 strip_newlines(status->text); 739 740 if (td->flags & TWITTER_MODE_CHAT) 741 twitter_status_show_chat(ic, status); 742 else 743 twitter_status_show_msg(ic, status); 744 745 // Update the timeline_id to hold the highest id, so that by the next request 746 // we won't pick up the updates already in the list. 747 td->timeline_id = MAX(td->timeline_id, status->rt_id); 748 } 749 750 static gboolean twitter_stream_handle_object(struct im_connection *ic, json_value *o); 751 752 static void twitter_http_stream(struct http_request *req) 753 { 754 struct im_connection *ic = req->data; 755 struct twitter_data *td; 756 json_value *parsed; 757 int len = 0; 758 char c, *nl; 759 760 if (!g_slist_find(twitter_connections, ic)) 761 return; 762 763 ic->flags |= OPT_PONGED; 764 td = ic->proto_data; 765 766 if ((req->flags & HTTPC_EOF) || !req->reply_body) { 767 td->stream = NULL; 768 imcb_error(ic, "Stream closed (%s)", req->status_string); 769 imc_logout(ic, TRUE); 770 return; 771 } 772 773 printf( "%d bytes in stream\n", req->body_size ); 774 775 /* MUST search for CRLF, not just LF: 776 https://dev.twitter.com/docs/streaming-apis/processing#Parsing_responses */ 777 nl = strstr(req->reply_body, "\r\n"); 778 779 if (!nl) { 780 printf("Incomplete data\n"); 781 return; 782 } 783 784 len = nl - req->reply_body; 785 if (len > 0) { 786 c = req->reply_body[len]; 787 req->reply_body[len] = '\0'; 788 789 printf("JSON: %s\n", req->reply_body); 790 printf("parsed: %p\n", (parsed = json_parse(req->reply_body))); 791 if (parsed) { 792 twitter_stream_handle_object(ic, parsed); 793 } 794 json_value_free(parsed); 795 req->reply_body[len] = c; 796 } 797 798 http_flush_bytes(req, len + 2); 799 800 /* One notification might bring multiple events! */ 801 if (req->body_size > 0) 802 twitter_http_stream(req); 803 } 804 805 static gboolean twitter_stream_handle_event(struct im_connection *ic, json_value *o); 806 static gboolean twitter_stream_handle_status(struct im_connection *ic, struct twitter_xml_status *txs); 807 808 static gboolean twitter_stream_handle_object(struct im_connection *ic, json_value *o) 809 { 810 struct twitter_data *td = ic->proto_data; 811 struct twitter_xml_status *txs; 812 json_value *c; 813 814 if ((txs = twitter_xt_get_status(o))) { 815 gboolean ret = twitter_stream_handle_status(ic, txs); 816 txs_free(txs); 817 return ret; 818 } else if ((c = json_o_get(o, "direct_message")) && 819 (txs = twitter_xt_get_dm(c))) { 820 if (strcmp(txs->user->screen_name, td->user) != 0) 821 imcb_buddy_msg(ic, txs->user->screen_name, 822 txs->text, 0, txs->created_at); 823 txs_free(txs); 824 return TRUE; 825 } else if ((c = json_o_get(o, "event")) && c->type == json_string) { 826 twitter_stream_handle_event(ic, o); 827 return TRUE; 828 } else if ((c = json_o_get(o, "disconnect")) && c->type == json_object) { 829 /* HACK: Because we're inside an event handler, we can't just 830 disconnect here. Instead, just change the HTTP status string 831 into a Twitter status string. */ 832 char *reason = json_o_strdup(c, "reason"); 833 if (reason) { 834 g_free(td->stream->status_string); 835 td->stream->status_string = reason; 836 } 837 return TRUE; 838 } 839 return FALSE; 840 } 841 842 static gboolean twitter_stream_handle_status(struct im_connection *ic, struct twitter_xml_status *txs) 843 { 844 struct twitter_data *td = ic->proto_data; 845 int i; 846 847 for (i = 0; i < TWITTER_LOG_LENGTH; i++) { 848 if (td->log[i].id == txs->id) { 849 /* Got a duplicate (RT, probably). Drop it. */ 850 return TRUE; 851 } 852 } 853 854 if (!(strcmp(txs->user->screen_name, td->user) == 0 || 855 set_getbool(&ic->acc->set, "fetch_mentions") || 856 bee_user_by_handle(ic->bee, ic, txs->user->screen_name))) { 857 /* Tweet is from an unknown person and the user does not want 858 to see @mentions, so drop it. twitter_stream_handle_event() 859 picks up new follows so this simple filter should be safe. */ 860 /* TODO: The streaming API seems to do poor @mention matching. 861 I.e. I'm getting mentions for @WilmerSomething, not just for 862 @Wilmer. But meh. You want spam, you get spam. */ 863 return TRUE; 864 } 865 866 twitter_status_show(ic, txs); 867 868 return TRUE; 869 } 870 871 static gboolean twitter_stream_handle_event(struct im_connection *ic, json_value *o) 872 { 873 struct twitter_data *td = ic->proto_data; 874 json_value *source = json_o_get(o, "source"); 875 json_value *target = json_o_get(o, "target"); 876 const char *type = json_o_str(o, "event"); 877 878 if (!type || !source || source->type != json_object 879 || !target || target->type != json_object) { 880 return FALSE; 881 } 882 883 if (strcmp(type, "follow") == 0) { 884 struct twitter_xml_user *us = twitter_xt_get_user(source); 885 struct twitter_xml_user *ut = twitter_xt_get_user(target); 886 if (strcmp(us->screen_name, td->user) == 0) { 887 twitter_add_buddy(ic, ut->screen_name, ut->name); 888 } 889 txu_free(us); 890 txu_free(ut); 891 } 892 893 return TRUE; 894 } 895 896 gboolean twitter_open_stream(struct im_connection *ic) 897 { 898 struct twitter_data *td = ic->proto_data; 899 char *args[2] = {"with", "followings"}; 900 901 if ((td->stream = twitter_http(ic, TWITTER_USER_STREAM_URL, 902 twitter_http_stream, ic, 0, args, 2))) { 903 /* This flag must be enabled or we'll get no data until EOF 904 (which err, kind of, defeats the purpose of a streaming API). */ 905 td->stream->flags |= HTTPC_STREAMING; 906 return TRUE; 907 } 908 909 return FALSE; 910 } 911 912 static void twitter_get_home_timeline(struct im_connection *ic, gint64 next_cursor); 913 static void twitter_get_mentions(struct im_connection *ic, gint64 next_cursor); 711 for (l = list; l; l = g_slist_next(l)) { 712 char *prefix = NULL, *text = NULL; 713 714 status = l->data; 715 if (status->user == NULL || status->text == NULL || last_id == status->id) 716 continue; 717 718 last_id = status->id; 719 720 strip_html(status->text); 721 if (mode_one) 722 prefix = g_strdup_printf("\002<\002%s\002>\002 ", 723 status->user->screen_name); 724 else 725 twitter_add_buddy(ic, status->user->screen_name, status->user->name); 726 727 text = twitter_msg_add_id(ic, status, prefix ? prefix : ""); 728 729 imcb_buddy_msg(ic, 730 mode_one ? from : status->user->screen_name, 731 text ? text : status->text, 0, status->created_at); 732 733 // Update the timeline_id to hold the highest id, so that by the next request 734 // we won't pick up the updates already in the list. 735 td->timeline_id = MAX(td->timeline_id, status->id); 736 737 g_free(text); 738 g_free(prefix); 739 } 740 } 741 742 static void twitter_http_get_home_timeline(struct http_request *req); 743 static void twitter_http_get_mentions(struct http_request *req); 914 744 915 745 /** … … 948 778 struct twitter_xml_list *home_timeline = td->home_timeline_obj; 949 779 struct twitter_xml_list *mentions = td->mentions_obj; 950 guint64 last_id = 0;951 780 GSList *output = NULL; 952 781 GSList *l; 953 782 954 imcb_connected(ic);955 956 783 if (!(td->flags & TWITTER_GOT_TIMELINE)) { 957 784 return; … … 977 804 } 978 805 } 806 807 if (!(ic->flags & OPT_LOGGED_IN)) 808 imcb_connected(ic); 979 809 980 810 // See if the user wants to see the messages in a groupchat window or as private messages. 981 while (output) { 982 struct twitter_xml_status *txs = output->data; 983 if (txs->id != last_id) 984 twitter_status_show(ic, txs); 985 last_id = txs->id; 986 output = g_slist_remove(output, txs); 987 } 811 if (g_strcasecmp(set_getstr(&ic->acc->set, "mode"), "chat") == 0) 812 twitter_groupchat(ic, output); 813 else 814 twitter_private_message_chat(ic, output); 815 816 g_slist_free(output); 988 817 989 818 txl_free(home_timeline); … … 994 823 } 995 824 996 static void twitter_http_get_home_timeline(struct http_request *req);997 static void twitter_http_get_mentions(struct http_request *req);998 999 825 /** 1000 826 * Get the timeline. 1001 827 */ 1002 staticvoid twitter_get_home_timeline(struct im_connection *ic, gint64 next_cursor)828 void twitter_get_home_timeline(struct im_connection *ic, gint64 next_cursor) 1003 829 { 1004 830 struct twitter_data *td = ic->proto_data; … … 1036 862 * Get mentions. 1037 863 */ 1038 staticvoid twitter_get_mentions(struct im_connection *ic, gint64 next_cursor)864 void twitter_get_mentions(struct im_connection *ic, gint64 next_cursor) 1039 865 { 1040 866 struct twitter_data *td = ic->proto_data; … … 1067 893 1068 894 g_free(args[1]); 1069 g_free(args[5]); 895 if (td->timeline_id) { 896 g_free(args[5]); 897 } 1070 898 } 1071 899 … … 1077 905 struct im_connection *ic = req->data; 1078 906 struct twitter_data *td; 1079 json_value *parsed;907 struct xt_node *parsed; 1080 908 struct twitter_xml_list *txl; 1081 909 … … 1093 921 goto end; 1094 922 twitter_xt_get_status_list(ic, parsed, txl); 1095 json_value_free(parsed);923 xt_free_node(parsed); 1096 924 1097 925 td->home_timeline_obj = txl; 1098 926 1099 927 end: 1100 if (!g_slist_find(twitter_connections, ic))1101 return;1102 1103 928 td->flags |= TWITTER_GOT_TIMELINE; 1104 929 … … 1113 938 struct im_connection *ic = req->data; 1114 939 struct twitter_data *td; 1115 json_value *parsed;940 struct xt_node *parsed; 1116 941 struct twitter_xml_list *txl; 1117 942 … … 1129 954 goto end; 1130 955 twitter_xt_get_status_list(ic, parsed, txl); 1131 json_value_free(parsed);956 xt_free_node(parsed); 1132 957 1133 958 td->mentions_obj = txl; 1134 959 1135 960 end: 1136 if (!g_slist_find(twitter_connections, ic))1137 return;1138 1139 961 td->flags |= TWITTER_GOT_MENTIONS; 1140 962 … … 1150 972 struct im_connection *ic = req->data; 1151 973 struct twitter_data *td; 1152 json_value *parsed, *id;974 struct xt_node *parsed, *node; 1153 975 1154 976 // Check if the connection is still active. … … 1162 984 return; 1163 985 1164 if ((id = json_o_get(parsed, "id")) && id->type == json_integer) { 1165 td->last_status_id = id->u.integer; 1166 } 1167 1168 json_value_free(parsed); 1169 1170 if (req->flags & TWITTER_HTTP_USER_ACK) 1171 twitter_log(ic, "Command processed successfully"); 986 if ((node = xt_find_node(parsed, "status")) && 987 (node = xt_find_node(node->children, "id")) && node->text) 988 td->last_status_id = g_ascii_strtoull(node->text, NULL, 10); 1172 989 } 1173 990 … … 1215 1032 char *url; 1216 1033 url = g_strdup_printf("%s%llu%s", TWITTER_STATUS_DESTROY_URL, 1217 (unsigned long long) id, ".json"); 1218 twitter_http_f(ic, url, twitter_http_post, ic, 1, NULL, 0, 1219 TWITTER_HTTP_USER_ACK); 1034 (unsigned long long) id, ".xml"); 1035 twitter_http(ic, url, twitter_http_post, ic, 1, NULL, 0); 1220 1036 g_free(url); 1221 1037 } … … 1225 1041 char *url; 1226 1042 url = g_strdup_printf("%s%llu%s", TWITTER_STATUS_RETWEET_URL, 1227 (unsigned long long) id, ".json"); 1228 twitter_http_f(ic, url, twitter_http_post, ic, 1, NULL, 0, 1229 TWITTER_HTTP_USER_ACK); 1043 (unsigned long long) id, ".xml"); 1044 twitter_http(ic, url, twitter_http_post, ic, 1, NULL, 0); 1230 1045 g_free(url); 1231 1046 } … … 1241 1056 }; 1242 1057 args[1] = screen_name; 1243 twitter_http _f(ic, TWITTER_REPORT_SPAM_URL, twitter_http_post,1244 ic, 1, args, 2, TWITTER_HTTP_USER_ACK);1058 twitter_http(ic, TWITTER_REPORT_SPAM_URL, twitter_http_post, 1059 ic, 1, args, 2); 1245 1060 } 1246 1061 … … 1252 1067 char *url; 1253 1068 url = g_strdup_printf("%s%llu%s", TWITTER_FAVORITE_CREATE_URL, 1254 (unsigned long long) id, ".json"); 1255 twitter_http_f(ic, url, twitter_http_post, ic, 1, NULL, 0, 1256 TWITTER_HTTP_USER_ACK); 1069 (unsigned long long) id, ".xml"); 1070 twitter_http(ic, url, twitter_http_post, ic, 1, NULL, 0); 1257 1071 g_free(url); 1258 1072 } -
protocols/twitter/twitter_lib.h
rcc6fdf8 r92d3044 29 29 #include "twitter_http.h" 30 30 31 #define TWITTER_API_URL "http://api.twitter.com/1 .1"31 #define TWITTER_API_URL "http://api.twitter.com/1" 32 32 #define IDENTICA_API_URL "https://identi.ca/api" 33 33 34 34 /* Status URLs */ 35 #define TWITTER_STATUS_UPDATE_URL "/statuses/update. json"35 #define TWITTER_STATUS_UPDATE_URL "/statuses/update.xml" 36 36 #define TWITTER_STATUS_SHOW_URL "/statuses/show/" 37 37 #define TWITTER_STATUS_DESTROY_URL "/statuses/destroy/" … … 39 39 40 40 /* Timeline URLs */ 41 #define TWITTER_PUBLIC_TIMELINE_URL "/statuses/public_timeline. json"42 #define TWITTER_FEATURED_USERS_URL "/statuses/featured. json"43 #define TWITTER_FRIENDS_TIMELINE_URL "/statuses/friends_timeline. json"44 #define TWITTER_HOME_TIMELINE_URL "/statuses/home_timeline. json"45 #define TWITTER_MENTIONS_URL "/statuses/mentions _timeline.json"46 #define TWITTER_USER_TIMELINE_URL "/statuses/user_timeline. json"41 #define TWITTER_PUBLIC_TIMELINE_URL "/statuses/public_timeline.xml" 42 #define TWITTER_FEATURED_USERS_URL "/statuses/featured.xml" 43 #define TWITTER_FRIENDS_TIMELINE_URL "/statuses/friends_timeline.xml" 44 #define TWITTER_HOME_TIMELINE_URL "/statuses/home_timeline.xml" 45 #define TWITTER_MENTIONS_URL "/statuses/mentions.xml" 46 #define TWITTER_USER_TIMELINE_URL "/statuses/user_timeline.xml" 47 47 48 48 /* Users URLs */ 49 #define TWITTER_USERS_LOOKUP_URL "/users/lookup. json"49 #define TWITTER_USERS_LOOKUP_URL "/users/lookup.xml" 50 50 51 51 /* Direct messages URLs */ 52 #define TWITTER_DIRECT_MESSAGES_URL "/direct_messages. json"53 #define TWITTER_DIRECT_MESSAGES_NEW_URL "/direct_messages/new. json"54 #define TWITTER_DIRECT_MESSAGES_SENT_URL "/direct_messages/sent. json"52 #define TWITTER_DIRECT_MESSAGES_URL "/direct_messages.xml" 53 #define TWITTER_DIRECT_MESSAGES_NEW_URL "/direct_messages/new.xml" 54 #define TWITTER_DIRECT_MESSAGES_SENT_URL "/direct_messages/sent.xml" 55 55 #define TWITTER_DIRECT_MESSAGES_DESTROY_URL "/direct_messages/destroy/" 56 56 57 57 /* Friendships URLs */ 58 #define TWITTER_FRIENDSHIPS_CREATE_URL "/friendships/create. json"59 #define TWITTER_FRIENDSHIPS_DESTROY_URL "/friendships/destroy. json"60 #define TWITTER_FRIENDSHIPS_SHOW_URL "/friendships/show. json"58 #define TWITTER_FRIENDSHIPS_CREATE_URL "/friendships/create.xml" 59 #define TWITTER_FRIENDSHIPS_DESTROY_URL "/friendships/destroy.xml" 60 #define TWITTER_FRIENDSHIPS_SHOW_URL "/friendships/show.xml" 61 61 62 62 /* Social graphs URLs */ 63 #define TWITTER_FRIENDS_IDS_URL "/friends/ids. json"64 #define TWITTER_FOLLOWERS_IDS_URL "/followers/ids. json"63 #define TWITTER_FRIENDS_IDS_URL "/friends/ids.xml" 64 #define TWITTER_FOLLOWERS_IDS_URL "/followers/ids.xml" 65 65 66 66 /* Account URLs */ 67 #define TWITTER_ACCOUNT_RATE_LIMIT_URL "/account/rate_limit_status. json"67 #define TWITTER_ACCOUNT_RATE_LIMIT_URL "/account/rate_limit_status.xml" 68 68 69 69 /* Favorites URLs */ 70 #define TWITTER_FAVORITES_GET_URL "/favorites. json"70 #define TWITTER_FAVORITES_GET_URL "/favorites.xml" 71 71 #define TWITTER_FAVORITE_CREATE_URL "/favorites/create/" 72 72 #define TWITTER_FAVORITE_DESTROY_URL "/favorites/destroy/" … … 77 77 78 78 /* Report spam */ 79 #define TWITTER_REPORT_SPAM_URL "/report_spam. json"79 #define TWITTER_REPORT_SPAM_URL "/report_spam.xml" 80 80 81 #define TWITTER_USER_STREAM_URL "https://userstream.twitter.com/1.1/user.json"82 83 gboolean twitter_open_stream(struct im_connection *ic);84 81 void twitter_get_timeline(struct im_connection *ic, gint64 next_cursor); 85 82 void twitter_get_friends_ids(struct im_connection *ic, gint64 next_cursor); 83 void twitter_get_home_timeline(struct im_connection *ic, gint64 next_cursor); 84 void twitter_get_mentions(struct im_connection *ic, gint64 next_cursor); 86 85 void twitter_get_statuses_friends(struct im_connection *ic, gint64 next_cursor); 87 86
Note: See TracChangeset
for help on using the changeset viewer.