Changes in / [cc6fdf8:92d3044]


Ignore:
Files:
4 deleted
18 edited

Legend:

Unmodified
Added
Removed
  • debian/copyright

    rcc6fdf8 r92d3044  
    88
    99Mainly 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 
     10Some parts are borrowed from Gaim (version 0.58) <http://gaim.sf.net/>.
     11For the copyrights on those parts, please read the Gaim source code.
    2112
    2213BitlBee License:
     
    4233
    4334The 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:
    4536
    4637============================================================================
    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
     470. PREAMBLE
     48
     49The purpose of this License is to make a manual, textbook, or other
     50written document "free" in the sense of freedom: to assure everyone
     51the effective freedom to copy and redistribute it, with or without
     52modifying it, either commercially or noncommercially.  Secondarily,
     53this License preserves for the author and publisher a way to get
     54credit for their work, while not being considered responsible for
     55modifications made by others.
     56
     57This License is a kind of "copyleft", which means that derivative
     58works of the document must themselves be free in the same sense.  It
     59complements the GNU General Public License, which is a copyleft
     60license designed for free software.
     61
     62We have designed this License in order to use it for manuals for free
     63software, because free software needs free documentation: a free
     64program should come with manuals providing the same freedoms that the
     65software does.  But this License is not limited to software manuals;
     66it can be used for any textual work, regardless of subject matter or
     67whether it is published as a printed book.  We recommend this License
     68principally for works whose purpose is instruction or reference.
     69
     70
     711. APPLICABILITY AND DEFINITIONS
     72
     73This License applies to any manual or other work that contains a
     74notice placed by the copyright holder saying it can be distributed
     75under the terms of this License.  The "Document", below, refers to any
     76such manual or work.  Any member of the public is a licensee, and is
     77addressed as "you".
     78
     79A "Modified Version" of the Document means any work containing the
     80Document or a portion of it, either copied verbatim, or with
     81modifications and/or translated into another language.
     82
     83A "Secondary Section" is a named appendix or a front-matter section of
     84the Document that deals exclusively with the relationship of the
     85publishers or authors of the Document to the Document's overall subject
     86(or to related matters) and contains nothing that could fall directly
     87within that overall subject.  (For example, if the Document is in part a
     88textbook of mathematics, a Secondary Section may not explain any
     89mathematics.)  The relationship could be a matter of historical
     90connection with the subject or with related matters, or of legal,
     91commercial, philosophical, ethical or political position regarding
     92them.
     93
     94The "Invariant Sections" are certain Secondary Sections whose titles
     95are designated, as being those of Invariant Sections, in the notice
     96that says that the Document is released under this License.
     97
     98The "Cover Texts" are certain short passages of text that are listed,
     99as Front-Cover Texts or Back-Cover Texts, in the notice that says that
     100the Document is released under this License.
     101
     102A "Transparent" copy of the Document means a machine-readable copy,
     103represented in a format whose specification is available to the
     104general public, whose contents can be viewed and edited directly and
     105straightforwardly with generic text editors or (for images composed of
     106pixels) generic paint programs or (for drawings) some widely available
     107drawing editor, and that is suitable for input to text formatters or
     108for automatic translation to a variety of formats suitable for input
     109to text formatters.  A copy made in an otherwise Transparent file
     110format whose markup has been designed to thwart or discourage
     111subsequent modification by readers is not Transparent.  A copy that is
     112not "Transparent" is called "Opaque".
     113
     114Examples of suitable formats for Transparent copies include plain
     115ASCII without markup, Texinfo input format, LaTeX input format, SGML
     116or XML using a publicly available DTD, and standard-conforming simple
     117HTML designed for human modification.  Opaque formats include
     118PostScript, PDF, proprietary formats that can be read and edited only
     119by proprietary word processors, SGML or XML for which the DTD and/or
     120processing tools are not generally available, and the
     121machine-generated HTML produced by some word processors for output
     122purposes only.
     123
     124The "Title Page" means, for a printed book, the title page itself,
     125plus such following pages as are needed to hold, legibly, the material
     126this License requires to appear in the title page.  For works in
     127formats which do not have any title page as such, "Title Page" means
     128the text near the most prominent appearance of the work's title,
     129preceding the beginning of the body of the text.
     130
     131
     1322. VERBATIM COPYING
     133
     134You may copy and distribute the Document in any medium, either
     135commercially or noncommercially, provided that this License, the
     136copyright notices, and the license notice saying this License applies
     137to the Document are reproduced in all copies, and that you add no other
     138conditions whatsoever to those of this License.  You may not use
     139technical measures to obstruct or control the reading or further
     140copying of the copies you make or distribute.  However, you may accept
     141compensation in exchange for copies.  If you distribute a large enough
     142number of copies you must also follow the conditions in section 3.
     143
     144You may also lend copies, under the same conditions stated above, and
     145you may publicly display copies.
     146
     147
     1483. COPYING IN QUANTITY
     149
     150If you publish printed copies of the Document numbering more than 100,
     151and the Document's license notice requires Cover Texts, you must enclose
     152the copies in covers that carry, clearly and legibly, all these Cover
     153Texts: Front-Cover Texts on the front cover, and Back-Cover Texts on
     154the back cover.  Both covers must also clearly and legibly identify
     155you as the publisher of these copies.  The front cover must present
     156the full title with all words of the title equally prominent and
     157visible.  You may add other material on the covers in addition.
     158Copying with changes limited to the covers, as long as they preserve
     159the title of the Document and satisfy these conditions, can be treated
     160as verbatim copying in other respects.
     161
     162If the required texts for either cover are too voluminous to fit
     163legibly, you should put the first ones listed (as many as fit
     164reasonably) on the actual cover, and continue the rest onto adjacent
     165pages.
     166
     167If you publish or distribute Opaque copies of the Document numbering
     168more than 100, you must either include a machine-readable Transparent
     169copy along with each Opaque copy, or state in or with each Opaque copy
     170a publicly-accessible computer-network location containing a complete
     171Transparent copy of the Document, free of added material, which the
     172general network-using public has access to download anonymously at no
     173charge using public-standard network protocols.  If you use the latter
     174option, you must take reasonably prudent steps, when you begin
     175distribution of Opaque copies in quantity, to ensure that this
     176Transparent copy will remain thus accessible at the stated location
     177until at least one year after the last time you distribute an Opaque
     178copy (directly or through your agents or retailers) of that edition to
     179the public.
     180
     181It is requested, but not required, that you contact the authors of the
     182Document well before redistributing any large number of copies, to give
     183them a chance to provide you with an updated version of the Document.
     184
     185
     1864. MODIFICATIONS
     187
     188You may copy and distribute a Modified Version of the Document under
     189the conditions of sections 2 and 3 above, provided that you release
     190the Modified Version under precisely this License, with the Modified
     191Version filling the role of the Document, thus licensing distribution
     192and modification of the Modified Version to whoever possesses a copy
     193of it.  In addition, you must do these things in the Modified Version:
     194
     195A. 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.
     200B. 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).
     204C. State on the Title page the name of the publisher of the
     205   Modified Version, as the publisher.
     206D. Preserve all the copyright notices of the Document.
     207E. Add an appropriate copyright notice for your modifications
     208   adjacent to the other copyright notices.
     209F. 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.
     212G. Preserve in that license notice the full lists of Invariant Sections
     213   and required Cover Texts given in the Document's license notice.
     214H. Include an unaltered copy of this License.
     215I. 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.
     222J. 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.
     229K. 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.
     233L. 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.
     236M. Delete any section entitled "Endorsements".  Such a section
     237   may not be included in the Modified Version.
     238N. Do not retitle any existing section as "Endorsements"
     239   or to conflict in title with any Invariant Section.
     240
     241If the Modified Version includes new front-matter sections or
     242appendices that qualify as Secondary Sections and contain no material
     243copied from the Document, you may at your option designate some or all
     244of these sections as invariant.  To do this, add their titles to the
     245list of Invariant Sections in the Modified Version's license notice.
     246These titles must be distinct from any other section titles.
     247
     248You may add a section entitled "Endorsements", provided it contains
     249nothing but endorsements of your Modified Version by various
     250parties--for example, statements of peer review or that the text has
     251been approved by an organization as the authoritative definition of a
     252standard.
     253
     254You may add a passage of up to five words as a Front-Cover Text, and a
     255passage of up to 25 words as a Back-Cover Text, to the end of the list
     256of Cover Texts in the Modified Version.  Only one passage of
     257Front-Cover Text and one of Back-Cover Text may be added by (or
     258through arrangements made by) any one entity.  If the Document already
     259includes a cover text for the same cover, previously added by you or
     260by arrangement made by the same entity you are acting on behalf of,
     261you may not add another; but you may replace the old one, on explicit
     262permission from the previous publisher that added the old one.
     263
     264The author(s) and publisher(s) of the Document do not by this License
     265give permission to use their names for publicity for or to assert or
     266imply endorsement of any Modified Version.
     267
     268
     2695. COMBINING DOCUMENTS
     270
     271You may combine the Document with other documents released under this
     272License, under the terms defined in section 4 above for modified
     273versions, provided that you include in the combination all of the
     274Invariant Sections of all of the original documents, unmodified, and
     275list them all as Invariant Sections of your combined work in its
     276license notice.
     277
     278The combined work need only contain one copy of this License, and
     279multiple identical Invariant Sections may be replaced with a single
     280copy.  If there are multiple Invariant Sections with the same name but
     281different contents, make the title of each such section unique by
     282adding at the end of it, in parentheses, the name of the original
     283author or publisher of that section if known, or else a unique number.
     284Make the same adjustment to the section titles in the list of
     285Invariant Sections in the license notice of the combined work.
     286
     287In the combination, you must combine any sections entitled "History"
     288in the various original documents, forming one section entitled
     289"History"; likewise combine any sections entitled "Acknowledgements",
     290and any sections entitled "Dedications".  You must delete all sections
     291entitled "Endorsements."
     292
     293
     2946. COLLECTIONS OF DOCUMENTS
     295
     296You may make a collection consisting of the Document and other documents
     297released under this License, and replace the individual copies of this
     298License in the various documents with a single copy that is included in
     299the collection, provided that you follow the rules of this License for
     300verbatim copying of each of the documents in all other respects.
     301
     302You may extract a single document from such a collection, and distribute
     303it individually under this License, provided you insert a copy of this
     304License into the extracted document, and follow this License in all
     305other respects regarding verbatim copying of that document.
     306
     307
     3087. AGGREGATION WITH INDEPENDENT WORKS
     309
     310A compilation of the Document or its derivatives with other separate
     311and independent documents or works, in or on a volume of a storage or
     312distribution medium, does not as a whole count as a Modified Version
     313of the Document, provided no compilation copyright is claimed for the
     314compilation.  Such a compilation is called an "aggregate", and this
     315License does not apply to the other self-contained works thus compiled
     316with the Document, on account of their being thus compiled, if they
     317are not themselves derivative works of the Document.
     318
     319If the Cover Text requirement of section 3 is applicable to these
     320copies of the Document, then if the Document is less than one quarter
     321of the entire aggregate, the Document's Cover Texts may be placed on
     322covers that surround only the Document within the aggregate.
     323Otherwise they must appear on covers around the whole aggregate.
     324
     325
     3268. TRANSLATION
     327
     328Translation is considered a kind of modification, so you may
     329distribute translations of the Document under the terms of section 4.
     330Replacing Invariant Sections with translations requires special
     331permission from their copyright holders, but you may include
     332translations of some or all Invariant Sections in addition to the
     333original versions of these Invariant Sections.  You may include a
     334translation of this License provided that you also include the
     335original English version of this License.  In case of a disagreement
     336between the translation and the original English version of this
     337License, the original English version will prevail.
     338
     339
     3409. TERMINATION
     341
     342You may not copy, modify, sublicense, or distribute the Document except
     343as expressly provided for under this License.  Any other attempt to
     344copy, modify, sublicense or distribute the Document is void, and will
     345automatically terminate your rights under this License.  However,
     346parties who have received copies, or rights, from you under this
     347License will not have their licenses terminated so long as such
     348parties remain in full compliance.
     349
     350
     35110. FUTURE REVISIONS OF THIS LICENSE
     352
     353The Free Software Foundation may publish new, revised versions
     354of the GNU Free Documentation License from time to time.  Such new
     355versions will be similar in spirit to the present version, but may
     356differ in detail to address new problems or concerns.  See
     357http://www.gnu.org/copyleft/.
     358
     359Each version of the License is given a distinguishing version number.
     360If the Document specifies that a particular numbered version of this
     361License "or any later version" applies to it, you have the option of
     362following the terms and conditions either of that specified version or
     363of any later version that has been published (not as a draft) by the
     364Free Software Foundation.  If the Document does not specify a version
     365number of this License, you may choose any version ever published (not
     366as a draft) by the Free Software Foundation.
     367
     368
     369ADDENDUM: How to use this License for your documents
     370
     371To use this License in a document you have written, include a copy of
     372the License in the document and put the following copyright and
     373license 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
     384If you have no Invariant Sections, write "with no Invariant Sections"
     385instead of saying which ones are invariant.  If you have no
     386Front-Cover Texts, write "no Front-Cover Texts" instead of
     387"Front-Cover Texts being LIST"; likewise for Back-Cover Texts.
     388
     389If your document contains nontrivial examples of program code, we
     390recommend releasing these examples in parallel under your choice of
     391free software license, such as the GNU General Public License,
     392to permit their use in free software.
    55393============================================================================
  • doc/user-guide/commands.xml

    rcc6fdf8 r92d3044  
    835835        <bitlbee-setting name="commands" type="boolean" scope="account">
    836836                <default>true</default>
    837                 <possible-values>true, false, strict</possible-values>
    838837
    839838                <description>
     
    854853
    855854                        <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! :-)
    857856                        </para>
    858857                </description>
     
    10241023
    10251024        </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>
    10411025       
    10421026        <bitlbee-setting name="target_url_length" type="integer" scope="account">
     
    13541338
    13551339        <bitlbee-setting name="show_ids" type="boolean" scope="account">
    1356                 <default>true</default>
     1340                <default>false</default>
    13571341
    13581342                <description>
  • lib/Makefile

    rcc6fdf8 r92d3044  
    1313
    1414# [SH] Program variables
    15 objects = arc.o base64.o $(EVENT_HANDLER) ftutil.o http_client.o ini.o json.o json_util.o md5.o misc.o oauth.o oauth2.o proxy.o sha1.o $(SSL_CLIENT) url.o xmltree.o
     15objects = 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
    1616
    1717LFLAGS += -r
  • lib/http_client.c

    rcc6fdf8 r92d3044  
    193193}
    194194
    195 static gboolean http_handle_headers( struct http_request *req );
    196 
    197195static gboolean http_incoming_data( gpointer data, int source, b_input_condition cond )
    198196{
    199197        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;
    202201        size_t content_length;
    203202        int st;
     
    219218                                   packets that abort connections! \o/ */
    220219                               
    221                                 goto eof;
     220                                goto got_reply;
    222221                        }
    223222                }
    224223                else if( st == 0 )
    225224                {
    226                         goto eof;
     225                        goto got_reply;
    227226                }
    228227        }
     
    240239                else if( st == 0 )
    241240                {
    242                         goto eof;
    243                 }
    244         }
    245        
    246         if( st > 0 && !req->sbuf )
     241                        goto got_reply;
     242                }
     243        }
     244       
     245        if( st > 0 )
    247246        {
    248247                req->reply_headers = g_realloc( req->reply_headers, req->bytes_read + st + 1 );
    249248                memcpy( req->reply_headers + req->bytes_read, buffer, st );
    250249                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        }
    295251       
    296252        /* There will be more! */
     
    299255                                 http_incoming_data, req );
    300256       
    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
     262got_reply:
    306263        /* Maybe if the webserver is overloaded, or when there's bad SSL
    307264           support... */
     
    312269        }
    313270       
    314         if( !( req->flags & HTTPC_STREAMING ) )
    315         {
    316                 /* Returns FALSE if we were redirected, in which case we should abort
    317                    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         else
    326                 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 handle
    350    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        
    356271        /* Zero termination is very convenient. */
    357         req->reply_headers[req->bytes_read] = '\0';
     272        req->reply_headers[req->bytes_read] = 0;
    358273       
    359274        /* Find the separation between headers and body, and keep stupid
     
    374289        {
    375290                req->status_string = g_strdup( "Malformed HTTP reply" );
    376                 return TRUE;
     291                goto cleanup;
    377292        }
    378293       
     
    391306        if( ( end1 = strchr( req->reply_headers, ' ' ) ) != NULL )
    392307        {
    393                 if( sscanf( end1 + 1, "%hd", &req->status_code ) != 1 )
     308                if( sscanf( end1 + 1, "%d", &req->status_code ) != 1 )
    394309                {
    395310                        req->status_string = g_strdup( "Can't parse status code" );
     
    434349                {
    435350                        req->status_string = g_strdup( "Can't locate Location: header" );
    436                         return TRUE;
     351                        goto cleanup;
    437352                }
    438353               
     
    454369                        req->status_string = g_strdup( "Can't handle recursive redirects" );
    455370                       
    456                         return TRUE;
     371                        goto cleanup;
    457372                }
    458373                else
     
    465380                        s = strstr( loc, "\r\n" );
    466381                        if( s == NULL )
    467                                 return TRUE;
     382                                goto cleanup;
    468383                       
    469384                        url = g_new0( url_t, 1 );
     
    474389                                req->status_string = g_strdup( "Malformed redirect URL" );
    475390                                g_free( url );
    476                                 return TRUE;
     391                                goto cleanup;
    477392                        }
    478393                       
     
    486401                                req->status_string = g_strdup( "Error while rebuilding request string" );
    487402                                g_free( url );
    488                                 return TRUE;
     403                                goto cleanup;
    489404                        }
    490405                       
     
    552467                        req->status_string = g_strdup( "Connection problem during redirect" );
    553468                        g_free( new_request );
    554                         return TRUE;
     469                        goto cleanup;
    555470                }
    556471               
     
    565480        }
    566481       
    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
     486cleanup:
    596487        if( req->ssl )
    597488                ssl_disconnect( req->ssl );
     
    599490                closesocket( req->fd );
    600491       
     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 );
    601509        http_free( req );
     510        return FALSE;
    602511}
    603512
     
    607516        g_free( req->reply_headers );
    608517        g_free( req->status_string );
    609         g_free( req->sbuf );
    610518        g_free( req );
    611519}
     520
  • lib/http_client.h

    rcc6fdf8 r92d3044  
    2626/* http_client allows you to talk (asynchronously, again) to HTTP servers.
    2727   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).
    3032   
    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. */
    3439
    3540#include <glib.h>
     
    3742
    3843struct http_request;
    39 
    40 typedef enum http_client_flags
    41 {
    42         HTTPC_STREAMING = 1,
    43         HTTPC_EOF = 2,
    44        
    45         /* Let's reserve 0x1000000+ for lib users. */
    46 } http_client_flags_t;
    4744
    4845/* Your callback function should look like this: */
     
    5653        char *request;          /* The request to send to the server. */
    5754        int request_length;     /* Its size. */
    58         short status_code;      /* The numeric HTTP status code. (Or -1
     55        int status_code;        /* The numeric HTTP status code. (Or -1
    5956                                   if something really went wrong) */
    6057        char *status_string;    /* The error text. */
     
    6259        char *reply_body;
    6360        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
    6564                                   http_client to follow them. */
    66        
    67         http_client_flags_t flags;
    6865       
    6966        http_input_function func;
     
    7168       
    7269        /* Please don't touch the things down here, you shouldn't need them. */
     70       
    7371        void *ssl;
    7472        int fd;
     
    7775        int bytes_written;
    7876        int bytes_read;
    79        
    80         /* Used in streaming mode. Caller should read from reply_body. */
    81         char *sbuf;
    82         size_t sblen;
    8377};
    8478
     
    8983struct http_request *http_dorequest( char *host, int port, int ssl, char *request, http_input_function func, gpointer data );
    9084struct 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  
    44*  Simple OAuth client (consumer) implementation.                           *
    55*                                                                           *
    6 *  Copyright 2010-2012 Wilmer van der Gaast <wilmer@gaast.net>              *
     6*  Copyright 2010-2011 Wilmer van der Gaast <wilmer@gaast.net>              *
    77*                                                                           *
    88*  This program is free software; you can redistribute it and/or modify     *
     
    2626#include "oauth2.h"
    2727#include "oauth.h"
    28 #include "json.h"
    2928#include "url.h"
    3029
     
    4544};
    4645
     46static char *oauth2_json_dumb_get( const char *json, const char *key );
    4747static void oauth2_access_token_done( struct http_request *req );
    4848
     
    115115        else if( content_type && strstr( content_type, "application/json" ) )
    116116        {
    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" );
    133119        }
    134120        else
     
    151137        g_free( cb_data );
    152138}
     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. */
     145static 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  
    3838
    3939static gboolean initialized = FALSE;
    40 gnutls_certificate_credentials_t xcred;
     40gnutls_certificate_credentials xcred;
    4141
    4242#include <limits.h>
     
    6060        gboolean verify;
    6161       
    62         gnutls_session_t session;
     62        gnutls_session session;
    6363};
    6464
     
    134134        conn->data = data;
    135135        conn->inpa = -1;
    136         conn->hostname = g_strdup( hostname );
     136        conn->hostname = hostname;
    137137       
    138138        /* For now, SSL verification is globally enabled by setting the cafile
     
    171171        int verifyret = 0;
    172172        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 );
    176176
    177177        gnutlsret = gnutls_certificate_verify_peers2( session, &status );
     
    211211                return VERIFY_CERT_ERROR;
    212212
    213         if( !gnutls_x509_crt_check_hostname( cert, conn->hostname ) )
     213        if( !gnutls_x509_crt_check_hostname( cert, hostname ) )
    214214        {
    215215                verifyret |= VERIFY_CERT_INVALID;
     
    267267       
    268268        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 );
    270271#if GNUTLS_VERSION_NUMBER < 0x020c00
    271272        gnutls_transport_set_lowat( conn->session, 0 );
     
    275276       
    276277        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 );
    278279       
    279280        return ssl_handshake( data, source, cond );
     
    401402        if( conn->session )
    402403                gnutls_deinit( conn->session );
    403         g_free( conn->hostname );
    404404        g_free( conn );
    405405}
  • nick.c

    rcc6fdf8 r92d3044  
    134134                                chop = fmt[1];
    135135                                if( chop == '\0' )
    136                                 {
    137                                         g_string_free( ret, TRUE );
    138136                                        return NULL;
    139                                 }
    140137                                fmt += 2;
    141138                        }
     
    190187                        else
    191188                        {
    192                                 g_string_free( ret, TRUE );
    193189                                return NULL;
    194190                        }
  • protocols/msn/msn.c

    rcc6fdf8 r92d3044  
    5353       
    5454        ic->proto_data = md;
    55         ic->flags |= OPT_PONGS | OPT_PONGED;
    5655       
    5756        if( strchr( acc->user, '@' ) == NULL )
  • protocols/msn/ns.c

    rcc6fdf8 r92d3044  
    576576                if( num_parts >= 7 )
    577577                        handler->msglen = atoi( cmd[6] );
    578         }
    579         else if( strcmp( cmd[0], "QNG" ) == 0 )
    580         {
    581                 ic->flags |= OPT_PONGED;
    582578        }
    583579        else if( isdigit( cmd[0][0] ) )
  • protocols/nogaim.c

    rcc6fdf8 r92d3044  
    261261        struct im_connection *ic = d;
    262262       
    263         if( ( ic->flags & OPT_PONGS ) && !( ic->flags & OPT_PONGED ) )
    264         {
    265                 /* This protocol is expected to ack keepalives and hasn't
    266                    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        
    273263        if( ic->acc->prpl->keepalive )
    274264                ic->acc->prpl->keepalive( ic );
    275265       
    276266        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;
    287267}
    288268
     
    297277        imcb_log( ic, "Logged in" );
    298278       
     279        b_event_remove( ic->keepalive );
     280        ic->keepalive = b_timeout_add( 60000, send_keepalive, ic );
    299281        ic->flags |= OPT_LOGGED_IN;
    300         start_keepalives( ic, 60000 );
    301282       
    302283        /* Necessary to send initial presence status, even if we're not away. */
  • protocols/nogaim.h

    rcc6fdf8 r92d3044  
    6868#define OPT_THINKING    0x00000200 /* about these values... Stupid me! */
    6969#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 */
    7270
    7371/* ok. now the fun begins. first we create a connection structure */
  • protocols/twitter/twitter.c

    rcc6fdf8 r92d3044  
    44*  Simple module to facilitate twitter functionality.                       *
    55*                                                                           *
    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>                 *
    87*                                                                           *
    98*  This library is free software; you can redistribute it and/or            *
     
    3029#include "url.h"
    3130
     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
    3240GSList *twitter_connections = NULL;
     41
    3342/**
    3443 * Main loop function
     
    5362        struct twitter_data *td = ic->proto_data;
    5463
    55         /* Create the room now that we "logged in". */
    56         if (td->flags & TWITTER_MODE_CHAT)
    57                 twitter_groupchat_init(ic);
    58 
    5964        imcb_log(ic, "Getting initial statuses");
    6065
    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.
    6367        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);
    10673}
    10774
     
    11683        if (set_getbool(&ic->acc->set, "oauth") && !td->oauth_info)
    11784                twitter_oauth_start(ic);
    118         else if (!(td->flags & TWITTER_MODE_ONE) &&
    119                 !(td->flags & TWITTER_HAVE_FRIENDS)) {
     85        else if (g_strcasecmp(set_getstr(&ic->acc->set, "mode"), "one") != 0 &&
     86                !(td->flags & TWITTER_HAVE_FRIENDS)) {
    12087                imcb_log(ic, "Getting contact list");
    12188                twitter_get_friends_ids(ic, -1);
     89                //twitter_get_statuses_friends(ic, -1);
    12290        } else
    12391                twitter_main_loop_start(ic);
     
    219187}
    220188
     189
     190static 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
    221199int twitter_url_len_diff(gchar *msg, unsigned int target_len)
    222200{
     
    255233                return TRUE;
    256234
    257         twitter_log(ic, "Maximum message length exceeded: %d > %d", len, max);
     235        imcb_error(ic, "Maximum message length exceeded: %d > %d", len, max);
    258236
    259237        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         else
    267                 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         else
    276                 return NULL;
    277238}
    278239
     
    296257        s->flags |= ACC_SET_OFFLINE_ONLY;
    297258
    298         s = set_add(&acc->set, "commands", "true", set_eval_commands, acc);
     259        s = set_add(&acc->set, "commands", "true", set_eval_bool, acc);
    299260
    300261        s = set_add(&acc->set, "fetch_interval", "60", set_eval_int, acc);
     
    313274
    314275        s = set_add(&acc->set, "show_ids", "true", set_eval_bool, acc);
     276        s->flags |= ACC_SET_OFFLINE_ONLY;
    315277
    316278        s = set_add(&acc->set, "show_old_mentions", "20", set_eval_int, acc);
    317279
    318280        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         }
    324281}
    325282
    326283/**
    327  * Login method. Since the twitter API works with separate HTTP request we
     284 * Login method. Since the twitter API works with seperate HTTP request we
    328285 * only save the user and pass to the twitter_data object.
    329286 */
     
    341298                imc_logout(ic, FALSE);
    342299                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.");
    349300        }
    350301
     
    389340        imcb_buddy_status(ic, name, OPT_LOGGED_IN, NULL, NULL);
    390341
    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);
    401344
    402345        twitter_login_finish(ic);
     
    420363
    421364        if (td) {
    422                 http_close(td->stream);
    423365                oauth_info_free(td->oauth_info);
    424366                g_free(td->user);
     
    553495 *  Returns 0 if the user provides garbage.
    554496 */
    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;
     497static guint64 twitter_message_id_from_command_arg(struct im_connection *ic, struct twitter_data *td, char *arg) {
    557498        struct twitter_user_data *tud;
    558         bee_user_t *bu = NULL;
     499        bee_user_t *bu;
    559500        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)
    575504                        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        }
    589512        return id;
    590513}
     
    594517        struct twitter_data *td = ic->proto_data;
    595518        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;
    600520
    601521        cmds = g_strdup(message);
     
    603523
    604524        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. */
    608529        } else if (g_strcasecmp(cmd[0], "undo") == 0) {
     530                guint64 id;
     531
    609532                if (cmd[1] == NULL)
    610533                        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                       
    612538                        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;
    617544        } 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]))) {
    619547                        twitter_favourite_tweet(ic, id);
    620548                } 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;
    624553        } else if (g_strcasecmp(cmd[0], "follow") == 0 && cmd[1]) {
    625554                twitter_add_buddy(ic, cmd[1], NULL);
    626                 goto eof;
     555                g_free(cmds);
     556                return;
    627557        } else if (g_strcasecmp(cmd[0], "unfollow") == 0 && cmd[1]) {
    628558                twitter_remove_buddy(ic, cmd[1], NULL);
    629                 goto eof;
     559                g_free(cmds);
     560                return;
    630561        } else if ((g_strcasecmp(cmd[0], "report") == 0 ||
    631562                    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];
    634566                /* Report nominally works on users but look up the user who
    635567                   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                }
    642576                twitter_report_spam(ic, screen_name);
    643                 goto eof;
     577                g_free(cmds);
     578                return;
    644579        } 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]);
    646581
    647582                td->last_status_id = 0;
     
    649584                        twitter_status_retweet(ic, id);
    650585                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 "
    652587                                    "post any statuses recently", cmd[1]);
    653588
    654                 goto eof;
     589                g_free(cmds);
     590                return;
    655591        } 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
    657616                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 "
    659618                                    "post any statuses recently", cmd[1]);
    660                         goto eof;
     619                        g_free(cmds);
     620                        return;
    661621                }
    662622                message = new = g_strdup_printf("@%s %s", bu->handle, message + (cmd[2] - cmd[0]));
    663623                in_reply_to = id;
    664                 allow_post = TRUE;
    665624        } else if (g_strcasecmp(cmd[0], "post") == 0) {
    666625                message += 5;
    667                 allow_post = TRUE;
    668         }
    669 
    670         if (allow_post) {
     626        }
     627
     628        {
    671629                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                }
    675637
    676638                s = cmd[0] + strlen(cmd[0]) - 1;
     
    695657                td->last_status_id = 0;
    696658                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        }
    702661        g_free(cmds);
    703662}
    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         else
    718                 imcb_log(ic, "%s", text);
    719        
    720         g_free(text);
    721 }
    722 
    723663
    724664void twitter_initmodule()
  • protocols/twitter/twitter.h

    rcc6fdf8 r92d3044  
    44*  Simple module to facilitate twitter functionality.                       *
    55*                                                                           *
    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>                 *
    87*                                                                           *
    98*  This library is free software; you can redistribute it and/or            *
     
    3635typedef enum
    3736{
    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,
    4238        TWITTER_DOING_TIMELINE = 0x10000,
    43         TWITTER_GOT_TIMELINE   = 0x20000,
    44         TWITTER_GOT_MENTIONS   = 0x40000,
     39        TWITTER_GOT_TIMELINE = 0x20000,
     40        TWITTER_GOT_MENTIONS = 0x40000,
    4541} twitter_flags_t;
    4642
     
    6157        guint64 last_status_id; /* For undo */
    6258        gint main_loop_id;
    63         struct http_request *stream;
    6459        struct groupchat *timeline_gc;
    6560        gint http_fails;
     
    8580};
    8681
    87 #define TWITTER_LOG_LENGTH 256
     82#define TWITTER_LOG_LENGTH 100
    8883struct twitter_log_data
    8984{
     
    10499char *twitter_parse_error( struct http_request *req );
    105100
    106 void twitter_log(struct im_connection *ic, char *format, ... );
    107 struct groupchat *twitter_groupchat_init(struct im_connection *ic);
    108 
    109101#endif //_TWITTER_H
  • protocols/twitter/twitter_http.c

    rcc6fdf8 r92d3044  
    4747 * This is actually pretty generic function... Perhaps it should move to the lib/http_client.c
    4848 */
    49 struct http_request *twitter_http(struct im_connection *ic, char *url_string, http_input_function func,
    50                                   gpointer data, int is_post, char **arguments, int arguments_len)
     49void *twitter_http(struct im_connection *ic, char *url_string, http_input_function func,
     50                   gpointer data, int is_post, char **arguments, int arguments_len)
    5151{
    5252        struct twitter_data *td = ic->proto_data;
     
    5555        void *ret;
    5656        char *url_arguments;
    57         url_t *base_url = NULL;
    5857
    5958        url_arguments = g_strdup("");
     
    6867                }
    6968        }
    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        
    7969        // Make the request.
    8070        g_string_printf(request, "%s %s%s%s%s HTTP/1.0\r\n"
     
    8272                        "User-Agent: BitlBee " BITLBEE_VERSION " " ARCH "/" CPU "\r\n",
    8373                        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);
    8876
    8977        // If a pass and user are given we append them to the request.
     
    9280                char *full_url;
    9381
    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);
    9883                full_header = oauth_http_header(td->oauth_info, is_post ? "POST" : "GET",
    9984                                                full_url, url_arguments);
     
    124109        }
    125110
    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);
    130112
    131113        g_free(url_arguments);
    132114        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;
    143115        return ret;
    144116}
  • protocols/twitter/twitter_http.h

    rcc6fdf8 r92d3044  
    2828#include "http_client.h"
    2929
    30 typedef enum {
    31         /* With this set, twitter_http_post() will post a generic confirmation
    32            message to the user. */
    33         TWITTER_HTTP_USER_ACK = 0x1000000,
    34 } twitter_http_flags_t;
    35 
    3630struct oauth_info;
    3731
    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);
     32void *twitter_http(struct im_connection *ic, char *url_string, http_input_function func,
     33                   gpointer data, int is_post, char** arguments, int arguments_len);
    4234
    4335#endif //_TWITTER_HTTP_H
  • protocols/twitter/twitter_lib.c

    rcc6fdf8 r92d3044  
    3535#include "misc.h"
    3636#include "base64.h"
     37#include "xmltree.h"
    3738#include "twitter_lib.h"
    38 #include "json_util.h"
    3939#include <ctype.h>
    4040#include <errno.h>
     
    6767        char *text;
    6868        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;
    7170};
     71
     72static void twitter_groupchat_init(struct im_connection *ic);
    7273
    7374/**
     
    147148        // Check if the buddy is already in the buddy list.
    148149        if (!bee_user_by_handle(ic->bee, ic, name)) {
     150                char *mode = set_getstr(&ic->acc->set, "mode");
     151
    149152                // The buddy is not in the list, add the buddy and set the status to logged in.
    150153                imcb_add_buddy(ic, name, NULL);
    151154                imcb_rename_buddy(ic, name, fullname);
    152                 if (td->flags & TWITTER_MODE_CHAT) {
     155                if (g_strcasecmp(mode, "chat") == 0) {
    153156                        /* Necessary so that nicks always get translated to the
    154157                           exact Twitter username. */
    155158                        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)
    159161                        imcb_buddy_status(ic, name, OPT_LOGGED_IN, NULL, NULL);
    160162        }
     
    166168{
    167169        static char *ret = NULL;
    168         json_value *root, *err;
     170        struct xt_node *root, *node, *err;
    169171
    170172        g_free(ret);
     
    172174
    173175        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);
    183185        }
    184186
     
    186188}
    187189
    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)
     190static struct xt_node *twitter_parse_response(struct im_connection *ic, struct http_request *req)
    191191{
    192192        gboolean logging_in = !(ic->flags & OPT_LOGGED_IN);
    193193        gboolean periodic;
    194194        struct twitter_data *td = ic->proto_data;
    195         json_value *ret;
     195        struct xt_node *ret;
    196196        char path[64] = "", *s;
    197197       
     
    211211                   throwing 401s so I'll keep treating this one as fatal
    212212                   only during login. */
    213                 imcb_error(ic, "Authentication failure (%s)",
    214                                twitter_parse_error(req));
     213                imcb_error(ic, "Authentication failure");
    215214                imc_logout(ic, FALSE);
    216215                return NULL;
     
    218217                // It didn't go well, output the error and return.
    219218                if (!periodic || logging_in || ++td->http_fails >= 5)
    220                         twitter_log(ic, "Error: Could not retrieve %s: %s",
    221                                     path, twitter_parse_error(req));
     219                        imcb_error(ic, "Could not retrieve %s: %s",
     220                                   path, twitter_parse_error(req));
    222221               
    223222                if (logging_in)
     
    228227        }
    229228
    230         if ((ret = json_parse(req->reply_body)) == NULL) {
     229        if ((ret = xt_from_string(req->reply_body, req->body_size)) == NULL) {
    231230                imcb_error(ic, "Could not retrieve %s: %s",
    232231                           path, "XML parse error");
     
    252251
    253252/**
     253 * Function to help fill a list.
     254 */
     255static 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/**
    254268 * Fill a list of ids.
    255269 */
    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;
     270static xt_status twitter_xt_get_friends_id_list(struct xt_node *node, struct twitter_xml_list *txl)
     271{
     272        struct xt_node *child;
    260273
    261274        // Set the list type.
    262275        txl->type = TXL_ID;
    263276
    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;
    283292}
    284293
     
    291300{
    292301        struct im_connection *ic;
    293         json_value *parsed;
     302        struct xt_node *parsed;
    294303        struct twitter_xml_list *txl;
    295304        struct twitter_data *td;
     
    303312        td = ic->proto_data;
    304313
     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
    305318        txl = g_new0(struct twitter_xml_list, 1);
    306319        txl->list = td->follow_ids;
     
    309322        if (!(parsed = twitter_parse_response(ic, req)))
    310323                return;
    311        
    312324        twitter_xt_get_friends_id_list(parsed, txl);
    313         json_value_free(parsed);
     325        xt_free_node(parsed);
    314326
    315327        td->follow_ids = txl->list;
     
    326338}
    327339
    328 static gboolean twitter_xt_get_users(json_value *node, struct twitter_xml_list *txl);
     340static xt_status twitter_xt_get_users(struct xt_node *node, struct twitter_xml_list *txl);
    329341static void twitter_http_get_users_lookup(struct http_request *req);
    330342
     
    367379{
    368380        struct im_connection *ic = req->data;
    369         json_value *parsed;
     381        struct xt_node *parsed;
    370382        struct twitter_xml_list *txl;
    371383        GSList *l = NULL;
     
    383395                return;
    384396        twitter_xt_get_users(parsed, txl);
    385         json_value_free(parsed);
     397        xt_free_node(parsed);
    386398
    387399        // Add the users as buddies.
     
    397409}
    398410
    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 */
     417static 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;
    408430}
    409431
     
    413435 *  - all <user>s from the <users> element.
    414436 */
    415 static gboolean twitter_xt_get_users(json_value *node, struct twitter_xml_list *txl)
     437static xt_status twitter_xt_get_users(struct xt_node *node, struct twitter_xml_list *txl)
    416438{
    417439        struct twitter_xml_user *txu;
    418         int i;
     440        struct xt_node *child;
    419441
    420442        // Set the type of the list.
    421443        txl->type = TXL_USER;
    422444
    423         if (!node || node->type != json_array)
    424                 return FALSE;
    425 
    426445        // The root <users> node should hold the list of users <user>
    427446        // 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.
    431452                        txl->list = g_slist_prepend(txl->list, txu);
    432         }
    433 
    434         return TRUE;
     453                }
     454        }
     455
     456        return XT_HANDLED;
    435457}
    436458
     
    440462#define TWITTER_TIME_FORMAT "%a %b %d %H:%M:%S +0000 %Y"
    441463#endif
    442 
    443 static char* expand_entities(char* text, const json_value *entities);
    444464
    445465/**
     
    451471 *  - the user in a twitter_xml_user struct.
    452472 */
    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) {
     473static 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) {
    469484                        struct tm parsed;
    470485
     
    472487                           this field. :-( Also assumes the timezone used
    473488                           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)
    475490                                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);
    484498                }
    485499        }
     
    488502           wasn't truncated because it may be lying. */
    489503        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) {
    495506                        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;
    553515               
    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)
    561521                                continue;
    562522                       
    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 &lt;%s&gt;%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;
    580544}
    581545
     
    586550 *  - the next_cursor.
    587551 */
    588 static gboolean twitter_xt_get_status_list(struct im_connection *ic, const json_value *node,
    589                                            struct twitter_xml_list *txl)
     552static xt_status twitter_xt_get_status_list(struct im_connection *ic, struct xt_node *node,
     553                                            struct twitter_xml_list *txl)
    590554{
    591555        struct twitter_xml_status *txs;
    592         int i;
     556        struct xt_node *child;
     557        bee_user_t *bu;
    593558
    594559        // Set the type of the list.
    595560        txl->type = TXL_STATUS;
    596        
    597         if (node->type != json_array)
    598                 return FALSE;
    599561
    600562        // The root <statuses> node should hold the list of statuses <status>
    601563        // 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
    615588static char *twitter_msg_add_id(struct im_connection *ic,
    616589                                struct twitter_xml_status *txs, const char *prefix)
    617590{
    618591        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);
    622603        if (txs->reply_to) {
    623604                int i;
    624605                for (i = 0; i < TWITTER_LOG_LENGTH; i++)
    625606                        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);
    627609                                break;
    628610                        }
    629611        }
    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);
    641614        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                 else
    656                         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                 else
    662                         return NULL;
     615
     616        return ret;
     617}
     618
     619static 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);
    663636        }
    664637}
     
    667640 * Function that is called to see the statuses in a groupchat window.
    668641 */
    669 static void twitter_status_show_chat(struct im_connection *ic, struct twitter_xml_status *status)
     642static void twitter_groupchat(struct im_connection *ic, GSList * list)
    670643{
    671644        struct twitter_data *td = ic->proto_data;
     645        GSList *l = NULL;
     646        struct twitter_xml_status *status;
    672647        struct groupchat *gc;
    673         gboolean me = g_strcasecmp(td->user, status->user->screen_name) == 0;
    674         char *msg;
     648        guint64 last_id = 0;
    675649
    676650        // 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        }
    693690}
    694691
     
    696693 * Function that is called to see statuses as private messages.
    697694 */
    698 static void twitter_status_show_msg(struct im_connection *ic, struct twitter_xml_status *status)
     695static void twitter_private_message_chat(struct im_connection *ic, GSList * list)
    699696{
    700697        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) {
    706707                g_snprintf(from, sizeof(from) - 1, "%s_%s", td->prefix, ic->acc->user);
    707708                from[MAX_STRING - 1] = '\0';
    708709        }
    709710
    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
     742static void twitter_http_get_home_timeline(struct http_request *req);
     743static void twitter_http_get_mentions(struct http_request *req);
    914744
    915745/**
     
    948778        struct twitter_xml_list *home_timeline = td->home_timeline_obj;
    949779        struct twitter_xml_list *mentions = td->mentions_obj;
    950         guint64 last_id = 0;
    951780        GSList *output = NULL;
    952781        GSList *l;
    953782
    954         imcb_connected(ic);
    955        
    956783        if (!(td->flags & TWITTER_GOT_TIMELINE)) {
    957784                return;
     
    977804                }
    978805        }
     806       
     807        if (!(ic->flags & OPT_LOGGED_IN))
     808                imcb_connected(ic);
    979809
    980810        // 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);
    988817
    989818        txl_free(home_timeline);
     
    994823}
    995824
    996 static void twitter_http_get_home_timeline(struct http_request *req);
    997 static void twitter_http_get_mentions(struct http_request *req);
    998 
    999825/**
    1000826 * Get the timeline.
    1001827 */
    1002 static void twitter_get_home_timeline(struct im_connection *ic, gint64 next_cursor)
     828void twitter_get_home_timeline(struct im_connection *ic, gint64 next_cursor)
    1003829{
    1004830        struct twitter_data *td = ic->proto_data;
     
    1036862 * Get mentions.
    1037863 */
    1038 static void twitter_get_mentions(struct im_connection *ic, gint64 next_cursor)
     864void twitter_get_mentions(struct im_connection *ic, gint64 next_cursor)
    1039865{
    1040866        struct twitter_data *td = ic->proto_data;
     
    1067893
    1068894        g_free(args[1]);
    1069         g_free(args[5]);
     895        if (td->timeline_id) {
     896                g_free(args[5]);
     897        }
    1070898}
    1071899
     
    1077905        struct im_connection *ic = req->data;
    1078906        struct twitter_data *td;
    1079         json_value *parsed;
     907        struct xt_node *parsed;
    1080908        struct twitter_xml_list *txl;
    1081909
     
    1093921                goto end;
    1094922        twitter_xt_get_status_list(ic, parsed, txl);
    1095         json_value_free(parsed);
     923        xt_free_node(parsed);
    1096924
    1097925        td->home_timeline_obj = txl;
    1098926
    1099927      end:
    1100         if (!g_slist_find(twitter_connections, ic))
    1101                 return;
    1102 
    1103928        td->flags |= TWITTER_GOT_TIMELINE;
    1104929
     
    1113938        struct im_connection *ic = req->data;
    1114939        struct twitter_data *td;
    1115         json_value *parsed;
     940        struct xt_node *parsed;
    1116941        struct twitter_xml_list *txl;
    1117942
     
    1129954                goto end;
    1130955        twitter_xt_get_status_list(ic, parsed, txl);
    1131         json_value_free(parsed);
     956        xt_free_node(parsed);
    1132957
    1133958        td->mentions_obj = txl;
    1134959
    1135960      end:
    1136         if (!g_slist_find(twitter_connections, ic))
    1137                 return;
    1138 
    1139961        td->flags |= TWITTER_GOT_MENTIONS;
    1140962
     
    1150972        struct im_connection *ic = req->data;
    1151973        struct twitter_data *td;
    1152         json_value *parsed, *id;
     974        struct xt_node *parsed, *node;
    1153975
    1154976        // Check if the connection is still active.
     
    1162984                return;
    1163985       
    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);
    1172989}
    1173990
     
    12151032        char *url;
    12161033        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);
    12201036        g_free(url);
    12211037}
     
    12251041        char *url;
    12261042        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);
    12301045        g_free(url);
    12311046}
     
    12411056        };
    12421057        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);
    12451060}
    12461061
     
    12521067        char *url;
    12531068        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);
    12571071        g_free(url);
    12581072}
  • protocols/twitter/twitter_lib.h

    rcc6fdf8 r92d3044  
    2929#include "twitter_http.h"
    3030
    31 #define TWITTER_API_URL "http://api.twitter.com/1.1"
     31#define TWITTER_API_URL "http://api.twitter.com/1"
    3232#define IDENTICA_API_URL "https://identi.ca/api"
    3333
    3434/* Status URLs */
    35 #define TWITTER_STATUS_UPDATE_URL "/statuses/update.json"
     35#define TWITTER_STATUS_UPDATE_URL "/statuses/update.xml"
    3636#define TWITTER_STATUS_SHOW_URL "/statuses/show/"
    3737#define TWITTER_STATUS_DESTROY_URL "/statuses/destroy/"
     
    3939
    4040/* 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"
    4747
    4848/* Users URLs */
    49 #define TWITTER_USERS_LOOKUP_URL "/users/lookup.json"
     49#define TWITTER_USERS_LOOKUP_URL "/users/lookup.xml"
    5050
    5151/* 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"
    5555#define TWITTER_DIRECT_MESSAGES_DESTROY_URL "/direct_messages/destroy/"
    5656
    5757/* 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"
    6161
    6262/* 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"
    6565
    6666/* 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"
    6868
    6969/* Favorites URLs */
    70 #define TWITTER_FAVORITES_GET_URL "/favorites.json"
     70#define TWITTER_FAVORITES_GET_URL "/favorites.xml"
    7171#define TWITTER_FAVORITE_CREATE_URL "/favorites/create/"
    7272#define TWITTER_FAVORITE_DESTROY_URL "/favorites/destroy/"
     
    7777
    7878/* Report spam */
    79 #define TWITTER_REPORT_SPAM_URL "/report_spam.json"
     79#define TWITTER_REPORT_SPAM_URL "/report_spam.xml"
    8080
    81 #define TWITTER_USER_STREAM_URL "https://userstream.twitter.com/1.1/user.json"
    82 
    83 gboolean twitter_open_stream(struct im_connection *ic);
    8481void twitter_get_timeline(struct im_connection *ic, gint64 next_cursor);
    8582void twitter_get_friends_ids(struct im_connection *ic, gint64 next_cursor);
     83void twitter_get_home_timeline(struct im_connection *ic, gint64 next_cursor);
     84void twitter_get_mentions(struct im_connection *ic, gint64 next_cursor);
    8685void twitter_get_statuses_friends(struct im_connection *ic, gint64 next_cursor);
    8786
Note: See TracChangeset for help on using the changeset viewer.