From b61ba851da0157ace3bdfc1ebbf87156b0b76413 Mon Sep 17 00:00:00 2001 From: Kirill Volinsky Date: Wed, 6 Jun 2012 08:58:27 +0000 Subject: protocols plugins moved to protocols git-svn-id: http://svn.miranda-ng.org/main/trunk@327 1316c22d-e87f-b044-9b9b-93d7a3e3ba9c --- protocols/Twitter/LICENSE.txt | 674 ++++++++++++++++++++++++++++++ protocols/Twitter/README.txt | 10 + protocols/Twitter/chat.cpp | 206 +++++++++ protocols/Twitter/common.h | 80 ++++ protocols/Twitter/connection.cpp | 435 +++++++++++++++++++ protocols/Twitter/contacts.cpp | 288 +++++++++++++ protocols/Twitter/http.cpp | 39 ++ protocols/Twitter/http.h | 39 ++ protocols/Twitter/icons/twitter.ico | Bin 0 -> 1406 bytes protocols/Twitter/main.cpp | 168 ++++++++ protocols/Twitter/proto.cpp | 528 +++++++++++++++++++++++ protocols/Twitter/proto.h | 179 ++++++++ protocols/Twitter/resource.h | 47 +++ protocols/Twitter/stubs.cpp | 140 +++++++ protocols/Twitter/theme.cpp | 183 ++++++++ protocols/Twitter/theme.h | 25 ++ protocols/Twitter/tinyjson.hpp | 586 ++++++++++++++++++++++++++ protocols/Twitter/twitter.cpp | 380 +++++++++++++++++ protocols/Twitter/twitter.h | 96 +++++ protocols/Twitter/twitter.rc | 224 ++++++++++ protocols/Twitter/twitter.sln | 26 ++ protocols/Twitter/twitter.vcxproj | 127 ++++++ protocols/Twitter/twitter.vcxproj.filters | 94 +++++ protocols/Twitter/ui.cpp | 529 +++++++++++++++++++++++ protocols/Twitter/ui.h | 25 ++ protocols/Twitter/utility.cpp | 133 ++++++ protocols/Twitter/utility.h | 107 +++++ protocols/Twitter/version.h | 20 + 28 files changed, 5388 insertions(+) create mode 100644 protocols/Twitter/LICENSE.txt create mode 100644 protocols/Twitter/README.txt create mode 100644 protocols/Twitter/chat.cpp create mode 100644 protocols/Twitter/common.h create mode 100644 protocols/Twitter/connection.cpp create mode 100644 protocols/Twitter/contacts.cpp create mode 100644 protocols/Twitter/http.cpp create mode 100644 protocols/Twitter/http.h create mode 100644 protocols/Twitter/icons/twitter.ico create mode 100644 protocols/Twitter/main.cpp create mode 100644 protocols/Twitter/proto.cpp create mode 100644 protocols/Twitter/proto.h create mode 100644 protocols/Twitter/resource.h create mode 100644 protocols/Twitter/stubs.cpp create mode 100644 protocols/Twitter/theme.cpp create mode 100644 protocols/Twitter/theme.h create mode 100644 protocols/Twitter/tinyjson.hpp create mode 100644 protocols/Twitter/twitter.cpp create mode 100644 protocols/Twitter/twitter.h create mode 100644 protocols/Twitter/twitter.rc create mode 100644 protocols/Twitter/twitter.sln create mode 100644 protocols/Twitter/twitter.vcxproj create mode 100644 protocols/Twitter/twitter.vcxproj.filters create mode 100644 protocols/Twitter/ui.cpp create mode 100644 protocols/Twitter/ui.h create mode 100644 protocols/Twitter/utility.cpp create mode 100644 protocols/Twitter/utility.h create mode 100644 protocols/Twitter/version.h (limited to 'protocols/Twitter') diff --git a/protocols/Twitter/LICENSE.txt b/protocols/Twitter/LICENSE.txt new file mode 100644 index 0000000000..818433ecc0 --- /dev/null +++ b/protocols/Twitter/LICENSE.txt @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/protocols/Twitter/README.txt b/protocols/Twitter/README.txt new file mode 100644 index 0000000000..b229b10796 --- /dev/null +++ b/protocols/Twitter/README.txt @@ -0,0 +1,10 @@ +Miranda-Twitter +--------------- + +Warning! I'm not maintaining this code anymore! It's still around for people who +want to work on it, but that's it; I make no promises that it even works +(indeed, without updates, it almost certainly won't pass Twitter's auth). + +To compile this code, you'll want to check out the Miranda IM header files and +install them into \miranda: + diff --git a/protocols/Twitter/chat.cpp b/protocols/Twitter/chat.cpp new file mode 100644 index 0000000000..4e659c2aa8 --- /dev/null +++ b/protocols/Twitter/chat.cpp @@ -0,0 +1,206 @@ +/* +Copyright © 2009 Jim Porter + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +#include "common.h" +#include "proto.h" + +#include +#include + +void TwitterProto::UpdateChat(const twitter_user &update) +{ + GCDEST gcd = { m_szModuleName }; + gcd.ptszID = const_cast(m_tszUserName); + gcd.iType = GC_EVENT_MESSAGE; + + GCEVENT gce = {sizeof(gce)}; + gce.pDest = &gcd; + gce.dwFlags = GC_TCHAR|GCEF_ADDTOLOG; + gce.pDest = &gcd; + gce.ptszUID = mir_a2t( update.username.c_str()); + gce.bIsMe = (update.username == twit_.get_username()); + gce.ptszText = ( TCHAR* )update.status.text.c_str(); + gce.time = static_cast(update.status.time); + + DBVARIANT nick; + HANDLE hContact = UsernameToHContact( _A2T(update.username.c_str())); + if (hContact && !DBGetContactSettingTString(hContact, "CList", "MyHandle", &nick)) { + gce.ptszNick = mir_tstrdup( nick.ptszVal ); + DBFreeVariant(&nick); + } + else gce.ptszNick = mir_a2t( update.username.c_str()); + + CallServiceSync(MS_GC_EVENT, 0, reinterpret_cast(&gce)); + + mir_free(const_cast(gce.ptszNick)); + mir_free(const_cast(gce.ptszUID)); + mir_free(const_cast(gce.ptszText)); +} + +int TwitterProto::OnChatOutgoing(WPARAM wParam, LPARAM lParam) +{ + GCHOOK *hook = reinterpret_cast(lParam); + if (strcmp(hook->pDest->pszModule, m_szModuleName)) + return 0; + + TCHAR *text; + switch(hook->pDest->iType) { + case GC_USER_MESSAGE: + text = mir_tstrdup(hook->ptszText); + ForkThread(&TwitterProto::SendTweetWorker, this, text); + break; + + case GC_USER_PRIVMESS: + text = mir_tstrdup(hook->ptszUID); + CallService(MS_MSG_SENDMESSAGE, reinterpret_cast( UsernameToHContact(text)), 0); + mir_free(text); + break; + } + + return 0; +} + +// TODO: remove nick? +void TwitterProto::AddChatContact(const TCHAR *name, const TCHAR *nick) +{ + GCDEST gcd = { m_szModuleName }; + gcd.ptszID = const_cast(m_tszUserName); + gcd.iType = GC_EVENT_JOIN; + + GCEVENT gce = {sizeof(gce)}; + gce.pDest = &gcd; + gce.dwFlags = GC_TCHAR; + gce.ptszNick = nick ? nick:name; + gce.ptszUID = name; + gce.bIsMe = false; + gce.ptszStatus = _T("Normal"); + gce.time = static_cast(time(0)); + CallServiceSync(MS_GC_EVENT, 0, reinterpret_cast(&gce)); +} + +void TwitterProto::DeleteChatContact(const TCHAR *name) +{ + GCDEST gcd = { m_szModuleName }; + gcd.ptszID = const_cast(m_tszUserName); + gcd.iType = GC_EVENT_PART; + + GCEVENT gce = {sizeof(gce)}; + gce.pDest = &gcd; + gce.dwFlags = GC_TCHAR; + gce.ptszNick = name; + gce.ptszUID = gce.ptszNick; + gce.time = static_cast(time(0)); + CallServiceSync(MS_GC_EVENT, 0, reinterpret_cast(&gce)); + + mir_free(const_cast(gce.ptszNick)); +} + +int TwitterProto::OnJoinChat(WPARAM, LPARAM suppress) +{ + GCSESSION gcw = {sizeof(gcw)}; + + // ***** Create the group chat session + gcw.dwFlags = GC_TCHAR; + gcw.iType = GCW_CHATROOM; + gcw.pszModule = m_szModuleName; + gcw.ptszName = m_tszUserName; + gcw.ptszID = m_tszUserName; + CallServiceSync(MS_GC_NEWSESSION, 0, (LPARAM)&gcw); + + if (m_iStatus != ID_STATUS_ONLINE) + return 0; + + // ***** Create a group + GCDEST gcd = { m_szModuleName }; + gcd.ptszID = const_cast(m_tszUserName); + + GCEVENT gce = {sizeof(gce)}; + gce.pDest = &gcd; + gce.dwFlags = GC_TCHAR; + + gcd.iType = GC_EVENT_ADDGROUP; + gce.ptszStatus = _T("Normal"); + CallServiceSync(MS_GC_EVENT, 0, reinterpret_cast(&gce)); + + // ***** Hook events + HookProtoEvent(ME_GC_EVENT, &TwitterProto::OnChatOutgoing, this); + + // Note: Initialization will finish up in SetChatStatus, called separately + if (!suppress) + SetChatStatus(m_iStatus); + + in_chat_ = true; + return 0; +} + +int TwitterProto::OnLeaveChat(WPARAM, LPARAM) +{ + in_chat_ = false; + + GCDEST gcd = { m_szModuleName }; + gcd.ptszID = const_cast(m_tszUserName); + gcd.iType = GC_EVENT_CONTROL; + + GCEVENT gce = {sizeof(gce)}; + gce.dwFlags = GC_TCHAR; + gce.pDest = &gcd; + + CallServiceSync(MS_GC_EVENT, SESSION_OFFLINE, reinterpret_cast(&gce)); + CallServiceSync(MS_GC_EVENT, SESSION_TERMINATE, reinterpret_cast(&gce)); + + return 0; +} + +void TwitterProto::SetChatStatus(int status) +{ + GCDEST gcd = { m_szModuleName }; + gcd.ptszID = const_cast(m_tszUserName); + gcd.iType = GC_EVENT_CONTROL; + + GCEVENT gce = {sizeof(gce)}; + gce.dwFlags = GC_TCHAR; + gce.pDest = &gcd; + + if (status == ID_STATUS_ONLINE) { + // Add all friends to contact list + for(HANDLE hContact = (HANDLE)CallService(MS_DB_CONTACT_FINDFIRST, 0, 0); + hContact; + hContact = (HANDLE)CallService(MS_DB_CONTACT_FINDNEXT, (WPARAM)hContact, 0)) + { + if (!IsMyContact(hContact)) + continue; + + DBVARIANT uid, nick; + if ( DBGetContactSettingTString(hContact, m_szModuleName, TWITTER_KEY_UN, &uid)) + continue; + + if ( !DBGetContactSettingTString(hContact, "CList", "MyHandle", &nick)) + AddChatContact(uid.ptszVal, nick.ptszVal); + else + AddChatContact(uid.ptszVal); + + DBFreeVariant(&nick); + DBFreeVariant(&uid); + } + + // For some reason, I have to send an INITDONE message, even if I'm not actually + // initializing the room... + CallServiceSync(MS_GC_EVENT, SESSION_INITDONE, reinterpret_cast(&gce)); + CallServiceSync(MS_GC_EVENT, SESSION_ONLINE, reinterpret_cast(&gce)); + } + else CallServiceSync(MS_GC_EVENT, SESSION_OFFLINE, reinterpret_cast(&gce)); +} \ No newline at end of file diff --git a/protocols/Twitter/common.h b/protocols/Twitter/common.h new file mode 100644 index 0000000000..751c1d22d6 --- /dev/null +++ b/protocols/Twitter/common.h @@ -0,0 +1,80 @@ +/* +Copyright © 2009 Jim Porter + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +#pragma once + +#include + +#include "resource.h" + +#pragma warning(push) +# pragma warning(disable:4312) +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#pragma warning(pop) + +extern HINSTANCE g_hInstance; + +#define TWITTER_KEY_UN "Username" +#define TWITTER_KEY_PASS "Password" +#define TWITTER_KEY_BASEURL "BaseURL" +#define TWITTER_KEY_CHATFEED "ChatFeed" +#define TWITTER_KEY_POLLRATE "PollRate" + +#define TWITTER_KEY_POPUP_SHOW "Popup/Show" +#define TWITTER_KEY_POPUP_SIGNON "Popup/Signon" +#define TWITTER_KEY_POPUP_COLBACK "Popup/ColorBack" +#define TWITTER_KEY_POPUP_COLTEXT "Popup/ColorText" +#define TWITTER_KEY_POPUP_TIMEOUT "Popup/Timeout" + +#define TWITTER_KEY_SINCEID "SinceID" +#define TWITTER_KEY_DMSINCEID "DMSinceID" +#define TWITTER_KEY_NEW "NewAcc" + +#define TWITTER_KEY_AV_URL "AvatarURL" + +#define TWITTER_DB_EVENT_TYPE_TWEET 2718 + +#define WM_SETREPLY WM_APP+10 \ No newline at end of file diff --git a/protocols/Twitter/connection.cpp b/protocols/Twitter/connection.cpp new file mode 100644 index 0000000000..83660a8256 --- /dev/null +++ b/protocols/Twitter/connection.cpp @@ -0,0 +1,435 @@ +/* +Copyright © 2009 Jim Porter + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +#include "common.h" +#include "proto.h" + +void CALLBACK TwitterProto::APC_callback(ULONG_PTR p) +{ + reinterpret_cast(p)->LOG("***** Executing APC"); +} + +template +inline static T db_pod_get(HANDLE hContact, const char *module, const char *setting, + T errorValue) +{ + DBVARIANT dbv; + DBCONTACTGETSETTING cgs; + + cgs.szModule = module; + cgs.szSetting = setting; + cgs.pValue = &dbv; + if (CallService(MS_DB_CONTACT_GETSETTING, (WPARAM)hContact, (LPARAM)&cgs)) + return errorValue; + + // TODO: remove this, it's just a temporary workaround + if (dbv.type == DBVT_DWORD) + return dbv.dVal; + + if (dbv.cpbVal != sizeof(T)) + return errorValue; + return *reinterpret_cast(dbv.pbVal); +} + +template +inline static INT_PTR db_pod_set(HANDLE hContact, const char *module, const char *setting, + T val) +{ + DBCONTACTWRITESETTING cws; + + cws.szModule = module; + cws.szSetting = setting; + cws.value.type = DBVT_BLOB; + cws.value.cpbVal = sizeof(T); + cws.value.pbVal = reinterpret_cast(&val); + return CallService(MS_DB_CONTACT_WRITESETTING, (WPARAM)hContact, (LPARAM)&cws); +} + +void TwitterProto::SignOn(void*) +{ + LOG("***** Beginning SignOn process"); + WaitForSingleObject(&signon_lock_, INFINITE); + + // Kill the old thread if it's still around + if (hMsgLoop_) + { + LOG("***** Requesting MessageLoop to exit"); + QueueUserAPC(APC_callback, hMsgLoop_, (ULONG_PTR)this); + LOG("***** Waiting for old MessageLoop to exit"); + WaitForSingleObject(hMsgLoop_, INFINITE); + CloseHandle(hMsgLoop_); + } + if (NegotiateConnection()) // Could this be? The legendary Go Time?? + { + if (!in_chat_ && db_byte_get(0, m_szModuleName, TWITTER_KEY_CHATFEED, 0)) + OnJoinChat(0, true); + + SetAllContactStatuses(ID_STATUS_ONLINE); + hMsgLoop_ = ForkThreadEx(&TwitterProto::MessageLoop, this); + } + + ReleaseMutex(signon_lock_); + LOG("***** SignOn complete"); +} + +bool TwitterProto::NegotiateConnection() +{ + LOG("***** Negotiating connection with Twitter"); + + int old_status = m_iStatus; + std::string user, pass; + DBVARIANT dbv; + + if ( !DBGetContactSettingString(0, m_szModuleName, TWITTER_KEY_UN, &dbv)) { + user = dbv.pszVal; + DBFreeVariant(&dbv); + } + else { + ShowPopup(TranslateT("Please enter a username.")); + return false; + } + + if ( !DBGetContactSettingString(0, m_szModuleName, TWITTER_KEY_PASS, &dbv)) { + CallService(MS_DB_CRYPT_DECODESTRING, strlen(dbv.pszVal)+1, + reinterpret_cast(dbv.pszVal)); + pass = dbv.pszVal; + DBFreeVariant(&dbv); + } + else { + ShowPopup(TranslateT("Please enter a password.")); + return false; + } + + if ( !DBGetContactSettingString(0, m_szModuleName, TWITTER_KEY_BASEURL, &dbv)) { + ScopedLock s(twitter_lock_); + twit_.set_base_url(dbv.pszVal); + DBFreeVariant(&dbv); + } + + bool success; + { + ScopedLock s(twitter_lock_); + success = twit_.set_credentials(user, pass); + } + + if (!success) { + ShowPopup(TranslateT("Your username/password combination was incorrect.")); + ProtoBroadcastAck(m_szModuleName, 0, ACKTYPE_STATUS, ACKRESULT_FAILED, + (HANDLE)old_status, m_iStatus); + + // Set to offline + old_status = m_iStatus; + m_iDesiredStatus = m_iStatus = ID_STATUS_OFFLINE; + ProtoBroadcastAck(m_szModuleName, 0, ACKTYPE_STATUS, ACKRESULT_SUCCESS, + (HANDLE)old_status, m_iStatus); + + return false; + } + + m_iStatus = m_iDesiredStatus; + ProtoBroadcastAck(m_szModuleName, 0, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE)old_status, m_iStatus); + return true; +} + + +void TwitterProto::MessageLoop(void*) +{ + LOG("***** Entering Twitter::MessageLoop"); + + since_id_ = db_pod_get(0, m_szModuleName, TWITTER_KEY_SINCEID, 0); + dm_since_id_ = db_pod_get(0, m_szModuleName, TWITTER_KEY_DMSINCEID, 0); + + bool new_account = db_byte_get(0, m_szModuleName, TWITTER_KEY_NEW, 1) != 0; + bool popups = db_byte_get(0, m_szModuleName, TWITTER_KEY_POPUP_SIGNON, 1) != 0; + + int poll_rate = db_dword_get(0, m_szModuleName, TWITTER_KEY_POLLRATE, 80); + + for(unsigned int i=0;;i++) + { + if (m_iStatus != ID_STATUS_ONLINE) + goto exit; + if (i%4 == 0) + UpdateFriends(); + + if (m_iStatus != ID_STATUS_ONLINE) + goto exit; + UpdateStatuses(new_account, popups); + + if (m_iStatus != ID_STATUS_ONLINE) + goto exit; + UpdateMessages(new_account); + + if (new_account) // Not anymore! + { + new_account = false; + DBWriteContactSettingByte(0, m_szModuleName, TWITTER_KEY_NEW, 0); + } + + if (m_iStatus != ID_STATUS_ONLINE) + goto exit; + LOG("***** TwitterProto::MessageLoop going to sleep..."); + if (SleepEx(poll_rate*1000, true) == WAIT_IO_COMPLETION) + goto exit; + LOG("***** TwitterProto::MessageLoop waking up..."); + + popups = true; + } + +exit: + { + ScopedLock s(twitter_lock_); + twit_.set_credentials("", "", false); + } + LOG("***** Exiting TwitterProto::MessageLoop"); +} + +struct update_avatar +{ + update_avatar(HANDLE hContact, const std::string &url) : hContact(hContact), url(url) {} + HANDLE hContact; + std::string url; +}; + +void TwitterProto::UpdateAvatarWorker(void *p) +{ + if (p == 0) + return; + std::auto_ptr data( static_cast(p)); + DBVARIANT dbv; + + if (DBGetContactSettingTString(data->hContact, m_szModuleName, TWITTER_KEY_UN, &dbv)) + return; + + std::string ext = data->url.substr(data->url.rfind('.')); + std::string filename = GetAvatarFolder() + '\\' + dbv.pszVal + ext; + DBFreeVariant(&dbv); + + PROTO_AVATAR_INFORMATION ai = {sizeof(ai)}; + ai.hContact = data->hContact; + ai.format = ext_to_format(ext); + strncpy(ai.filename, filename.c_str(), MAX_PATH); + + LOG("***** Updating avatar: %s", data->url.c_str()); + WaitForSingleObjectEx(avatar_lock_, INFINITE, true); + if (CallService(MS_SYSTEM_TERMINATED, 0, 0)) + { + LOG("***** Terminating avatar update early: %s", data->url.c_str()); + return; + } + + if (save_url(hAvatarNetlib_, data->url, filename)) + { + DBWriteContactSettingString(data->hContact, m_szModuleName, TWITTER_KEY_AV_URL, + data->url.c_str()); + ProtoBroadcastAck(m_szModuleName, data->hContact, ACKTYPE_AVATAR, + ACKRESULT_SUCCESS, &ai, 0); + } + else + ProtoBroadcastAck(m_szModuleName, data->hContact, ACKTYPE_AVATAR, + ACKRESULT_FAILED, &ai, 0); + ReleaseMutex(avatar_lock_); + LOG("***** Done avatar: %s", data->url.c_str()); +} + +void TwitterProto::UpdateAvatar(HANDLE hContact, const std::string &url, bool force) +{ + DBVARIANT dbv; + + if ( !force && + ( !DBGetContactSettingString(hContact, m_szModuleName, TWITTER_KEY_AV_URL, &dbv) && + url == dbv.pszVal)) + { + LOG("***** Avatar already up-to-date: %s", url.c_str()); + } + else + { + // TODO: more defaults (configurable?) + if (url == "http://static.twitter.com/images/default_profile_normal.png") + { + PROTO_AVATAR_INFORMATION ai = {sizeof(ai), hContact}; + + db_string_set(hContact, m_szModuleName, TWITTER_KEY_AV_URL, url.c_str()); + ProtoBroadcastAck(m_szModuleName, hContact, ACKTYPE_AVATAR, + ACKRESULT_SUCCESS, &ai, 0); + } + else + { + ForkThread(&TwitterProto::UpdateAvatarWorker, this, + new update_avatar(hContact, url)); + } + } + + DBFreeVariant(&dbv); +} + +void TwitterProto::UpdateFriends() +{ + try + { + ScopedLock s(twitter_lock_); + std::vector friends = twit_.get_friends(); + s.Unlock(); + + for(std::vector::iterator i=friends.begin(); i!=friends.end(); ++i) { + if (i->username == twit_.get_username()) + continue; + + HANDLE hContact = AddToClientList( _A2T(i->username.c_str()), i->status.text.c_str()); + UpdateAvatar(hContact, i->profile_image_url); + } + LOG("***** Friends list updated"); + } + catch(const bad_response &) + { + LOG("***** Bad response from server, signing off"); + SetStatus(ID_STATUS_OFFLINE); + } + catch(const std::exception &e) + { + ShowPopup( (std::string("While updating friends list, an error occurred: ") + +e.what()).c_str()); + LOG("***** Error updating friends list: %s", e.what()); + } + +} + +void TwitterProto::ShowContactPopup(HANDLE hContact, const std::tstring &text) +{ + if (!ServiceExists(MS_POPUP_ADDPOPUPT) || DBGetContactSettingByte(0, m_szModuleName, TWITTER_KEY_POPUP_SHOW, 0) == 0) + return; + + POPUPDATAT popup = {}; + popup.lchContact = hContact; + popup.iSeconds = db_dword_get(0, m_szModuleName, TWITTER_KEY_POPUP_TIMEOUT, 0); + + popup.colorBack = db_dword_get(0, m_szModuleName, TWITTER_KEY_POPUP_COLBACK, 0); + if (popup.colorBack == 0xFFFFFFFF) + popup.colorBack = GetSysColor(COLOR_WINDOW); + popup.colorText = db_dword_get(0, m_szModuleName, TWITTER_KEY_POPUP_COLTEXT, 0); + if (popup.colorBack == 0xFFFFFFFF) + popup.colorBack = GetSysColor(COLOR_WINDOWTEXT); + + DBVARIANT dbv; + if ( !DBGetContactSettingString(hContact, "CList", "MyHandle", &dbv) || + !DBGetContactSettingString(hContact, m_szModuleName, TWITTER_KEY_UN, &dbv)) + { + mbcs_to_tcs(CP_UTF8, dbv.pszVal, popup.lptzContactName, MAX_CONTACTNAME); + DBFreeVariant(&dbv); + } + + CallService(MS_POPUP_ADDPOPUPT, reinterpret_cast(text.c_str()), 0); +} + +void TwitterProto::UpdateStatuses(bool pre_read, bool popups) +{ + try + { + ScopedLock s(twitter_lock_); + twitter::status_list updates = twit_.get_statuses(200, since_id_); + s.Unlock(); + + if (!updates.empty()) + since_id_ = std::max(since_id_, updates[0].status.id); + + for(twitter::status_list::reverse_iterator i=updates.rbegin(); i!=updates.rend(); ++i) + { + if (!pre_read && in_chat_) + UpdateChat(*i); + + if (i->username == twit_.get_username()) + continue; + + HANDLE hContact = AddToClientList( _A2T(i->username.c_str()), _T("")); + + DBEVENTINFO dbei = {sizeof(dbei)}; + + dbei.pBlob = (BYTE*)(i->status.text.c_str()); + dbei.cbBlob = i->status.text.size()+1; + dbei.eventType = TWITTER_DB_EVENT_TYPE_TWEET; + dbei.flags = DBEF_UTF; + //dbei.flags = DBEF_READ; + dbei.timestamp = static_cast(i->status.time); + dbei.szModule = m_szModuleName; + CallService(MS_DB_EVENT_ADD, (WPARAM)hContact, (LPARAM)&dbei); + + DBWriteContactSettingTString(hContact, "CList", "StatusMsg", i->status.text.c_str()); + + if ( !pre_read && popups ) + ShowContactPopup(hContact, i->status.text); + } + + db_pod_set(0, m_szModuleName, TWITTER_KEY_SINCEID, since_id_); + LOG("***** Status messages updated"); + } + catch(const bad_response &) + { + LOG("***** Bad response from server, signing off"); + SetStatus(ID_STATUS_OFFLINE); + } + catch(const std::exception &e) + { + ShowPopup( (std::string("While updating status messages, an error occurred: ") + +e.what()).c_str()); + LOG("***** Error updating status messages: %s", e.what()); + } +} + +void TwitterProto::UpdateMessages(bool pre_read) +{ + try + { + ScopedLock s(twitter_lock_); + twitter::status_list messages = twit_.get_direct(dm_since_id_); + s.Unlock(); + + if (messages.size()) + dm_since_id_ = std::max(dm_since_id_, messages[0].status.id); + + for(twitter::status_list::reverse_iterator i=messages.rbegin(); i!=messages.rend(); ++i) { + HANDLE hContact = AddToClientList( _A2T(i->username.c_str()), _T("")); + + PROTORECVEVENT recv = {}; + CCSDATA ccs = {}; + + recv.flags = PREF_TCHAR; + if (pre_read) + recv.flags |= PREF_CREATEREAD; + recv.szMessage = ( char* )i->status.text.c_str(); + recv.timestamp = static_cast(i->status.time); + + ccs.hContact = hContact; + ccs.szProtoService = PSR_MESSAGE; + ccs.wParam = ID_STATUS_ONLINE; + ccs.lParam = reinterpret_cast(&recv); + CallService(MS_PROTO_CHAINRECV, 0, reinterpret_cast(&ccs)); + } + + db_pod_set(0, m_szModuleName, TWITTER_KEY_DMSINCEID, dm_since_id_); + LOG("***** Direct messages updated"); + } + catch(const bad_response &) + { + LOG("***** Bad response from server, signing off"); + SetStatus(ID_STATUS_OFFLINE); + } + catch(const std::exception &e) + { + ShowPopup( (std::string("While updating direct messages, an error occurred: ") + +e.what()).c_str()); + LOG("***** Error updating direct messages: %s", e.what()); + } +} \ No newline at end of file diff --git a/protocols/Twitter/contacts.cpp b/protocols/Twitter/contacts.cpp new file mode 100644 index 0000000000..03aad22601 --- /dev/null +++ b/protocols/Twitter/contacts.cpp @@ -0,0 +1,288 @@ +/* +Copyright © 2009 Jim Porter + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +#include "common.h" +#include "proto.h" + + +void TwitterProto::AddToListWorker(void *p) +{ + // TODO: what happens if there is an error? + if (p == 0) + return; + TCHAR *name = static_cast(p); + + try + { + ScopedLock s(twitter_lock_); + twitter_user user = twit_.add_friend(name); + s.Unlock(); + + HANDLE hContact = UsernameToHContact(name); + UpdateAvatar(hContact, user.profile_image_url); + } + catch(const std::exception &e) + { + ShowPopup((std::string("While adding a friend, an error occurred: ") + +e.what()).c_str()); + LOG("***** Error adding friend: %s", e.what()); + } + mir_free(name); +} + +HANDLE TwitterProto::AddToList(int flags, PROTOSEARCHRESULT *result) +{ + if (m_iStatus != ID_STATUS_ONLINE) + return 0; + + ForkThread(&TwitterProto::AddToListWorker, this, mir_tstrdup(result->nick)); + return AddToClientList(result->nick, _T("")); +} + +// ************************* + +void TwitterProto::UpdateInfoWorker(void *hContact) +{ + twitter_user user; + std::tstring username; + + DBVARIANT dbv; + if ( !DBGetContactSettingTString(hContact, m_szModuleName, TWITTER_KEY_UN, &dbv)) + { + username = dbv.ptszVal; + DBFreeVariant(&dbv); + } + else + return; + + { + ScopedLock s(twitter_lock_); + twit_.get_info(username, &user); + } + + UpdateAvatar(hContact, user.profile_image_url, true); + ProtoBroadcastAck(m_szModuleName, hContact, ACKTYPE_GETINFO, ACKRESULT_SUCCESS, 0, 0); +} + +int TwitterProto::GetInfo(HANDLE hContact, int info_type) +{ + if (m_iStatus != ID_STATUS_ONLINE) + return 1; + + if (!IsMyContact(hContact)) // Do nothing for chat rooms + return 1; + + if (info_type == 0) // From clicking "Update" in the Userinfo dialog + { + ForkThread(&TwitterProto::UpdateInfoWorker, this, hContact); + return 0; + } + + return 1; +} + +// ************************* + +struct search_query +{ + search_query(const std::tstring &query, bool by_email) : query(query), by_email(by_email) + {} + std::tstring query; + bool by_email; +}; + +void TwitterProto::DoSearch(void *p) +{ + if (p == 0) + return; + search_query *query = static_cast(p); + + twitter_user info; + PROTOSEARCHRESULT psr = {sizeof(psr)}; + + bool found; + try + { + ScopedLock s(twitter_lock_); + if (query->by_email) + found = twit_.get_info_by_email(query->query, &info); + else + found = twit_.get_info(query->query, &info); + } + catch(const std::exception &e) + { + ShowPopup( (std::string("While searching for contacts, an error occurred: ") + +e.what()).c_str()); + LOG("***** Error searching for contacts: %s", e.what()); + } + + if (found) + { + psr.nick = ( TCHAR* )info.username. c_str(); + psr.firstName = ( TCHAR* )info.real_name.c_str(); + + ProtoBroadcastAck(m_szModuleName, 0, ACKTYPE_SEARCH, ACKRESULT_DATA, (HANDLE)1, + (LPARAM)&psr); + ProtoBroadcastAck(m_szModuleName, 0, ACKTYPE_SEARCH, ACKRESULT_SUCCESS, (HANDLE)1, 0); + } + else + { + ProtoBroadcastAck(m_szModuleName, 0, ACKTYPE_SEARCH, ACKRESULT_SUCCESS, (HANDLE)1, 0); + } + + delete query; +} + +HANDLE TwitterProto::SearchBasic(const TCHAR *username) +{ + ForkThread(&TwitterProto::DoSearch, this, new search_query(username, false)); + return (HANDLE)1; +} + +HANDLE TwitterProto::SearchByEmail(const TCHAR *email) +{ + ForkThread(&TwitterProto::DoSearch, this, new search_query(email, true)); + return (HANDLE)1; +} + +// ************************* + +void TwitterProto::GetAwayMsgWorker(void *hContact) +{ + if (hContact == 0) + return; + + DBVARIANT dbv; + if ( !DBGetContactSettingTString(hContact, "CList", "StatusMsg", &dbv)) { + ProtoBroadcastAck(m_szModuleName, hContact, ACKTYPE_AWAYMSG, ACKRESULT_SUCCESS, (HANDLE)1, (LPARAM)dbv.ptszVal); + DBFreeVariant(&dbv); + } + else ProtoBroadcastAck(m_szModuleName, hContact, ACKTYPE_AWAYMSG, ACKRESULT_FAILED, (HANDLE)1, 0); +} + +HANDLE TwitterProto::GetAwayMsg(HANDLE hContact) +{ + ForkThread(&TwitterProto::GetAwayMsgWorker, this, hContact); + return (HANDLE)1; +} + +int TwitterProto::OnContactDeleted(WPARAM wParam, LPARAM lParam) +{ + if (m_iStatus != ID_STATUS_ONLINE) + return 0; + + const HANDLE hContact = reinterpret_cast(wParam); + + if (!IsMyContact(hContact)) + return 0; + + DBVARIANT dbv; + if ( !DBGetContactSettingTString(hContact, m_szModuleName, TWITTER_KEY_UN, &dbv)) { + if ( in_chat_ ) + DeleteChatContact(dbv.ptszVal); + + ScopedLock s(twitter_lock_); + twit_.remove_friend(dbv.ptszVal); // Be careful about this until Miranda is fixed + DBFreeVariant(&dbv); + } + return 0; +} + +// ************************* + +bool TwitterProto::IsMyContact(HANDLE hContact, bool include_chat) +{ + const char *proto = reinterpret_cast( CallService(MS_PROTO_GETCONTACTBASEPROTO, + reinterpret_cast(hContact), 0)); + + if (proto && strcmp(m_szModuleName, proto) == 0) + { + if (include_chat) + return true; + else + return DBGetContactSettingByte(hContact, m_szModuleName, "ChatRoom", 0) == 0; + } + else + return false; +} + +HANDLE TwitterProto::UsernameToHContact(const TCHAR *name) +{ + for(HANDLE hContact = (HANDLE)CallService(MS_DB_CONTACT_FINDFIRST, 0, 0); + hContact; + hContact = (HANDLE)CallService(MS_DB_CONTACT_FINDNEXT, (WPARAM)hContact, 0)) + { + if (!IsMyContact(hContact)) + continue; + + DBVARIANT dbv; + if ( !DBGetContactSettingTString(hContact, m_szModuleName, TWITTER_KEY_UN, &dbv)) { + if ( lstrcmp(name, dbv.ptszVal) == 0) { + DBFreeVariant(&dbv); + return hContact; + } + DBFreeVariant(&dbv); + } + } + + return 0; +} + +HANDLE TwitterProto::AddToClientList(const TCHAR *name, const TCHAR *status) +{ + // First, check if this contact exists + HANDLE hContact = UsernameToHContact(name); + if (hContact) + return hContact; + + if (in_chat_) + AddChatContact(name); + + // If not, make a new contact! + hContact = (HANDLE)CallService(MS_DB_CONTACT_ADD, 0, 0); + if (hContact ) { + if (CallService(MS_PROTO_ADDTOCONTACT, (WPARAM)hContact, (LPARAM)m_szModuleName) == 0) { + DBWriteContactSettingTString(hContact, m_szModuleName, TWITTER_KEY_UN, name); + DBWriteContactSettingWord(hContact, m_szModuleName, "Status", ID_STATUS_ONLINE); + DBWriteContactSettingTString(hContact, "CList", "StatusMsg", status); + + std::string url = profile_base_url(twit_.get_base_url()) + http::url_encode((char*)_T2A(name)); + DBWriteContactSettingString(hContact, m_szModuleName, "Homepage", url.c_str()); + + return hContact; + } + + CallService(MS_DB_CONTACT_DELETE, (WPARAM)hContact, 0); + } + + return 0; +} + +void TwitterProto::SetAllContactStatuses(int status) +{ + for(HANDLE hContact = (HANDLE)CallService(MS_DB_CONTACT_FINDFIRST, 0, 0); + hContact; + hContact = (HANDLE)CallService(MS_DB_CONTACT_FINDNEXT, (WPARAM)hContact, 0)) + { + if (!IsMyContact(hContact)) + continue; + + DBWriteContactSettingWord(hContact, m_szModuleName, "Status", status); + } + + SetChatStatus(status); +} \ No newline at end of file diff --git a/protocols/Twitter/http.cpp b/protocols/Twitter/http.cpp new file mode 100644 index 0000000000..63218ca616 --- /dev/null +++ b/protocols/Twitter/http.cpp @@ -0,0 +1,39 @@ +/* +Copyright © 2009 Jim Porter + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +#include "common.h" +#include "http.h" + +std::string http::url_encode(const std::string &s) +{ + char *encoded = (char*)CallService( MS_NETLIB_URLENCODE, 0, ( LPARAM )s.c_str()); + std::string ret = encoded; + HeapFree(GetProcessHeap(),0,encoded); + + return ret; +} + +std::string http::url_encode(const std::wstring &s) +{ + char* data = mir_u2a( s.c_str()); + char *encoded = (char*)CallService( MS_NETLIB_URLENCODE, 0, ( LPARAM )data); + mir_free( data ); + std::string ret = encoded; + HeapFree(GetProcessHeap(),0,encoded); + + return ret; +} diff --git a/protocols/Twitter/http.h b/protocols/Twitter/http.h new file mode 100644 index 0000000000..04f5468b30 --- /dev/null +++ b/protocols/Twitter/http.h @@ -0,0 +1,39 @@ +/* +Copyright © 2009 Jim Porter + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +#pragma once + +#include + +namespace http +{ + enum method + { + get, + post + }; + + struct response + { + response() : code(0) {} + int code; + std::string data; + }; + + std::string url_encode(const std::string &); + std::string url_encode(const std::wstring &); +} \ No newline at end of file diff --git a/protocols/Twitter/icons/twitter.ico b/protocols/Twitter/icons/twitter.ico new file mode 100644 index 0000000000..2b6eaa1eae Binary files /dev/null and b/protocols/Twitter/icons/twitter.ico differ diff --git a/protocols/Twitter/main.cpp b/protocols/Twitter/main.cpp new file mode 100644 index 0000000000..d28cc30b65 --- /dev/null +++ b/protocols/Twitter/main.cpp @@ -0,0 +1,168 @@ +/* +Copyright © 2009 Jim Porter + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +#include "common.h" +#include "version.h" + +#include "proto.h" +#include "theme.h" + +#include "m_updater.h" + +PLUGINLINK *pluginLink; +MD5_INTERFACE md5i; +MM_INTERFACE mmi; +UTF8_INTERFACE utfi; +LIST_INTERFACE li; +int hLangpack = 0; + +CLIST_INTERFACE* pcli; + +HINSTANCE g_hInstance; + +PLUGININFOEX pluginInfo={ + sizeof(PLUGININFOEX), + "Twitter Plugin", + __VERSION_DWORD, + "Provides basic support for Twitter protocol. [Built: "__DATE__" "__TIME__"]", + "dentist", + "", + "© 2009-2010 dentist", + "http://github.com/dentist/miranda-twitter", + UNICODE_AWARE, //not transient + 0, //doesn't replace anything built-in + //{BC09A71B-B86E-4d33-B18D-82D30451DD3C} + { 0xbc09a71b, 0xb86e, 0x4d33, { 0xb1, 0x8d, 0x82, 0xd3, 0x4, 0x51, 0xdd, 0x3c } } +}; + +///////////////////////////////////////////////////////////////////////////// +// Protocol instances +static int compare_protos(const TwitterProto *p1, const TwitterProto *p2) +{ + return _tcscmp(p1->m_tszUserName, p2->m_tszUserName); +} + +OBJLIST g_Instances(1, compare_protos); + +DWORD WINAPI DllMain(HINSTANCE hInstance,DWORD,LPVOID) +{ + g_hInstance = hInstance; + return TRUE; +} + +extern "C" __declspec(dllexport) PLUGININFOEX* MirandaPluginInfoEx(DWORD mirandaVersion) +{ + return &pluginInfo; +} + +///////////////////////////////////////////////////////////////////////////////////////// +// Interface information + +static const MUUID interfaces[] = {MIID_PROTOCOL, MIID_LAST}; + +extern "C" __declspec(dllexport) const MUUID* MirandaPluginInterfaces(void) +{ + return interfaces; +} + +///////////////////////////////////////////////////////////////////////////////////////// +// Load + +static PROTO_INTERFACE* protoInit(const char *proto_name,const TCHAR *username ) +{ + TwitterProto *proto = new TwitterProto(proto_name,username); + g_Instances.insert(proto); + return proto; +} + +static int protoUninit(PROTO_INTERFACE *proto) +{ + g_Instances.remove(static_cast(proto)); + return 0; +} + +int OnModulesLoaded(WPARAM,LPARAM) +{ + if (ServiceExists(MS_UPDATE_REGISTER)) + { + Update upd = {sizeof(upd)}; + char curr_version[30]; + + upd.szComponentName = pluginInfo.shortName; + + upd.szUpdateURL = UPDATER_AUTOREGISTER; + + upd.szBetaVersionURL = "http://www.teamboxel.com/update/twitter/version"; + upd.szBetaChangelogURL = "http://code.google.com/p/miranda-twitter/wiki/Changes"; + upd.pbBetaVersionPrefix = reinterpret_cast("Twitter "); + upd.cpbBetaVersionPrefix = strlen(reinterpret_cast(upd.pbBetaVersionPrefix)); +#ifdef UNICODE + upd.szBetaUpdateURL = "http://www.teamboxel.com/update/twitter"; +#else + upd.szBetaUpdateURL = "http://www.teamboxel.com/update/twitter/ansi"; +#endif + + upd.pbVersion = reinterpret_cast( CreateVersionStringPlugin( + reinterpret_cast(&pluginInfo),curr_version)); + upd.cpbVersion = strlen(reinterpret_cast(upd.pbVersion)); + + CallService(MS_UPDATE_REGISTER,0,(LPARAM)&upd); + } + + return 0; +} + +static HANDLE g_hEvents[1]; + +extern "C" int __declspec(dllexport) Load(PLUGINLINK *link) +{ + pluginLink = link; + mir_getMMI(&mmi); + mir_getMD5I(&md5i); + mir_getUTFI(&utfi); + mir_getLI(&li); + mir_getLP(&pluginInfo); + + pcli = reinterpret_cast( CallService( + MS_CLIST_RETRIEVE_INTERFACE,0,reinterpret_cast(g_hInstance))); + + PROTOCOLDESCRIPTOR pd = {sizeof(pd)}; + pd.szName = "Twitter"; + pd.type = PROTOTYPE_PROTOCOL; + pd.fnInit = protoInit; + pd.fnUninit = protoUninit; + CallService(MS_PROTO_REGISTERMODULE,0,reinterpret_cast(&pd)); + + g_hEvents[0] = HookEvent(ME_SYSTEM_MODULESLOADED,OnModulesLoaded); + + InitIcons(); + InitContactMenus(); + + return 0; +} + +///////////////////////////////////////////////////////////////////////////////////////// +// Unload + +extern "C" int __declspec(dllexport) Unload(void) +{ + UninitContactMenus(); + for(size_t i=1; i. +*/ + +#include "common.h" +#include "proto.h" + +#include "utility.h" +#include "theme.h" +#include "ui.h" + +#include "m_folders.h" +#include "m_historyevents.h" + +#include +#include +#include +#include + +TwitterProto::TwitterProto(const char *proto_name, const TCHAR *username) +{ + m_szProtoName = mir_strdup (proto_name); + m_szModuleName = mir_strdup (proto_name); + m_tszUserName = mir_tstrdup(username); + + CreateProtoService(m_szModuleName, PS_CREATEACCMGRUI, + &TwitterProto::SvcCreateAccMgrUI, this); + CreateProtoService(m_szModuleName, PS_GETNAME, &TwitterProto::GetName, this); + CreateProtoService(m_szModuleName, PS_GETSTATUS, &TwitterProto::GetStatus, this); + + CreateProtoService(m_szModuleName, PS_JOINCHAT, &TwitterProto::OnJoinChat, this); + CreateProtoService(m_szModuleName, PS_LEAVECHAT, &TwitterProto::OnLeaveChat, this); + + CreateProtoService(m_szModuleName, PS_GETMYAVATAR, &TwitterProto::GetAvatar, this); + CreateProtoService(m_szModuleName, PS_SETMYAVATAR, &TwitterProto::SetAvatar, this); + + HookProtoEvent(ME_DB_CONTACT_DELETED, &TwitterProto::OnContactDeleted, this); + HookProtoEvent(ME_CLIST_PREBUILDSTATUSMENU, &TwitterProto::OnBuildStatusMenu, this); + HookProtoEvent(ME_OPT_INITIALISE, &TwitterProto::OnOptionsInit, this); + + char *profile = Utils_ReplaceVars("%miranda_avatarcache%"); + def_avatar_folder_ = std::string(profile)+"\\"+m_szModuleName; + mir_free(profile); + hAvatarFolder_ = FoldersRegisterCustomPath(m_szModuleName, "Avatars", + def_avatar_folder_.c_str()); + + // Initialize hotkeys + char text[512]; + HOTKEYDESC hkd = {sizeof(hkd)}; + hkd.cbSize = sizeof(hkd); + hkd.pszName = text; + hkd.pszService = text; + hkd.pszSection = m_szModuleName; // Section title; TODO: use username? + + mir_snprintf(text, SIZEOF(text), "%s/Tweet", m_szModuleName); + hkd.pszDescription = "Send Tweet"; + CallService(MS_HOTKEY_REGISTER, 0, (LPARAM)&hkd); + + signon_lock_ = CreateMutex(0, false, 0); + avatar_lock_ = CreateMutex(0, false, 0); + twitter_lock_ = CreateMutex(0, false, 0); + + SetAllContactStatuses(ID_STATUS_OFFLINE); // In case we crashed last time +} + +TwitterProto::~TwitterProto() +{ + CloseHandle(twitter_lock_); + CloseHandle(avatar_lock_); + CloseHandle(signon_lock_); + + mir_free(m_szProtoName); + mir_free(m_szModuleName); + mir_free(m_tszUserName); + + if (hNetlib_) + Netlib_CloseHandle(hNetlib_); + if (hAvatarNetlib_) + Netlib_CloseHandle(hAvatarNetlib_); +} + +// ************************* + +DWORD TwitterProto::GetCaps(int type, HANDLE hContact) +{ + switch(type) + { + case PFLAGNUM_1: + return PF1_IM | PF1_MODEMSGRECV | PF1_BASICSEARCH | PF1_SEARCHBYEMAIL | + PF1_SERVERCLIST | PF1_CHANGEINFO; + case PFLAGNUM_2: + return PF2_ONLINE; + case PFLAGNUM_3: + return PF2_ONLINE; + case PFLAGNUM_4: + return PF4_NOCUSTOMAUTH | PF4_IMSENDUTF | PF4_AVATARS; + case PFLAG_MAXLENOFMESSAGE: + return 140; + case PFLAG_UNIQUEIDTEXT: + return (int) "Username"; + case PFLAG_UNIQUEIDSETTING: + return (int) TWITTER_KEY_UN; + } + return 0; +} + +HICON TwitterProto::GetIcon(int index) +{ + if (LOWORD(index) == PLI_PROTOCOL) + { + HICON ico = (HICON)CallService(MS_SKIN2_GETICON, 0, (LPARAM)"Twitter_twitter"); + return CopyIcon(ico); + } + else + return 0; +} + +// ************************* + +int TwitterProto::RecvMsg(HANDLE hContact, PROTORECVEVENT *pre) +{ + CCSDATA ccs = { hContact, PSR_MESSAGE, 0, reinterpret_cast(pre) }; + return CallService(MS_PROTO_RECVMSG, 0, reinterpret_cast(&ccs)); +} + +// ************************* + +struct send_direct +{ + send_direct(HANDLE hContact, const std::tstring &msg) : hContact(hContact), msg(msg) {} + HANDLE hContact; + std::tstring msg; +}; + +void TwitterProto::SendSuccess(void *p) +{ + if (p == 0) + return; + send_direct *data = static_cast(p); + + DBVARIANT dbv; + if ( !DBGetContactSettingTString(data->hContact, m_szModuleName, TWITTER_KEY_UN, &dbv)) + { + ScopedLock s(twitter_lock_); + twit_.send_direct(dbv.ptszVal, data->msg); + + ProtoBroadcastAck(m_szModuleName, data->hContact, ACKTYPE_MESSAGE, ACKRESULT_SUCCESS, + (HANDLE)1, 0); + DBFreeVariant(&dbv); + } + + delete data; +} + +int TwitterProto::SendMsg(HANDLE hContact, int flags, const char *msg) +{ + if (m_iStatus != ID_STATUS_ONLINE) + return 0; + + std::tstring tMsg = _A2T(msg); + ForkThread(&TwitterProto::SendSuccess, this, new send_direct(hContact, tMsg)); + return 1; +} + +// ************************* + +int TwitterProto::SetStatus(int new_status) +{ + int old_status = m_iStatus; + if (new_status == m_iStatus) + return 0; + + m_iDesiredStatus = new_status; + + if (new_status == ID_STATUS_ONLINE) + { + if (old_status == ID_STATUS_CONNECTING) + return 0; + + m_iStatus = ID_STATUS_CONNECTING; + ProtoBroadcastAck(m_szModuleName, 0, ACKTYPE_STATUS, ACKRESULT_SUCCESS, + (HANDLE)old_status, m_iStatus); + + ForkThread(&TwitterProto::SignOn, this); + } + else if (new_status == ID_STATUS_OFFLINE) + { + m_iStatus = m_iDesiredStatus; + SetAllContactStatuses(ID_STATUS_OFFLINE); + + ProtoBroadcastAck(m_szModuleName, 0, ACKTYPE_STATUS, ACKRESULT_SUCCESS, + (HANDLE)old_status, m_iStatus); + } + + return 0; +} + +// ************************* + +int TwitterProto::OnEvent(PROTOEVENTTYPE event, WPARAM wParam, LPARAM lParam) +{ + switch(event) + { + case EV_PROTO_ONLOAD: return OnModulesLoaded(wParam, lParam); + case EV_PROTO_ONEXIT: return OnPreShutdown (wParam, lParam); + case EV_PROTO_ONOPTIONS: return OnOptionsInit (wParam, lParam); + } + + return 1; +} + +// ************************* + +int TwitterProto::SvcCreateAccMgrUI(WPARAM wParam, LPARAM lParam) +{ + return (int)CreateDialogParam(g_hInstance, MAKEINTRESOURCE(IDD_TWITTERACCOUNT), + (HWND)lParam, first_run_dialog, (LPARAM)this ); +} + +int TwitterProto::GetName(WPARAM wParam, LPARAM lParam) +{ + lstrcpynA(reinterpret_cast(lParam), m_szProtoName, wParam); + return 0; +} + +int TwitterProto::GetStatus(WPARAM wParam, LPARAM lParam) +{ + return m_iStatus; +} + +int TwitterProto::ReplyToTweet(WPARAM wParam, LPARAM lParam) +{ + // TODO: support replying to tweets instead of just users + HANDLE hContact = reinterpret_cast(wParam); + + HWND hDlg = CreateDialogParam(g_hInstance, MAKEINTRESOURCE(IDD_TWEET), + (HWND)0, tweet_proc, reinterpret_cast(this)); + + DBVARIANT dbv; + if ( !DBGetContactSettingTString(hContact, m_szModuleName, TWITTER_KEY_UN, &dbv)) { + SendMessage(hDlg, WM_SETREPLY, reinterpret_cast(dbv.ptszVal), 0); + DBFreeVariant(&dbv); + } + + ShowWindow(hDlg, SW_SHOW); + + return 0; +} + +int TwitterProto::VisitHomepage(WPARAM wParam, LPARAM lParam) +{ + HANDLE hContact = reinterpret_cast(wParam); + + DBVARIANT dbv; + if ( !DBGetContactSettingString(hContact, m_szModuleName, "Homepage", &dbv)) { + CallService(MS_UTILS_OPENURL, 1, reinterpret_cast(dbv.pszVal)); + DBFreeVariant(&dbv); + } + else { + // TODO: remove this + if ( !DBGetContactSettingString(hContact, m_szModuleName, TWITTER_KEY_UN, &dbv)) { + std::string url = profile_base_url(twit_.get_base_url()) + http::url_encode(dbv.pszVal); + DBWriteContactSettingString(hContact, m_szModuleName, "Homepage", url.c_str()); + + CallService(MS_UTILS_OPENURL, 1, reinterpret_cast(url.c_str())); + DBFreeVariant(&dbv); + } + } + + return 0; +} + +// ************************* + +int TwitterProto::OnBuildStatusMenu(WPARAM wParam, LPARAM lParam) +{ + HGENMENU hRoot = pcli->pfnGetProtocolMenu(m_szModuleName); + if (hRoot == NULL) + return 0; + + CLISTMENUITEM mi = {sizeof(mi)}; + + char text[200]; + strcpy(text, m_szModuleName); + char *tDest = text+strlen(text); + mi.pszService = text; + + mi.hParentMenu = hRoot; + mi.flags = CMIF_ICONFROMICOLIB|CMIF_ROOTHANDLE; + mi.position = 1001; + + HANDLE m_hMenuRoot = reinterpret_cast( CallService( + MS_CLIST_ADDSTATUSMENUITEM, 0, reinterpret_cast(&mi))); + + // "Send Tweet..." + CreateProtoService(m_szModuleName, "/Tweet", &TwitterProto::OnTweet, this); + strcpy(tDest, "/Tweet"); + mi.pszName = LPGEN("Send Tweet..."); + mi.popupPosition = 200001; + mi.icolibItem = GetIconHandle("tweet"); + HANDLE m_hMenuBookmarks = reinterpret_cast( CallService( + MS_CLIST_ADDSTATUSMENUITEM, 0, reinterpret_cast(&mi))); + + return 0; +} + +int TwitterProto::OnOptionsInit(WPARAM wParam, LPARAM lParam) +{ + OPTIONSDIALOGPAGE odp = {sizeof(odp)}; + odp.position = 271828; + odp.hInstance = g_hInstance; + odp.ptszGroup = LPGENT("Network"); + odp.ptszTitle = m_tszUserName; + odp.dwInitParam = LPARAM(this); + odp.flags = ODPF_BOLDGROUPS | ODPF_TCHAR; + + odp.ptszTab = LPGENT("Basic"); + odp.pszTemplate = MAKEINTRESOURCEA(IDD_OPTIONS); + odp.pfnDlgProc = options_proc; + CallService(MS_OPT_ADDPAGE, wParam, (LPARAM)&odp); + + if (ServiceExists(MS_POPUP_ADDPOPUPT)) + { + odp.ptszTab = LPGENT("Popups"); + odp.pszTemplate = MAKEINTRESOURCEA(IDD_OPTIONS_POPUPS); + odp.pfnDlgProc = popup_options_proc; + CallService(MS_OPT_ADDPAGE, wParam, (LPARAM)&odp); + } + + return 0; +} + +int TwitterProto::OnTweet(WPARAM wParam, LPARAM lParam) +{ + if (m_iStatus != ID_STATUS_ONLINE) + return 1; + + HWND hDlg = CreateDialogParam(g_hInstance, MAKEINTRESOURCE(IDD_TWEET), + (HWND)0, tweet_proc, reinterpret_cast(this)); + ShowWindow(hDlg, SW_SHOW); + return 0; +} + +int TwitterProto::OnModulesLoaded(WPARAM wParam, LPARAM lParam) +{ + TCHAR descr[512]; + NETLIBUSER nlu = {sizeof(nlu)}; + nlu.flags = NUF_OUTGOING | NUF_INCOMING | NUF_HTTPCONNS | NUF_TCHAR; + nlu.szSettingsModule = m_szModuleName; + + // Create standard network connection + mir_sntprintf(descr, SIZEOF(descr), TranslateT("%s server connection"), m_tszUserName); + nlu.ptszDescriptiveName = descr; + hNetlib_ = (HANDLE)CallService(MS_NETLIB_REGISTERUSER, 0, (LPARAM)&nlu); + if (hNetlib_ == 0) + MessageBoxA(0, "Unable to get Netlib connection for Twitter", "", 0); + + // Create avatar network connection (TODO: probably remove this) + char module[512]; + mir_snprintf(module, SIZEOF(module), "%sAv", m_szModuleName); + nlu.szSettingsModule = module; + mir_sntprintf(descr, SIZEOF(descr), TranslateT("%s avatar connection"), m_tszUserName); + nlu.ptszDescriptiveName = descr; + hAvatarNetlib_ = (HANDLE)CallService(MS_NETLIB_REGISTERUSER, 0, (LPARAM)&nlu); + if (hAvatarNetlib_ == 0) + MessageBoxA(0, "Unable to get avatar Netlib connection for Twitter", "", 0); + + twit_.set_handle(hNetlib_); + + GCREGISTER gcr = {sizeof(gcr)}; + gcr.pszModule = m_szModuleName; + gcr.pszModuleDispName = m_szModuleName; + gcr.iMaxText = 140; + CallService(MS_GC_REGISTER, 0, reinterpret_cast(&gcr)); + + if (ServiceExists(MS_HISTORYEVENTS_REGISTER)) + { + HISTORY_EVENT_HANDLER heh = {0}; + heh.cbSize = sizeof(heh); + heh.module = m_szModuleName; + heh.name = "tweet"; + heh.description = "Tweet"; + heh.eventType = TWITTER_DB_EVENT_TYPE_TWEET; + heh.defaultIconName = "Twitter_tweet"; + heh.flags = HISTORYEVENTS_FLAG_SHOW_IM_SRMM + | HISTORYEVENTS_FLAG_EXPECT_CONTACT_NAME_BEFORE +// Not sure: | HISTORYEVENTS_FLAG_FLASH_MSG_WINDOW + | HISTORYEVENTS_REGISTERED_IN_ICOLIB; + CallService(MS_HISTORYEVENTS_REGISTER, (WPARAM) &heh, 0); + } + else + { + DBEVENTTYPEDESCR evt = {sizeof(evt)}; + evt.eventType = TWITTER_DB_EVENT_TYPE_TWEET; + evt.module = m_szModuleName; + evt.descr = "Tweet"; + evt.flags = DETF_HISTORY | DETF_MSGWINDOW; + CallService(MS_DB_EVENT_REGISTERTYPE, 0, reinterpret_cast(&evt)); + } + + return 0; +} + +int TwitterProto::OnPreShutdown(WPARAM wParam, LPARAM lParam) +{ + Netlib_Shutdown(hNetlib_); + Netlib_Shutdown(hAvatarNetlib_); + return 0; +} + +int TwitterProto::OnPrebuildContactMenu(WPARAM wParam, LPARAM lParam) +{ + HANDLE hContact = reinterpret_cast(wParam); + if (IsMyContact(hContact)) + ShowContactMenus(true); + + return 0; +} + +void TwitterProto::ShowPopup(const wchar_t *text) +{ + POPUPDATAT popup = {}; + _sntprintf(popup.lptzContactName, MAX_CONTACTNAME, TranslateT("%s Protocol"), m_tszUserName); + wcs_to_tcs(CP_UTF8, text, popup.lptzText, MAX_SECONDLINE); + + if (ServiceExists(MS_POPUP_ADDPOPUPT)) + CallService(MS_POPUP_ADDPOPUPT, reinterpret_cast(&popup), 0); + else + MessageBox(0, popup.lptzText, popup.lptzContactName, 0); +} + +void TwitterProto::ShowPopup(const char *text) +{ + POPUPDATAT popup = {}; + _sntprintf(popup.lptzContactName, MAX_CONTACTNAME, TranslateT("%s Protocol"), m_tszUserName); + mbcs_to_tcs(CP_UTF8, text, popup.lptzText, MAX_SECONDLINE); + + if (ServiceExists(MS_POPUP_ADDPOPUPT)) + CallService(MS_POPUP_ADDPOPUPT, reinterpret_cast(&popup), 0); + else + MessageBox(0, popup.lptzText, popup.lptzContactName, 0); +} + +int TwitterProto::LOG(const char *fmt, ...) +{ + va_list va; + char text[1024]; + if (!hNetlib_) + return 0; + + va_start(va, fmt); + mir_vsnprintf(text, sizeof(text), fmt, va); + va_end(va); + + return CallService(MS_NETLIB_LOG, (WPARAM)hNetlib_, (LPARAM)text); +} + +// TODO: the more I think about it, the more I think all twit.* methods should +// be in MessageLoop +void TwitterProto::SendTweetWorker(void *p) +{ + if (p == 0) + return; + + TCHAR *text = static_cast(p); + + ScopedLock s(twitter_lock_); + twit_.set_status(text); + + mir_free(text); +} + +void TwitterProto::UpdateSettings() +{ + if (db_byte_get(0, m_szModuleName, TWITTER_KEY_CHATFEED, 0)) + { + if (!in_chat_) + OnJoinChat(0, 0); + } + else + { + if (in_chat_) + OnLeaveChat(0, 0); + + for(HANDLE hContact = (HANDLE)CallService(MS_DB_CONTACT_FINDFIRST, 0, 0); + hContact; + hContact = (HANDLE)CallService(MS_DB_CONTACT_FINDNEXT, (WPARAM)hContact, 0)) + { + if (!IsMyContact(hContact, true)) + continue; + + if (db_byte_get(hContact, m_szModuleName, "ChatRoom", 0)) + CallService(MS_DB_CONTACT_DELETE, reinterpret_cast(hContact), 0); + } + } +} + +std::string TwitterProto::GetAvatarFolder() +{ + char path[MAX_PATH]; + if (hAvatarFolder_ && FoldersGetCustomPath(hAvatarFolder_, path, sizeof(path), "") == 0) + return path; + else + return def_avatar_folder_; +} + +int TwitterProto::GetAvatar(WPARAM, LPARAM) +{ + return 0; +} + +int TwitterProto::SetAvatar(WPARAM, LPARAM) +{ + return 0; +} \ No newline at end of file diff --git a/protocols/Twitter/proto.h b/protocols/Twitter/proto.h new file mode 100644 index 0000000000..af7a43d321 --- /dev/null +++ b/protocols/Twitter/proto.h @@ -0,0 +1,179 @@ +/* +Copyright © 2009 Jim Porter + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +#pragma once + +#include "utility.h" + +#include + +class TwitterProto : public PROTO_INTERFACE +{ +public: + TwitterProto(const char *, const TCHAR *); + ~TwitterProto(); + + __inline void* operator new(size_t size) + { + return calloc(1, size); + } + __inline void operator delete(void *p) + { + free(p); + } + + inline const char * ModuleName() const + { + return m_szModuleName; + } + + //PROTO_INTERFACE + + virtual HANDLE __cdecl AddToList(int, PROTOSEARCHRESULT *); + virtual HANDLE __cdecl AddToListByEvent(int, int, HANDLE); + + virtual int __cdecl Authorize(HANDLE); + virtual int __cdecl AuthDeny(HANDLE, const TCHAR *); + virtual int __cdecl AuthRecv(HANDLE, PROTORECVEVENT *); + virtual int __cdecl AuthRequest(HANDLE, const TCHAR *); + + virtual HANDLE __cdecl ChangeInfo(int, void *); + + virtual HANDLE __cdecl FileAllow(HANDLE, HANDLE, const TCHAR *); + virtual int __cdecl FileCancel(HANDLE, HANDLE); + virtual int __cdecl FileDeny(HANDLE, HANDLE, const TCHAR *); + virtual int __cdecl FileResume(HANDLE, int *, const TCHAR **); + + virtual DWORD __cdecl GetCaps(int, HANDLE = 0); + virtual HICON __cdecl GetIcon(int); + virtual int __cdecl GetInfo(HANDLE, int); + + virtual HANDLE __cdecl SearchBasic(const TCHAR *); + virtual HANDLE __cdecl SearchByEmail(const TCHAR *); + virtual HANDLE __cdecl SearchByName(const TCHAR *, const TCHAR *, const TCHAR *); + virtual HWND __cdecl SearchAdvanced(HWND); + virtual HWND __cdecl CreateExtendedSearchUI(HWND); + + virtual int __cdecl RecvContacts(HANDLE, PROTORECVEVENT *); + virtual int __cdecl RecvFile(HANDLE, PROTORECVFILET *); + virtual int __cdecl RecvMsg(HANDLE, PROTORECVEVENT *); + virtual int __cdecl RecvUrl(HANDLE, PROTORECVEVENT *); + + virtual int __cdecl SendContacts(HANDLE, int, int, HANDLE *); + virtual HANDLE __cdecl SendFile(HANDLE, const TCHAR *, TCHAR **); + virtual int __cdecl SendMsg(HANDLE, int, const char *); + virtual int __cdecl SendUrl(HANDLE, int, const char *); + + virtual int __cdecl SetApparentMode(HANDLE, int); + virtual int __cdecl SetStatus(int); + + virtual HANDLE __cdecl GetAwayMsg(HANDLE); + virtual int __cdecl RecvAwayMsg(HANDLE, int, PROTORECVEVENT *); + virtual int __cdecl SendAwayMsg(HANDLE, HANDLE, const char *); + virtual int __cdecl SetAwayMsg(int, const TCHAR *); + + virtual int __cdecl UserIsTyping(HANDLE, int); + + virtual int __cdecl OnEvent(PROTOEVENTTYPE, WPARAM, LPARAM); + + void UpdateSettings(); + + // Services + int __cdecl SvcCreateAccMgrUI(WPARAM, LPARAM); + int __cdecl GetName(WPARAM, LPARAM); + int __cdecl GetStatus(WPARAM, LPARAM); + int __cdecl ReplyToTweet(WPARAM, LPARAM); + int __cdecl VisitHomepage(WPARAM, LPARAM); + int __cdecl GetAvatar(WPARAM, LPARAM); + int __cdecl SetAvatar(WPARAM, LPARAM); + + // Events + int __cdecl OnContactDeleted(WPARAM, LPARAM); + int __cdecl OnBuildStatusMenu(WPARAM, LPARAM); + int __cdecl OnOptionsInit(WPARAM, LPARAM); + int __cdecl OnTweet(WPARAM, LPARAM); + int __cdecl OnModulesLoaded(WPARAM, LPARAM); + int __cdecl OnPreShutdown(WPARAM, LPARAM); + int __cdecl OnPrebuildContactMenu(WPARAM, LPARAM); + int __cdecl OnChatOutgoing(WPARAM, LPARAM); + int __cdecl OnJoinChat(WPARAM, LPARAM); + int __cdecl OnLeaveChat(WPARAM, LPARAM); + + void __cdecl SendTweetWorker(void *); +private: + // Worker threads + void __cdecl AddToListWorker(void *p); + void __cdecl SendSuccess(void *); + void __cdecl DoSearch(void *); + void __cdecl SignOn(void *); + void __cdecl MessageLoop(void *); + void __cdecl GetAwayMsgWorker(void *); + void __cdecl UpdateAvatarWorker(void *); + void __cdecl UpdateInfoWorker(void *); + + bool NegotiateConnection(); + void UpdateStatuses(bool pre_read, bool popups); + void UpdateMessages(bool pre_read); + void UpdateFriends(); + void UpdateAvatar(HANDLE, const std::string &, bool force=false); + + void ShowPopup(const wchar_t *); + void ShowPopup(const char *); + void ShowContactPopup(HANDLE, const std::tstring &); + + bool IsMyContact(HANDLE, bool include_chat = false); + HANDLE UsernameToHContact(const TCHAR *); + HANDLE AddToClientList(const TCHAR *, const TCHAR *); + void SetAllContactStatuses(int); + + int LOG(const char *fmt, ...); + static void CALLBACK APC_callback(ULONG_PTR p); + + void UpdateChat(const twitter_user &update); + void AddChatContact(const TCHAR *name, const TCHAR *nick=0); + void DeleteChatContact(const TCHAR *name); + void SetChatStatus(int); + + std::string GetAvatarFolder(); + + HANDLE signon_lock_; + HANDLE avatar_lock_; + HANDLE twitter_lock_; + + HANDLE hNetlib_; + HANDLE hAvatarNetlib_; + HANDLE hMsgLoop_; + mir_twitter twit_; + + twitter_id since_id_; + twitter_id dm_since_id_; + + std::string def_avatar_folder_; + HANDLE hAvatarFolder_; + + bool in_chat_; +}; + +// TODO: remove this +inline std::string profile_base_url(const std::string &url) +{ + size_t x = url.find("://"); + if (x == std::string::npos) + return url.substr(0, url.find('/')+1); + else + return url.substr(0, url.find('/', x+3)+1); +} \ No newline at end of file diff --git a/protocols/Twitter/resource.h b/protocols/Twitter/resource.h new file mode 100644 index 0000000000..21fc21c049 --- /dev/null +++ b/protocols/Twitter/resource.h @@ -0,0 +1,47 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by twitter.rc +// +#define IDD_TWITTERACCOUNT 101 +#define IDI_TWITTER 102 +#define IDD_TWEET 103 +#define IDD_OPTIONS 104 +#define IDD_OPTIONS_POPUPS 107 +#define IDC_NEWACCOUNTLINK 1001 +#define IDC_UN 1002 +#define IDC_PW 1003 +#define IDC_TWEETMSG 1004 +#define IDC_CHARACTERS 1005 +#define IDC_USERDETAILS 1006 +#define IDC_MISC 1007 +#define IDC_CHATFEED 1008 +#define IDC_BASEURL 1009 +#define IDC_POLLRATE 1010 +#define IDC_COLBACK 1011 +#define IDC_COLTEXT 1012 +#define IDC_TIMEOUT_DEFAULT 1013 +#define IDC_TIMEOUT_CUSTOM 1014 +#define IDC_TIMEOUT_SPIN 1015 +#define IDC_TIMEOUT 1016 +#define IDC_TIMEOUT_PERMANENT 1017 +#define IDC_COL_WINDOWS 1018 +#define IDC_COL_POPUP 1019 +#define IDC_COL_CUSTOM 1020 +#define IDC_POPUPS_ENABLE 1021 +#define IDC_SHOWPOPUPS 1021 +#define IDC_PREVIEW 1022 +#define IDC_NOSIGNONPOPUPS 1023 +#define IDC_RECONNECT 1024 +#define IDC_COMBO1 1025 +#define IDC_SERVER 1025 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 108 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1026 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/protocols/Twitter/stubs.cpp b/protocols/Twitter/stubs.cpp new file mode 100644 index 0000000000..331a4cdeac --- /dev/null +++ b/protocols/Twitter/stubs.cpp @@ -0,0 +1,140 @@ +/* +Copyright © 2009 Jim Porter + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +#include "common.h" +#include "proto.h" + +HANDLE TwitterProto::AddToListByEvent(int flags,int iContact,HANDLE hDbEvent) +{ + return 0; +} + +int TwitterProto::Authorize(HANDLE hContact) +{ + return 0; +} + +int TwitterProto::AuthDeny(HANDLE hContact,const TCHAR *reason) +{ + return 0; +} + +int TwitterProto::AuthRecv(HANDLE hContact,PROTORECVEVENT *) +{ + return 0; +} + +int TwitterProto::AuthRequest(HANDLE hContact,const TCHAR *message) +{ + return 0; +} + +HANDLE TwitterProto::ChangeInfo(int type,void *info_data) +{ + MessageBoxA(0,"ChangeInfo","",0); + return 0; +} + +HANDLE TwitterProto::FileAllow(HANDLE hContact,HANDLE hTransfer,const TCHAR *path) +{ + return 0; +} + +int TwitterProto::FileCancel(HANDLE hContact,HANDLE hTransfer) +{ + return 0; +} + +int TwitterProto::FileDeny(HANDLE hContact,HANDLE hTransfer,const TCHAR *reason) +{ + return 0; +} + +int TwitterProto::FileResume(HANDLE hTransfer,int *action,const TCHAR **filename) +{ + return 0; +} + +HANDLE TwitterProto::SearchByName(const TCHAR *nick,const TCHAR *first_name, const TCHAR *last_name) +{ + return 0; +} + +HWND TwitterProto::SearchAdvanced(HWND owner) +{ + return 0; +} + +HWND TwitterProto::CreateExtendedSearchUI(HWND owner) +{ + return 0; +} + +int TwitterProto::RecvContacts(HANDLE hContact,PROTORECVEVENT *) +{ + return 0; +} + +int TwitterProto::RecvFile(HANDLE hContact,PROTORECVFILET *) +{ + return 0; +} + +int TwitterProto::RecvUrl(HANDLE hContact,PROTORECVEVENT *) +{ + return 0; +} + +int TwitterProto::SendContacts(HANDLE hContact,int flags,int nContacts,HANDLE *hContactsList) +{ + return 0; +} + +HANDLE TwitterProto::SendFile(HANDLE hContact,const TCHAR *desc, TCHAR **files) +{ + return 0; +} + +int TwitterProto::SendUrl(HANDLE hContact,int flags,const char *url) +{ + return 0; +} + +int TwitterProto::SetApparentMode(HANDLE hContact,int mode) +{ + return 0; +} + +int TwitterProto::RecvAwayMsg(HANDLE hContact,int mode,PROTORECVEVENT *evt) +{ + return 0; +} + +int TwitterProto::SendAwayMsg(HANDLE hContact,HANDLE hProcess,const char *msg) +{ + return 0; +} + +int TwitterProto::SetAwayMsg(int status,const TCHAR *msg) +{ + return 0; +} + +int TwitterProto::UserIsTyping(HANDLE hContact,int type) +{ + return 0; +} \ No newline at end of file diff --git a/protocols/Twitter/theme.cpp b/protocols/Twitter/theme.cpp new file mode 100644 index 0000000000..f35b3a5aa1 --- /dev/null +++ b/protocols/Twitter/theme.cpp @@ -0,0 +1,183 @@ +/* +Copyright © 2009 Jim Porter + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +#include "common.h" +#include "theme.h" +#include "proto.h" + +extern OBJLIST g_Instances; + +struct +{ + const char* name; + const char* descr; + int defIconID; + const char* section; +} +static const icons[] = +{ + { "twitter", "Twitter Icon", IDI_TWITTER }, + { "tweet", "Tweet", IDI_TWITTER }, + { "reply", "Reply to Tweet", IDI_TWITTER }, + + { "homepage", "Visit Homepage", 0, "core_main_2" }, +}; + +static HANDLE hIconLibItem[SIZEOF(icons)]; + +// TODO: uninit +void InitIcons(void) +{ + TCHAR szFile[MAX_PATH]; + GetModuleFileName(g_hInstance, szFile, SIZEOF(szFile)); + + char setting_name[100]; + char section_name[100]; + + SKINICONDESC sid = {0}; + sid.cbSize = sizeof(SKINICONDESC); + sid.ptszDefaultFile = szFile; + sid.cx = sid.cy = 16; + sid.pszName = setting_name; + sid.pszSection = section_name; + sid.flags = SIDF_PATH_TCHAR; + + for (int i=0; i( CallService(MS_PROTO_GETCONTACTBASEPROTO, + reinterpret_cast(hContact),0)); + if (!proto) + return 0; + + for(int i=0; i +int GlobalService(WPARAM wParam,LPARAM lParam) +{ + TwitterProto *proto = GetInstanceByHContact(reinterpret_cast(wParam)); + return proto ? (proto->*Fcn)(wParam,lParam) : 0; +} + +static int PrebuildContactMenu(WPARAM wParam,LPARAM lParam) +{ + ShowContactMenus(false); + + TwitterProto *proto = GetInstanceByHContact(reinterpret_cast(wParam)); + return proto ? proto->OnPrebuildContactMenu(wParam,lParam) : 0; +} + +void InitContactMenus() +{ + g_hMenuEvts[0] = HookEvent(ME_CLIST_PREBUILDCONTACTMENU, + PrebuildContactMenu); + + CLISTMENUITEM mi = {sizeof(mi)}; + mi.flags = CMIF_NOTOFFLINE | CMIF_ICONFROMICOLIB; + + mi.position=-2000006000; + mi.icolibItem = GetIconHandle("reply"); + mi.pszName = LPGEN("Reply..."); + mi.pszService = "Twitter/ReplyToTweet"; + g_hMenuEvts[1] = CreateServiceFunction(mi.pszService, + GlobalService<&TwitterProto::ReplyToTweet>); + g_hMenuItems[0] = reinterpret_cast( + CallService(MS_CLIST_ADDCONTACTMENUITEM,0,(LPARAM)&mi)); + + mi.position=-2000006000; + mi.icolibItem = GetIconHandle("homepage"); + mi.pszName = LPGEN("Visit Homepage"); + mi.pszService = "Twitter/VisitHomepage"; + g_hMenuEvts[2] = CreateServiceFunction(mi.pszService, + GlobalService<&TwitterProto::VisitHomepage>); + g_hMenuItems[1] = reinterpret_cast( + CallService(MS_CLIST_ADDCONTACTMENUITEM,0,(LPARAM)&mi)); +} + +void UninitContactMenus() +{ + for(size_t i=0; i(g_hMenuItems[i]), + reinterpret_cast(&item)); + } +} \ No newline at end of file diff --git a/protocols/Twitter/theme.h b/protocols/Twitter/theme.h new file mode 100644 index 0000000000..e74f5da2e3 --- /dev/null +++ b/protocols/Twitter/theme.h @@ -0,0 +1,25 @@ +/* +Copyright © 2009 Jim Porter + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +#pragma once + +void InitIcons(void); +HANDLE GetIconHandle(const char *name); + +void InitContactMenus(void); +void UninitContactMenus(void); +void ShowContactMenus(bool show); \ No newline at end of file diff --git a/protocols/Twitter/tinyjson.hpp b/protocols/Twitter/tinyjson.hpp new file mode 100644 index 0000000000..19e1210d84 --- /dev/null +++ b/protocols/Twitter/tinyjson.hpp @@ -0,0 +1,586 @@ +/* + * TinyJson 1.3.0 + * A Minimalistic JSON Reader Based On Boost.Spirit, Boost.Any, and Boost.Smart_Ptr. + * + * Copyright (c) 2008 Thomas Jansen (thomas@beef.de) + * + * Distributed under the Boost Software License, Version 1.0. (See accompanying + * file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + * + * See http://blog.beef.de/projects/tinyjson/ for documentation. + * + * (view source with tab-size = 3) + * + * 16 Mar 2009 - allow root of JSON to be array (Jim Porter) + * 29 Mar 2008 - use strict_real_p for number parsing, small cleanup (Thomas Jansen) + * 26 Mar 2008 - made json::grammar a template (Boris Schaeling) + * 20 Mar 2008 - optimized by using boost::shared_ptr (Thomas Jansen) + * 29 Jan 2008 - Small bugfixes (Thomas Jansen) + * 04 Jan 2008 - Released to the public (Thomas Jansen) + * 13 Nov 2007 - initial release (Thomas Jansen) * + * + * 29 Mar 2008 + */ + + +#ifndef TINYJSON_HPP +#define TINYJSON_HPP + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + + +namespace json +{ + boost::spirit::int_parser const + longlong_p = boost::spirit::int_parser(); + + // ========================================================================================================== + // === U N I C O D E _ C O N V E R T === + // ========================================================================================================== + + template< typename Char > + struct unicodecvt + { + static std::basic_string< Char > convert(int iUnicode) + { + return std::basic_string< Char >(1, static_cast< Char >(iUnicode)); + } + }; + + + // ---[ TEMPLATE SPECIALIZATION FOR CHAR ]-------------------------------------------------------------------- + + template<> + struct unicodecvt< char > + { + static std::string convert(int iUnicode) + { + std::string strString; + + if (iUnicode < 0x0080) + { + // character 0x0000 - 0x007f... + + strString.push_back(0x00 | ((iUnicode & 0x007f) >> 0)); + } + else if (iUnicode < 0x0800) + { + // character 0x0080 - 0x07ff... + + strString.push_back(0xc0 | ((iUnicode & 0x07c0) >> 6)); + strString.push_back(0x80 | ((iUnicode & 0x003f) >> 0)); + } + else + { + // character 0x0800 - 0xffff... + + strString.push_back(0xe0 | ((iUnicode & 0x00f000) >> 12)); + strString.push_back(0x80 | ((iUnicode & 0x000fc0) >> 6)); + strString.push_back(0x80 | ((iUnicode & 0x00003f) >> 0)); + } + + return strString; + } + }; + + + // ========================================================================================================== + // === T H E J S O N G R A M M A R === + // ========================================================================================================== + + template< typename Char > + class grammar : public boost::spirit::grammar< grammar< Char > > + { + public: + + // ---[ TYPEDEFINITIONS ]--------------------------------------------------------------------------------- + + typedef boost::shared_ptr< boost::any > variant; // pointer to a shared variant + + typedef std::stack< variant > stack; // a stack of json variants + typedef std::pair< std::basic_string< Char >, variant > pair; // a pair as it appears in json + + typedef std::deque< variant > array; // an array of json variants + typedef std::map< std::basic_string< Char >, variant > object; // an object with json pairs + + protected: + + // ---[ SEMANTIC ACTION: PUSH A STRING ON THE STACK (AND ENCODE AS UTF-8) ]------------------------------- + + struct push_string + { + stack & m_stack; + push_string(stack & stack) : m_stack(stack) { } + + template + void operator() (Iterator szStart, Iterator szEnd) const + { + // 1: skip the quotes... + + ++szStart; + --szEnd; + + // 2: traverse through the original string and check for escape codes.. + + std::basic_string< typename Iterator::value_type > strString; + + while(szStart < szEnd) + { + // 2.1: if it's no escape code, just append to the resulting string... + + if (*szStart != static_cast< typename Iterator::value_type >('\\')) + { + // 2.1.1: append the character... + + strString.push_back(*szStart); + } + else + { + // 2.1.2: otherwise, check the escape code... + + ++szStart; + + switch(*szStart) + { + default: + + strString.push_back(*szStart); + break; + + case 'b': + + strString.push_back(static_cast< typename Iterator::value_type >('\b')); + break; + + case 'f': + + strString.push_back(static_cast< typename Iterator::value_type >('\f')); + break; + + case 'n': + + strString.push_back(static_cast< typename Iterator::value_type >('\n')); + break; + + case 'r': + + strString.push_back(static_cast< typename Iterator::value_type >('\r')); + break; + + case 't': + + strString.push_back(static_cast< typename Iterator::value_type >('\t')); + break; + + case 'u': + { + // 2.1.2.1: convert the following hex value into an int... + + int iUnicode; + std::basic_istringstream< Char >(std::basic_string< typename Iterator::value_type >(&szStart[1], 4)) >> std::hex >> iUnicode; + + szStart += 4; + + // 2.1.2.2: append the unicode int... + + strString.append(unicodecvt< typename Iterator::value_type >::convert(iUnicode)); + } + } + } + + // 2.2: go on with the next character... + + ++szStart; + } + + // 3: finally, push the string on the stack... + + m_stack.push(variant(new boost::any(strString))); + } + }; + + + // ---[ SEMANTIC ACTION: PUSH A REAL ON THE STACK ]------------------------------------------------------- + + struct push_double + { + stack & m_stack; + push_double(stack & stack) : m_stack(stack) { } + + void operator() (double dValue) const + { + m_stack.push(variant(new boost::any(dValue))); + } + }; + + + // ---[ SEMANTIC ACTION: PUSH AN INT ON THE STACK ]------------------------------------------------------- + + struct push_int + { + stack & m_stack; + push_int(stack & stack) : m_stack(stack) { } + + void operator() (long long iValue) const + { + m_stack.push(variant(new boost::any(iValue))); + } + }; + + + // ---[ SEMANTIC ACTION: PUSH A BOOLEAN ON THE STACK ]---------------------------------------------------- + + struct push_boolean + { + stack & m_stack; + push_boolean(stack & stack) : m_stack(stack) { } + + template + void operator() (Iterator szStart, Iterator /* szEnd */ ) const + { + // 1: push a boolean that is "true" if the string starts with 't' and "false" otherwise... + + m_stack.push(variant(new boost::any(*szStart == static_cast< typename Iterator::value_type >('t')))); + } + }; + + + // ---[ SEMANTIC ACTION: PUSH A NULL VALUE ON THE STACK ]------------------------------------------------- + + struct push_null + { + stack & m_stack; + push_null(stack & stack) : m_stack(stack) { } + + template + void operator() (Iterator /* szStart */ , Iterator /* szEnd */ ) const + { + m_stack.push(variant(new boost::any())); + } + }; + + + // ---[ SEMANTIC ACTION: CREATE A "JSON PAIR" ON THE STACK ]---------------------------------------------- + + struct create_pair + { + stack & m_stack; + create_pair(stack & stack) : m_stack(stack) { } + + template + void operator() (Iterator /* szStart */, Iterator /* szEnd */ ) const + { + // 1: get the variant from the stack... + + variant var = m_stack.top(); + m_stack.pop(); + + // 2: get the name from the stack... + + std::basic_string< typename Iterator::value_type > strName; + + try + { + strName = boost::any_cast< std::basic_string< typename Iterator::value_type > >(*m_stack.top()); + } + catch(boost::bad_any_cast &) { /* NOTHING */ } + + m_stack.pop(); + + // 3: push a pair of both on the stack... + + m_stack.push(variant(new boost::any(pair(strName, var)))); + } + }; + + + // ---[ SEMANTIC ACTION: BEGIN AN ARRAY ]----------------------------------------------------------------- + + class array_delimiter { /* EMPTY CLASS */ }; + + struct begin_array + { + stack & m_stack; + begin_array(stack & stack) : m_stack(stack) { } + + template + void operator() (Iterator /* cCharacter */) const + { + m_stack.push(variant(new boost::any(array_delimiter()))); + } + }; + + + // ---[ SEMANTIC ACTION: CREATE AN ARRAY FROM THE VALUES ON THE STACK ]----------------------------------- + + struct end_array + { + stack & m_stack; + end_array(stack & stack) : m_stack(stack) { } + + // - -[ functional operator ] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + template + void operator() (Iterator /* cCharacter */) const + { + // 1: create an array object and push everything in it, that's on the stack... + + variant varArray(new boost::any(array())); + + while(!m_stack.empty()) + { + // 1.1: get the top most variant of the stack... + + variant var = m_stack.top(); + m_stack.pop(); + + // 1.2: is it the end of the array? if yes => break the loop... + + if (boost::any_cast< array_delimiter >(var.get()) != NULL) + { + break; + } + + // 1.3: otherwise, add to the array... + + boost::any_cast< array >(varArray.get())->push_front(var); + } + + // 2: finally, push the array at the end of the stack... + + m_stack.push(varArray); + } + }; + + + // ---[ SEMANTIC ACTION: BEGIN AN OBJECT ]---------------------------------------------------------------- + + class object_delimiter { /* EMPTY CLASS */ }; + + struct begin_object + { + stack & m_stack; + begin_object(stack & stack) : m_stack(stack) { } + + template + void operator() (Iterator /* cCharacter */) const + { + m_stack.push(variant(new boost::any(object_delimiter()))); + } + }; + + + // ---[ SEMANTIC ACTION: CREATE AN OBJECT FROM THE VALUES ON THE STACK ]---------------------------------- + + struct end_object + { + stack & m_stack; + end_object(stack & stack) : m_stack(stack) { } + + template + void operator() (Iterator /* cCharacter */) const + { + // 1: create an array object and push everything in it, that's on the stack... + + variant varObject(new boost::any(object())); + + while(!m_stack.empty()) + { + // 1.1: get the top most variant of the stack... + + variant var = m_stack.top(); + m_stack.pop(); + + // 1.2: is it the end of the array? if yes => break the loop... + + if (boost::any_cast< object_delimiter >(var.get()) != NULL) + { + break; + } + + // 1.3: if this is not a pair, we have a problem... + + pair * pPair = boost::any_cast< pair >(var.get()); + if (!pPair) + { + /* BIG PROBLEM!! */ + + continue; + } + + // 1.4: set the child of this object... + + boost::any_cast< object >(varObject.get())->insert(std::make_pair(pPair->first, pPair->second)); + } + + // 2: finally, push the array at the end of the stack... + + m_stack.push(varObject); + } + }; + + public: + + stack & m_stack; + grammar(stack & stack) : m_stack(stack) { } + + // ---[ THE ACTUAL GRAMMAR DEFINITION ]------------------------------------------------------------------- + + template + class definition + { + boost::spirit::rule< SCANNER > m_start; + boost::spirit::rule< SCANNER > m_object; + boost::spirit::rule< SCANNER > m_array; + boost::spirit::rule< SCANNER > m_pair; + boost::spirit::rule< SCANNER > m_value; + boost::spirit::rule< SCANNER > m_string; + boost::spirit::rule< SCANNER > m_number; + boost::spirit::rule< SCANNER > m_boolean; + boost::spirit::rule< SCANNER > m_null; + + public: + + boost::spirit::rule< SCANNER > const & start() const { return m_start; } + + // - -[ create the definition ] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + definition(grammar const & self) + { + using namespace boost::spirit; + + // 0: JSON can either be an object or an array + + m_start + = m_object + | m_array; + + // 1: an object is an unordered set of pairs (seperated by commas)... + + m_object + = ch_p('{') [ begin_object(self.m_stack) ] >> + !(m_pair >> *(ch_p(',') >> m_pair)) >> + ch_p('}') [ end_object (self.m_stack) ]; + + // 2: an array is an ordered collection of values (seperated by commas)... + + m_array + = ch_p('[') [ begin_array(self.m_stack) ] >> + !(m_value >> *(ch_p(',') >> m_value)) >> + ch_p(']') [ end_array (self.m_stack) ]; + + // 3: a pair is given by a name and a value... + + m_pair + = ( m_string >> ch_p(':') >> m_value ) + [ create_pair(self.m_stack) ] + ; + + // 4: a value can be a string in double quotes, a number, a boolean, an object or an array. + + m_value + = m_string + | m_number + | m_object + | m_array + | m_boolean + | m_null + ; + + // 5: a string is a collection of zero or more unicode characters, wrapped in double quotes... + + m_string + = lexeme_d + [ + ( ch_p('"') >> *( + ( (anychar_p - (ch_p('"') | ch_p('\\'))) + | ch_p('\\') >> + ( ch_p('\"') + | ch_p('\\') + | ch_p('/') + | ch_p('b') + | ch_p('f') + | ch_p('n') + | ch_p('r') + | ch_p('t') + | (ch_p('u') >> repeat_p(4)[ xdigit_p ]) + ) + )) >> ch_p('"') + ) + [ push_string(self.m_stack) ] + ] + ; + + // 6: a number is very much like a C or java number... + + m_number + = strict_real_p [ push_double(self.m_stack) ] + | longlong_p [ push_int (self.m_stack) ] + ; + + // 7: a boolean can be "true" or "false"... + + m_boolean + = ( str_p("true") + | str_p("false") + ) + [ push_boolean(self.m_stack) ] + ; + + // 8: finally, a value also can be a 'null', i.e. an empty item... + + m_null + = str_p("null") + [ push_null(self.m_stack) ] + ; + } + }; + }; + + + // ========================================================================================================== + // === T H E F I N A L P A R S I N G R O U T I N E === + // ========================================================================================================== + + template + typename json::grammar< typename Iterator::value_type >::variant parse(Iterator const & szFirst, Iterator const & szEnd) + { + // 1: parse the input... + + json::grammar< typename Iterator::value_type >::stack st; + json::grammar< typename Iterator::value_type > gr(st); + + boost::spirit::parse_info pi = boost::spirit::parse(szFirst, szEnd, gr, boost::spirit::space_p); + + // 2: skip any spaces at the end of the parsed section... + + while((pi.stop != szEnd) && (*pi.stop == static_cast< typename Iterator::value_type >(' '))) + { + ++pi.stop; + } + + // 3: if the input's end wasn't reached or if there is more than one object on the stack => cancel... + + if ((pi.stop != szEnd) || (st.size() != 1)) + { + return json::grammar< typename Iterator::value_type >::variant(new boost::any()); + } + + // 4: otherwise, return the result... + + return st.top(); + } +}; + + +#endif // TINYJSON_HPP diff --git a/protocols/Twitter/twitter.cpp b/protocols/Twitter/twitter.cpp new file mode 100644 index 0000000000..9512292707 --- /dev/null +++ b/protocols/Twitter/twitter.cpp @@ -0,0 +1,380 @@ +/* +Copyright © 2009 Jim Porter + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +//#include "common.h" + +#include +#include +#include +#include + +#include "twitter.h" + +#include "tinyjson.hpp" +#include + +typedef json::grammar js; + +// utility functions + +template +static T cast_and_decode(boost::any &a,bool allow_null) +{ + if (allow_null && a.type() == typeid(void)) + return T(); + return boost::any_cast(a); +} + +template <> +static std::string cast_and_decode(boost::any &a,bool allow_null) +{ + if (allow_null && a.type() == typeid(void)) + return std::string(); + std::string s = boost::any_cast(a); + + // Twitter *only* encodes < and >, so decode them + size_t off; + while( (off = s.find("<")) != std::string::npos) + s.replace(off,4,"<"); + while( (off = s.find(">")) != std::string::npos) + s.replace(off,4,">"); + + return s; +} + +template +static T retrieve(const js::object &o,const std::string &key,bool allow_null = false) +{ + using boost::any_cast; + + js::object::const_iterator i = o.find(key); + if (i == o.end()) + throw std::exception( ("unable to retrieve key '"+key+"'").c_str()); + try + { + return cast_and_decode(*i->second,allow_null); + } + catch(const boost::bad_any_cast &) + { + throw std::exception( ("unable to cast key '"+key+"' to target type").c_str()); + } +} + + + +twitter::twitter() : base_url_("https://twitter.com/") +{} + +bool twitter::set_credentials(const std::string &username,const std::string &password, + bool test) +{ + username_ = username; + password_ = password; + + if (test) + return slurp(base_url_+"account/verify_credentials.json",http::get).code == 200; + else + return true; +} + +void twitter::set_base_url(const std::string &base_url) +{ + base_url_ = base_url; +} + +const std::string & twitter::get_username() const +{ + return username_; +} + +const std::string & twitter::get_base_url() const +{ + return base_url_; +} + +std::vector twitter::get_friends() +{ + std::vector friends; + http::response resp = slurp(base_url_+"statuses/friends.json",http::get); + + if (resp.code != 200) + throw bad_response(); + + const js::variant var = json::parse( resp.data.begin(),resp.data.end()); + if (var->type() != typeid(js::array)) + throw std::exception("unable to parse response"); + + const js::array &list = boost::any_cast(*var); + for(js::array::const_iterator i=list.begin(); i!=list.end(); ++i) + { + if ((*i)->type() == typeid(js::object)) + { + const js::object &one = boost::any_cast(**i); + + twitter_user user; + user.username = retrieve(one,"screen_name"); + user.real_name = retrieve(one,"name",true); + user.profile_image_url = retrieve(one,"profile_image_url",true); + + if (one.find("status") != one.end()) + { + js::object &status = retrieve(one,"status"); + user.status.text = retrieve(status,"text"); + + user.status.id = retrieve(status,"id"); + + std::string timestr = retrieve(status,"created_at"); + user.status.time = parse_time(timestr); + } + + friends.push_back(user); + } + } + + return friends; +} + +bool twitter::get_info(const std::tstring &name,twitter_user *info) +{ + if (!info) + return false; + + std::string url = base_url_+"users/show/"+http::url_encode(name)+".json"; + + http::response resp = slurp(url,http::get); + if (resp.code != 200) + throw bad_response(); + + const js::variant var = json::parse( resp.data.begin(),resp.data.end()); + if (var->type() == typeid(js::object)) + { + const js::object &user_info = boost::any_cast(*var); + if (user_info.find("error") != user_info.end()) + return false; + + info->username = retrieve(user_info,"screen_name"); + info->real_name = retrieve(user_info,"name",true); + info->profile_image_url = retrieve(user_info,"profile_image_url",true); + + return true; + } + else + return false; +} + +bool twitter::get_info_by_email(const std::tstring &email,twitter_user *info) +{ + if (!info) + return false; + + std::string url = base_url_+"users/show.json?email="+http::url_encode(email); + + http::response resp = slurp(url,http::get); + if (resp.code != 200) + throw bad_response(); + + js::variant var = json::parse( resp.data.begin(),resp.data.end()); + if (var->type() == typeid(js::object)) + { + const js::object &user_info = boost::any_cast(*var); + if (user_info.find("error") != user_info.end()) + return false; + + info->username = retrieve(user_info,"screen_name"); + info->real_name = retrieve(user_info,"name",true); + info->profile_image_url = retrieve(user_info,"profile_image_url",true); + + return true; + } + else + return false; +} + +twitter_user twitter::add_friend(const std::tstring &name) +{ + std::string url = base_url_+"friendships/create/"+http::url_encode(name)+".json"; + + twitter_user ret; + http::response resp = slurp(url,http::post); + if (resp.code != 200) + throw bad_response(); + + js::variant var = json::parse( resp.data.begin(),resp.data.end()); + if (var->type() != typeid(js::object)) + throw std::exception("unable to parse response"); + + const js::object &user_info = boost::any_cast(*var); + ret.username = retrieve(user_info,"screen_name"); + ret.real_name = retrieve(user_info,"name",true); + ret.profile_image_url = retrieve(user_info,"profile_image_url",true); + + if (user_info.find("status") != user_info.end()) + { + // TODO: fill in more fields + const js::object &status = retrieve(user_info,"status"); + ret.status.text = retrieve(status,"text"); + } + + return ret; +} + +void twitter::remove_friend(const std::tstring &name) +{ + std::string url = base_url_+"friendships/destroy/"+http::url_encode(name)+".json"; + + slurp(url,http::post); +} + +void twitter::set_status(const std::tstring &text) +{ + if (text.size()) + { + slurp(base_url_+"statuses/update.json",http::post, + "status="+http::url_encode(text)+ + "&source=mirandaim"); + } +} + +void twitter::send_direct(const std::tstring &name,const std::tstring &text) +{ + slurp(base_url_+"direct_messages/new.json",http::post, + "user=" +http::url_encode(name)+ + "&text="+http::url_encode(text)); +} + +std::vector twitter::get_statuses(int count,twitter_id id) +{ + using boost::lexical_cast; + std::vector statuses; + + std::string url = base_url_+"statuses/friends_timeline.json?count="+ + lexical_cast(count); + if (id != 0) + url += "&since_id="+boost::lexical_cast(id); + + http::response resp = slurp(url,http::get); + if (resp.code != 200) + throw bad_response(); + + js::variant var = json::parse( resp.data.begin(),resp.data.end()); + if (var->type() != typeid(js::array)) + throw std::exception("unable to parse response"); + + const js::array &list = boost::any_cast(*var); + for(js::array::const_iterator i=list.begin(); i!=list.end(); ++i) + { + if ((*i)->type() == typeid(js::object)) + { + const js::object &one = boost::any_cast(**i); + const js::object &user = retrieve(one,"user"); + + twitter_user u; + u.username = retrieve(user,"screen_name"); + + u.status.text = retrieve(one,"text"); + u.status.id = retrieve(one,"id"); + std::string timestr = retrieve(one,"created_at"); + u.status.time = parse_time(timestr); + + statuses.push_back(u); + } + } + + + return statuses; +} + +std::vector twitter::get_direct(twitter_id id) +{ + std::vector messages; + + std::string url = base_url_+"direct_messages.json"; + if (id != 0) + url += "?since_id="+boost::lexical_cast(id); + + http::response resp = slurp(url,http::get); + if (resp.code != 200) + throw bad_response(); + + js::variant var = json::parse( resp.data.begin(),resp.data.end()); + if (var->type() != typeid(js::array)) + throw std::exception("unable to parse response"); + + const js::array &list = boost::any_cast(*var); + for(js::array::const_iterator i=list.begin(); i!=list.end(); ++i) + { + if ((*i)->type() == typeid(js::object)) + { + const js::object &one = boost::any_cast(**i); + + twitter_user u; + u.username = retrieve(one,"sender_screen_name"); + + u.status.text = retrieve(one,"text"); + u.status.id = retrieve(one,"id"); + std::string timestr = retrieve(one,"created_at"); + u.status.time = parse_time(timestr); + + messages.push_back(u); + } + } + + + return messages; +} + +// Some Unices get this, now we do too! +time_t timegm(struct tm *t) +{ + _tzset(); + t->tm_sec -= _timezone; + t->tm_isdst = 0; + + return mktime(t); +} + +static char *month_names[] = { "Jan","Feb","Mar","Apr","May","Jun", + "Jul","Aug","Sep","Oct","Nov","Dec" }; + +int parse_month(const char *m) +{ + for(size_t i=0; i<12; i++) + { + if (strcmp(month_names[i],m) == 0) + return i; + } + return -1; +} + +time_t parse_time(const std::string &s) +{ + struct tm t; + char day[4],month[4]; + char plus; + int zone; + if (sscanf(s.c_str(),"%3s %3s %d %d:%d:%d %c%d %d", + day,month,&t.tm_mday,&t.tm_hour,&t.tm_min,&t.tm_sec, + &plus,&zone,&t.tm_year) == 9) + { + t.tm_year -= 1900; + t.tm_mon = parse_month(month); + if (t.tm_mon == -1) + return 0; + return timegm(&t); + } + return 0; +} \ No newline at end of file diff --git a/protocols/Twitter/twitter.h b/protocols/Twitter/twitter.h new file mode 100644 index 0000000000..06f8aca954 --- /dev/null +++ b/protocols/Twitter/twitter.h @@ -0,0 +1,96 @@ +/* +Copyright © 2009 Jim Porter + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +#pragma once + +#include +#include +#include + +#include "http.h" +#include + +#if !defined(tstring) + #ifdef _UNICODE + #define tstring wstring + #else + #define tstring string + #endif +#endif + +typedef unsigned long long twitter_id; + +struct twitter_status +{ + std::tstring text; + twitter_id id; + time_t time; +}; + +struct twitter_user +{ + std::string username; + std::tstring real_name; + std::string profile_image_url; + twitter_status status; +}; + +time_t parse_time(const std::string &); + +class bad_response : public std::exception +{ +public: + virtual const char * what() const + { + return "bad http response"; + } +}; + +class twitter +{ +public: + typedef std::vector status_list; + typedef std::map status_map; + + twitter(); + + bool set_credentials(const std::string &username,const std::string &password, bool test = true); + void set_base_url(const std::string &base_url); + + const std::string & get_username() const; + const std::string & get_base_url() const; + + bool get_info(const std::tstring &name,twitter_user *); + bool get_info_by_email(const std::tstring &email,twitter_user *); + std::vector get_friends(); + + twitter_user add_friend(const std::tstring &name); + void remove_friend(const std::tstring &name); + + void set_status(const std::tstring &text); + std::vector get_statuses(int count=20,twitter_id id=0); + + void send_direct(const std::tstring &name,const std::tstring &text); + std::vector get_direct(twitter_id id=0); + +protected: + virtual http::response slurp(const std::string &,http::method, const std::string & = "") = 0; + + std::string username_; + std::string password_; + std::string base_url_; +}; \ No newline at end of file diff --git a/protocols/Twitter/twitter.rc b/protocols/Twitter/twitter.rc new file mode 100644 index 0000000000..0c32e19b59 --- /dev/null +++ b/protocols/Twitter/twitter.rc @@ -0,0 +1,224 @@ +// Microsoft Visual C++ generated resource script. +// +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "afxres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (U.S.) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +#ifdef _WIN32 +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US +#pragma code_page(1252) +#endif //_WIN32 + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""afxres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Dialog +// + +IDD_TWITTERACCOUNT DIALOGEX 0, 0, 186, 134 +STYLE DS_SETFONT | DS_FIXEDSYS | WS_CHILD +EXSTYLE WS_EX_CONTROLPARENT +FONT 8, "MS Shell Dlg", 0, 0, 0x0 +BEGIN + LTEXT "Username:",IDC_STATIC,0,0,53,12 + EDITTEXT IDC_UN,54,0,131,12,ES_AUTOHSCROLL + LTEXT "Password:",IDC_STATIC,0,16,53,12 + EDITTEXT IDC_PW,54,16,131,12,ES_PASSWORD | ES_AUTOHSCROLL + LTEXT "Server:",IDC_STATIC,0,32,53,12 + COMBOBOX IDC_SERVER,54,32,131,12,CBS_DROPDOWN | WS_VSCROLL | WS_TABSTOP + CONTROL "Create a new Twitter account",IDC_NEWACCOUNTLINK, + "Hyperlink",WS_TABSTOP,0,57,174,12 +END + +IDD_TWEET DIALOGEX 0, 0, 186, 64 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | DS_CENTER | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Send Tweet" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + EDITTEXT IDC_TWEETMSG,7,7,172,30,ES_MULTILINE | ES_AUTOVSCROLL | ES_WANTRETURN | WS_VSCROLL + DEFPUSHBUTTON "Send",IDOK,71,43,50,14 + PUSHBUTTON "Cancel",IDCANCEL,129,43,50,14 + LTEXT "",IDC_CHARACTERS,7,47,19,10 +END + +IDD_OPTIONS DIALOGEX 0, 0, 305, 217 +STYLE DS_SETFONT | DS_FIXEDSYS | WS_CHILD +EXSTYLE WS_EX_CONTROLPARENT +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + GROUPBOX "User Details",IDC_USERDETAILS,7,7,152,51 + LTEXT "Username:",IDC_STATIC,15,20,41,8 + EDITTEXT IDC_UN,62,18,89,14,ES_AUTOHSCROLL + LTEXT "Password:",IDC_STATIC,15,37,41,8 + EDITTEXT IDC_PW,62,35,89,14,ES_PASSWORD | ES_AUTOHSCROLL + GROUPBOX "Misc. Options",IDC_MISC,7,67,152,65 + CONTROL "Use group chat for Twitter feed",IDC_CHATFEED,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,15,81,133,10 + LTEXT "Base URL:",IDC_STATIC,15,96,41,8 + COMBOBOX IDC_BASEURL,62,94,89,30,CBS_DROPDOWN | CBS_AUTOHSCROLL | WS_VSCROLL | WS_TABSTOP + LTEXT "Polling rate:",IDC_STATIC,15,113,41,8 + LTEXT "Once every",IDC_STATIC,62,113,40,8 + EDITTEXT IDC_POLLRATE,103,111,30,14,ES_AUTOHSCROLL | ES_NUMBER + LTEXT "sec",IDC_STATIC,138,113,19,8 + LTEXT "Please cycle your connection for these changes to take effect",IDC_RECONNECT,53,202,199,8,NOT WS_VISIBLE +END + +IDD_OPTIONS_POPUPS DIALOGEX 0, 0, 305, 217 +STYLE DS_SETFONT | DS_FIXEDSYS | WS_CHILD +EXSTYLE WS_EX_CONTROLPARENT +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + CONTROL "Enable popup notifications for Tweets",IDC_SHOWPOPUPS, + "Button",BS_AUTOCHECKBOX | WS_TABSTOP,16,7,137,10 + GROUPBOX "Colors",IDC_STATIC,6,32,164,59 + CONTROL "Use Windows colors",IDC_COL_WINDOWS,"Button",BS_AUTORADIOBUTTON | WS_GROUP | WS_TABSTOP,16,46,79,10 + CONTROL "Use Popup colors",IDC_COL_POPUP,"Button",BS_AUTORADIOBUTTON | WS_TABSTOP,16,60,71,10 + CONTROL "Use custom colors",IDC_COL_CUSTOM,"Button",BS_AUTORADIOBUTTON | WS_TABSTOP,16,74,73,10 + LTEXT "Back",IDC_STATIC,103,64,16,8 + LTEXT "Text",IDC_STATIC,136,64,16,8 + CONTROL "",IDC_COLBACK,"ColourPicker",WS_TABSTOP,99,73,24,13 + CONTROL "",IDC_COLTEXT,"ColourPicker",WS_TABSTOP,132,73,24,13 + GROUPBOX "Timeouts",IDC_STATIC,184,32,115,59 + CONTROL "Use default",IDC_TIMEOUT_DEFAULT,"Button",BS_AUTORADIOBUTTON | WS_GROUP | WS_TABSTOP,196,46,53,10 + CONTROL "Custom",IDC_TIMEOUT_CUSTOM,"Button",BS_AUTORADIOBUTTON | WS_TABSTOP,196,60,40,10 + EDITTEXT IDC_TIMEOUT,249,58,40,14,ES_AUTOHSCROLL + CONTROL "",IDC_TIMEOUT_SPIN,"msctls_updown32",UDS_SETBUDDYINT | UDS_ALIGNRIGHT | UDS_AUTOBUDDY | UDS_ARROWKEYS,279,58,10,14 + CONTROL "Permanent",IDC_TIMEOUT_PERMANENT,"Button",BS_AUTORADIOBUTTON | WS_TABSTOP,196,74,51,10 + PUSHBUTTON "Preview",IDC_PREVIEW,184,99,50,14 + CONTROL "But not during sign-on",IDC_NOSIGNONPOPUPS,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,27,19,87,10 +END + + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDI_TWITTER ICON "icons\\twitter.ico" + +///////////////////////////////////////////////////////////////////////////// +// +// DESIGNINFO +// + +#ifdef APSTUDIO_INVOKED +GUIDELINES DESIGNINFO +BEGIN + IDD_TWEET, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 179 + TOPMARGIN, 7 + BOTTOMMARGIN, 57 + END + + IDD_OPTIONS, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 298 + TOPMARGIN, 7 + BOTTOMMARGIN, 210 + END + + IDD_OPTIONS_POPUPS, DIALOG + BEGIN + LEFTMARGIN, 6 + RIGHTMARGIN, 299 + VERTGUIDE, 184 + TOPMARGIN, 7 + BOTTOMMARGIN, 210 + END +END +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +VS_VERSION_INFO VERSIONINFO + FILEVERSION 0,0,8,4 + PRODUCTVERSION 0,0,8,4 + FILEFLAGSMASK 0x17L +#ifdef _DEBUG + FILEFLAGS 0x1L +#else + FILEFLAGS 0x0L +#endif + FILEOS 0x4L + FILETYPE 0x2L + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" + BEGIN + VALUE "FileDescription", "Twitter protocol plugin for Miranda IM" + VALUE "FileVersion", "0, 0, 8, 4" + VALUE "InternalName", "twitter" + VALUE "LegalCopyright", "Copyright © 2009" + VALUE "OriginalFilename", "twitter.dll" + VALUE "ProductName", "Miranda-Twitter" + VALUE "ProductVersion", "0, 0, 8, 4" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 + END +END + +#endif // English (U.S.) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + diff --git a/protocols/Twitter/twitter.sln b/protocols/Twitter/twitter.sln new file mode 100644 index 0000000000..af7c8278cd --- /dev/null +++ b/protocols/Twitter/twitter.sln @@ -0,0 +1,26 @@ + +Microsoft Visual Studio Solution File, Format Version 9.00 +# Visual Studio 2005 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "twitter", "twitter.vcproj", "{DADE9455-DC28-465A-9604-2CA28052B9FB}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug ANSI|Win32 = Debug ANSI|Win32 + Debug|Win32 = Debug|Win32 + Release ANSI|Win32 = Release ANSI|Win32 + Release|Win32 = Release|Win32 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {DADE9455-DC28-465A-9604-2CA28052B9FB}.Debug ANSI|Win32.ActiveCfg = Debug ANSI|Win32 + {DADE9455-DC28-465A-9604-2CA28052B9FB}.Debug ANSI|Win32.Build.0 = Debug ANSI|Win32 + {DADE9455-DC28-465A-9604-2CA28052B9FB}.Debug|Win32.ActiveCfg = Debug|Win32 + {DADE9455-DC28-465A-9604-2CA28052B9FB}.Debug|Win32.Build.0 = Debug|Win32 + {DADE9455-DC28-465A-9604-2CA28052B9FB}.Release ANSI|Win32.ActiveCfg = Release ANSI|Win32 + {DADE9455-DC28-465A-9604-2CA28052B9FB}.Release ANSI|Win32.Build.0 = Release ANSI|Win32 + {DADE9455-DC28-465A-9604-2CA28052B9FB}.Release|Win32.ActiveCfg = Release|Win32 + {DADE9455-DC28-465A-9604-2CA28052B9FB}.Release|Win32.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/protocols/Twitter/twitter.vcxproj b/protocols/Twitter/twitter.vcxproj new file mode 100644 index 0000000000..d529d1dcbb --- /dev/null +++ b/protocols/Twitter/twitter.vcxproj @@ -0,0 +1,127 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + + {DADE9455-DC28-465A-9604-2CA28052B9FB} + twitter + Win32Proj + + + + DynamicLibrary + Unicode + true + + + DynamicLibrary + Unicode + + + + + + + + + + + + + <_ProjectFileVersion>10.0.40219.1 + $(SolutionDir)$(Configuration)\Plugins\ + $(SolutionDir)$(Configuration)\Obj\$(ProjectName)\ + true + $(SolutionDir)$(Configuration)\Plugins\ + $(SolutionDir)$(Configuration)\Obj\$(ProjectName)\ + false + + + + Disabled + ..\..\include;..\..\..\boost_1_49_0;..\plugins\ExternalAPI;%(AdditionalIncludeDirectories) + WIN32;_WINDOWS;_USRDLL;TWITTER_EXPORTS;_CRT_SECURE_NO_WARNINGS;NOMINMAX;%(PreprocessorDefinitions) + true + EnableFastChecks + MultiThreadedDLL + Use + Level3 + EditAndContinue + common.h + + + true + Windows + MachineX86 + + + + + ..\..\include;..\..\..\boost_1_49_0;..\plugins\ExternalAPI;%(AdditionalIncludeDirectories) + WIN32;NDEBUG;_WINDOWS;_USRDLL;TWITTER_EXPORTS;_CRT_SECURE_NO_WARNINGS;NOMINMAX;%(PreprocessorDefinitions) + MultiThreadedDLL + Use + Level3 + ProgramDatabase + Full + Size + true + common.h + + + true + Windows + true + true + MachineX86 + + + + + + + + + Create + Create + + + + + + NotUsing + NotUsing + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/protocols/Twitter/twitter.vcxproj.filters b/protocols/Twitter/twitter.vcxproj.filters new file mode 100644 index 0000000000..70ac860513 --- /dev/null +++ b/protocols/Twitter/twitter.vcxproj.filters @@ -0,0 +1,94 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + + + Resource Files + + + + + Resource Files + + + \ No newline at end of file diff --git a/protocols/Twitter/ui.cpp b/protocols/Twitter/ui.cpp new file mode 100644 index 0000000000..b3e68542df --- /dev/null +++ b/protocols/Twitter/ui.cpp @@ -0,0 +1,529 @@ +/* +Copyright © 2009 Jim Porter + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +#include "common.h" +#include "ui.h" + +#include +#include + +#include "proto.h" +#include "twitter.h" + +static const TCHAR *sites[] = { + _T("https://twitter.com/"), + _T("https://identi.ca/api/") +}; + +INT_PTR CALLBACK first_run_dialog(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam) +{ + TwitterProto *proto; + + switch(msg) + { + case WM_INITDIALOG: + TranslateDialogDefault(hwndDlg); + + proto = reinterpret_cast(lParam); + SetWindowLong(hwndDlg, GWL_USERDATA, lParam); + + DBVARIANT dbv; + if ( !DBGetContactSettingTString(0, proto->ModuleName(), TWITTER_KEY_UN, &dbv)) { + SetDlgItemText(hwndDlg, IDC_UN, dbv.ptszVal); + DBFreeVariant(&dbv); + } + + if ( !DBGetContactSettingString(0, proto->ModuleName(), TWITTER_KEY_PASS, &dbv)) { + CallService(MS_DB_CRYPT_DECODESTRING, strlen(dbv.pszVal)+1, + reinterpret_cast(dbv.pszVal)); + SetDlgItemTextA(hwndDlg, IDC_PW, dbv.pszVal); + DBFreeVariant(&dbv); + } + + for(size_t i=0; i(sites[i])); + } + if ( !DBGetContactSettingString(0, proto->ModuleName(), TWITTER_KEY_BASEURL, &dbv)) + { + SetDlgItemTextA(hwndDlg, IDC_SERVER, dbv.pszVal); + DBFreeVariant(&dbv); + } + else + { + SendDlgItemMessage(hwndDlg, IDC_SERVER, CB_SETCURSEL, 0, 0); + } + + return true; + case WM_COMMAND: + if (LOWORD(wParam) == IDC_NEWACCOUNTLINK) + { + CallService(MS_UTILS_OPENURL, 1, reinterpret_cast + ("http://twitter.com/signup")); + return true; + } + + if (GetWindowLong(hwndDlg, GWL_USERDATA)) // Window is done initializing + { + switch(HIWORD(wParam)) + { + case EN_CHANGE: + case CBN_EDITCHANGE: + case CBN_SELCHANGE: + SendMessage(GetParent(hwndDlg), PSM_CHANGED, 0, 0); + } + } + break; + + case WM_NOTIFY: + if (reinterpret_cast(lParam)->code == PSN_APPLY) + { + proto = reinterpret_cast(GetWindowLong(hwndDlg, GWL_USERDATA)); + char str[128]; + + GetDlgItemTextA(hwndDlg, IDC_UN, str, sizeof(str)); + DBWriteContactSettingString(0, proto->ModuleName(), TWITTER_KEY_UN, str); + + GetDlgItemTextA(hwndDlg, IDC_PW, str, sizeof(str)); + CallService(MS_DB_CRYPT_ENCODESTRING, sizeof(str), reinterpret_cast(str)); + DBWriteContactSettingString(0, proto->ModuleName(), TWITTER_KEY_PASS, str); + + GetDlgItemTextA(hwndDlg, IDC_SERVER, str, sizeof(str)-1); + if (str[strlen(str)-1] != '/') + strncat(str, "/", sizeof(str)); + DBWriteContactSettingString(0, proto->ModuleName(), TWITTER_KEY_BASEURL, str); + + return true; + } + break; + } + + return false; +} + +INT_PTR CALLBACK tweet_proc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam) +{ + TwitterProto *proto; + + switch(msg) + { + case WM_INITDIALOG: + TranslateDialogDefault(hwndDlg); + + proto = reinterpret_cast(lParam); + SetWindowLong(hwndDlg, GWL_USERDATA, lParam); + SendDlgItemMessage(hwndDlg, IDC_TWEETMSG, EM_LIMITTEXT, 140, 0); + SetDlgItemText(hwndDlg, IDC_CHARACTERS, _T("140")); + + // Set window title + TCHAR title[512]; + mir_sntprintf(title, SIZEOF(title), _T("Send Tweet for %s"), proto->m_tszUserName); + SendMessage(hwndDlg, WM_SETTEXT, 0, (LPARAM)title); + + return true; + case WM_COMMAND: + if (LOWORD(wParam) == IDOK) + { + TCHAR msg[141]; + proto = reinterpret_cast(GetWindowLong(hwndDlg, GWL_USERDATA)); + + GetDlgItemText(hwndDlg, IDC_TWEETMSG, msg, SIZEOF(msg)); + ShowWindow(hwndDlg, SW_HIDE); + + char *narrow = mir_t2a_cp(msg, CP_UTF8); + ForkThread(&TwitterProto::SendTweetWorker, proto, narrow); + + EndDialog(hwndDlg, wParam); + return true; + } + else if (LOWORD(wParam) == IDCANCEL) + { + EndDialog(hwndDlg, wParam); + return true; + } + else if (LOWORD(wParam) == IDC_TWEETMSG && HIWORD(wParam) == EN_CHANGE) + { + size_t len = SendDlgItemMessage(hwndDlg, IDC_TWEETMSG, WM_GETTEXTLENGTH, 0, 0); + char str[4]; + _snprintf(str, sizeof(str), "%d", 140-len); + SetDlgItemTextA(hwndDlg, IDC_CHARACTERS, str); + + return true; + } + + break; + case WM_SETREPLY: + { + char foo[512]; + _snprintf(foo, sizeof(foo), "@%s ", (char*)wParam); + size_t len = strlen(foo); + + SetDlgItemTextA(hwndDlg, IDC_TWEETMSG, foo); + SendDlgItemMessage(hwndDlg, IDC_TWEETMSG, EM_SETSEL, len, len); + + char str[4]; + _snprintf(str, sizeof(str), "%d", 140-len); + SetDlgItemTextA(hwndDlg, IDC_CHARACTERS, str); + + return true; + } + break; + } + + return false; +} + +INT_PTR CALLBACK options_proc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam) +{ + TwitterProto *proto; + + switch(msg) + { + case WM_INITDIALOG: + TranslateDialogDefault(hwndDlg); + + proto = reinterpret_cast(lParam); + + DBVARIANT dbv; + if ( !DBGetContactSettingString(0, proto->ModuleName(), TWITTER_KEY_UN, &dbv)) + { + SetDlgItemTextA(hwndDlg, IDC_UN, dbv.pszVal); + DBFreeVariant(&dbv); + } + + if ( !DBGetContactSettingString(0, proto->ModuleName(), TWITTER_KEY_PASS, &dbv)) + { + CallService(MS_DB_CRYPT_DECODESTRING, strlen(dbv.pszVal)+1, + reinterpret_cast(dbv.pszVal)); + SetDlgItemTextA(hwndDlg, IDC_PW, dbv.pszVal); + DBFreeVariant(&dbv); + } + + CheckDlgButton(hwndDlg, IDC_CHATFEED, DBGetContactSettingByte(0, + proto->ModuleName(), TWITTER_KEY_CHATFEED, 0)); + + for(size_t i=0; i(sites[i])); + } + + if ( !DBGetContactSettingString(0, proto->ModuleName(), TWITTER_KEY_BASEURL, &dbv)) + { + SetDlgItemTextA(hwndDlg, IDC_BASEURL, dbv.pszVal); + DBFreeVariant(&dbv); + } + else + { + SendDlgItemMessage(hwndDlg, IDC_BASEURL, CB_SETCURSEL, 0, 0); + } + + char pollrate_str[32]; + mir_snprintf(pollrate_str, sizeof(pollrate_str), "%d", + DBGetContactSettingDword(0, proto->ModuleName(), TWITTER_KEY_POLLRATE, 80)); + SetDlgItemTextA(hwndDlg, IDC_POLLRATE, pollrate_str); + + + // Do this last so that any events propagated by pre-filling the form don't + // instigate a PSM_CHANGED message + SetWindowLong(hwndDlg, GWL_USERDATA, lParam); + + break; + case WM_COMMAND: + if (GetWindowLong(hwndDlg, GWL_USERDATA)) // Window is done initializing + { + switch(HIWORD(wParam)) + { + case EN_CHANGE: + case BN_CLICKED: + case CBN_EDITCHANGE: + case CBN_SELCHANGE: + switch(LOWORD(wParam)) + { + case IDC_UN: + case IDC_PW: + case IDC_BASEURL: + ShowWindow(GetDlgItem(hwndDlg, IDC_RECONNECT), SW_SHOW); + } + SendMessage(GetParent(hwndDlg), PSM_CHANGED, 0, 0); + } + } + + break; + case WM_NOTIFY: + if (reinterpret_cast(lParam)->code == PSN_APPLY) + { + proto = reinterpret_cast(GetWindowLong(hwndDlg, GWL_USERDATA)); + char str[128]; + + GetDlgItemTextA(hwndDlg, IDC_UN, str, sizeof(str)); + DBWriteContactSettingString(0, proto->ModuleName(), TWITTER_KEY_UN, str); + + GetDlgItemTextA(hwndDlg, IDC_PW, str, sizeof(str)); + CallService(MS_DB_CRYPT_ENCODESTRING, sizeof(str), reinterpret_cast(str)); + DBWriteContactSettingString(0, proto->ModuleName(), TWITTER_KEY_PASS, str); + + GetDlgItemTextA(hwndDlg, IDC_BASEURL, str, sizeof(str)-1); + if (str[strlen(str)-1] != '/') + strncat(str, "/", sizeof(str)); + DBWriteContactSettingString(0, proto->ModuleName(), TWITTER_KEY_BASEURL, str); + + DBWriteContactSettingByte(0, proto->ModuleName(), TWITTER_KEY_CHATFEED, + IsDlgButtonChecked(hwndDlg, IDC_CHATFEED)); + + GetDlgItemTextA(hwndDlg, IDC_POLLRATE, str, sizeof(str)); + int rate = atoi(str); + if (rate == 0) + rate = 80; + DBWriteContactSettingDword(0, proto->ModuleName(), TWITTER_KEY_POLLRATE, rate); + + proto->UpdateSettings(); + return true; + } + } + + return false; +} + +namespace popup_options +{ + static int get_timeout(HWND hwndDlg) + { + if (IsDlgButtonChecked(hwndDlg, IDC_TIMEOUT_PERMANENT)) + return -1; + else if (IsDlgButtonChecked(hwndDlg, IDC_TIMEOUT_CUSTOM)) + { + char str[32]; + GetDlgItemTextA(hwndDlg, IDC_TIMEOUT, str, sizeof(str)); + return atoi(str); + } + else // Default checked (probably) + return 0; + } + + static COLORREF get_text_color(HWND hwndDlg, bool for_db) + { + if (IsDlgButtonChecked(hwndDlg, IDC_COL_WINDOWS)) + { + if (for_db) + return -1; + else + return GetSysColor(COLOR_WINDOWTEXT); + } + else if (IsDlgButtonChecked(hwndDlg, IDC_COL_CUSTOM)) + return SendDlgItemMessage(hwndDlg, IDC_COLTEXT, CPM_GETCOLOUR, 0, 0); + else // Default checked (probably) + return 0; + } + + static COLORREF get_back_color(HWND hwndDlg, bool for_db) + { + if (IsDlgButtonChecked(hwndDlg, IDC_COL_WINDOWS)) + { + if (for_db) + return -1; + else + return GetSysColor(COLOR_WINDOW); + } + else if (IsDlgButtonChecked(hwndDlg, IDC_COL_CUSTOM)) + return SendDlgItemMessage(hwndDlg, IDC_COLBACK, CPM_GETCOLOUR, 0, 0); + else // Default checked (probably) + return 0; + } + + struct + { + TCHAR *name; + TCHAR *text; + } const quotes[] = { + { _T("Dorothy Parker"), _T("If, with the literate, I am\n") + _T("Impelled to try an epigram, \n") + _T("I never seek to take the credit;\n") + _T("We all assume that Oscar said it.") }, + { _T("Steve Ballmer"), _T("I have never, honestly, thrown a chair in my life.") }, + { _T("James Joyce"), _T("I think I would know Nora's fart anywhere. I think ") + _T("I could pick hers out in a roomful of farting women.") }, + { _T("Brooke Shields"), _T("Smoking kills. If you're killed, you've lost a very ") + _T("important part of your life.") }, + { _T("Yogi Berra"), _T("Always go to other peoples' funerals, otherwise ") + _T("they won't go to yours.") }, + }; + + static void preview(HWND hwndDlg) + { + POPUPDATAT popup = {}; + + // Pick a random contact + HANDLE hContact = 0; + int n_contacts = CallService(MS_DB_CONTACT_GETCOUNT, 0, 0); + + if (n_contacts != 0) + { + int contact = rand() % n_contacts; + hContact = (HANDLE)CallService(MS_DB_CONTACT_FINDFIRST, 0, 0); + for(int i=0; i(&popup), 0); + } +} + +void CheckAndUpdateDlgButton(HWND hWnd, int button, BOOL check) +{ + CheckDlgButton(hWnd, button, check); + SendMessage(hWnd, WM_COMMAND, MAKELONG(button, BN_CLICKED), + (LPARAM)GetDlgItem(hWnd, button)); +} + +INT_PTR CALLBACK popup_options_proc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam) +{ + using namespace popup_options; + TwitterProto *proto; + + int text_color, back_color, timeout; + + switch(msg) + { + case WM_INITDIALOG: + TranslateDialogDefault(hwndDlg); + + proto = reinterpret_cast(lParam); + + CheckAndUpdateDlgButton(hwndDlg, IDC_SHOWPOPUPS, + db_byte_get(0, proto->ModuleName(), TWITTER_KEY_POPUP_SHOW, 0)); + CheckDlgButton(hwndDlg, IDC_NOSIGNONPOPUPS, + !db_byte_get(0, proto->ModuleName(), TWITTER_KEY_POPUP_SIGNON, 0)); + + + // ***** Get color information + back_color = db_dword_get(0, proto->ModuleName(), TWITTER_KEY_POPUP_COLBACK, 0); + text_color = db_dword_get(0, proto->ModuleName(), TWITTER_KEY_POPUP_COLTEXT, 0); + + SendDlgItemMessage(hwndDlg, IDC_COLBACK, CPM_SETCOLOUR, 0, RGB(255, 255, 255)); + SendDlgItemMessage(hwndDlg, IDC_COLTEXT, CPM_SETCOLOUR, 0, RGB( 0, 0, 0)); + + if (back_color == -1 && text_color == -1) // Windows defaults + CheckAndUpdateDlgButton(hwndDlg, IDC_COL_WINDOWS, true); + else if (back_color == 0 && text_color == 0) // Popup defaults + CheckAndUpdateDlgButton(hwndDlg, IDC_COL_POPUP, true); + else // Custom colors + { + CheckAndUpdateDlgButton(hwndDlg, IDC_COL_CUSTOM, true); + SendDlgItemMessage(hwndDlg, IDC_COLBACK, CPM_SETCOLOUR, 0, back_color); + SendDlgItemMessage(hwndDlg, IDC_COLTEXT, CPM_SETCOLOUR, 0, text_color); + } + + // ***** Get timeout information + timeout = db_dword_get(0, proto->ModuleName(), TWITTER_KEY_POPUP_TIMEOUT, 0); + SetDlgItemTextA(hwndDlg, IDC_TIMEOUT, "5"); + + if (timeout == 0) + CheckAndUpdateDlgButton(hwndDlg, IDC_TIMEOUT_DEFAULT, true); + else if (timeout < 0) + CheckAndUpdateDlgButton(hwndDlg, IDC_TIMEOUT_PERMANENT, true); + else + { + char str[32]; + _snprintf(str, sizeof(str), "%d", timeout); + SetDlgItemTextA(hwndDlg, IDC_TIMEOUT, str); + CheckAndUpdateDlgButton(hwndDlg, IDC_TIMEOUT_CUSTOM, true); + } + + SendDlgItemMessage(hwndDlg, IDC_TIMEOUT_SPIN, UDM_SETRANGE32, 1, INT_MAX); + SetWindowLong(hwndDlg, GWL_USERDATA, lParam); + + return true; + case WM_COMMAND: + switch(HIWORD(wParam)) + { + case BN_CLICKED: + switch(LOWORD(wParam)) + { + case IDC_SHOWPOPUPS: + EnableWindow(GetDlgItem(hwndDlg, IDC_NOSIGNONPOPUPS), + IsDlgButtonChecked(hwndDlg, IDC_SHOWPOPUPS)); + break; + + case IDC_COL_CUSTOM: + EnableWindow(GetDlgItem(hwndDlg, IDC_COLBACK), true); + EnableWindow(GetDlgItem(hwndDlg, IDC_COLTEXT), true); + break; + case IDC_COL_WINDOWS: + case IDC_COL_POPUP: + EnableWindow(GetDlgItem(hwndDlg, IDC_COLBACK), false); + EnableWindow(GetDlgItem(hwndDlg, IDC_COLTEXT), false); + break; + + case IDC_TIMEOUT_CUSTOM: + EnableWindow(GetDlgItem(hwndDlg, IDC_TIMEOUT), true); + EnableWindow(GetDlgItem(hwndDlg, IDC_TIMEOUT_SPIN), true); + break; + case IDC_TIMEOUT_DEFAULT: + case IDC_TIMEOUT_PERMANENT: + EnableWindow(GetDlgItem(hwndDlg, IDC_TIMEOUT), false); + EnableWindow(GetDlgItem(hwndDlg, IDC_TIMEOUT_SPIN), false); + break; + + case IDC_PREVIEW: + preview(hwndDlg); + break; + } + + case EN_CHANGE: + if (GetWindowLong(hwndDlg, GWL_USERDATA)) // Window is done initializing + SendMessage(GetParent(hwndDlg), PSM_CHANGED, 0, 0); + } + break; + case WM_NOTIFY: + if (reinterpret_cast(lParam)->code == PSN_APPLY) + { + proto = reinterpret_cast(GetWindowLong(hwndDlg, GWL_USERDATA)); + + DBWriteContactSettingByte(0, proto->ModuleName(), TWITTER_KEY_POPUP_SHOW, + IsDlgButtonChecked(hwndDlg, IDC_SHOWPOPUPS)); + DBWriteContactSettingByte(0, proto->ModuleName(), TWITTER_KEY_POPUP_SIGNON, + !IsDlgButtonChecked(hwndDlg, IDC_NOSIGNONPOPUPS)); + + // ***** Write color settings + DBWriteContactSettingDword(0, proto->ModuleName(), TWITTER_KEY_POPUP_COLBACK, + get_back_color(hwndDlg, true)); + DBWriteContactSettingDword(0, proto->ModuleName(), TWITTER_KEY_POPUP_COLTEXT, + get_text_color(hwndDlg, true)); + + // ***** Write timeout setting + DBWriteContactSettingDword(0, proto->ModuleName(), TWITTER_KEY_POPUP_TIMEOUT, + get_timeout(hwndDlg)); + + return true; + } + break; + } + + return false; +} diff --git a/protocols/Twitter/ui.h b/protocols/Twitter/ui.h new file mode 100644 index 0000000000..f036f8d55d --- /dev/null +++ b/protocols/Twitter/ui.h @@ -0,0 +1,25 @@ +/* +Copyright © 2009 Jim Porter + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +#pragma once + +#include + +INT_PTR CALLBACK first_run_dialog(HWND hwndDlg,UINT msg,WPARAM wParam,LPARAM lParam); +INT_PTR CALLBACK tweet_proc(HWND hwndDlg,UINT msg,WPARAM wParam,LPARAM lParam); +INT_PTR CALLBACK options_proc(HWND hwndDlg,UINT msg,WPARAM wParam,LPARAM lParam); +INT_PTR CALLBACK popup_options_proc(HWND hwndDlg,UINT msg,WPARAM wParam,LPARAM lParam); \ No newline at end of file diff --git a/protocols/Twitter/utility.cpp b/protocols/Twitter/utility.cpp new file mode 100644 index 0000000000..221e413253 --- /dev/null +++ b/protocols/Twitter/utility.cpp @@ -0,0 +1,133 @@ +/* +Copyright © 2009 Jim Porter + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +#include "common.h" +#include "utility.h" + +#include + +std::string b64encode(const std::string &s) +{ + NETLIBBASE64 encode; + encode.cbDecoded = s.length(); + encode.pbDecoded = (BYTE*)s.c_str(); + encode.cchEncoded = Netlib_GetBase64EncodedBufferSize(encode.cbDecoded); + encode.pszEncoded = new char[encode.cchEncoded+1]; + CallService(MS_NETLIB_BASE64ENCODE,0,(LPARAM)&encode); + std::string ret = encode.pszEncoded; + delete[] encode.pszEncoded; + + return ret; +} + +http::response mir_twitter::slurp(const std::string &url, http::method meth, const std::string &post_data) +{ + NETLIBHTTPREQUEST req = {sizeof(req)}; + NETLIBHTTPREQUEST *resp; + req.requestType = (meth == http::get) ? REQUEST_GET:REQUEST_POST; + req.szUrl = ( char* )url.c_str(); + + // probably not super-efficient to do this every time, but I don't really care + std::string auth = "Basic " + b64encode(username_) + ":" + password_; + + NETLIBHTTPHEADER hdr[2]; + hdr[0].szName = "Authorization"; + hdr[0].szValue = (char*)( auth.c_str()); + + req.headers = hdr; + req.headersCount = 1; + + if (meth == http::post) + { + hdr[1].szName = "Content-Type"; + hdr[1].szValue = "application/x-www-form-urlencoded"; + + req.headersCount = 2; + req.dataLength = post_data.size(); + req.pData = ( char* )post_data.c_str(); + } + + http::response resp_data; + + resp = reinterpret_cast(CallService( MS_NETLIB_HTTPTRANSACTION, + reinterpret_cast(handle_), reinterpret_cast(&req))); + + if (resp) + { + resp_data.code = resp->resultCode; + resp_data.data = resp->pData ? resp->pData:""; + + CallService(MS_NETLIB_FREEHTTPREQUESTSTRUCT,0,(LPARAM)resp); + } + + return resp_data; +} + + + +bool save_url(HANDLE hNetlib,const std::string &url,const std::string &filename) +{ + NETLIBHTTPREQUEST req = {sizeof(req)}; + NETLIBHTTPREQUEST *resp; + req.requestType = REQUEST_GET; + req.szUrl = const_cast(url.c_str()); + + resp = reinterpret_cast(CallService( MS_NETLIB_HTTPTRANSACTION, + reinterpret_cast(hNetlib), reinterpret_cast(&req))); + + if (resp) + { + // Create folder if necessary + std::string dir = filename.substr(0,filename.rfind('\\')); + if (_access(dir.c_str(),0)) + CallService(MS_UTILS_CREATEDIRTREE, 0, (LPARAM)dir.c_str()); + + // Write to file + FILE *f = fopen(filename.c_str(),"wb"); + fwrite(resp->pData,1,resp->dataLength,f); + fclose(f); + + CallService(MS_NETLIB_FREEHTTPREQUESTSTRUCT,0,(LPARAM)resp); + return true; + } + else + return false; +} + +static const struct +{ + char *ext; + int fmt; +} formats[] = { + { ".png", PA_FORMAT_PNG }, + { ".jpg", PA_FORMAT_JPEG }, + { ".jpeg", PA_FORMAT_JPEG }, + { ".ico", PA_FORMAT_ICON }, + { ".bmp", PA_FORMAT_BMP }, + { ".gif", PA_FORMAT_GIF }, +}; + +int ext_to_format(const std::string &ext) +{ + for(size_t i=0; i. +*/ + +#pragma once + +#include "http.h" +#include "twitter.h" + +template +void CreateProtoService(const char *module,const char *service, + int (__cdecl T::*serviceProc)(WPARAM,LPARAM),T *self) +{ + char temp[MAX_PATH*2]; + + mir_snprintf(temp,sizeof(temp),"%s%s",module,service); + CreateServiceFunctionObj(temp,( MIRANDASERVICEOBJ )*(void**)&serviceProc, self ); +} + +template +void HookProtoEvent(const char* evt, int (__cdecl T::*eventProc)(WPARAM,LPARAM), T *self) +{ + ::HookEventObj(evt,(MIRANDAHOOKOBJ)*(void**)&eventProc,self); +} + +template +HANDLE ForkThreadEx(void (__cdecl T::*thread)(void*),T *self,void *data = 0) +{ + return reinterpret_cast( mir_forkthreadowner( + (pThreadFuncOwner)*(void**)&thread,self,data,0)); +} + +template +void ForkThread(void (__cdecl T::*thread)(void*),T *self,void *data = 0) +{ + CloseHandle(ForkThreadEx(thread,self,data)); +} + +std::string b64encode(const std::string &s); + +class mir_twitter : public twitter +{ +public: + void set_handle(HANDLE h) + { + handle_ = h; + } +protected: + http::response slurp(const std::string &,http::method,const std::string &); + HANDLE handle_; +}; + +inline void mbcs_to_tcs(UINT code_page,const char *mbstr,TCHAR *tstr,int tlen) +{ +#ifdef UNICODE + MultiByteToWideChar(code_page,0,mbstr,-1,tstr,tlen); +#else + strncpy(tstr,mbstr,tlen); +#endif +} + +inline void wcs_to_tcs(UINT code_page,const wchar_t *wstr,TCHAR *tstr,int tlen) +{ +#ifdef UNICODE + wcsncpy(tstr,wstr,tlen); +#else + WideCharToMultiByte(code_page,0,wstr,-1,tstr,tlen,0,0); +#endif +} + +class ScopedLock +{ +public: + ScopedLock(HANDLE h) : handle_(h) + { + WaitForSingleObject(handle_,INFINITE); + } + ~ScopedLock() + { + if (handle_) + ReleaseMutex(handle_); + } + + void Unlock() + { + ReleaseMutex(handle_); + handle_ = 0; + } +private: + HANDLE handle_; +}; + +int ext_to_format(const std::string &ext); +bool save_url(HANDLE hNetlib,const std::string &url,const std::string &filename); \ No newline at end of file diff --git a/protocols/Twitter/version.h b/protocols/Twitter/version.h new file mode 100644 index 0000000000..b27c53811e --- /dev/null +++ b/protocols/Twitter/version.h @@ -0,0 +1,20 @@ +/* +Copyright © 2009 Jim Porter + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +#pragma once + +#define __VERSION_DWORD PLUGIN_MAKE_VERSION(0, 0, 8, 4) \ No newline at end of file -- cgit v1.2.3