diff options
Diffstat (limited to 'protocols/Twitter')
28 files changed, 5388 insertions, 0 deletions
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. <http://fsf.org/>
+ 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.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ 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 <http://www.gnu.org/licenses/>.
+
+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:
+
+ <program> Copyright (C) <year> <name of author>
+ 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
+<http://www.gnu.org/licenses/>.
+
+ 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
+<http://www.gnu.org/philosophy/why-not-lgpl.html>.
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 <source>\miranda:
+<https://miranda.svn.sourceforge.net/svnroot/miranda/trunk/miranda/include>
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 <http://www.gnu.org/licenses/>.
+*/
+
+#include "common.h"
+#include "proto.h"
+
+#include <set>
+#include <ctime>
+
+void TwitterProto::UpdateChat(const twitter_user &update)
+{
+ GCDEST gcd = { m_szModuleName };
+ gcd.ptszID = const_cast<TCHAR*>(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<DWORD>(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<LPARAM>(&gce));
+
+ mir_free(const_cast<TCHAR*>(gce.ptszNick));
+ mir_free(const_cast<TCHAR*>(gce.ptszUID));
+ mir_free(const_cast<TCHAR*>(gce.ptszText));
+}
+
+int TwitterProto::OnChatOutgoing(WPARAM wParam, LPARAM lParam)
+{
+ GCHOOK *hook = reinterpret_cast<GCHOOK*>(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<WPARAM>( 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<TCHAR*>(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<DWORD>(time(0));
+ CallServiceSync(MS_GC_EVENT, 0, reinterpret_cast<LPARAM>(&gce));
+}
+
+void TwitterProto::DeleteChatContact(const TCHAR *name)
+{
+ GCDEST gcd = { m_szModuleName };
+ gcd.ptszID = const_cast<TCHAR*>(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<DWORD>(time(0));
+ CallServiceSync(MS_GC_EVENT, 0, reinterpret_cast<LPARAM>(&gce));
+
+ mir_free(const_cast<TCHAR*>(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<TCHAR*>(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<LPARAM>(&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<TCHAR*>(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<LPARAM>(&gce));
+ CallServiceSync(MS_GC_EVENT, SESSION_TERMINATE, reinterpret_cast<LPARAM>(&gce));
+
+ return 0;
+}
+
+void TwitterProto::SetChatStatus(int status)
+{
+ GCDEST gcd = { m_szModuleName };
+ gcd.ptszID = const_cast<TCHAR*>(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<LPARAM>(&gce));
+ CallServiceSync(MS_GC_EVENT, SESSION_ONLINE, reinterpret_cast<LPARAM>(&gce));
+ }
+ else CallServiceSync(MS_GC_EVENT, SESSION_OFFLINE, reinterpret_cast<LPARAM>(&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 <http://www.gnu.org/licenses/>.
+*/
+
+#pragma once
+
+#include <windows.h>
+
+#include "resource.h"
+
+#pragma warning(push)
+# pragma warning(disable:4312)
+#include <newpluginapi.h>
+#include <m_avatars.h>
+#include <m_button.h>
+#include <m_chat.h>
+#include <m_clc.h>
+#include <m_clist.h>
+#include <m_clistint.h>
+#include <m_clui.h>
+#include <m_database.h>
+#include <m_history.h>
+#include <m_idle.h>
+#include <m_langpack.h>
+#include <m_message.h>
+#include <m_netlib.h>
+#include <m_options.h>
+#include <m_popup.h>
+#include <m_protocols.h>
+#include <m_protomod.h>
+#include <m_protosvc.h>
+#include <m_skin.h>
+#include <statusmodes.h>
+#include <m_system.h>
+#include <m_system_cpp.h>
+#include <m_userinfo.h>
+#include <m_addcontact.h>
+#include <m_icolib.h>
+#include <m_utils.h>
+#include <m_system_cpp.h>
+#include <m_hotkeys.h>
+#include <win2k.h>
+#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 <http://www.gnu.org/licenses/>.
+*/
+
+#include "common.h"
+#include "proto.h"
+
+void CALLBACK TwitterProto::APC_callback(ULONG_PTR p)
+{
+ reinterpret_cast<TwitterProto*>(p)->LOG("***** Executing APC");
+}
+
+template<typename T>
+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<T*>(dbv.pbVal);
+}
+
+template<typename T>
+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<BYTE*>(&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<LPARAM>(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<twitter_id>(0, m_szModuleName, TWITTER_KEY_SINCEID, 0);
+ dm_since_id_ = db_pod_get<twitter_id>(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<update_avatar> data( static_cast<update_avatar*>(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<twitter_user> friends = twit_.get_friends();
+ s.Unlock();
+
+ for(std::vector<twitter_user>::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<WPARAM>(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<DWORD>(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<DWORD>(i->status.time);
+
+ ccs.hContact = hContact;
+ ccs.szProtoService = PSR_MESSAGE;
+ ccs.wParam = ID_STATUS_ONLINE;
+ ccs.lParam = reinterpret_cast<LPARAM>(&recv);
+ CallService(MS_PROTO_CHAINRECV, 0, reinterpret_cast<LPARAM>(&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 <http://www.gnu.org/licenses/>.
+*/
+
+#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<TCHAR*>(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<search_query*>(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<HANDLE>(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<char*>( CallService(MS_PROTO_GETCONTACTBASEPROTO,
+ reinterpret_cast<WPARAM>(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 <http://www.gnu.org/licenses/>.
+*/
+
+#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 <http://www.gnu.org/licenses/>.
+*/
+
+#pragma once
+
+#include <string>
+
+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 Binary files differnew file mode 100644 index 0000000000..2b6eaa1eae --- /dev/null +++ b/protocols/Twitter/icons/twitter.ico 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 <http://www.gnu.org/licenses/>.
+*/
+
+#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<TwitterProto> 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<TwitterProto*>(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<BYTE*>("Twitter ");
+ upd.cpbBetaVersionPrefix = strlen(reinterpret_cast<char*>(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<BYTE*>( CreateVersionStringPlugin(
+ reinterpret_cast<PLUGININFO*>(&pluginInfo),curr_version));
+ upd.cpbVersion = strlen(reinterpret_cast<char*>(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<CLIST_INTERFACE*>( CallService(
+ MS_CLIST_RETRIEVE_INTERFACE,0,reinterpret_cast<LPARAM>(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<LPARAM>(&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<SIZEOF(g_hEvents); i++)
+ UnhookEvent(g_hEvents[i]);
+
+ return 0;
+}
diff --git a/protocols/Twitter/proto.cpp b/protocols/Twitter/proto.cpp new file mode 100644 index 0000000000..44bd6a3ebc --- /dev/null +++ b/protocols/Twitter/proto.cpp @@ -0,0 +1,528 @@ +/*
+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 <http://www.gnu.org/licenses/>.
+*/
+
+#include "common.h"
+#include "proto.h"
+
+#include "utility.h"
+#include "theme.h"
+#include "ui.h"
+
+#include "m_folders.h"
+#include "m_historyevents.h"
+
+#include <algorithm>
+#include <cstdio>
+#include <ctime>
+#include <direct.h>
+
+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<LPARAM>(pre) };
+ return CallService(MS_PROTO_RECVMSG, 0, reinterpret_cast<LPARAM>(&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<send_direct*>(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<char*>(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<HANDLE>(wParam);
+
+ HWND hDlg = CreateDialogParam(g_hInstance, MAKEINTRESOURCE(IDD_TWEET),
+ (HWND)0, tweet_proc, reinterpret_cast<LPARAM>(this));
+
+ DBVARIANT dbv;
+ if ( !DBGetContactSettingTString(hContact, m_szModuleName, TWITTER_KEY_UN, &dbv)) {
+ SendMessage(hDlg, WM_SETREPLY, reinterpret_cast<WPARAM>(dbv.ptszVal), 0);
+ DBFreeVariant(&dbv);
+ }
+
+ ShowWindow(hDlg, SW_SHOW);
+
+ return 0;
+}
+
+int TwitterProto::VisitHomepage(WPARAM wParam, LPARAM lParam)
+{
+ HANDLE hContact = reinterpret_cast<HANDLE>(wParam);
+
+ DBVARIANT dbv;
+ if ( !DBGetContactSettingString(hContact, m_szModuleName, "Homepage", &dbv)) {
+ CallService(MS_UTILS_OPENURL, 1, reinterpret_cast<LPARAM>(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<LPARAM>(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<HGENMENU>( CallService(
+ MS_CLIST_ADDSTATUSMENUITEM, 0, reinterpret_cast<LPARAM>(&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<HGENMENU>( CallService(
+ MS_CLIST_ADDSTATUSMENUITEM, 0, reinterpret_cast<LPARAM>(&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<LPARAM>(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<LPARAM>(&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<LPARAM>(&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<HANDLE>(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<WPARAM>(&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<WPARAM>(&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<TCHAR*>(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<WPARAM>(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 <http://www.gnu.org/licenses/>.
+*/
+
+#pragma once
+
+#include "utility.h"
+
+#include <m_protoint.h>
+
+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 <http://www.gnu.org/licenses/>.
+*/
+
+#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 <http://www.gnu.org/licenses/>.
+*/
+
+#include "common.h"
+#include "theme.h"
+#include "proto.h"
+
+extern OBJLIST<TwitterProto> 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<SIZEOF(icons); i++)
+ {
+ if (icons[i].defIconID)
+ {
+ mir_snprintf(setting_name,sizeof(setting_name),"%s_%s","Twitter",icons[i].name);
+
+ if (icons[i].section)
+ {
+ mir_snprintf(section_name,sizeof(section_name),"%s/%s/%s",LPGEN("Protocols"),
+ LPGEN("Twitter"), icons[i].section);
+ }
+ else
+ {
+ mir_snprintf(section_name,sizeof(section_name),"%s/%s",LPGEN("Protocols"),
+ LPGEN("Twitter"));
+ }
+
+ sid.pszDescription = (char*)icons[i].descr;
+ sid.iDefaultIndex = -icons[i].defIconID;
+ hIconLibItem[i] = (HANDLE)CallService(MS_SKIN2_ADDICON,0,(LPARAM)&sid);
+ }
+ else // External icons
+ {
+ hIconLibItem[i] = (HANDLE)CallService(MS_SKIN2_GETICONHANDLE,0,
+ (LPARAM)icons[i].section);
+ }
+ }
+}
+
+HANDLE GetIconHandle(const char* name)
+{
+ for(size_t i=0; i<SIZEOF(icons); i++)
+ {
+ if (strcmp(icons[i].name,name) == 0)
+ return hIconLibItem[i];
+ }
+ return 0;
+}
+
+
+
+// Contact List menu stuff
+static HANDLE g_hMenuItems[2];
+static HANDLE g_hMenuEvts[3];
+
+// Helper functions
+static TwitterProto * GetInstanceByHContact(HANDLE hContact)
+{
+ char *proto = reinterpret_cast<char*>( CallService(MS_PROTO_GETCONTACTBASEPROTO,
+ reinterpret_cast<WPARAM>(hContact),0));
+ if (!proto)
+ return 0;
+
+ for(int i=0; i<g_Instances.getCount(); i++)
+ if (!strcmp(proto,g_Instances[i].m_szModuleName))
+ return &g_Instances[i];
+
+ return 0;
+}
+
+template<int (__cdecl TwitterProto::*Fcn)(WPARAM,LPARAM)>
+int GlobalService(WPARAM wParam,LPARAM lParam)
+{
+ TwitterProto *proto = GetInstanceByHContact(reinterpret_cast<HANDLE>(wParam));
+ return proto ? (proto->*Fcn)(wParam,lParam) : 0;
+}
+
+static int PrebuildContactMenu(WPARAM wParam,LPARAM lParam)
+{
+ ShowContactMenus(false);
+
+ TwitterProto *proto = GetInstanceByHContact(reinterpret_cast<HANDLE>(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<HANDLE>(
+ 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<HANDLE>(
+ CallService(MS_CLIST_ADDCONTACTMENUITEM,0,(LPARAM)&mi));
+}
+
+void UninitContactMenus()
+{
+ for(size_t i=0; i<SIZEOF(g_hMenuItems); i++)
+ CallService(MS_CLIST_REMOVECONTACTMENUITEM,(WPARAM)g_hMenuItems[i],0);
+
+ UnhookEvent(g_hMenuEvts[0]);
+ for(size_t i=1; i<SIZEOF(g_hMenuEvts); i++)
+ DestroyServiceFunction(g_hMenuEvts[i]);
+}
+
+void ShowContactMenus(bool show)
+{
+ for(size_t i=0; i<SIZEOF(g_hMenuItems); i++)
+ {
+ CLISTMENUITEM item = { sizeof(item) };
+ item.flags = CMIM_FLAGS | CMIF_NOTOFFLINE;
+ if (!show)
+ item.flags |= CMIF_HIDDEN;
+
+ CallService(MS_CLIST_MODIFYMENUITEM,reinterpret_cast<WPARAM>(g_hMenuItems[i]),
+ reinterpret_cast<LPARAM>(&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 <http://www.gnu.org/licenses/>.
+*/
+
+#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 <boost/shared_ptr.hpp>
+#include <boost/any.hpp>
+#include <boost/spirit/core.hpp>
+#include <boost/spirit/utility/loops.hpp>
+#include <boost/lexical_cast.hpp>
+
+#include <string>
+#include <stack>
+#include <utility>
+#include <deque>
+#include <map>
+
+
+namespace json
+{
+ boost::spirit::int_parser<long long> const
+ longlong_p = boost::spirit::int_parser<long long>();
+
+ // ==========================================================================================================
+ // === 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 <typename Iterator>
+ 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 <typename Iterator>
+ 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 <typename Iterator>
+ 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 <typename Iterator>
+ 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 <typename Iterator>
+ 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 <typename Iterator>
+ 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 <typename Iterator>
+ 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 <typename Iterator>
+ 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 <typename SCANNER>
+ 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 Iterator>
+ 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<Iterator> 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 <http://www.gnu.org/licenses/>.
+*/
+
+//#include "common.h"
+
+#include <windows.h>
+#include <cstring>
+#include <sstream>
+#include <ctime>
+
+#include "twitter.h"
+
+#include "tinyjson.hpp"
+#include <boost/lexical_cast.hpp>
+
+typedef json::grammar<char> js;
+
+// utility functions
+
+template <typename T>
+static T cast_and_decode(boost::any &a,bool allow_null)
+{
+ if (allow_null && a.type() == typeid(void))
+ return T();
+ return boost::any_cast<T>(a);
+}
+
+template <>
+static std::string cast_and_decode<std::string>(boost::any &a,bool allow_null)
+{
+ if (allow_null && a.type() == typeid(void))
+ return std::string();
+ std::string s = boost::any_cast<std::string>(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 <typename T>
+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<T>(*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_user> twitter::get_friends()
+{
+ std::vector<twitter_user> 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<js::array>(*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<js::object>(**i);
+
+ twitter_user user;
+ user.username = retrieve<std::string>(one,"screen_name");
+ user.real_name = retrieve<std::tstring>(one,"name",true);
+ user.profile_image_url = retrieve<std::string>(one,"profile_image_url",true);
+
+ if (one.find("status") != one.end())
+ {
+ js::object &status = retrieve<js::object>(one,"status");
+ user.status.text = retrieve<std::tstring>(status,"text");
+
+ user.status.id = retrieve<long long>(status,"id");
+
+ std::string timestr = retrieve<std::string>(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<js::object>(*var);
+ if (user_info.find("error") != user_info.end())
+ return false;
+
+ info->username = retrieve<std::string>(user_info,"screen_name");
+ info->real_name = retrieve<std::tstring>(user_info,"name",true);
+ info->profile_image_url = retrieve<std::string>(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<js::object>(*var);
+ if (user_info.find("error") != user_info.end())
+ return false;
+
+ info->username = retrieve<std::string>(user_info,"screen_name");
+ info->real_name = retrieve<std::tstring>(user_info,"name",true);
+ info->profile_image_url = retrieve<std::string>(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<js::object>(*var);
+ ret.username = retrieve<std::string>(user_info,"screen_name");
+ ret.real_name = retrieve<std::tstring>(user_info,"name",true);
+ ret.profile_image_url = retrieve<std::string>(user_info,"profile_image_url",true);
+
+ if (user_info.find("status") != user_info.end())
+ {
+ // TODO: fill in more fields
+ const js::object &status = retrieve<js::object>(user_info,"status");
+ ret.status.text = retrieve<std::tstring>(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_user> twitter::get_statuses(int count,twitter_id id)
+{
+ using boost::lexical_cast;
+ std::vector<twitter_user> statuses;
+
+ std::string url = base_url_+"statuses/friends_timeline.json?count="+
+ lexical_cast<std::string>(count);
+ if (id != 0)
+ url += "&since_id="+boost::lexical_cast<std::string>(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<js::array>(*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<js::object>(**i);
+ const js::object &user = retrieve<js::object>(one,"user");
+
+ twitter_user u;
+ u.username = retrieve<std::string>(user,"screen_name");
+
+ u.status.text = retrieve<std::tstring>(one,"text");
+ u.status.id = retrieve<long long>(one,"id");
+ std::string timestr = retrieve<std::string>(one,"created_at");
+ u.status.time = parse_time(timestr);
+
+ statuses.push_back(u);
+ }
+ }
+
+
+ return statuses;
+}
+
+std::vector<twitter_user> twitter::get_direct(twitter_id id)
+{
+ std::vector<twitter_user> messages;
+
+ std::string url = base_url_+"direct_messages.json";
+ if (id != 0)
+ url += "?since_id="+boost::lexical_cast<std::string>(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<js::array>(*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<js::object>(**i);
+
+ twitter_user u;
+ u.username = retrieve<std::string>(one,"sender_screen_name");
+
+ u.status.text = retrieve<std::tstring>(one,"text");
+ u.status.id = retrieve<long long>(one,"id");
+ std::string timestr = retrieve<std::string>(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 <http://www.gnu.org/licenses/>.
+*/
+
+#pragma once
+
+#include <map>
+#include <vector>
+#include <string>
+
+#include "http.h"
+#include <tchar.h>
+
+#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<twitter_user> status_list;
+ typedef std::map<std::tstring,status_list> 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<twitter_user> 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<twitter_user> get_statuses(int count=20,twitter_id id=0);
+
+ void send_direct(const std::tstring &name,const std::tstring &text);
+ std::vector<twitter_user> 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 @@ +<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup Label="ProjectConfigurations">
+ <ProjectConfiguration Include="Debug|Win32">
+ <Configuration>Debug</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Release|Win32">
+ <Configuration>Release</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ </ItemGroup>
+ <PropertyGroup Label="Globals">
+ <ProjectGuid>{DADE9455-DC28-465A-9604-2CA28052B9FB}</ProjectGuid>
+ <RootNamespace>twitter</RootNamespace>
+ <Keyword>Win32Proj</Keyword>
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
+ <ConfigurationType>DynamicLibrary</ConfigurationType>
+ <CharacterSet>Unicode</CharacterSet>
+ <WholeProgramOptimization>true</WholeProgramOptimization>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
+ <ConfigurationType>DynamicLibrary</ConfigurationType>
+ <CharacterSet>Unicode</CharacterSet>
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
+ <ImportGroup Label="ExtensionSettings">
+ </ImportGroup>
+ <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="PropertySheets">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="PropertySheets">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <PropertyGroup Label="UserMacros" />
+ <PropertyGroup>
+ <_ProjectFileVersion>10.0.40219.1</_ProjectFileVersion>
+ <OutDir Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">$(SolutionDir)$(Configuration)\Plugins\</OutDir>
+ <IntDir Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">$(SolutionDir)$(Configuration)\Obj\$(ProjectName)\</IntDir>
+ <LinkIncremental Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</LinkIncremental>
+ <OutDir Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">$(SolutionDir)$(Configuration)\Plugins\</OutDir>
+ <IntDir Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">$(SolutionDir)$(Configuration)\Obj\$(ProjectName)\</IntDir>
+ <LinkIncremental Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">false</LinkIncremental>
+ </PropertyGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <ClCompile>
+ <Optimization>Disabled</Optimization>
+ <AdditionalIncludeDirectories>..\..\include;..\..\..\boost_1_49_0;..\plugins\ExternalAPI;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>WIN32;_WINDOWS;_USRDLL;TWITTER_EXPORTS;_CRT_SECURE_NO_WARNINGS;NOMINMAX;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <MinimalRebuild>true</MinimalRebuild>
+ <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
+ <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
+ <PrecompiledHeader>Use</PrecompiledHeader>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>EditAndContinue</DebugInformationFormat>
+ <PrecompiledHeaderFile>common.h</PrecompiledHeaderFile>
+ </ClCompile>
+ <Link>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ <SubSystem>Windows</SubSystem>
+ <TargetMachine>MachineX86</TargetMachine>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <ClCompile>
+ <AdditionalIncludeDirectories>..\..\include;..\..\..\boost_1_49_0;..\plugins\ExternalAPI;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>WIN32;NDEBUG;_WINDOWS;_USRDLL;TWITTER_EXPORTS;_CRT_SECURE_NO_WARNINGS;NOMINMAX;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
+ <PrecompiledHeader>Use</PrecompiledHeader>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
+ <Optimization>Full</Optimization>
+ <FavorSizeOrSpeed>Size</FavorSizeOrSpeed>
+ <StringPooling>true</StringPooling>
+ <PrecompiledHeaderFile>common.h</PrecompiledHeaderFile>
+ </ClCompile>
+ <Link>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ <SubSystem>Windows</SubSystem>
+ <OptimizeReferences>true</OptimizeReferences>
+ <EnableCOMDATFolding>true</EnableCOMDATFolding>
+ <TargetMachine>MachineX86</TargetMachine>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemGroup>
+ <ClCompile Include="chat.cpp" />
+ <ClCompile Include="connection.cpp" />
+ <ClCompile Include="contacts.cpp" />
+ <ClCompile Include="http.cpp" />
+ <ClCompile Include="main.cpp">
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">Create</PrecompiledHeader>
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">Create</PrecompiledHeader>
+ </ClCompile>
+ <ClCompile Include="proto.cpp" />
+ <ClCompile Include="stubs.cpp" />
+ <ClCompile Include="theme.cpp" />
+ <ClCompile Include="twitter.cpp">
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">NotUsing</PrecompiledHeader>
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">NotUsing</PrecompiledHeader>
+ </ClCompile>
+ <ClCompile Include="ui.cpp" />
+ <ClCompile Include="utility.cpp" />
+ </ItemGroup>
+ <ItemGroup>
+ <ClInclude Include="common.h" />
+ <ClInclude Include="http.h" />
+ <ClInclude Include="proto.h" />
+ <ClInclude Include="resource.h" />
+ <ClInclude Include="theme.h" />
+ <ClInclude Include="tinyjson.hpp" />
+ <ClInclude Include="twitter.h" />
+ <ClInclude Include="ui.h" />
+ <ClInclude Include="utility.h" />
+ <ClInclude Include="version.h" />
+ </ItemGroup>
+ <ItemGroup>
+ <None Include="icons\twitter.ico" />
+ </ItemGroup>
+ <ItemGroup>
+ <ResourceCompile Include="twitter.rc" />
+ </ItemGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
+ <ImportGroup Label="ExtensionTargets">
+ </ImportGroup>
+</Project>
\ 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 @@ +<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup>
+ <Filter Include="Source Files">
+ <UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
+ <Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
+ </Filter>
+ <Filter Include="Header Files">
+ <UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
+ <Extensions>h;hpp;hxx;hm;inl;inc;xsd</Extensions>
+ </Filter>
+ <Filter Include="Resource Files">
+ <UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
+ <Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav</Extensions>
+ </Filter>
+ </ItemGroup>
+ <ItemGroup>
+ <ClCompile Include="chat.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="connection.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="contacts.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="http.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="main.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="proto.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="stubs.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="theme.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="twitter.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="ui.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="utility.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ </ItemGroup>
+ <ItemGroup>
+ <ClInclude Include="common.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="http.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="proto.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="resource.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="theme.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="tinyjson.hpp">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="twitter.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="ui.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="utility.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="version.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ </ItemGroup>
+ <ItemGroup>
+ <None Include="icons\twitter.ico">
+ <Filter>Resource Files</Filter>
+ </None>
+ </ItemGroup>
+ <ItemGroup>
+ <ResourceCompile Include="twitter.rc">
+ <Filter>Resource Files</Filter>
+ </ResourceCompile>
+ </ItemGroup>
+</Project>
\ 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 <http://www.gnu.org/licenses/>.
+*/
+
+#include "common.h"
+#include "ui.h"
+
+#include <cstdio>
+#include <commctrl.h>
+
+#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<TwitterProto*>(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<LPARAM>(dbv.pszVal));
+ SetDlgItemTextA(hwndDlg, IDC_PW, dbv.pszVal);
+ DBFreeVariant(&dbv);
+ }
+
+ for(size_t i=0; i<SIZEOF(sites); i++)
+ {
+ SendDlgItemMessage(hwndDlg, IDC_SERVER, CB_ADDSTRING, 0,
+ reinterpret_cast<LPARAM>(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<LPARAM>
+ ("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<NMHDR*>(lParam)->code == PSN_APPLY)
+ {
+ proto = reinterpret_cast<TwitterProto*>(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<LPARAM>(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<TwitterProto*>(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<TwitterProto*>(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<TwitterProto*>(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<LPARAM>(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<SIZEOF(sites); i++)
+ {
+ SendDlgItemMessage(hwndDlg, IDC_BASEURL, CB_ADDSTRING, 0,
+ reinterpret_cast<LPARAM>(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<NMHDR*>(lParam)->code == PSN_APPLY)
+ {
+ proto = reinterpret_cast<TwitterProto*>(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<LPARAM>(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<contact; i++)
+ hContact = (HANDLE)CallService(MS_DB_CONTACT_FINDNEXT, (WPARAM)hContact, 0);
+ }
+
+ // Pick a random quote
+ int q = rand() % SIZEOF(quotes);
+ _tcsncpy(popup.lptzContactName, quotes[q].name, MAX_CONTACTNAME);
+ _tcsncpy(popup.lptzText, quotes[q].text, MAX_SECONDLINE);
+
+ popup.lchContact = hContact;
+ popup.iSeconds = get_timeout(hwndDlg);
+ popup.colorText = get_text_color(hwndDlg, false);
+ popup.colorBack = get_back_color(hwndDlg, false);
+
+ CallService(MS_POPUP_ADDPOPUPT, reinterpret_cast<WPARAM>(&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<TwitterProto*>(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<NMHDR*>(lParam)->code == PSN_APPLY)
+ {
+ proto = reinterpret_cast<TwitterProto*>(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 <http://www.gnu.org/licenses/>.
+*/
+
+#pragma once
+
+#include <windows.h>
+
+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 <http://www.gnu.org/licenses/>.
+*/
+
+#include "common.h"
+#include "utility.h"
+
+#include <io.h>
+
+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<NETLIBHTTPREQUEST*>(CallService( MS_NETLIB_HTTPTRANSACTION,
+ reinterpret_cast<WPARAM>(handle_), reinterpret_cast<LPARAM>(&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<char*>(url.c_str());
+
+ resp = reinterpret_cast<NETLIBHTTPREQUEST*>(CallService( MS_NETLIB_HTTPTRANSACTION,
+ reinterpret_cast<WPARAM>(hNetlib), reinterpret_cast<LPARAM>(&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<SIZEOF(formats); i++)
+ {
+ if (ext == formats[i].ext)
+ return formats[i].fmt;
+ }
+
+ return PA_FORMAT_UNKNOWN;
+}
\ No newline at end of file diff --git a/protocols/Twitter/utility.h b/protocols/Twitter/utility.h new file mode 100644 index 0000000000..b68a4fcb9d --- /dev/null +++ b/protocols/Twitter/utility.h @@ -0,0 +1,107 @@ +/*
+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 <http://www.gnu.org/licenses/>.
+*/
+
+#pragma once
+
+#include "http.h"
+#include "twitter.h"
+
+template<typename T>
+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<typename T>
+void HookProtoEvent(const char* evt, int (__cdecl T::*eventProc)(WPARAM,LPARAM), T *self)
+{
+ ::HookEventObj(evt,(MIRANDAHOOKOBJ)*(void**)&eventProc,self);
+}
+
+template<typename T>
+HANDLE ForkThreadEx(void (__cdecl T::*thread)(void*),T *self,void *data = 0)
+{
+ return reinterpret_cast<HANDLE>( mir_forkthreadowner(
+ (pThreadFuncOwner)*(void**)&thread,self,data,0));
+}
+
+template<typename T>
+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 <http://www.gnu.org/licenses/>.
+*/
+
+#pragma once
+
+#define __VERSION_DWORD PLUGIN_MAKE_VERSION(0, 0, 8, 4)
\ No newline at end of file |