summaryrefslogtreecommitdiff
path: root/plugins/Twitter
diff options
context:
space:
mode:
Diffstat (limited to 'plugins/Twitter')
-rw-r--r--plugins/Twitter/LICENSE.txt674
-rw-r--r--plugins/Twitter/README.txt10
-rw-r--r--plugins/Twitter/chat.cpp206
-rw-r--r--plugins/Twitter/common.h80
-rw-r--r--plugins/Twitter/connection.cpp438
-rw-r--r--plugins/Twitter/contacts.cpp288
-rw-r--r--plugins/Twitter/http.cpp37
-rw-r--r--plugins/Twitter/http.h39
-rw-r--r--plugins/Twitter/icons/twitter.icobin0 -> 1406 bytes
-rw-r--r--plugins/Twitter/m_folders.h282
-rw-r--r--plugins/Twitter/m_historyevents.h453
-rw-r--r--plugins/Twitter/m_updater.h146
-rw-r--r--plugins/Twitter/main.cpp168
-rw-r--r--plugins/Twitter/proto.cpp528
-rw-r--r--plugins/Twitter/proto.h179
-rw-r--r--plugins/Twitter/resource.h47
-rw-r--r--plugins/Twitter/stubs.cpp140
-rw-r--r--plugins/Twitter/theme.cpp183
-rw-r--r--plugins/Twitter/theme.h25
-rw-r--r--plugins/Twitter/tinyjson.hpp586
-rw-r--r--plugins/Twitter/twitter.cpp380
-rw-r--r--plugins/Twitter/twitter.h89
-rw-r--r--plugins/Twitter/twitter.rc224
-rw-r--r--plugins/Twitter/twitter.sln26
-rw-r--r--plugins/Twitter/twitter.vcproj439
-rw-r--r--plugins/Twitter/twitter.vcxproj127
-rw-r--r--plugins/Twitter/twitter.vcxproj.filters94
-rw-r--r--plugins/Twitter/ui.cpp529
-rw-r--r--plugins/Twitter/ui.h25
-rw-r--r--plugins/Twitter/utility.cpp133
-rw-r--r--plugins/Twitter/utility.h107
-rw-r--r--plugins/Twitter/version.h20
32 files changed, 6702 insertions, 0 deletions
diff --git a/plugins/Twitter/LICENSE.txt b/plugins/Twitter/LICENSE.txt
new file mode 100644
index 0000000000..818433ecc0
--- /dev/null
+++ b/plugins/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/plugins/Twitter/README.txt b/plugins/Twitter/README.txt
new file mode 100644
index 0000000000..b229b10796
--- /dev/null
+++ b/plugins/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/plugins/Twitter/chat.cpp b/plugins/Twitter/chat.cpp
new file mode 100644
index 0000000000..46496259f2
--- /dev/null
+++ b/plugins/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 = 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/plugins/Twitter/common.h b/plugins/Twitter/common.h
new file mode 100644
index 0000000000..751c1d22d6
--- /dev/null
+++ b/plugins/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/plugins/Twitter/connection.cpp b/plugins/Twitter/connection.cpp
new file mode 100644
index 0000000000..18715e5b84
--- /dev/null
+++ b/plugins/Twitter/connection.cpp
@@ -0,0 +1,438 @@
+/*
+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::wstring &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/plugins/Twitter/contacts.cpp b/plugins/Twitter/contacts.cpp
new file mode 100644
index 0000000000..03aad22601
--- /dev/null
+++ b/plugins/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/plugins/Twitter/http.cpp b/plugins/Twitter/http.cpp
new file mode 100644
index 0000000000..aaf9210ac4
--- /dev/null
+++ b/plugins/Twitter/http.cpp
@@ -0,0 +1,37 @@
+/*
+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 *encoded = (char*)CallService( MS_NETLIB_URLENCODE, 0, ( LPARAM )( char* )_T2A(s.c_str()));
+ std::string ret = encoded;
+ HeapFree(GetProcessHeap(),0,encoded);
+
+ return ret;
+}
diff --git a/plugins/Twitter/http.h b/plugins/Twitter/http.h
new file mode 100644
index 0000000000..04f5468b30
--- /dev/null
+++ b/plugins/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/plugins/Twitter/icons/twitter.ico b/plugins/Twitter/icons/twitter.ico
new file mode 100644
index 0000000000..2b6eaa1eae
--- /dev/null
+++ b/plugins/Twitter/icons/twitter.ico
Binary files differ
diff --git a/plugins/Twitter/m_folders.h b/plugins/Twitter/m_folders.h
new file mode 100644
index 0000000000..54d19589c2
--- /dev/null
+++ b/plugins/Twitter/m_folders.h
@@ -0,0 +1,282 @@
+/*
+Custom profile folders plugin for Miranda IM
+
+Copyright © 2005 Cristian Libotean
+
+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, write to the Free Software
+Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+*/
+
+#ifndef M_CUSTOM_FOLDERS_H
+#define M_CUSTOM_FOLDERS_H
+
+#define FOLDERS_API 501 //dunno why it's here but it is :)
+
+#define PROFILE_PATH "%profile_path%"
+#define CURRENT_PROFILE "%current_profile%"
+#define MIRANDA_PATH "%miranda_path%"
+#define PLUGINS_PATH "%miranda_path%" "\\plugins"
+
+#define TO_WIDE(x) L ## x
+
+#define PROFILE_PATHW L"%profile_path%"
+#define CURRENT_PROFILEW L"%current_profile%"
+#define MIRANDA_PATHW L"%miranda_path%"
+
+#define FOLDER_AVATARS PROFILE_PATH "\\" CURRENT_PROFILE "\\avatars"
+#define FOLDER_VCARDS PROFILE_PATH "\\" CURRENT_PROFILE "\\vcards"
+#define FOLDER_LOGS PROFILE_PATH "\\" CURRENT_PROFILE "\\logs"
+#define FOLDER_RECEIVED_FILES PROFILE_PATH "\\" CURRENT_PROFILE "\\received files"
+#define FOLDER_DOCS MIRANDA_PATH "\\" "docs"
+
+#define FOLDER_CONFIG PLUGINS_PATH "\\" "config"
+
+#define FOLDER_SCRIPTS MIRANDA_PATH "\\" "scripts"
+
+#define FOLDER_UPDATES MIRANDA_PATH "\\" "updates"
+
+#define FOLDER_CUSTOMIZE MIRANDA_PATH "\\" "customize"
+#define FOLDER_CUSTOMIZE_SOUNDS FOLDER_CUSTOMIZE "\\sounds"
+#define FOLDER_CUSTOMIZE_ICONS FOLDER_CUSTOMIZE "\\icons"
+#define FOLDER_CUSTOMIZE_SMILEYS FOLDER_CUSTOMIZE "\\smileys"
+#define FOLDER_CUSTOMIZE_SKINS FOLDER_CUSTOMIZE "\\skins"
+#define FOLDER_CUSTOMIZE_THEMES FOLDER_CUSTOMIZE "\\themes"
+
+
+#define FOLDERS_NAME_MAX_SIZE 64 //maximum name and section size
+
+#define FF_UNICODE 0x00000001
+
+#if defined (UNICODE)
+ #define FF_TCHAR FF_UNICODE
+#else
+ #define FF_TCHAR 0
+#endif
+
+typedef struct{
+ int cbSize; //size of struct
+ char szSection[FOLDERS_NAME_MAX_SIZE]; //section name, if it doesn't exist it will be created otherwise it will just add this entry to it
+ char szName[FOLDERS_NAME_MAX_SIZE]; //entry name - will be shown in options
+ union{
+ const char *szFormat; //default string format. Fallback string in case there's no entry in the database for this folder. This should be the initial value for the path, users will be able to change it later.
+ const wchar_t *szFormatW; //String is dup()'d so you can free it later. If you set the unicode string don't forget to set the flag accordingly.
+ const TCHAR *szFormatT;
+ };
+ DWORD flags; //FF_* flags
+} FOLDERSDATA;
+
+/*Folders/Register/Path service
+ wParam - not used, must be 0
+ lParam - (LPARAM) (const FOLDERDATA *) - Data structure filled with
+ the necessary information.
+ Returns a handle to the registered path or 0 on error.
+ You need to use this to call the other services.
+*/
+#define MS_FOLDERS_REGISTER_PATH "Folders/Register/Path"
+
+/*Folders/Get/PathSize service
+ wParam - (WPARAM) (int) - handle to registered path
+ lParam - (LPARAM) (int *) - pointer to the variable that receives the size of the path
+ string (not including the null character). Depending on the flags set when creating the path
+ it will either call strlen() or wcslen() to get the length of the string.
+ Returns the size of the buffer.
+*/
+#define MS_FOLDERS_GET_SIZE "Folders/Get/PathSize"
+
+typedef struct{
+ int cbSize;
+ int nMaxPathSize; //maximum size of buffer. This represents the number of characters that can be copied to it (so for unicode strings you don't send the number of bytes but the length of the string).
+ union{
+ char *szPath; //pointer to the buffer that receives the path without the last "\\"
+ wchar_t *szPathW; //unicode version of the buffer.
+ TCHAR *szPathT;
+ };
+} FOLDERSGETDATA;
+
+/*Folders/Get/Path service
+ wParam - (WPARAM) (int) - handle to registered path
+ lParam - (LPARAM) (FOLDERSGETDATA *) pointer to a FOLDERSGETDATA that has all the relevant fields filled.
+ Should return 0 on success, or nonzero otherwise.
+*/
+#define MS_FOLDERS_GET_PATH "Folders/Get/Path"
+
+typedef struct{
+ int cbSize;
+ union{
+ char **szPath; //address of a string variable (char *) or (wchar_t*) where the path should be stored (the last \ won't be copied).
+ wchar_t **szPathW; //unicode version of string.
+ TCHAR **szPathT;
+ };
+} FOLDERSGETALLOCDATA;
+
+/*Folders/GetRelativePath/Alloc service
+ wParam - (WPARAM) (int) - Handle to registered path
+ lParam - (LPARAM) (FOLDERSALLOCDATA *) data
+ This service is the same as MS_FOLDERS_GET_PATH with the difference that this service
+ allocates the needed space for the buffer. It uses miranda's memory functions for that and you need
+ to use those to free the resulting buffer.
+ Should return 0 on success, or nonzero otherwise. Currently it only returns 0.
+*/
+#define MS_FOLDERS_GET_PATH_ALLOC "Folders/Get/Path/Alloc"
+
+
+/*Folders/On/Path/Changed
+ wParam - (WPARAM) 0
+ lParam - (LPARAM) 0
+ Triggered when the folders change, you should reget the paths you registered.
+*/
+#define ME_FOLDERS_PATH_CHANGED "Folders/On/Path/Changed"
+
+#ifndef FOLDERS_NO_HELPER_FUNCTIONS
+
+#ifndef M_UTILS_H__
+#error The helper functions require that m_utils.h be included in the project. Please include that file if you want to use the helper functions. If you don''t want to use the functions just define FOLDERS_NO_HELPER_FUNCTIONS.
+#endif
+//#include "../../../include/newpluginapi.h"
+
+__inline static HANDLE FoldersRegisterCustomPath(const char *section, const char *name, const char *defaultPath)
+{
+ FOLDERSDATA fd = {0};
+ if (!ServiceExists(MS_FOLDERS_REGISTER_PATH)) return 0;
+ fd.cbSize = sizeof(FOLDERSDATA);
+ strncpy(fd.szSection, section, FOLDERS_NAME_MAX_SIZE);
+ fd.szSection[FOLDERS_NAME_MAX_SIZE - 1] = '\0';
+ strncpy(fd.szName, name, FOLDERS_NAME_MAX_SIZE);
+ fd.szName[FOLDERS_NAME_MAX_SIZE - 1] = '\0';
+ fd.szFormat = defaultPath;
+ return (HANDLE) CallService(MS_FOLDERS_REGISTER_PATH, 0, (LPARAM) &fd);
+}
+
+__inline static HANDLE FoldersRegisterCustomPathW(const char *section, const char *name, const wchar_t *defaultPathW)
+{
+ FOLDERSDATA fd = {0};
+ if (!ServiceExists(MS_FOLDERS_REGISTER_PATH)) return 0;
+ fd.cbSize = sizeof(FOLDERSDATA);
+ strncpy(fd.szSection, section, FOLDERS_NAME_MAX_SIZE);
+ fd.szSection[FOLDERS_NAME_MAX_SIZE - 1] = '\0'; //make sure it's NULL terminated
+ strncpy(fd.szName, name, FOLDERS_NAME_MAX_SIZE);
+ fd.szName[FOLDERS_NAME_MAX_SIZE - 1] = '\0'; //make sure it's NULL terminated
+ fd.szFormatW = defaultPathW;
+ fd.flags = FF_UNICODE;
+ return (HANDLE) CallService(MS_FOLDERS_REGISTER_PATH, 0, (LPARAM) &fd);
+}
+
+__inline static int FoldersGetCustomPath(HANDLE hFolderEntry, char *path, const int size, const char *notFound)
+{
+ FOLDERSGETDATA fgd = {0};
+ int res;
+ fgd.cbSize = sizeof(FOLDERSGETDATA);
+ fgd.nMaxPathSize = size;
+ fgd.szPath = path;
+ res = CallService(MS_FOLDERS_GET_PATH, (WPARAM) hFolderEntry, (LPARAM) &fgd);
+ if (res)
+ {
+ char buffer[MAX_PATH];
+ CallService(MS_UTILS_PATHTOABSOLUTE, (WPARAM) notFound, (LPARAM) buffer);
+ mir_snprintf(path, size, "%s", buffer);
+ }
+
+ return res;
+}
+
+__inline static int FoldersGetCustomPathW(HANDLE hFolderEntry, wchar_t *pathW, const int count, const wchar_t *notFoundW)
+{
+ FOLDERSGETDATA fgd = {0};
+ int res;
+ fgd.cbSize = sizeof(FOLDERSGETDATA);
+ fgd.nMaxPathSize = count;
+ fgd.szPathW = pathW;
+ res = CallService(MS_FOLDERS_GET_PATH, (WPARAM) hFolderEntry, (LPARAM) &fgd);
+ if (res)
+ {
+ wcsncpy(pathW, notFoundW, count);
+ pathW[count - 1] = '\0';
+ }
+
+ return res;
+}
+
+__inline static int FoldersGetCustomPathEx(HANDLE hFolderEntry, char *path, const int size, char *notFound, char *fileName)
+{
+ FOLDERSGETDATA fgd = {0};
+ int res;
+ fgd.cbSize = sizeof(FOLDERSGETDATA);
+ fgd.nMaxPathSize = size;
+ fgd.szPath = path;
+ res = CallService(MS_FOLDERS_GET_PATH, (WPARAM) hFolderEntry, (LPARAM) &fgd);
+ if (res)
+ {
+ char buffer[MAX_PATH];
+ CallService(MS_UTILS_PATHTOABSOLUTE, (WPARAM) notFound, (LPARAM) buffer);
+ mir_snprintf(path, size, "%s", buffer);
+ }
+ if (strlen(path) > 0)
+ {
+ strcat(path, "\\");
+ }
+ else{
+ path[0] = '\0';
+ }
+
+ if (fileName)
+ {
+ strcat(path, fileName);
+ }
+
+ return res;
+}
+
+__inline static int FoldersGetCustomPathExW(HANDLE hFolderEntry, wchar_t *pathW, const int count, wchar_t *notFoundW, wchar_t *fileNameW)
+{
+ FOLDERSGETDATA fgd = {0};
+ int res;
+ fgd.cbSize = sizeof(FOLDERSGETDATA);
+ fgd.nMaxPathSize = count;
+ fgd.szPathW = pathW;
+ res = CallService(MS_FOLDERS_GET_PATH, (WPARAM) hFolderEntry, (LPARAM) &fgd);
+ if (res)
+ {
+ wcsncpy(pathW, notFoundW, count);
+ pathW[count - 1] = '\0';
+ }
+
+ if (wcslen(pathW) > 0)
+ {
+ wcscat(pathW, L"\\");
+ }
+ else{
+ pathW[0] = L'\0';
+ }
+
+ if (fileNameW)
+ {
+ wcscat(pathW, fileNameW);
+ }
+
+ return res;
+}
+
+# ifdef _UNICODE
+# define FoldersGetCustomPathT FoldersGetCustomPathW
+# define FoldersGetCustomPathExT FoldersGetCustomPathExW
+# define FoldersRegisterCustomPathT FoldersRegisterCustomPathW
+#else
+# define FoldersGetCustomPathT FoldersGetCustomPath
+# define FoldersGetCustomPathExT FoldersGetCustomPath
+# define FoldersRegisterCustomPathT FoldersRegisterCustomPath
+#endif
+
+#endif
+
+#endif //M_CUSTOM_FOLDERS_H
diff --git a/plugins/Twitter/m_historyevents.h b/plugins/Twitter/m_historyevents.h
new file mode 100644
index 0000000000..aa44637861
--- /dev/null
+++ b/plugins/Twitter/m_historyevents.h
@@ -0,0 +1,453 @@
+/*
+Copyright (C) 2006 Ricardo Pescuma Domenecci
+
+This is free software; you can redistribute it and/or
+modify it under the terms of the GNU Library General Public
+License as published by the Free Software Foundation; either
+version 2 of the License, or (at your option) any later version.
+
+This 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
+Library General Public License for more details.
+
+You should have received a copy of the GNU Library General Public
+License along with this file; see the file license.txt. If
+not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+Boston, MA 02111-1307, USA.
+*/
+
+
+#ifndef __M_HISTORYEVENTS_H__
+# define __M_HISTORYEVENTS_H__
+
+
+#define MIID_HISTORYEVENTS { 0xc8be8543, 0x6618, 0x4030, { 0x85, 0xcf, 0x90, 0x82, 0xc7, 0xde, 0x7f, 0xf7 } }
+
+
+#define HISTORYEVENTS_FORMAT_CHAR 1
+#define HISTORYEVENTS_FORMAT_WCHAR 2
+#define HISTORYEVENTS_FORMAT_RICH_TEXT 4
+#define HISTORYEVENTS_FORMAT_HTML 8
+
+#define HISTORYEVENTS_FLAG_DEFAULT (1 << 0) // Is a miranda core event type
+#define HISTORYEVENTS_FLAG_SHOW_IM_SRMM (1 << 1) // If this event has to be shown in srmm dialog
+#define HISTORYEVENTS_FLAG_USE_SENT_FLAG (1 << 2) // Means that it can be a sent or received and uses DBEF_SENT to mark that
+#define HISTORYEVENTS_FLAG_EXPECT_CONTACT_NAME_BEFORE (1 << 3) // Means that who is drawing this should draw the contact name before the text
+#define HISTORYEVENTS_FLAG_ONLY_LOG_IF_SRMM_OPEN (1 << 4) // If this event will be logged only if the message window is open
+#define HISTORYEVENTS_FLAG_FLASH_MSG_WINDOW (1 << 5) // If this event will trigger the openning/flashing of the message window
+#define HISTORYEVENTS_REGISTERED_IN_ICOLIB (9 << 16) // If the icon is a name already registered in icolib
+#define HISTORYEVENTS_FLAG_KEEP_ONE_YEAR (1 << 8) // By default store in db for 1 year
+#define HISTORYEVENTS_FLAG_KEEP_SIX_MONTHS (2 << 8) // By default store in db for 6 months
+#define HISTORYEVENTS_FLAG_KEEP_ONE_MONTH (3 << 8) // By default store in db for 1 month
+#define HISTORYEVENTS_FLAG_KEEP_ONE_WEEK (4 << 8) // By default store in db for 1 week
+#define HISTORYEVENTS_FLAG_KEEP_ONE_DAY (5 << 8) // By default store in db for 1 day
+#define HISTORYEVENTS_FLAG_KEEP_FOR_SRMM (6 << 8) // By default store in db only enought for message log
+#define HISTORYEVENTS_FLAG_KEEP_MAX_TEN (7 << 8) // By default store in db max 10 entries
+#define HISTORYEVENTS_FLAG_KEEP_MAX_HUNDRED (8 << 8) // By default store in db for 100 entries
+#define HISTORYEVENTS_FLAG_KEEP_DONT (9 << 8) // By default don't store in db (aka ignore it)
+
+
+// This function must be implemented by subscribers. It must return a pointer or NULL
+// to say it can't handle the text
+typedef void * (*fGetHistoryEventText)(HANDLE hContact, HANDLE hDbEvent, DBEVENTINFO *dbe, int format);
+
+typedef struct {
+ int cbSize;
+ char *module;
+ char *name; // Internal event name
+ char *description; // Will be translated. When retrieving it is already translated
+ WORD eventType; // The event type it can handle
+ union {
+ HICON defaultIcon;
+ char * defaultIconName; // if HISTORYEVENTS_REGISTERED_IN_ICOLIB is set. Always use this one when retrieving
+ };
+ int supports; // What kind of return is supported - or of HISTORYEVENTS_FORMAT_*
+ int flags; // or of HISTORYEVENTS_FLAG_*
+ fGetHistoryEventText pfGetHistoryEventText; // NULL to use default get text (similar to message, without extra format)
+
+ // Aditional data if wants to use add to history services
+ char **templates; // Each entry is: "Name\nDefault\n%var%\tDescription\n%var%\tDescription\n%var%\tDescription"
+ int numTemplates;
+
+} HISTORY_EVENT_HANDLER;
+
+
+/*
+Get the number of registered events
+
+wParam: ignored
+lParam: ignored
+Return: The number of events registered with the plugin
+*/
+#define MS_HISTORYEVENTS_GET_COUNT "HistoryEvents/GetCount"
+
+
+/*
+Get an event by number or by type.
+To retrieve by number, pass -1 as type. To retrieve by type, pass -1 as number.
+
+wParam: (int) event number
+lParam: (int) event type
+Return: (const HISTORY_EVENT_HANDLER *) if the event exists, NULL otherwise. Don't change the
+ returned strunc: it is a pointer to the internall struct.
+*/
+#define MS_HISTORYEVENTS_GET_EVENT "HistoryEvents/GetEvent"
+
+
+/*
+Register a plugin that can handle an event type. This must be called during the call to the
+Load function of the plugin. In ModulesLoaded callback all plugins have to be already registered,
+so srmm and history modules can query then.
+
+wParam: HISTORY_EVENT_HANDLER *
+lParam: ignored
+Return: 0 for success
+*/
+#define MS_HISTORYEVENTS_REGISTER "HistoryEvents/Register"
+
+
+typedef struct {
+ int cbSize;
+ HANDLE hDbEvent;
+ DBEVENTINFO *dbe; // Optional
+ int format; // one of HISTORYEVENTS_FORMAT_*
+
+} HISTORY_EVENT_PARAM;
+
+/*
+Check if an event can be handled by any subscribers
+
+wParam: WORD - event type
+lParam: ignored
+Return: BOOL
+*/
+#define MS_HISTORYEVENTS_CAN_HANDLE "HistoryEvents/CanHandle"
+
+/*
+Get the icon for a history event type
+
+wParam: WORD - event type
+lParam: BOOL - TRUE to copy the icon (should be released with DestroyObject),
+ FALSE to use icolib one (should be released with MS_HISTORYEVENTS_RELEASE_ICON)
+Return: HICON
+*/
+#define MS_HISTORYEVENTS_GET_ICON "HistoryEvents/GetIcon"
+
+/*
+Get the flags for a history event type
+
+wParam: WORD - event type
+lParam: ignored
+Return: int - or of HISTORYEVENTS_FLAG_* or -1 if error
+*/
+#define MS_HISTORYEVENTS_GET_FLAGS "HistoryEvents/GetFlags"
+
+/*
+Release the icon for a history event type. This is really just a forward to icolib
+
+wParam: HICON
+lParam: ignored
+*/
+#define MS_HISTORYEVENTS_RELEASE_ICON "Skin2/Icons/ReleaseIcon"
+
+/*
+Get the text for a history event type
+
+wParam: HISTORY_EVENT_PARAM *
+lParam: ignored
+Return: char * or wchar * depending on sent flags. Free with mir_free or MS_HISTORYEVENTS_RELEASE_TEXT
+*/
+#define MS_HISTORYEVENTS_GET_TEXT "HistoryEvents/GetText"
+
+/*
+Release the text for a history event type. Internally is just a call to mir_free
+
+wParam: char * or wchar *
+lParam: ignored
+*/
+#define MS_HISTORYEVENTS_RELEASE_TEXT "HistoryEvents/ReleaseText"
+
+
+
+typedef struct {
+ int cbSize;
+ HANDLE hContact;
+ WORD eventType;
+ int templateNum;
+ TCHAR **variables;
+ int numVariables;
+ PBYTE additionalData;
+ int additionalDataSize;
+ int flags; // Flags for the event type
+ DWORD timestamp; // 0 for now
+ BOOL addToMetaToo;
+} HISTORY_EVENT_ADD;
+
+/*
+Add an registered event to the history. This is a helper service
+
+wParam: HISTORY_EVENT_ADD
+lParam: ignored
+Return: HANDLE to the db event
+*/
+#define MS_HISTORYEVENTS_ADD_TO_HISTORY "HistoryEvents/AddToHistory"
+
+/*
+Check if a template is enabled
+
+wParam: event type
+lParam: template num
+Return: TRUE or FALSE
+*/
+#define MS_HISTORYEVENTS_IS_ENABLED_TEMPLATE "HistoryEvents/IsEnabledTemplate"
+
+
+
+// Helper functions //////////////////////////////////////////////////////////////////////////////
+
+
+
+
+static int HistoryEvents_Register(char *module, char *name, char *description, int eventType, HICON defaultIcon,
+ int supports, int flags, fGetHistoryEventText pfGetHistoryEventText)
+{
+ HISTORY_EVENT_HANDLER heh = {0};
+
+ if (!ServiceExists(MS_HISTORYEVENTS_REGISTER))
+ return 1;
+
+ heh.cbSize = sizeof(heh);
+ heh.module = module;
+ heh.name = name;
+ heh.description = description;
+ heh.eventType = eventType;
+ heh.defaultIcon = defaultIcon;
+ heh.supports = supports;
+ heh.flags = flags;
+ heh.pfGetHistoryEventText = pfGetHistoryEventText;
+ return CallService(MS_HISTORYEVENTS_REGISTER, (WPARAM) &heh, 0);
+}
+
+static int HistoryEvents_RegisterWithTemplates(char *module, char *name, char *description, int eventType, HICON defaultIcon,
+ int supports, int flags, fGetHistoryEventText pfGetHistoryEventText,
+ char **templates, int numTemplates)
+{
+ HISTORY_EVENT_HANDLER heh = {0};
+
+ if (!ServiceExists(MS_HISTORYEVENTS_REGISTER))
+ return 1;
+
+ heh.cbSize = sizeof(heh);
+ heh.module = module;
+ heh.name = name;
+ heh.description = description;
+ heh.eventType = eventType;
+ heh.defaultIcon = defaultIcon;
+ heh.supports = supports;
+ heh.flags = flags;
+ heh.pfGetHistoryEventText = pfGetHistoryEventText;
+ heh.templates = templates;
+ heh.numTemplates = numTemplates;
+ return CallService(MS_HISTORYEVENTS_REGISTER, (WPARAM) &heh, 0);
+}
+
+static int HistoryEvents_RegisterMessageStyle(char *module, char *name, char *description, int eventType, HICON defaultIcon,
+ int flags, char **templates, int numTemplates)
+{
+ HISTORY_EVENT_HANDLER heh = {0};
+
+ if (!ServiceExists(MS_HISTORYEVENTS_REGISTER))
+ return 1;
+
+ heh.cbSize = sizeof(heh);
+ heh.module = module;
+ heh.name = name;
+ heh.description = description;
+ heh.eventType = eventType;
+ heh.defaultIcon = defaultIcon;
+ heh.flags = flags;
+ heh.templates = templates;
+ heh.numTemplates = numTemplates;
+ return CallService(MS_HISTORYEVENTS_REGISTER, (WPARAM) &heh, 0);
+}
+
+static BOOL HistoryEvents_CanHandle(WORD eventType)
+{
+ if (!ServiceExists(MS_HISTORYEVENTS_CAN_HANDLE))
+ return FALSE;
+
+ return (BOOL) CallService(MS_HISTORYEVENTS_CAN_HANDLE, (WPARAM) eventType, 0);
+}
+
+static HICON HistoryEvents_GetIcon(WORD eventType)
+{
+ if (!ServiceExists(MS_HISTORYEVENTS_GET_ICON))
+ return NULL;
+
+ return (HICON) CallService(MS_HISTORYEVENTS_GET_ICON, (WPARAM) eventType, 0);
+}
+
+static int HistoryEvents_GetFlags(WORD eventType)
+{
+ if (!ServiceExists(MS_HISTORYEVENTS_GET_FLAGS))
+ return -1;
+
+ return (int) CallService(MS_HISTORYEVENTS_GET_FLAGS, (WPARAM) eventType, 0);
+}
+
+static void HistoryEvents_ReleaseIcon(HICON icon)
+{
+ CallService(MS_HISTORYEVENTS_RELEASE_ICON, (WPARAM) icon, 0);
+}
+
+static char * HistoryEvents_GetTextA(HANDLE hDbEvent, DBEVENTINFO *dbe)
+{
+ HISTORY_EVENT_PARAM hep = {0};
+
+ if (!ServiceExists(MS_HISTORYEVENTS_GET_TEXT))
+ return NULL;
+
+ hep.cbSize = sizeof(hep);
+ hep.hDbEvent = hDbEvent;
+ hep.dbe = dbe;
+ hep.format = HISTORYEVENTS_FORMAT_CHAR;
+ return (char *) CallService(MS_HISTORYEVENTS_GET_TEXT, (WPARAM) &hep, 0);
+}
+
+static wchar_t * HistoryEvents_GetTextW(HANDLE hDbEvent, DBEVENTINFO *dbe)
+{
+ HISTORY_EVENT_PARAM hep = {0};
+
+ if (!ServiceExists(MS_HISTORYEVENTS_GET_TEXT))
+ return NULL;
+
+ hep.cbSize = sizeof(hep);
+ hep.hDbEvent = hDbEvent;
+ hep.dbe = dbe;
+ hep.format = HISTORYEVENTS_FORMAT_WCHAR;
+ return (wchar_t *) CallService(MS_HISTORYEVENTS_GET_TEXT, (WPARAM) &hep, 0);
+}
+
+static char * HistoryEvents_GetRichText(HANDLE hDbEvent, DBEVENTINFO *dbe)
+{
+ HISTORY_EVENT_PARAM hep = {0};
+
+ if (!ServiceExists(MS_HISTORYEVENTS_GET_TEXT))
+ return NULL;
+
+ hep.cbSize = sizeof(hep);
+ hep.hDbEvent = hDbEvent;
+ hep.dbe = dbe;
+ hep.format = HISTORYEVENTS_FORMAT_RICH_TEXT;
+ return (char *) CallService(MS_HISTORYEVENTS_GET_TEXT, (WPARAM) &hep, 0);
+}
+
+#define HistoryEvents_ReleaseText mir_free
+//static void HistoryEvents_ReleaseText(void *str)
+//{
+// if (!ServiceExists(MS_HISTORYEVENTS_RELEASE_TEXT))
+// return;
+//
+// CallService(MS_HISTORYEVENTS_RELEASE_TEXT, (WPARAM) str, 0);
+//}
+
+
+#ifdef __cplusplus
+static HANDLE HistoryEvents_AddToHistoryEx(HANDLE hContact, WORD eventType, int templateNum,
+ TCHAR **variables, int numVariables,
+ PBYTE additionalData, int additionalDataSize,
+ int flags = 0, DWORD timestamp = 0, BOOL addToMetaToo = FALSE)
+#else
+static HANDLE HistoryEvents_AddToHistoryEx(HANDLE hContact, WORD eventType, int templateNum,
+ TCHAR **variables, int numVariables,
+ PBYTE additionalData, int additionalDataSize,
+ int flags, DWORD timestamp, BOOL addToMetaToo)
+#endif
+{
+ HISTORY_EVENT_ADD hea = {0};
+
+ if (!ServiceExists(MS_HISTORYEVENTS_ADD_TO_HISTORY))
+ return NULL;
+
+ hea.cbSize = sizeof(hea);
+ hea.hContact = hContact;
+ hea.eventType = eventType;
+ hea.templateNum = templateNum;
+ hea.numVariables = numVariables;
+ hea.variables = variables;
+ hea.additionalData = additionalData;
+ hea.additionalDataSize = additionalDataSize;
+ hea.flags = flags;
+ hea.timestamp = timestamp;
+ hea.addToMetaToo = addToMetaToo;
+
+ return (HANDLE) CallService(MS_HISTORYEVENTS_ADD_TO_HISTORY, (WPARAM) &hea, 0);
+}
+
+#ifdef __cplusplus
+static HANDLE HistoryEvents_AddToHistoryVars(HANDLE hContact, WORD eventType, int templateNum,
+ TCHAR **variables, int numVariables,
+ int flags = 0, DWORD timestamp = 0, BOOL addToMetaToo = FALSE)
+#else
+static HANDLE HistoryEvents_AddToHistoryVars(HANDLE hContact, WORD eventType, int templateNum,
+ TCHAR **variables, int numVariables,
+ int flags, DWORD timestamp, BOOL addToMetaToo)
+#endif
+{
+ HISTORY_EVENT_ADD hea = {0};
+
+ if (!ServiceExists(MS_HISTORYEVENTS_ADD_TO_HISTORY))
+ return NULL;
+
+ hea.cbSize = sizeof(hea);
+ hea.hContact = hContact;
+ hea.eventType = eventType;
+ hea.templateNum = templateNum;
+ hea.numVariables = numVariables;
+ hea.variables = variables;
+ hea.flags = flags;
+ hea.timestamp = timestamp;
+ hea.addToMetaToo = addToMetaToo;
+
+ return (HANDLE) CallService(MS_HISTORYEVENTS_ADD_TO_HISTORY, (WPARAM) &hea, 0);
+}
+
+#ifdef __cplusplus
+static HANDLE HistoryEvents_AddToHistorySimple(HANDLE hContact, WORD eventType, int templateNum,
+ int flags = 0, DWORD timestamp = 0, BOOL addToMetaToo = FALSE)
+#else
+static HANDLE HistoryEvents_AddToHistorySimple(HANDLE hContact, WORD eventType, int templateNum,
+ int flags, DWORD timestamp, BOOL addToMetaToo)
+#endif
+{
+ HISTORY_EVENT_ADD hea = {0};
+
+ if (!ServiceExists(MS_HISTORYEVENTS_ADD_TO_HISTORY))
+ return NULL;
+
+ hea.cbSize = sizeof(hea);
+ hea.hContact = hContact;
+ hea.eventType = eventType;
+ hea.templateNum = templateNum;
+ hea.flags = flags;
+ hea.timestamp = timestamp;
+ hea.addToMetaToo = addToMetaToo;
+
+ return (HANDLE) CallService(MS_HISTORYEVENTS_ADD_TO_HISTORY, (WPARAM) &hea, 0);
+}
+
+static BOOL HistoryEvents_IsEnabledTemplate(WORD eventType, int templateNum)
+{
+ return (BOOL) CallService(MS_HISTORYEVENTS_IS_ENABLED_TEMPLATE, eventType, templateNum);
+}
+
+#ifdef UNICODE
+# define HistoryEvents_GetTextT HistoryEvents_GetTextW
+#else
+# define HistoryEvents_GetTextT HistoryEvents_GetTextA
+#endif
+
+
+
+#endif // __M_HISTORYEVENTS_H__
diff --git a/plugins/Twitter/m_updater.h b/plugins/Twitter/m_updater.h
new file mode 100644
index 0000000000..371b7437a0
--- /dev/null
+++ b/plugins/Twitter/m_updater.h
@@ -0,0 +1,146 @@
+#ifndef _M_UPDATER_H
+#define _M_UPDATER_H
+
+// NOTES:
+// - For langpack updates, include a string of the following format in the langpack text file:
+// ";FLID: <file listing name> <version>"
+// version must be four numbers seperated by '.', in the range 0-255 inclusive
+// - Updater will disable plugins that are downloaded but were not active prior to the update (this is so that, if an archive contains e.g. ansi and
+// unicode versions, the correct plugin will be the only one active after the new version is installed)...so if you add a support plugin, you may need
+// to install an ini file to make the plugin activate when miranda restarts after the update
+// - Updater will replace all dlls that have the same internal shortName as a downloaded update dll (this is so that msn1.dll and msn2.dll, for example,
+// will both be updated) - so if you have a unicode and a non-unicode version of a plugin in your archive, you should make the internal names different (which will break automatic
+// updates from the file listing if there is only one file listing entry for both versions, unless you use the 'MS_UPDATE_REGISTER' service below)
+// - Updater will install all files in the root of the archive into the plugins folder, except for langpack files that contain the FLID string which go into the root folder (same
+// folder as miranda32.exe)...all folders in the archive will also be copied to miranda's root folder, and their contents transferred into the new folders. The only exception is a
+// special folder called 'root_files' - if there is a folder by that name in the archive, it's contents will also be copied into miranda's root folder - this is intended to be used
+// to install additional dlls etc that a plugin may require)
+
+// if you set Update.szUpdateURL to the following value when registering, as well as setting your beta site and version data,
+// Updater will ignore szVersionURL and pbVersionPrefix, and attempt to find the file listing URL's from the backend XML data.
+// for this to work, the plugin name in pluginInfo.shortName must match the file listing exactly (except for case)
+#define UPDATER_AUTOREGISTER "UpdaterAUTOREGISTER"
+// Updater will also use the backend xml data if you provide URL's that reference the miranda file listing for updates (so you can use that method
+// if e.g. your plugin shortName does not match the file listing) - it will grab the file listing id from the end of these URLs
+
+typedef struct Update_tag {
+ int cbSize;
+ char *szComponentName; // component name as it will appear in the UI (will be translated before displaying)
+
+ char *szVersionURL; // URL where the current version can be found (NULL to disable)
+ BYTE *pbVersionPrefix; // bytes occuring in VersionURL before the version, used to locate the version information within the URL data
+ // (note that this URL could point at a binary file - dunno why, but it could :)
+ int cpbVersionPrefix; // number of bytes pointed to by pbVersionPrefix
+ char *szUpdateURL; // URL where dll/zip is located
+ // set to UPDATER_AUTOREGISTER if you want Updater to find the file listing URLs (ensure plugin shortName matches file listing!)
+
+ char *szBetaVersionURL; // URL where the beta version can be found (NULL to disable betas)
+ BYTE *pbBetaVersionPrefix; // bytes occuring in VersionURL before the version, used to locate the version information within the URL data
+ int cpbBetaVersionPrefix; // number of bytes pointed to by pbVersionPrefix
+ char *szBetaUpdateURL; // URL where dll/zip is located
+
+ BYTE *pbVersion; // bytes of current version, used for comparison with those in VersionURL
+ int cpbVersion; // number of bytes pointed to by pbVersion
+
+ char *szBetaChangelogURL; // url for displaying changelog for beta versions
+} Update;
+
+// register a comonent with Updater
+//
+// wparam = 0
+// lparam = (LPARAM)&Update
+#define MS_UPDATE_REGISTER "Update/Register"
+
+// utility functions to create a version string from a DWORD or from pluginInfo
+// point buf at a buffer at least 16 chars wide - but note the version string returned may be shorter
+//
+__inline static char *CreateVersionString(DWORD version, char *buf) {
+ mir_snprintf(buf, 16, "%d.%d.%d.%d", (version >> 24) & 0xFF, (version >> 16) & 0xFF, (version >> 8) & 0xFF, version & 0xFF);
+ return buf;
+}
+
+__inline static char *CreateVersionStringPlugin(PLUGININFO *pluginInfo, char *buf) {
+ return CreateVersionString(pluginInfo->version, buf);
+}
+
+
+// register the 'easy' way - use this method if you have no beta URL and the plugin is on the miranda file listing
+// NOTE: the plugin version string on the file listing must be the string version of the version in pluginInfo (i.e. 0.0.0.1,
+// four numbers between 0 and 255 inclusivem, so no letters, brackets, etc.)
+//
+// wParam = (int)fileID - this is the file ID from the file listing (i.e. the number at the end of the download link)
+// lParam = (PLUGININFO*)&pluginInfo
+#define MS_UPDATE_REGISTERFL "Update/RegisterFL"
+
+// this function can be used to 'unregister' components - useful for plugins that register non-plugin/langpack components and
+// may need to change those components on the fly
+// lParam = (char *)szComponentName
+#define MS_UPDATE_UNREGISTER "Update/Unregister"
+
+// this event is fired when the startup process is complete, but NOT if a restart is imminent
+// it is designed for status managment plugins to use as a trigger for beggining their own startup process
+// wParam = lParam = 0 (unused)
+// (added in version 0.1.6.0)
+#define ME_UPDATE_STARTUPDONE "Update/StartupDone"
+
+// this service can be used to enable/disable Updater's global status control
+// it can be called from the StartupDone event handler
+// wParam = (BOOL)enable
+// lParam = 0
+// (added in version 0.1.6.0)
+#define MS_UPDATE_ENABLESTATUSCONTROL "Update/EnableStatusControl"
+
+// An description of usage of the above service and event:
+// Say you are a status control plugin that normally sets protocol or global statuses in your ModulesLoaded event handler.
+// In order to make yourself 'Updater compatible', you would move the status control code from ModulesLoaded to another function,
+// say DoStartup. Then, in ModulesLoaded you would check for the existence of the MS_UPDATE_ENABLESTATUSCONTROL service.
+// If it does not exist, call DoStartup. If it does exist, hook the ME_UPDATE_STARTUPDONE event and call DoStartup from there. You may
+// also wish to call MS_UPDATE_ENABLESTATUSCONTROL with wParam == FALSE at this time, to disable Updater's own status control feature.
+
+// this service can be used to determine whether updates are possible for a component with the given name
+// wParam = 0
+// lParam = (char *)szComponentName
+// returns TRUE if updates are supported, FALSE otherwise
+#define MS_UPDATE_ISUPDATESUPPORTED "Update/IsUpdateSupported"
+
+#endif
+
+
+/////////////// Usage Example ///////////////
+
+#ifdef EXAMPLE_CODE
+
+// you need to #include "m_updater.h" and HookEvent(ME_SYSTEM_MODULESLOADED, OnModulesLoaded) in your Load function...
+
+int OnModulesLoaded(WPARAM wParam, LPARAM lParam) {
+
+ Update update = {0}; // for c you'd use memset or ZeroMemory...
+ char szVersion[16];
+
+ update.cbSize = sizeof(Update);
+
+ update.szComponentName = pluginInfo.shortName;
+ update.pbVersion = (BYTE *)CreateVersionString(&pluginInfo, szVersion);
+ update.cpbVersion = strlen((char *)update.pbVersion);
+
+ // these are the three lines that matter - the archive, the page containing the version string, and the text (or data)
+ // before the version that we use to locate it on the page
+ // (note that if the update URL and the version URL point to standard file listing entries, the backend xml
+ // data will be used to check for updates rather than the actual web page - this is not true for beta urls)
+ update.szUpdateURL = "http://scottellis.com.au:81/test/updater.zip";
+ update.szVersionURL = "http://scottellis.com.au:81/test/updater_test.html";
+ update.pbVersionPrefix = (BYTE *)"Updater version ";
+
+ update.cpbVersionPrefix = strlen((char *)update.pbVersionPrefix);
+
+ // do the same for the beta versions of the above struct members if you wish to allow beta updates from another URL
+
+ CallService(MS_UPDATE_REGISTER, 0, (WPARAM)&update);
+
+ // Alternatively, to register a plugin with e.g. file ID 2254 on the file listing...
+ // CallService(MS_UPDATE_REGISTERFL, (WPARAM)2254, (LPARAM)&pluginInfo);
+
+ return 0;
+}
+
+#endif
diff --git a/plugins/Twitter/main.cpp b/plugins/Twitter/main.cpp
new file mode 100644
index 0000000000..d28cc30b65
--- /dev/null
+++ b/plugins/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/plugins/Twitter/proto.cpp b/plugins/Twitter/proto.cpp
new file mode 100644
index 0000000000..44bd6a3ebc
--- /dev/null
+++ b/plugins/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/plugins/Twitter/proto.h b/plugins/Twitter/proto.h
new file mode 100644
index 0000000000..82d02e6c18
--- /dev/null
+++ b/plugins/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::wstring &);
+
+ 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/plugins/Twitter/resource.h b/plugins/Twitter/resource.h
new file mode 100644
index 0000000000..21fc21c049
--- /dev/null
+++ b/plugins/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/plugins/Twitter/stubs.cpp b/plugins/Twitter/stubs.cpp
new file mode 100644
index 0000000000..331a4cdeac
--- /dev/null
+++ b/plugins/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/plugins/Twitter/theme.cpp b/plugins/Twitter/theme.cpp
new file mode 100644
index 0000000000..f35b3a5aa1
--- /dev/null
+++ b/plugins/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/plugins/Twitter/theme.h b/plugins/Twitter/theme.h
new file mode 100644
index 0000000000..e74f5da2e3
--- /dev/null
+++ b/plugins/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/plugins/Twitter/tinyjson.hpp b/plugins/Twitter/tinyjson.hpp
new file mode 100644
index 0000000000..19e1210d84
--- /dev/null
+++ b/plugins/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/plugins/Twitter/twitter.cpp b/plugins/Twitter/twitter.cpp
new file mode 100644
index 0000000000..9512292707
--- /dev/null
+++ b/plugins/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("&lt;")) != std::string::npos)
+ s.replace(off,4,"<");
+ while( (off = s.find("&gt;")) != 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/plugins/Twitter/twitter.h b/plugins/Twitter/twitter.h
new file mode 100644
index 0000000000..cf60265152
--- /dev/null
+++ b/plugins/Twitter/twitter.h
@@ -0,0 +1,89 @@
+/*
+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>
+
+#define tstring wstring
+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/plugins/Twitter/twitter.rc b/plugins/Twitter/twitter.rc
new file mode 100644
index 0000000000..0c32e19b59
--- /dev/null
+++ b/plugins/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/plugins/Twitter/twitter.sln b/plugins/Twitter/twitter.sln
new file mode 100644
index 0000000000..af7c8278cd
--- /dev/null
+++ b/plugins/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/plugins/Twitter/twitter.vcproj b/plugins/Twitter/twitter.vcproj
new file mode 100644
index 0000000000..1ddb297182
--- /dev/null
+++ b/plugins/Twitter/twitter.vcproj
@@ -0,0 +1,439 @@
+<?xml version="1.0" encoding="Windows-1252"?>
+<VisualStudioProject
+ ProjectType="Visual C++"
+ Version="8.00"
+ Name="twitter"
+ ProjectGUID="{DADE9455-DC28-465A-9604-2CA28052B9FB}"
+ RootNamespace="twitter"
+ Keyword="Win32Proj"
+ >
+ <Platforms>
+ <Platform
+ Name="Win32"
+ />
+ </Platforms>
+ <ToolFiles>
+ </ToolFiles>
+ <Configurations>
+ <Configuration
+ Name="Debug|Win32"
+ OutputDirectory="$(SolutionDir)$(ConfigurationName)"
+ IntermediateDirectory="$(ConfigurationName)"
+ ConfigurationType="2"
+ CharacterSet="1"
+ >
+ <Tool
+ Name="VCPreBuildEventTool"
+ />
+ <Tool
+ Name="VCCustomBuildTool"
+ />
+ <Tool
+ Name="VCXMLDataGeneratorTool"
+ />
+ <Tool
+ Name="VCWebServiceProxyGeneratorTool"
+ />
+ <Tool
+ Name="VCMIDLTool"
+ />
+ <Tool
+ Name="VCCLCompilerTool"
+ Optimization="0"
+ AdditionalIncludeDirectories=".\miranda"
+ PreprocessorDefinitions="WIN32;_WINDOWS;_USRDLL;TWITTER_EXPORTS;_CRT_SECURE_NO_WARNINGS;NOMINMAX"
+ MinimalRebuild="true"
+ BasicRuntimeChecks="3"
+ RuntimeLibrary="2"
+ UsePrecompiledHeader="0"
+ WarningLevel="3"
+ Detect64BitPortabilityProblems="false"
+ DebugInformationFormat="4"
+ />
+ <Tool
+ Name="VCManagedResourceCompilerTool"
+ />
+ <Tool
+ Name="VCResourceCompilerTool"
+ />
+ <Tool
+ Name="VCPreLinkEventTool"
+ />
+ <Tool
+ Name="VCLinkerTool"
+ LinkIncremental="2"
+ GenerateDebugInformation="true"
+ SubSystem="2"
+ TargetMachine="1"
+ />
+ <Tool
+ Name="VCALinkTool"
+ />
+ <Tool
+ Name="VCManifestTool"
+ />
+ <Tool
+ Name="VCXDCMakeTool"
+ />
+ <Tool
+ Name="VCBscMakeTool"
+ />
+ <Tool
+ Name="VCFxCopTool"
+ />
+ <Tool
+ Name="VCAppVerifierTool"
+ />
+ <Tool
+ Name="VCWebDeploymentTool"
+ />
+ <Tool
+ Name="VCPostBuildEventTool"
+ />
+ </Configuration>
+ <Configuration
+ Name="Release|Win32"
+ OutputDirectory="$(SolutionDir)$(ConfigurationName)"
+ IntermediateDirectory="$(ConfigurationName)"
+ ConfigurationType="2"
+ CharacterSet="1"
+ WholeProgramOptimization="1"
+ >
+ <Tool
+ Name="VCPreBuildEventTool"
+ />
+ <Tool
+ Name="VCCustomBuildTool"
+ />
+ <Tool
+ Name="VCXMLDataGeneratorTool"
+ />
+ <Tool
+ Name="VCWebServiceProxyGeneratorTool"
+ />
+ <Tool
+ Name="VCMIDLTool"
+ />
+ <Tool
+ Name="VCCLCompilerTool"
+ AdditionalIncludeDirectories=".\miranda"
+ PreprocessorDefinitions="WIN32;NDEBUG;_WINDOWS;_USRDLL;TWITTER_EXPORTS;_CRT_SECURE_NO_WARNINGS;NOMINMAX"
+ RuntimeLibrary="2"
+ UsePrecompiledHeader="0"
+ WarningLevel="3"
+ Detect64BitPortabilityProblems="false"
+ DebugInformationFormat="3"
+ />
+ <Tool
+ Name="VCManagedResourceCompilerTool"
+ />
+ <Tool
+ Name="VCResourceCompilerTool"
+ />
+ <Tool
+ Name="VCPreLinkEventTool"
+ />
+ <Tool
+ Name="VCLinkerTool"
+ LinkIncremental="1"
+ GenerateDebugInformation="true"
+ SubSystem="2"
+ OptimizeReferences="2"
+ EnableCOMDATFolding="2"
+ TargetMachine="1"
+ />
+ <Tool
+ Name="VCALinkTool"
+ />
+ <Tool
+ Name="VCManifestTool"
+ />
+ <Tool
+ Name="VCXDCMakeTool"
+ />
+ <Tool
+ Name="VCBscMakeTool"
+ />
+ <Tool
+ Name="VCFxCopTool"
+ />
+ <Tool
+ Name="VCAppVerifierTool"
+ />
+ <Tool
+ Name="VCWebDeploymentTool"
+ />
+ <Tool
+ Name="VCPostBuildEventTool"
+ />
+ </Configuration>
+ <Configuration
+ Name="Release ANSI|Win32"
+ OutputDirectory="$(SolutionDir)$(ConfigurationName)"
+ IntermediateDirectory="$(ConfigurationName)"
+ ConfigurationType="2"
+ CharacterSet="2"
+ WholeProgramOptimization="1"
+ >
+ <Tool
+ Name="VCPreBuildEventTool"
+ />
+ <Tool
+ Name="VCCustomBuildTool"
+ />
+ <Tool
+ Name="VCXMLDataGeneratorTool"
+ />
+ <Tool
+ Name="VCWebServiceProxyGeneratorTool"
+ />
+ <Tool
+ Name="VCMIDLTool"
+ />
+ <Tool
+ Name="VCCLCompilerTool"
+ AdditionalIncludeDirectories=".\miranda"
+ PreprocessorDefinitions="WIN32;NDEBUG;_WINDOWS;_USRDLL;TWITTER_EXPORTS;_CRT_SECURE_NO_WARNINGS;NOMINMAX"
+ RuntimeLibrary="2"
+ UsePrecompiledHeader="0"
+ WarningLevel="3"
+ Detect64BitPortabilityProblems="false"
+ DebugInformationFormat="3"
+ />
+ <Tool
+ Name="VCManagedResourceCompilerTool"
+ />
+ <Tool
+ Name="VCResourceCompilerTool"
+ />
+ <Tool
+ Name="VCPreLinkEventTool"
+ />
+ <Tool
+ Name="VCLinkerTool"
+ LinkIncremental="1"
+ GenerateDebugInformation="true"
+ SubSystem="2"
+ OptimizeReferences="2"
+ EnableCOMDATFolding="2"
+ TargetMachine="1"
+ />
+ <Tool
+ Name="VCALinkTool"
+ />
+ <Tool
+ Name="VCManifestTool"
+ />
+ <Tool
+ Name="VCXDCMakeTool"
+ />
+ <Tool
+ Name="VCBscMakeTool"
+ />
+ <Tool
+ Name="VCFxCopTool"
+ />
+ <Tool
+ Name="VCAppVerifierTool"
+ />
+ <Tool
+ Name="VCWebDeploymentTool"
+ />
+ <Tool
+ Name="VCPostBuildEventTool"
+ />
+ </Configuration>
+ <Configuration
+ Name="Debug ANSI|Win32"
+ OutputDirectory="$(SolutionDir)$(ConfigurationName)"
+ IntermediateDirectory="$(ConfigurationName)"
+ ConfigurationType="2"
+ CharacterSet="2"
+ >
+ <Tool
+ Name="VCPreBuildEventTool"
+ />
+ <Tool
+ Name="VCCustomBuildTool"
+ />
+ <Tool
+ Name="VCXMLDataGeneratorTool"
+ />
+ <Tool
+ Name="VCWebServiceProxyGeneratorTool"
+ />
+ <Tool
+ Name="VCMIDLTool"
+ />
+ <Tool
+ Name="VCCLCompilerTool"
+ Optimization="0"
+ AdditionalIncludeDirectories=".\miranda"
+ PreprocessorDefinitions="WIN32;_WINDOWS;_USRDLL;TWITTER_EXPORTS;_CRT_SECURE_NO_WARNINGS;NOMINMAX"
+ MinimalRebuild="true"
+ BasicRuntimeChecks="3"
+ RuntimeLibrary="2"
+ UsePrecompiledHeader="0"
+ WarningLevel="3"
+ Detect64BitPortabilityProblems="false"
+ DebugInformationFormat="4"
+ />
+ <Tool
+ Name="VCManagedResourceCompilerTool"
+ />
+ <Tool
+ Name="VCResourceCompilerTool"
+ />
+ <Tool
+ Name="VCPreLinkEventTool"
+ />
+ <Tool
+ Name="VCLinkerTool"
+ LinkIncremental="2"
+ GenerateDebugInformation="true"
+ SubSystem="2"
+ TargetMachine="1"
+ />
+ <Tool
+ Name="VCALinkTool"
+ />
+ <Tool
+ Name="VCManifestTool"
+ />
+ <Tool
+ Name="VCXDCMakeTool"
+ />
+ <Tool
+ Name="VCBscMakeTool"
+ />
+ <Tool
+ Name="VCFxCopTool"
+ />
+ <Tool
+ Name="VCAppVerifierTool"
+ />
+ <Tool
+ Name="VCWebDeploymentTool"
+ />
+ <Tool
+ Name="VCPostBuildEventTool"
+ />
+ </Configuration>
+ </Configurations>
+ <References>
+ </References>
+ <Files>
+ <Filter
+ Name="Source Files"
+ Filter="cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx"
+ UniqueIdentifier="{4FC737F1-C7A5-4376-A066-2A32D752A2FF}"
+ >
+ <File
+ RelativePath=".\chat.cpp"
+ >
+ </File>
+ <File
+ RelativePath=".\connection.cpp"
+ >
+ </File>
+ <File
+ RelativePath=".\contacts.cpp"
+ >
+ </File>
+ <File
+ RelativePath=".\http.cpp"
+ >
+ </File>
+ <File
+ RelativePath=".\main.cpp"
+ >
+ </File>
+ <File
+ RelativePath=".\proto.cpp"
+ >
+ </File>
+ <File
+ RelativePath=".\stubs.cpp"
+ >
+ </File>
+ <File
+ RelativePath=".\theme.cpp"
+ >
+ </File>
+ <File
+ RelativePath=".\twitter.cpp"
+ >
+ </File>
+ <File
+ RelativePath=".\ui.cpp"
+ >
+ </File>
+ <File
+ RelativePath=".\utility.cpp"
+ >
+ </File>
+ </Filter>
+ <Filter
+ Name="Header Files"
+ Filter="h;hpp;hxx;hm;inl;inc;xsd"
+ UniqueIdentifier="{93995380-89BD-4b04-88EB-625FBE52EBFB}"
+ >
+ <File
+ RelativePath=".\common.h"
+ >
+ </File>
+ <File
+ RelativePath=".\http.h"
+ >
+ </File>
+ <File
+ RelativePath=".\proto.h"
+ >
+ </File>
+ <File
+ RelativePath=".\resource.h"
+ >
+ </File>
+ <File
+ RelativePath=".\theme.h"
+ >
+ </File>
+ <File
+ RelativePath=".\tinyjson.hpp"
+ >
+ </File>
+ <File
+ RelativePath=".\twitter.h"
+ >
+ </File>
+ <File
+ RelativePath=".\ui.h"
+ >
+ </File>
+ <File
+ RelativePath=".\utility.h"
+ >
+ </File>
+ <File
+ RelativePath=".\version.h"
+ >
+ </File>
+ </Filter>
+ <Filter
+ Name="Resource Files"
+ Filter="rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav"
+ UniqueIdentifier="{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}"
+ >
+ <File
+ RelativePath=".\icons\twitter.ico"
+ >
+ </File>
+ <File
+ RelativePath=".\twitter.rc"
+ >
+ </File>
+ </Filter>
+ </Files>
+ <Globals>
+ </Globals>
+</VisualStudioProject>
diff --git a/plugins/Twitter/twitter.vcxproj b/plugins/Twitter/twitter.vcxproj
new file mode 100644
index 0000000000..0bfbabcd7a
--- /dev/null
+++ b/plugins/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>MultiByte</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;..\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;..\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/plugins/Twitter/twitter.vcxproj.filters b/plugins/Twitter/twitter.vcxproj.filters
new file mode 100644
index 0000000000..70ac860513
--- /dev/null
+++ b/plugins/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/plugins/Twitter/ui.cpp b/plugins/Twitter/ui.cpp
new file mode 100644
index 0000000000..b3e68542df
--- /dev/null
+++ b/plugins/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/plugins/Twitter/ui.h b/plugins/Twitter/ui.h
new file mode 100644
index 0000000000..f036f8d55d
--- /dev/null
+++ b/plugins/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/plugins/Twitter/utility.cpp b/plugins/Twitter/utility.cpp
new file mode 100644
index 0000000000..221e413253
--- /dev/null
+++ b/plugins/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/plugins/Twitter/utility.h b/plugins/Twitter/utility.h
new file mode 100644
index 0000000000..b68a4fcb9d
--- /dev/null
+++ b/plugins/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/plugins/Twitter/version.h b/plugins/Twitter/version.h
new file mode 100644
index 0000000000..b27c53811e
--- /dev/null
+++ b/plugins/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