diff options
author | Mathias Hasselmann <hasselmm@gnome.org> | 2009-10-19 22:40:21 +0200 |
---|---|---|
committer | Mathias Hasselmann <hasselmm@gnome.org> | 2009-10-20 21:19:54 +0200 |
commit | 324498ec6af10a73133c1ff8222658a2dbc46615 (patch) | |
tree | 2d4f592c64f3df0379939bdce4c15a88ee0c8b9a |
Initial commit
-rw-r--r-- | .gitignore | 21 | ||||
-rw-r--r-- | AUTHORS | 1 | ||||
-rw-r--r-- | COPYING | 340 | ||||
-rw-r--r-- | ChangeLog | 0 | ||||
-rw-r--r-- | INSTALL | 229 | ||||
-rw-r--r-- | Makefile.am | 20 | ||||
-rw-r--r-- | NEWS | 1 | ||||
-rw-r--r-- | README | 8 | ||||
-rwxr-xr-x | autogen.sh | 5 | ||||
-rw-r--r-- | configure.ac | 22 | ||||
-rw-r--r-- | data/facebook.profile | 45 | ||||
-rw-r--r-- | data/gruschler.manager | 7 | ||||
-rw-r--r-- | data/org.freedesktop.Telepathy.ConnectionManager.gruschler.service | 4 | ||||
-rw-r--r-- | m4/facebook.m4 | 32 | ||||
-rw-r--r-- | src/connection-manager.c | 93 | ||||
-rw-r--r-- | src/connection-manager.h | 53 | ||||
-rw-r--r-- | src/facebook-connection.c | 1101 | ||||
-rw-r--r-- | src/facebook-connection.h | 67 | ||||
-rw-r--r-- | src/facebook-contact-list.c | 508 | ||||
-rw-r--r-- | src/facebook-contact-list.h | 62 | ||||
-rw-r--r-- | src/main.c | 35 | ||||
-rw-r--r-- | tests/test-facebook.c | 299 |
22 files changed, 2953 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f434f72 --- /dev/null +++ b/.gitignore @@ -0,0 +1,21 @@ +.*.sw? +.deps +.dirstamp +*.o + +/Makefile +/Makefile.in +/aclocal.m4 +/autom4te.cache +/compile +/config.* +/configure +/depcomp +/facebook.conf +/install-sh +/libtool +/ltmain.sh +/missing +/src/telepathy-gruschler +/stamp-h1 +/tests/test-facebook @@ -0,0 +1 @@ +Mathias Hasselmann @@ -0,0 +1,340 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) 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 +this service 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 make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. 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. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute 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 and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +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 +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the 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 a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, 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. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE 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. + + 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 +convey 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 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 + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision 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, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + <signature of Ty Coon>, 1 April 1989 + Ty Coon, President of Vice + +This 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 Library General +Public License instead of this License. diff --git a/ChangeLog b/ChangeLog new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/ChangeLog @@ -0,0 +1,229 @@ +Copyright (C) 1994, 1995, 1996, 1999, 2000, 2001, 2002 Free Software +Foundation, Inc. + + This file is free documentation; the Free Software Foundation gives +unlimited permission to copy, distribute and modify it. + +Basic Installation +================== + + These are generic installation instructions. + + The `configure' shell script attempts to guess correct values for +various system-dependent variables used during compilation. It uses +those values to create a `Makefile' in each directory of the package. +It may also create one or more `.h' files containing system-dependent +definitions. Finally, it creates a shell script `config.status' that +you can run in the future to recreate the current configuration, and a +file `config.log' containing compiler output (useful mainly for +debugging `configure'). + + It can also use an optional file (typically called `config.cache' +and enabled with `--cache-file=config.cache' or simply `-C') that saves +the results of its tests to speed up reconfiguring. (Caching is +disabled by default to prevent problems with accidental use of stale +cache files.) + + If you need to do unusual things to compile the package, please try +to figure out how `configure' could check whether to do them, and mail +diffs or instructions to the address given in the `README' so they can +be considered for the next release. If you are using the cache, and at +some point `config.cache' contains results you don't want to keep, you +may remove or edit it. + + The file `configure.ac' (or `configure.in') is used to create +`configure' by a program called `autoconf'. You only need +`configure.ac' if you want to change it or regenerate `configure' using +a newer version of `autoconf'. + +The simplest way to compile this package is: + + 1. `cd' to the directory containing the package's source code and type + `./configure' to configure the package for your system. If you're + using `csh' on an old version of System V, you might need to type + `sh ./configure' instead to prevent `csh' from trying to execute + `configure' itself. + + Running `configure' takes awhile. While running, it prints some + messages telling which features it is checking for. + + 2. Type `make' to compile the package. + + 3. Optionally, type `make check' to run any self-tests that come with + the package. + + 4. Type `make install' to install the programs and any data files and + documentation. + + 5. You can remove the program binaries and object files from the + source code directory by typing `make clean'. To also remove the + files that `configure' created (so you can compile the package for + a different kind of computer), type `make distclean'. There is + also a `make maintainer-clean' target, but that is intended mainly + for the package's developers. If you use it, you may have to get + all sorts of other programs in order to regenerate files that came + with the distribution. + +Compilers and Options +===================== + + Some systems require unusual options for compilation or linking that +the `configure' script does not know about. Run `./configure --help' +for details on some of the pertinent environment variables. + + You can give `configure' initial values for configuration parameters +by setting variables in the command line or in the environment. Here +is an example: + + ./configure CC=c89 CFLAGS=-O2 LIBS=-lposix + + *Note Defining Variables::, for more details. + +Compiling For Multiple Architectures +==================================== + + You can compile the package for more than one kind of computer at the +same time, by placing the object files for each architecture in their +own directory. To do this, you must use a version of `make' that +supports the `VPATH' variable, such as GNU `make'. `cd' to the +directory where you want the object files and executables to go and run +the `configure' script. `configure' automatically checks for the +source code in the directory that `configure' is in and in `..'. + + If you have to use a `make' that does not support the `VPATH' +variable, you have to compile the package for one architecture at a +time in the source code directory. After you have installed the +package for one architecture, use `make distclean' before reconfiguring +for another architecture. + +Installation Names +================== + + By default, `make install' will install the package's files in +`/usr/local/bin', `/usr/local/man', etc. You can specify an +installation prefix other than `/usr/local' by giving `configure' the +option `--prefix=PATH'. + + You can specify separate installation prefixes for +architecture-specific files and architecture-independent files. If you +give `configure' the option `--exec-prefix=PATH', the package will use +PATH as the prefix for installing programs and libraries. +Documentation and other data files will still use the regular prefix. + + In addition, if you use an unusual directory layout you can give +options like `--bindir=PATH' to specify different values for particular +kinds of files. Run `configure --help' for a list of the directories +you can set and what kinds of files go in them. + + If the package supports it, you can cause programs to be installed +with an extra prefix or suffix on their names by giving `configure' the +option `--program-prefix=PREFIX' or `--program-suffix=SUFFIX'. + +Optional Features +================= + + Some packages pay attention to `--enable-FEATURE' options to +`configure', where FEATURE indicates an optional part of the package. +They may also pay attention to `--with-PACKAGE' options, where PACKAGE +is something like `gnu-as' or `x' (for the X Window System). The +`README' should mention any `--enable-' and `--with-' options that the +package recognizes. + + For packages that use the X Window System, `configure' can usually +find the X include and library files automatically, but if it doesn't, +you can use the `configure' options `--x-includes=DIR' and +`--x-libraries=DIR' to specify their locations. + +Specifying the System Type +========================== + + There may be some features `configure' cannot figure out +automatically, but needs to determine by the type of machine the package +will run on. Usually, assuming the package is built to be run on the +_same_ architectures, `configure' can figure that out, but if it prints +a message saying it cannot guess the machine type, give it the +`--build=TYPE' option. TYPE can either be a short name for the system +type, such as `sun4', or a canonical name which has the form: + + CPU-COMPANY-SYSTEM + +where SYSTEM can have one of these forms: + + OS KERNEL-OS + + See the file `config.sub' for the possible values of each field. If +`config.sub' isn't included in this package, then this package doesn't +need to know the machine type. + + If you are _building_ compiler tools for cross-compiling, you should +use the `--target=TYPE' option to select the type of system they will +produce code for. + + If you want to _use_ a cross compiler, that generates code for a +platform different from the build platform, you should specify the +"host" platform (i.e., that on which the generated programs will +eventually be run) with `--host=TYPE'. + +Sharing Defaults +================ + + If you want to set default values for `configure' scripts to share, +you can create a site shell script called `config.site' that gives +default values for variables like `CC', `cache_file', and `prefix'. +`configure' looks for `PREFIX/share/config.site' if it exists, then +`PREFIX/etc/config.site' if it exists. Or, you can set the +`CONFIG_SITE' environment variable to the location of the site script. +A warning: not all `configure' scripts look for a site script. + +Defining Variables +================== + + Variables not defined in a site shell script can be set in the +environment passed to `configure'. However, some packages may run +configure again during the build, and the customized values of these +variables may be lost. In order to avoid this problem, you should set +them in the `configure' command line, using `VAR=value'. For example: + + ./configure CC=/usr/local2/bin/gcc + +will cause the specified gcc to be used as the C compiler (unless it is +overridden in the site shell script). + +`configure' Invocation +====================== + + `configure' recognizes the following options to control how it +operates. + +`--help' +`-h' + Print a summary of the options to `configure', and exit. + +`--version' +`-V' + Print the version of Autoconf used to generate the `configure' + script, and exit. + +`--cache-file=FILE' + Enable the cache: use and save the results of the tests in FILE, + traditionally `config.cache'. FILE defaults to `/dev/null' to + disable caching. + +`--config-cache' +`-C' + Alias for `--cache-file=config.cache'. + +`--quiet' +`--silent' +`-q' + Do not print messages saying which checks are being made. To + suppress all normal output, redirect it to `/dev/null' (any error + messages will still be shown). + +`--srcdir=DIR' + Look for the package's source code in directory DIR. Usually + `configure' can determine that directory automatically. + +`configure' also accepts some other, not widely useful, options. Run +`configure --help' for more details. + diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 0000000..16a8c29 --- /dev/null +++ b/Makefile.am @@ -0,0 +1,20 @@ +AM_CFLAGS = -Wall -Werror +ACLOCAL_AMFLAGS = -I m4 + +telepathy_libdir=$(libdir)/telepathy +telepathy_lib_PROGRAMS = src/telepathy-gruschler +noinst_PROGRAMS = tests/test-facebook + +src_telepathy_gruschler_CFLAGS = $(AM_CFLAGS) $(REST_CFLAGS) $(TELEPATHY_CFLAGS) +src_telepathy_gruschler_LDADD = $(AM_LDFLAGS) $(REST_LIBS) $(TELEPATHY_LIBS) + +src_telepathy_gruschler_SOURCES = src/connection-manager.c \ + src/connection-manager.h \ + src/facebook-connection.c \ + src/facebook-connection.h \ + src/facebook-contact-list.c \ + src/facebook-contact-list.h \ + src/main.c + +tests_test_facebook_CFLAGS = $(AM_CFLAGS) $(REST_CFLAGS) +tests_test_facebook_LDADD = $(AM_LDFLAGS) $(REST_LIBS) @@ -0,0 +1 @@ +No public release yet. @@ -0,0 +1,8 @@ +This is a connection manager for integrating social networks +like Facebook into the Telepathy communication framework. + +Get the latest source code from: + + git://anongit.freedesktop.org/~hasselmm/telepathy-gruschler + ssh://git.freedesktop.org/~hasselmm/telepathy-gruschler + diff --git a/autogen.sh b/autogen.sh new file mode 100755 index 0000000..adcf2ec --- /dev/null +++ b/autogen.sh @@ -0,0 +1,5 @@ +#!/bin/sh -e + +autoreconf -i -f -I m4 +exec ./configure "$@" + diff --git a/configure.ac b/configure.ac new file mode 100644 index 0000000..4f00c57 --- /dev/null +++ b/configure.ac @@ -0,0 +1,22 @@ +AC_INIT([telepathy-gruschler],[0.1], + [http://live.gnome.org/telepathy-gruschler]) + +AM_INIT_AUTOMAKE([check-news dist-bzip2 subdir-objects -Wall -Werror]) +m4_ifdef([AM_SILENT_RULES],[AM_SILENT_RULES([yes])]) +AC_CONFIG_MACRO_DIR([m4]) + +AC_PROG_CC +AM_PROG_CC_C_O +AC_PROG_LIBTOOL +AM_PATH_GLIB_2_0([2.20.3]) + +PKG_CHECK_MODULES([TELEPATHY], [rtcom-telepathy-glib >= 0.1.38 + telepathy-glib >= 0.7.35]) +PKG_CHECK_MODULES([REST], [rest-extras-0.6 >= 0.6.1]) + +FACEBOOK_APPKEY([GRUSCHLER]) + +AC_CONFIG_HEADERS([config.h]) +AC_CONFIG_FILES([Makefile]) + +AC_OUTPUT diff --git a/data/facebook.profile b/data/facebook.profile new file mode 100644 index 0000000..0493092 --- /dev/null +++ b/data/facebook.profile @@ -0,0 +1,45 @@ +# FIXME: This belongs into a separate accounts-plugin-package +[Profile] +DisplayName = Facebook +IconName = general_facebook +Manager = gruschler +Protocol = facebook +VCardDefault = 1 +VCardField = X-FACEBOOK +Capabilities = chat-p2p, chat-room, chat-room-list, voice-p2p, video-p2p, contact-search, registration-ui, supports-avatars, supports-alias, supports-roster +#SupportedPresences = away,extended-away,hidden,do-not-disturb +#SupportsPresenceMessage = true +#ConfigurationUI = jabber-plugin + +#[Presence available] +#Name = pres_bd_jabber_online +#IconName = general_presence_online +#Type = 2 +# +#[Presence dnd] +#Name = pres_bd_jabber_do_not_disturb +#IconName = general_presence_busy +#Type = 6 +# +#[Presence hidden] +#Name = pres_bd_jabber_invisible +#IconName = general_presence_invisible +#Type = 5 +# +#[Presence offline] +#Name = pres_bd_jabber_offline +#IconName = general_presence_offline +#Type = 1 +# +#[Action chat] +#Name = addr_bd_cont_starter_im_service_chat +#IconName = general_sms +#VCardFields = X-JABBER +#prop-org.freedesktop.Telepathy.Channel.ChannelType-s = org.freedesktop.Telepathy.Channel.Type.Text +# +#[Action call] +#Name = addr_bd_cont_starter_im_service_call +#IconName = general_call +#VCardFields = X-JABBER +#prop-org.freedesktop.Telepathy.Channel.ChannelType-s = org.freedesktop.Telepathy.Channel.Type.StreamedMedia + diff --git a/data/gruschler.manager b/data/gruschler.manager new file mode 100644 index 0000000..e5b373e --- /dev/null +++ b/data/gruschler.manager @@ -0,0 +1,7 @@ +[ConnectionManager] +BusName=org.freedesktop.Telepathy.ConnectionManager.gruschler +ObjectPath=/org/freedesktop/Telepathy/ConnectionManager/gruschler + +[Protocol facebook] +param-email=s required +param-password=s required secret diff --git a/data/org.freedesktop.Telepathy.ConnectionManager.gruschler.service b/data/org.freedesktop.Telepathy.ConnectionManager.gruschler.service new file mode 100644 index 0000000..432aa08 --- /dev/null +++ b/data/org.freedesktop.Telepathy.ConnectionManager.gruschler.service @@ -0,0 +1,4 @@ +[D-BUS Service] +Name=org.freedesktop.Telepathy.ConnectionManager.gruschler +Exec=/usr/lib/telepathy/telepathy-gruschler + diff --git a/m4/facebook.m4 b/m4/facebook.m4 new file mode 100644 index 0000000..4a2fca1 --- /dev/null +++ b/m4/facebook.m4 @@ -0,0 +1,32 @@ +AC_DEFUN([FACEBOOK_APPKEY],[ + +AC_MSG_CHECKING([Facebook keys]) + +if ! test -r "facebook.conf"; then + AC_MSG_ERROR([facebook.conf not found]) +fi + +facebook_apikey=`sed -ne 's/^apikey=//p' facebook.conf` +facebook_secret=`sed -ne 's/^secret=//p' facebook.conf` +facebook_email=`sed -ne 's/^email=//p' facebook.conf` +facebook_password=`sed -ne 's/^password=//p' facebook.conf` + +if ! test -n "$facebook_apikey"; then + AC_MSG_ERROR([API key not found]) +fi + +if ! test -n "$facebook_secret"; then + AC_MSG_ERROR([secret key not found]) +fi + +AC_DEFINE_UNQUOTED([$1_FACEBOOK_APIKEY], ["$facebook_apikey"], + [The API key for accessing Facebook]) +AC_DEFINE_UNQUOTED([$1_FACEBOOK_SECRET], ["$facebook_secret"], + [The secret key for accessing Facebook]) +AC_DEFINE_UNQUOTED([$1_FACEBOOK_DEFAULT_EMAIL], ["$facebook_email"], + [Default email address for accessing Facebook]) +AC_DEFINE_UNQUOTED([$1_FACEBOOK_DEFAULT_PASSWORD], ["$facebook_password"], + [Default password for accessing Facebook]) + +AC_MSG_RESULT([$facebook_apikey]) +]) diff --git a/src/connection-manager.c b/src/connection-manager.c new file mode 100644 index 0000000..824ea2e --- /dev/null +++ b/src/connection-manager.c @@ -0,0 +1,93 @@ +/* telepathy-gruschler - A Telepathy connection manager for social networks. + * Copyright (C) 2009 Mathias Hasselmann + * + * 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 + */ +#include "config.h" +#include "connection-manager.h" +#include "facebook-connection.h" + +G_DEFINE_TYPE (GruschlerConnectionManager, + gruschler_connection_manager, + TP_TYPE_BASE_CONNECTION_MANAGER); + +static const TpCMParamSpec gruschler_facebook_connection_params[] = { + { "email", "s", G_TYPE_STRING, + TP_CONN_MGR_PARAM_FLAG_REQUIRED | + TP_CONN_MGR_PARAM_FLAG_HAS_DEFAULT, + GRUSCHLER_FACEBOOK_DEFAULT_EMAIL, + G_STRUCT_OFFSET (GruschlerFacebookConnectionParams, email), + NULL, NULL, NULL }, + + { "password", "s", G_TYPE_STRING, + TP_CONN_MGR_PARAM_FLAG_REQUIRED | + TP_CONN_MGR_PARAM_FLAG_HAS_DEFAULT | + TP_CONN_MGR_PARAM_FLAG_SECRET, + GRUSCHLER_FACEBOOK_DEFAULT_PASSWORD, + G_STRUCT_OFFSET (GruschlerFacebookConnectionParams, password), + NULL, NULL, NULL }, + + { NULL, } +}; + +static void +gruschler_connection_manager_init (GruschlerConnectionManager *self) +{ +} + +static TpBaseConnection * +gruschler_connection_manager_new_connection (TpBaseConnectionManager *self, + const char *proto, + TpIntSet *params_present, + void *parsed_params, + GError **error) +{ + if (!g_strcmp0 (proto, "facebook")) + return gruschler_facebook_connection_new (self, parsed_params, error); + + g_set_error (error, TP_ERRORS, + TP_ERROR_INVALID_ARGUMENT, + "unknown protocol name %s", proto); + + return NULL; +} + +static void +gruschler_connection_manager_class_init (GruschlerConnectionManagerClass *class) +{ + static const TpCMProtocolSpec protocol_params[] = { + { "facebook", + gruschler_facebook_connection_params, + gruschler_facebook_connection_params_new, + gruschler_facebook_connection_params_free, NULL + }, + + { NULL, } + }; + + TpBaseConnectionManagerClass *base_cm_class; + + base_cm_class = TP_BASE_CONNECTION_MANAGER_CLASS (class); + base_cm_class->cm_dbus_name = "gruschler"; + base_cm_class->protocol_params = protocol_params; + base_cm_class->new_connection = gruschler_connection_manager_new_connection; +} + +TpBaseConnectionManager * +gruschler_connection_manager_new (void) +{ + return g_object_new (GRUSCHLER_TYPE_CONNECTION_MANAGER, NULL); +} + diff --git a/src/connection-manager.h b/src/connection-manager.h new file mode 100644 index 0000000..3609fbd --- /dev/null +++ b/src/connection-manager.h @@ -0,0 +1,53 @@ +/* telepathy-gruschler - A Telepathy connection manager for social networks. + * Copyright (C) 2009 Mathias Hasselmann + * + * 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 __GRUSCHLER_CONNECTION_MANAGER_H__ +#define __GRUSCHLER_CONNECTION_MANAGER_H__ + +#include <telepathy-glib/base-connection-manager.h> + +G_BEGIN_DECLS + +#define GRUSCHLER_TYPE_CONNECTION_MANAGER (gruschler_connection_manager_get_type ()) +#define GRUSCHLER_CONNECTION_MANAGER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GRUSCHLER_TYPE_CONNECTION_MANAGER, GruschlerConnectionManager)) +#define GRUSCHLER_CONNECTION_MANAGER_CLASS(cls) (G_TYPE_CHECK_CLASS_CAST ((cls), GRUSCHLER_TYPE_CONNECTION_MANAGER, GruschlerConnectionManagerClass)) +#define GRUSCHLER_IS_CONNECTION_MANAGER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GRUSCHLER_TYPE_CONNECTION_MANAGER)) +#define GRUSCHLER_IS_CONNECTION_MANAGER_CLASS(obj) (G_TYPE_CHECK_CLASS_TYPE ((obj), GRUSCHLER_TYPE_CONNECTION_MANAGER)) +#define GRUSCHLER_CONNECTION_MANAGER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GRUSCHLER_TYPE_CONNECTION_MANAGER, GruschlerConnectionManagerClass)) + +typedef struct _GruschlerConnectionManager GruschlerConnectionManager; +typedef struct _GruschlerConnectionManagerClass GruschlerConnectionManagerClass; +typedef struct _GruschlerConnectionManagerPrivate GruschlerConnectionManagerPrivate; + +struct _GruschlerConnectionManager { + TpBaseConnectionManager parent_instance; + GruschlerConnectionManagerPrivate *priv; +}; + +struct _GruschlerConnectionManagerClass { + TpBaseConnectionManagerClass parent_class; +}; + +GType +gruschler_connection_manager_get_type (void) G_GNUC_CONST; + +TpBaseConnectionManager * +gruschler_connection_manager_new (void); + +G_END_DECLS + +#endif /* __GRUSCHLER_CONNECTION_MANAGER_H__ */ diff --git a/src/facebook-connection.c b/src/facebook-connection.c new file mode 100644 index 0000000..d0fc959 --- /dev/null +++ b/src/facebook-connection.c @@ -0,0 +1,1101 @@ +/* telepathy-gruschler - A Telepathy connection manager for social networks. + * Copyright (C) 2009 Mathias Hasselmann + * + * 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 + */ +#include "config.h" +#include "facebook-connection.h" + +#include "facebook-contact-list.h" + +#include <libsoup/soup.h> + +#include <rest-extras/facebook-proxy.h> +#include <rest/rest-xml-parser.h> + +#include <rtcom-telepathy-glib/extensions.h> + +#include <telepathy-glib/dbus.h> +#include <telepathy-glib/handle-repo-dynamic.h> +#include <telepathy-glib/handle-repo-static.h> +#include <telepathy-glib/interfaces.h> + +#include <string.h> + +enum { + PROP_0, + PROP_EMAIL, + PROP_PASSWORD, +}; + +static const char *const _fixed_properties[] = { + TP_IFACE_CHANNEL ".ChannelType", + TP_IFACE_CHANNEL ".TargetHandleType", + NULL +}; + +static const char *const _allowed_properties[] = { + TP_IFACE_CHANNEL ".TargetHandle", + TP_IFACE_CHANNEL ".TargetID", + NULL +}; + +struct _GruschlerFacebookConnectionPrivate { + RestProxy *facebook; + SoupSession *session; + + TpHandleRepoIface *contacts; + TpHandleRepoIface *groups; + TpHandleRepoIface *lists; + + GHashTable *group_channels; + GHashTable *list_channels; + + char *email; + char *password; + char *token; +}; + +static void +_channel_manager_iface_init (TpChannelManagerIface *iface); + +G_DEFINE_TYPE_WITH_CODE (GruschlerFacebookConnection, + gruschler_facebook_connection, + TP_TYPE_BASE_CONNECTION, + + G_IMPLEMENT_INTERFACE (TP_TYPE_CHANNEL_MANAGER, + _channel_manager_iface_init); +); + +static void +_set_property (GObject *object, + unsigned prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GruschlerFacebookConnection *self; + + self = GRUSCHLER_FACEBOOK_CONNECTION (object); + + switch (prop_id) + { + case PROP_EMAIL: + g_free (self->priv->email); + self->priv->email = g_value_dup_string (value); + break; + + case PROP_PASSWORD: + g_free (self->priv->password); + self->priv->password = g_value_dup_string (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +_get_property (GObject *object, + unsigned prop_id, + GValue *value, + GParamSpec *pspec) +{ + GruschlerFacebookConnection *self; + + self = GRUSCHLER_FACEBOOK_CONNECTION (object); + + switch (prop_id) + { + case PROP_EMAIL: + g_value_set_string (value, self->priv->email); + break; + + case PROP_PASSWORD: + g_value_set_string (value, self->priv->password); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +_finalize (GObject *object) +{ + GObjectClass *object_class; + GruschlerFacebookConnection *self; + + self = GRUSCHLER_FACEBOOK_CONNECTION (object); + + if (self->priv->facebook) + g_object_unref (self->priv->facebook); + if (self->priv->session) + g_object_unref (self->priv->session); + if (self->priv->contacts) + g_object_unref (self->priv->contacts); + if (self->priv->groups) + g_object_unref (self->priv->groups); + if (self->priv->lists) + g_object_unref (self->priv->lists); + if (self->priv->list_channels) + g_hash_table_unref (self->priv->list_channels); + if (self->priv->group_channels) + g_hash_table_unref (self->priv->group_channels); + + g_free (self->priv->email); + g_free (self->priv->password); + g_free (self->priv->token); + + object_class = G_OBJECT_CLASS (gruschler_facebook_connection_parent_class); + object_class->finalize (object); +} + +static void +_create_handle_repos (TpBaseConnection *connection, + TpHandleRepoIface *repos[NUM_TP_HANDLE_TYPES]) +{ + static const char *list_handle_names[] = { + "publish", /* GRUSCHLER_FACEBOOK_LIST_HANDLE_PUBLISH */ + "subscribe", /* GRUSCHLER_FACEBOOK_LIST_HANDLE_SUBSCRIBE */ + "stored", /* GRUSCHLER_FACEBOOK_LIST_HANDLE_STORED */ + "deny", /* GRUSCHLER_FACEBOOK_LIST_HANDLE_DENY */ + NULL + }; + + GruschlerFacebookConnection *self; + + self = GRUSCHLER_FACEBOOK_CONNECTION (connection); + + self->priv->contacts = tp_dynamic_handle_repo_new (TP_HANDLE_TYPE_CONTACT, NULL, NULL); + self->priv->groups = tp_dynamic_handle_repo_new (TP_HANDLE_TYPE_GROUP, NULL, NULL); + self->priv->lists = tp_static_handle_repo_new (TP_HANDLE_TYPE_LIST, list_handle_names); + + repos[TP_HANDLE_TYPE_CONTACT] = self->priv->contacts; + repos[TP_HANDLE_TYPE_GROUP] = self->priv->groups; + repos[TP_HANDLE_TYPE_LIST] = self->priv->lists; +} + +static GPtrArray * +_create_channel_managers (TpBaseConnection *connection) +{ + GPtrArray *managers = g_ptr_array_new (); + g_ptr_array_add (managers, connection); + return managers; +} + +static GHashTable * +_get_channel_map (GruschlerFacebookConnection *self, + TpHandleType handle_type) +{ + switch (handle_type) + { + case TP_HANDLE_TYPE_LIST: + return self->priv->list_channels; + case TP_HANDLE_TYPE_GROUP: + return self->priv->group_channels; + default: + break; + } + + g_warning ("%s: invalid handle type %s", + G_STRLOC, tp_handle_type_to_string (handle_type)); + + return NULL; +} + +static void +_contact_list_closed_cb (TpExportableChannel *channel, + gpointer user_data) +{ + GruschlerFacebookConnection *self; + TpChannelManager *manager = user_data; + gboolean destroyed; + TpHandleType handle_type; + TpHandle handle; + + self = GRUSCHLER_FACEBOOK_CONNECTION (manager); + + tp_channel_manager_emit_channel_closed_for_object (manager, channel); + + g_object_get (channel, + "handle", &handle, + "handle-type", &handle_type, + "channel-destroyed", &destroyed, NULL); + + if (destroyed) + { + g_debug ("removing channel with %s %u", + tp_handle_type_to_string (handle_type), handle); + + g_hash_table_remove (_get_channel_map (self, handle_type), + GUINT_TO_POINTER (handle)); + } + else + { + g_debug ("reopening channel with handle %u", handle); + tp_channel_manager_emit_new_channel (manager, channel, NULL); + } +} + +static TpExportableChannel * +_create_contact_list (GruschlerFacebookConnection *self, + TpHandleType handle_type, + TpHandle handle, + gpointer request_token) +{ + TpExportableChannel *channel; + GHashTable *channel_map; + char *object_path; + char *mangled_name; + const char *handle_name; + GSList *requests = NULL; + TpHandleRepoIface *handle_repo; + + channel_map = _get_channel_map (self, handle_type); + + g_assert (!g_hash_table_lookup (channel_map, GUINT_TO_POINTER (handle))); + + handle_repo = tp_base_connection_get_handles (TP_BASE_CONNECTION (self), handle_type); + handle_name = tp_handle_inspect (handle_repo, handle); + mangled_name = tp_escape_as_identifier (handle_name); + + g_debug ("instantiating channel %s:%u \"%s\"", + tp_handle_type_to_string (handle_type), + handle, handle_name); + + object_path = g_strdup_printf ("%s/ContactList/%s/%s", + TP_BASE_CONNECTION (self)->object_path, + TP_HANDLE_TYPE_LIST == handle_type ? "List" + : "Group", + mangled_name); + + channel = g_object_new (GRUSCHLER_TYPE_FACEBOOK_CONTACT_LIST, + "connection", self, + "object-path", object_path, + "handle", handle, + "handle-type", handle_type, + NULL); + + g_debug ("created %s", object_path); + + g_hash_table_insert (channel_map, GUINT_TO_POINTER (handle), channel); + + if (request_token) + requests = g_slist_prepend (requests, request_token); + +/* TODO: check if list received */ + tp_channel_manager_emit_new_channel (TP_CHANNEL_MANAGER (self), channel, requests); + + g_signal_connect (channel, "closed", + G_CALLBACK (_contact_list_closed_cb), self); + + g_slist_free (requests); + g_free (mangled_name); + g_free (object_path); + + return channel; +} + +static TpExportableChannel * +_ensure_contact_list (GruschlerFacebookConnection *self, + TpHandleType handle_type, + TpHandle handle, + gpointer request_token) +{ + TpExportableChannel *channel; + + channel = g_hash_table_lookup (_get_channel_map (self, handle_type), + GUINT_TO_POINTER (handle)); + + if (!channel) + channel = _create_contact_list (self, handle_type, handle, request_token); + + return channel; +} + +static char * +_get_unique_connection_name (TpBaseConnection *connection) +{ + GruschlerFacebookConnection *self; + self = GRUSCHLER_FACEBOOK_CONNECTION (connection); + return tp_escape_as_identifier (self->priv->email); +} + +const char * +_get_uid (GruschlerFacebookConnection *self) +{ + return tp_handle_inspect (self->priv->contacts, + TP_BASE_CONNECTION (self)->self_handle); +} + +static void +_report_network_error (GruschlerFacebookConnection *self) +{ + tp_base_connection_change_status (TP_BASE_CONNECTION (self), + TP_CONNECTION_STATUS_DISCONNECTED, + TP_CONNECTION_STATUS_REASON_NETWORK_ERROR); +} + +static RestXmlNode * +_get_xml_root (RestProxyCall *call, + const char *response_name) +{ + static RestXmlParser *parser = NULL; + RestXmlNode *root, *node; + + if (parser == NULL) + parser = rest_xml_parser_new (); + + root = rest_xml_parser_parse_from_data (parser, + rest_proxy_call_get_payload (call), + rest_proxy_call_get_payload_length (call)); + + if (!g_strcmp0 (root->name, "error_response")) + { + node = rest_xml_node_find (root, "error_msg"); + g_warning ("error from facebook: %s", node->content); + rest_xml_node_unref (root); + root = NULL; + } + else if (g_strcmp0 (root->name, response_name)) + { + g_warning ("unexpected response from facebook: %s", root->name); + rest_xml_node_unref (root); + root = NULL; + } + + return root; +} + +static void +_update_uids (GruschlerFacebookConnection *self, + RestXmlNode *result) +{ + TpIntSet *added, *removed; + RestXmlNode *node, *uid; + TpExportableChannel *channel; + TpHandle handle; + + added = tp_intset_new (); + removed = tp_intset_new (); + + for (node = rest_xml_node_find (result, "friend_info"); + node; node = node->next) + { + if (g_strcmp0 (node->name, "friend_info")) + continue; + + uid = rest_xml_node_find (node, "uid2"); + + if (!uid) + continue; + + handle = tp_handle_ensure (self->priv->contacts, + uid->content, NULL, NULL); + + /* TODO: really check changes */ + tp_intset_add (added, handle); + } + + channel = _ensure_contact_list (self, TP_HANDLE_TYPE_LIST, + GRUSCHLER_FACEBOOK_LIST_HANDLE_PUBLISH, NULL); + + tp_group_mixin_change_members (G_OBJECT (channel), NULL, + added, removed, NULL, NULL, + TP_BASE_CONNECTION (self)->self_handle, + TP_CHANNEL_GROUP_CHANGE_REASON_NONE); + + channel = _ensure_contact_list (self, TP_HANDLE_TYPE_LIST, + GRUSCHLER_FACEBOOK_LIST_HANDLE_SUBSCRIBE, NULL); + + tp_group_mixin_change_members (G_OBJECT (channel), NULL, + added, removed, NULL, NULL, + TP_BASE_CONNECTION (self)->self_handle, + TP_CHANNEL_GROUP_CHANGE_REASON_NONE); + + channel = _ensure_contact_list (self, TP_HANDLE_TYPE_LIST, + GRUSCHLER_FACEBOOK_LIST_HANDLE_STORED, NULL); + + tp_group_mixin_change_members (G_OBJECT (channel), NULL, + added, removed, NULL, NULL, + TP_BASE_CONNECTION (self)->self_handle, + TP_CHANNEL_GROUP_CHANGE_REASON_NONE); + + tp_intset_destroy (removed); + tp_intset_destroy (added); +} + +static void +_update_profiles (GruschlerFacebookConnection *self, + RestXmlNode *result) +{ + /* TODO: read profiles to make aliasing interface work */ +} + +static void +_update_friends_cb (RestProxyCall *call, + GError *error, + GObject *weak_object, + gpointer user_data) +{ + GruschlerFacebookConnection *self; + RestXmlNode *root, *result, *name; + + self = GRUSCHLER_FACEBOOK_CONNECTION (weak_object); + root = _get_xml_root (call, "fql_multiquery_response"); + + if (!root) + { + _report_network_error (self); + return; + } + + for (result = rest_xml_node_find (root, "fql_result"); + result; result = result->next) + { + name = rest_xml_node_find (result, "name"); + + if (!name) + continue; + + if (!g_strcmp0 (name->content, "uids")) + { + _update_uids (self, result); + continue; + } + + if (!g_strcmp0 (name->content, "profiles")) + { + _update_profiles (self, result); + continue; + } + } + + rest_xml_node_unref (root); +} + +static void +_update_friends (GruschlerFacebookConnection *self) +{ + GError *error = NULL; + GString *queries; + RestProxyCall *call; + + queries = g_string_new (NULL); + + g_string_append_c (queries, '{'); + + g_string_append_printf (queries, + "\"uids\":" + "\"SELECT uid2 FROM friend WHERE uid1=%s\"", + _get_uid (self)); + + g_string_append_c (queries, ','); + + g_string_append_printf (queries, + "\"profiles\":" + "\"SELECT uid,name,profile_update_time " + "FROM user WHERE uid IN (SELECT uid2 FROM friend WHERE uid1=%s)\"", + _get_uid (self)); + /* TODO: "AND profile_update_time > 0" */ + + g_string_append_c (queries, '}'); + +g_debug ("queries=%s\n", queries->str); + + call = rest_proxy_new_call (self->priv->facebook); + rest_proxy_call_set_function (call, "fql.multiquery"); + rest_proxy_call_add_param (call, "auth_token", self->priv->token); + rest_proxy_call_add_param (call, "queries", queries->str); + + if (! rest_proxy_call_async (call, _update_friends_cb, + G_OBJECT (self), NULL, &error)) + { + /* TODO: properly handle errors */ + g_warning ("%s: %s", G_STRLOC, error->message); + g_error_free (error); + } + + g_string_free (queries, TRUE); + g_object_unref (call); +} + +static void +_auth_get_session_cb (RestProxyCall *call, + GError *error, + GObject *weak_object, + gpointer user_data) +{ + GruschlerFacebookConnection *self; + const char *session_key = NULL; + const char *secret = NULL; + const char *uid = NULL; + RestXmlNode *root, *node; + TpHandle handle; + + self = GRUSCHLER_FACEBOOK_CONNECTION (weak_object); + root = _get_xml_root (call, "auth_getSession_response"); + + if (!root) + { + _report_network_error (self); + return; + } + + if (NULL != (node = rest_xml_node_find (root, "session_key"))) + session_key = node->content; + if (NULL != (node = rest_xml_node_find (root, "secret"))) + secret = node->content; + if (NULL != (node = rest_xml_node_find (root, "uid"))) + uid = node->content; + + if (!session_key || !secret || !uid) + { + _report_network_error (self); + return; + } + + g_debug ("got new secret %s and session key %s for %s\n", secret, session_key, uid); + facebook_proxy_set_session_key (FACEBOOK_PROXY (self->priv->facebook), session_key); + facebook_proxy_set_app_secret (FACEBOOK_PROXY (self->priv->facebook), secret); + + handle = tp_handle_ensure (self->priv->contacts, uid, NULL, NULL); + + tp_base_connection_set_self_handle (TP_BASE_CONNECTION (self), handle); + + tp_base_connection_change_status (TP_BASE_CONNECTION (self), + TP_CONNECTION_STATUS_CONNECTED, + TP_CONNECTION_STATUS_REASON_REQUESTED); + + rest_xml_node_unref (root); + _update_friends (self); +} + +static void +_post_login_data_cb (SoupSession *session, + SoupMessage *message, + gpointer user_data) +{ + GruschlerFacebookConnection *self = user_data; + GError *error = NULL; + RestProxyCall *call; + SoupURI *uri; + + uri = soup_message_get_uri (message); + +g_debug ("status=%d length=%lld", message->status_code, message->response_body->length); +g_debug ("%s %s", uri->path, uri->query); + + if (!g_strcmp0 (uri->path, "/login.php")) + { + tp_base_connection_change_status (TP_BASE_CONNECTION (self), + TP_CONNECTION_STATUS_DISCONNECTED, + TP_CONNECTION_STATUS_REASON_AUTHENTICATION_FAILED); + } + else if (!g_strcmp0 (uri->path, "/desktopapp.php") && + !g_strcmp0 (uri->query, "api_key=" GRUSCHLER_FACEBOOK_APIKEY)) + { + call = rest_proxy_new_call (self->priv->facebook); + rest_proxy_call_set_function (call, "auth.getSession"); + rest_proxy_call_add_param (call, "auth_token", self->priv->token); + rest_proxy_call_async (call, _auth_get_session_cb, + G_OBJECT (self), NULL, &error); + /* TODO: handle error */ + } + else + _report_network_error (self); +} + +static GHashTable * +_parse_form (const char *data, + gsize length) +{ + GRegex *regex_field, *regex_attrs; + GMatchInfo *match_field, *match_attrs; + GHashTable *params; + + params = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); + + regex_field = g_regex_new ("<input\\s+(.*?)>", G_REGEX_CASELESS | G_REGEX_DOTALL, 0, NULL); + regex_attrs = g_regex_new ("\\b(\\S+?)=\"(.*?)\"", G_REGEX_CASELESS | G_REGEX_DOTALL, 0, NULL); + + if (g_regex_match_full (regex_field, data, length, 0, 0, &match_field, NULL)) + { + do + { + char *name = NULL; + char *value = NULL; + int start, end; + + g_match_info_fetch_pos (match_field, 1, &start, &end); + + if (g_regex_match_full (regex_attrs, data + start, + end - start, 0, 0, &match_attrs, NULL)) + { + do + { + char *param = g_match_info_fetch (match_attrs, 1); + + if (!g_strcmp0 (param, "name")) + name = g_match_info_fetch (match_attrs, 2); + else if (!g_strcmp0 (param, "value")) + value = g_match_info_fetch (match_attrs, 2); + + g_free (param); + } + while (g_match_info_next (match_attrs, NULL)); + } + + g_match_info_free (match_attrs); + + if (!name || !value) + { + g_free (value); + g_free (name); + continue; + } + + g_hash_table_insert (params, name, value); + } + while (g_match_info_next (match_field, NULL)); + } + + g_match_info_free (match_field); + g_regex_unref (regex_attrs); + g_regex_unref (regex_field); + + return params; +} + +static void +_fetch_login_form_cb (SoupSession *session, + SoupMessage *message, + gpointer user_data) +{ + GruschlerFacebookConnection *self = user_data; + char *form = NULL; + char *url = NULL; + int start, end; + GHashTable *params; + GRegex *regex; + GMatchInfo *match; + + if (SOUP_STATUS_OK != message->status_code) + { + g_warning ("cannot fetch login form: %s", message->reason_phrase); + _report_network_error (self); + return; + } + + regex = g_regex_new ("<form.*?action=\"(.*?)\".*?>(.*?)</form>", + G_REGEX_CASELESS | G_REGEX_DOTALL, 0, NULL); + + if (g_regex_match_full (regex, message->response_body->data, + message->response_body->length, 0, 0, &match, NULL)) + { + url = g_match_info_fetch (match, 1); + g_match_info_fetch_pos (match, 2, &start, &end); + } + + g_match_info_free (match); + g_regex_unref (regex); + + if (!url) + { + g_warning ("cannot find login URL"); + _report_network_error (self); + return; + } + + params = _parse_form (message->response_body->data + start, end - start); + g_hash_table_insert (params, g_strdup ("email"), g_strdup (self->priv->email)); + g_hash_table_insert (params, g_strdup ("pass"), g_strdup (self->priv->password)); + g_hash_table_remove (params, "charset_test"); + form = soup_form_encode_hash (params); + g_hash_table_unref (params); + + message = soup_message_new (SOUP_METHOD_POST, url); + + soup_message_set_request (message, "application/x-www-form-urlencoded", + SOUP_MEMORY_TAKE, form, strlen (form)); + soup_session_queue_message (self->priv->session, message, + _post_login_data_cb, self); + + g_object_unref (message); + g_free (url); +} + +static void +_auth_create_token_cb (RestProxyCall *call, + GError *error, + GObject *weak_object, + gpointer user_data) +{ + GruschlerFacebookConnection *self; + RestXmlNode *root; + SoupMessage *message; + char *url; + + self = GRUSCHLER_FACEBOOK_CONNECTION (weak_object); + root = _get_xml_root (call, "auth_createToken_response"); + + if (!root) + { + _report_network_error (self); + return; + } + + g_free (self->priv->token); + self->priv->token = g_strdup (root->content); + rest_xml_node_unref (root); + + g_debug ("login token is %s", self->priv->token); + + url = facebook_proxy_build_login_url (FACEBOOK_PROXY (self->priv->facebook), + self->priv->token); + + message = soup_message_new (SOUP_METHOD_GET, url); + + soup_session_queue_message (self->priv->session, message, + _fetch_login_form_cb, self); + + g_object_unref (message); + g_free (url); +} + +static gboolean +_start_connecting (TpBaseConnection *connection, + GError **error_out) +{ + GruschlerFacebookConnection *self; + GError *error = NULL; + RestProxyCall *call = NULL; + gboolean success = FALSE; + + self = GRUSCHLER_FACEBOOK_CONNECTION (connection); + + if (self->priv->facebook || self->priv->session) + { + g_set_error_literal (error_out, TP_ERRORS, TP_ERROR_NOT_AVAILABLE, + "invalid state: session already exists"); + goto OUT; + } + + self->priv->facebook = facebook_proxy_new (GRUSCHLER_FACEBOOK_APIKEY, + GRUSCHLER_FACEBOOK_SECRET); + + self->priv->session = soup_session_sync_new_with_options + (SOUP_SESSION_USER_AGENT, "Firefox/3.0 ", + SOUP_SESSION_ADD_FEATURE_BY_TYPE, + SOUP_TYPE_COOKIE_JAR, NULL); + + /* TODO: try to re-use last session key */ + + call = rest_proxy_new_call (self->priv->facebook); + rest_proxy_call_set_function (call, "auth.createToken"); + success = rest_proxy_call_async (call, _auth_create_token_cb, + G_OBJECT (self), NULL, &error); + +OUT: + if (error) + { + g_set_error (error_out, TP_ERRORS, TP_ERROR_NETWORK_ERROR, "%s (%s:%d)", + error->message, g_quark_to_string (error->domain), + error->code); + g_error_free (error); + } + else if (!success) + g_assert (!error_out || *error_out); + + if (call) + g_object_unref (call); + + return success; +} + +static void +_shut_down (TpBaseConnection *connection) +{ + GruschlerFacebookConnection *self; + + self = GRUSCHLER_FACEBOOK_CONNECTION (connection); + + if (self->priv->facebook) + { + g_object_unref (self->priv->facebook); + self->priv->facebook = NULL; + } +} + +static void +gruschler_facebook_connection_class_init (GruschlerFacebookConnectionClass *class) +{ + static const char *interfaces[] = { +#if 0 + TP_IFACE_CONNECTION_INTERFACE_ALIASING, + TP_IFACE_CONNECTION_INTERFACE_AVATARS, + TP_IFACE_CONNECTION_INTERFACE_SIMPLE_PRESENCE, + TP_IFACE_CONNECTION_INTERFACE_CONTACTS, + TP_IFACE_CONNECTION_INTERFACE_LOCATION, + RTCOM_TP_IFACE_CONNECTION_INTERFACE_CONTACT_INFO, +#endif + NULL + }; + + GParamSpec *pspec; + GObjectClass *object_class; + TpBaseConnectionClass *connection_class; + + object_class = G_OBJECT_CLASS (class); + object_class->set_property = _set_property; + object_class->get_property = _get_property; + object_class->finalize = _finalize; + + connection_class = TP_BASE_CONNECTION_CLASS (class); + connection_class->create_handle_repos = _create_handle_repos; + connection_class->create_channel_managers = _create_channel_managers; + connection_class->get_unique_connection_name = _get_unique_connection_name; + connection_class->start_connecting = _start_connecting; + connection_class->shut_down = _shut_down; + connection_class->interfaces_always_present = interfaces; + + pspec = g_param_spec_string ("email", + "Email", + "Email address for accessing Facebook", + GRUSCHLER_FACEBOOK_DEFAULT_EMAIL, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | + G_PARAM_CONSTRUCT_ONLY); + + g_object_class_install_property (object_class, PROP_EMAIL, pspec); + + pspec = g_param_spec_string ("password", + "Password", + "Password for accessing Facebook", + GRUSCHLER_FACEBOOK_DEFAULT_PASSWORD, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | + G_PARAM_CONSTRUCT_ONLY); + + g_object_class_install_property (object_class, PROP_PASSWORD, pspec); + + g_type_class_add_private (class, sizeof (GruschlerFacebookConnectionPrivate)); +} + +static void +_foreach_channel (TpChannelManager *manager, + TpExportableChannelFunc callback, + gpointer user_data) +{ + GruschlerFacebookConnection *self; + GHashTableIter iter; + TpExportableChannel *channel; + + self = GRUSCHLER_FACEBOOK_CONNECTION (manager); + + g_return_if_fail (NULL != self->priv->list_channels); + g_return_if_fail (NULL != self->priv->group_channels); + + g_hash_table_iter_init (&iter, self->priv->list_channels); + + while (g_hash_table_iter_next (&iter, NULL, (gpointer) &channel)) + callback (channel, user_data); + + g_hash_table_iter_init (&iter, self->priv->group_channels); + + while (g_hash_table_iter_next (&iter, NULL, (gpointer) &channel)) + callback (channel, user_data); +} + +static void +_foreach_channel_class (TpChannelManager *manager, + TpChannelManagerChannelClassFunc callback, + gpointer user_data) +{ + GHashTable *fixed; + + fixed = tp_asv_new (_fixed_properties[0], G_TYPE_STRING, + TP_IFACE_CHANNEL_TYPE_CONTACT_LIST, + _fixed_properties[1], G_TYPE_UINT, + TP_HANDLE_TYPE_LIST, NULL); + + callback (manager, fixed, _allowed_properties, user_data); + + tp_asv_set_uint32 (fixed, _fixed_properties[1], + TP_HANDLE_TYPE_GROUP); + + callback (manager, fixed, _allowed_properties, user_data); + + g_hash_table_unref (fixed); +} + +static gboolean +_handle_channel_request (TpChannelManager *manager, + gpointer request_token, + GHashTable *request_properties, + gboolean require_new) +{ + GruschlerFacebookConnection *self; + GError *error = NULL; + TpExportableChannel *channel = NULL; + const char *channel_type; + TpHandleType handle_type; + TpHandleRepoIface *handle_repo; + TpHandle handle; + + self = GRUSCHLER_FACEBOOK_CONNECTION (manager); + + channel_type = tp_asv_get_string (request_properties, TP_IFACE_CHANNEL ".ChannelType"); + handle_type = tp_asv_get_uint32 (request_properties, TP_IFACE_CHANNEL ".TargetHandleType", NULL); + + if (handle_type != TP_HANDLE_TYPE_LIST && + handle_type != TP_HANDLE_TYPE_GROUP) + return FALSE; + if (g_strcmp0 (channel_type, TP_IFACE_CHANNEL_TYPE_CONTACT_LIST)) + return FALSE; + + /* Check if the handle is valid */ + handle = tp_asv_get_uint32 (request_properties, TP_IFACE_CHANNEL ".TargetHandle", NULL); + handle_repo = tp_base_connection_get_handles (TP_BASE_CONNECTION (self), handle_type); + + if (!tp_handle_is_valid (handle_repo, handle, &error)) + goto OUT; + + /* Check if there are any other properties that we don't understand */ + if (tp_channel_manager_asv_has_unknown_properties (request_properties, + _fixed_properties, + _allowed_properties, + &error)) + goto OUT; + + channel = g_hash_table_lookup (_get_channel_map (self, handle_type), + GUINT_TO_POINTER (handle)); + + if (!channel) + { + _create_contact_list (self, handle_type, handle, request_token); + return TRUE; + } + + if (require_new) + g_set_error (&error, TP_ERRORS, TP_ERROR_NOT_AVAILABLE, + "Contact list #%u has been created already", handle); + +OUT: + if (channel) + tp_channel_manager_emit_request_already_satisfied (self, request_token, channel); + else + { + tp_channel_manager_emit_request_failed (self, request_token, error->domain, + error->code, error->message); + g_error_free (error); + } + + return TRUE; +} + +static gboolean +_create_channel (TpChannelManager *manager, + gpointer request_token, + GHashTable *request_properties) +{ + return _handle_channel_request (manager, request_token, request_properties, TRUE); +} + +static gboolean +_request_channel (TpChannelManager *manager, + gpointer request_token, + GHashTable *request_properties) +{ + return _handle_channel_request (manager, request_token, request_properties, FALSE); +} + +static gboolean +_ensure_channel (TpChannelManager *manager, + gpointer request_token, + GHashTable *request_properties) +{ + return _handle_channel_request (manager, request_token, request_properties, FALSE); +} + +static void +_channel_manager_iface_init (TpChannelManagerIface *iface) +{ + iface->foreach_channel = _foreach_channel; + iface->foreach_channel_class = _foreach_channel_class; + iface->create_channel = _create_channel; + iface->request_channel = _request_channel; + iface->ensure_channel = _ensure_channel; +} + +static void +gruschler_facebook_connection_init (GruschlerFacebookConnection *self) +{ + self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, + GRUSCHLER_TYPE_FACEBOOK_CONNECTION, + GruschlerFacebookConnectionPrivate); + + self->priv->list_channels = g_hash_table_new_full + (g_direct_hash, g_direct_equal, NULL, g_object_unref); + self->priv->group_channels = g_hash_table_new_full + (g_direct_hash, g_direct_equal, NULL, g_object_unref); +} + +TpBaseConnection * +gruschler_facebook_connection_new (TpBaseConnectionManager *cm, + const GruschlerFacebookConnectionParams *params, + GError **error) +{ + g_return_val_if_fail (TP_IS_BASE_CONNECTION_MANAGER (cm), NULL); + g_return_val_if_fail (NULL != params, NULL); + + if (!params->email || !params->email[0]) + { + g_set_error_literal (error, TP_ERRORS, + TP_ERROR_INVALID_ARGUMENT, + "mandatory email argument is empty"); + return NULL; + } + + if (!params->password || !params->password[0]) + { + g_set_error_literal (error, TP_ERRORS, + TP_ERROR_INVALID_ARGUMENT, + "mandatory password argument is empty"); + return NULL; + } + + return g_object_new (GRUSCHLER_TYPE_FACEBOOK_CONNECTION, + "protocol", "facebook", + "email", params->email, + "password", params->password, NULL); +} + +void * +gruschler_facebook_connection_params_new (void) +{ + return g_new0 (GruschlerFacebookConnectionParams, 1); +} + +void +gruschler_facebook_connection_params_free (void *parsed_params) +{ + GruschlerFacebookConnectionParams *params = parsed_params; + + g_free (params->email); + g_free (params->password); + g_free (params); +} + diff --git a/src/facebook-connection.h b/src/facebook-connection.h new file mode 100644 index 0000000..2e389aa --- /dev/null +++ b/src/facebook-connection.h @@ -0,0 +1,67 @@ +/* telepathy-gruschler - A Telepathy connection manager for social networks. + * Copyright (C) 2009 Mathias Hasselmann + * + * 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 __GRUSCHLER_FACEBOOK_CONNECTION_H__ +#define __GRUSCHLER_FACEBOOK_CONNECTION_H__ + +#include <telepathy-glib/base-connection-manager.h> + +G_BEGIN_DECLS + +#define GRUSCHLER_TYPE_FACEBOOK_CONNECTION (gruschler_facebook_connection_get_type ()) +#define GRUSCHLER_FACEBOOK_CONNECTION(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GRUSCHLER_TYPE_FACEBOOK_CONNECTION, GruschlerFacebookConnection)) +#define GRUSCHLER_FACEBOOK_CONNECTION_CLASS(cls) (G_TYPE_CHECK_CLASS_CAST ((cls), GRUSCHLER_TYPE_FACEBOOK_CONNECTION, GruschlerFacebookConnectionClass)) +#define GRUSCHLER_IS_FACEBOOK_CONNECTION(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GRUSCHLER_TYPE_FACEBOOK_CONNECTION)) +#define GRUSCHLER_IS_FACEBOOK_CONNECTION_CLASS(obj) (G_TYPE_CHECK_CLASS_TYPE ((obj), GRUSCHLER_TYPE_FACEBOOK_CONNECTION)) +#define GRUSCHLER_FACEBOOK_CONNECTION_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GRUSCHLER_TYPE_FACEBOOK_CONNECTION, GruschlerFacebookConnectionClass)) + +typedef struct _GruschlerFacebookConnection GruschlerFacebookConnection; +typedef struct _GruschlerFacebookConnectionClass GruschlerFacebookConnectionClass; +typedef struct _GruschlerFacebookConnectionPrivate GruschlerFacebookConnectionPrivate; +typedef struct _GruschlerFacebookConnectionParams GruschlerFacebookConnectionParams; + +struct _GruschlerFacebookConnection { + TpBaseConnection parent_instance; + GruschlerFacebookConnectionPrivate *priv; +}; + +struct _GruschlerFacebookConnectionClass { + TpBaseConnectionClass parent_class; +}; + +struct _GruschlerFacebookConnectionParams { + char *email; + char *password; +}; + +GType +gruschler_facebook_connection_get_type (void) G_GNUC_CONST; + +TpBaseConnection * +gruschler_facebook_connection_new (TpBaseConnectionManager *cm, + const GruschlerFacebookConnectionParams *params, + GError **error); + +void * +gruschler_facebook_connection_params_new (void); + +void +gruschler_facebook_connection_params_free (void *params); + +G_END_DECLS + +#endif /* __GRUSCHLER_FACEBOOK_CONNECTION_H__ */ diff --git a/src/facebook-contact-list.c b/src/facebook-contact-list.c new file mode 100644 index 0000000..020d8d1 --- /dev/null +++ b/src/facebook-contact-list.c @@ -0,0 +1,508 @@ +/* telepathy-gruschler - A Telepathy connection manager for social networks. + * Copyright (C) 2009 Mathias Hasselmann + * + * 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 + */ +#include "config.h" +#include "facebook-contact-list.h" + +#include <telepathy-glib/base-connection.h> +#include <telepathy-glib/channel-iface.h> +#include <telepathy-glib/dbus.h> +#include <telepathy-glib/exportable-channel.h> +#include <telepathy-glib/handle.h> +#include <telepathy-glib/interfaces.h> +#include <telepathy-glib/svc-channel.h> +#include <telepathy-glib/svc-generic.h> + +struct _GruschlerFacebookContactListPrivate { + TpBaseConnection *connection; + char *object_path; + + TpHandle handle; + TpHandleType handle_type; + + unsigned closed : 1; + unsigned disposed : 1; +}; + +static void _channel_iface_init (TpSvcChannelClass *iface); + +G_DEFINE_TYPE_WITH_CODE (GruschlerFacebookContactList, + gruschler_facebook_contact_list, + G_TYPE_OBJECT, + + G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL, _channel_iface_init); + + G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL_INTERFACE_GROUP, + tp_group_mixin_iface_init); + G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_DBUS_PROPERTIES, + tp_dbus_properties_mixin_iface_init); + + G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL_TYPE_CONTACT_LIST, NULL); + G_IMPLEMENT_INTERFACE (TP_TYPE_EXPORTABLE_CHANNEL, NULL); + G_IMPLEMENT_INTERFACE (TP_TYPE_CHANNEL_IFACE, NULL); +); + +/* property enum */ +enum { + PROP_0, + PROP_OBJECT_PATH, + PROP_CHANNEL_TYPE, + PROP_HANDLE_TYPE, + PROP_HANDLE, + PROP_TARGET_ID, + PROP_REQUESTED, + PROP_INITIATOR_HANDLE, + PROP_INITIATOR_ID, + PROP_CONNECTION, + PROP_INTERFACES, + PROP_CHANNEL_DESTROYED, + PROP_CHANNEL_PROPERTIES, +}; + +const char *_channel_interfaces[] = { + TP_IFACE_CHANNEL_INTERFACE_GROUP, +}; + +static void +gruschler_facebook_contact_list_init (GruschlerFacebookContactList *self) +{ + self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, + GRUSCHLER_TYPE_FACEBOOK_CONTACT_LIST, + GruschlerFacebookContactListPrivate); +} + +static GObject * +_constructor (GType type, + unsigned n_props, + GObjectConstructParam *props) +{ + GruschlerFacebookContactList *self; + GObject *object; + GObjectClass *parent_class; + TpHandleRepoIface *handle_repo; + TpHandleRepoIface *contact_repo; + + parent_class = G_OBJECT_CLASS (gruschler_facebook_contact_list_parent_class); + object = parent_class->constructor (type, n_props, props); + self = GRUSCHLER_FACEBOOK_CONTACT_LIST (object); + + g_assert (self->priv->handle_type == TP_HANDLE_TYPE_GROUP || + self->priv->handle_type == TP_HANDLE_TYPE_LIST); + + handle_repo = tp_base_connection_get_handles (self->priv->connection, + self->priv->handle_type); + contact_repo = tp_base_connection_get_handles (self->priv->connection, + TP_HANDLE_TYPE_CONTACT); + + g_assert (tp_handle_is_valid (handle_repo, self->priv->handle, NULL)); + tp_handle_ref (handle_repo, self->priv->handle); + + /* initialize group mixin */ + tp_group_mixin_init (object, + G_STRUCT_OFFSET (GruschlerFacebookContactList, group), + contact_repo, self->priv->connection->self_handle); + + if (TP_HANDLE_TYPE_GROUP == self->priv->handle_type) + { + tp_group_mixin_change_flags (object, + TP_CHANNEL_GROUP_FLAG_CAN_ADD | + TP_CHANNEL_GROUP_FLAG_CAN_REMOVE | + TP_CHANNEL_GROUP_FLAG_PROPERTIES, 0); + } + else if (TP_HANDLE_TYPE_LIST != self->priv->handle_type) + { + g_assert_not_reached (); + } + /* magic contact lists from here down... */ + else if (self->priv->handle == GRUSCHLER_FACEBOOK_LIST_HANDLE_PUBLISH) + { + tp_group_mixin_change_flags (object, + TP_CHANNEL_GROUP_FLAG_CAN_REMOVE | + TP_CHANNEL_GROUP_FLAG_MESSAGE_ACCEPT | + TP_CHANNEL_GROUP_FLAG_MESSAGE_REMOVE | + TP_CHANNEL_GROUP_FLAG_PROPERTIES, 0); + } + else if (self->priv->handle == GRUSCHLER_FACEBOOK_LIST_HANDLE_SUBSCRIBE) + { + tp_group_mixin_change_flags (object, + TP_CHANNEL_GROUP_FLAG_CAN_ADD | + TP_CHANNEL_GROUP_FLAG_CAN_REMOVE | + TP_CHANNEL_GROUP_FLAG_CAN_RESCIND | + TP_CHANNEL_GROUP_FLAG_MESSAGE_ADD | + TP_CHANNEL_GROUP_FLAG_MESSAGE_REMOVE | + TP_CHANNEL_GROUP_FLAG_MESSAGE_RESCIND | + TP_CHANNEL_GROUP_FLAG_PROPERTIES, 0); + } + else if (self->priv->handle == GRUSCHLER_FACEBOOK_LIST_HANDLE_STORED) + { + tp_group_mixin_change_flags (object, + TP_CHANNEL_GROUP_FLAG_CAN_REMOVE | + TP_CHANNEL_GROUP_FLAG_PROPERTIES, 0); + } + else if (self->priv->handle == GRUSCHLER_FACEBOOK_LIST_HANDLE_DENY) + { + tp_group_mixin_change_flags (object, + TP_CHANNEL_GROUP_FLAG_CAN_ADD | + TP_CHANNEL_GROUP_FLAG_CAN_REMOVE | + TP_CHANNEL_GROUP_FLAG_PROPERTIES, 0); + } + else + { + g_assert_not_reached (); + } + + /* register object on the bus */ + dbus_g_connection_register_g_object (tp_get_bus(), + self->priv->object_path, object); + + return object; +} + +static void +_get_property (GObject *object, + unsigned prop_id, + GValue *value, + GParamSpec *pspec) +{ + GruschlerFacebookContactList *self; + TpHandleRepoIface *handles; + const char *handle_name; + + self = GRUSCHLER_FACEBOOK_CONTACT_LIST (object); + + switch (prop_id) + { + case PROP_OBJECT_PATH: + g_value_set_string (value, self->priv->object_path); + break; + case PROP_CHANNEL_TYPE: + g_value_set_static_string (value, TP_IFACE_CHANNEL_TYPE_CONTACT_LIST); + break; + case PROP_HANDLE_TYPE: + g_value_set_uint (value, self->priv->handle_type); + break; + case PROP_HANDLE: + g_value_set_uint (value, self->priv->handle); + break; + case PROP_TARGET_ID: + handles = tp_base_connection_get_handles (self->priv->connection, + self->priv->handle_type); + handle_name = tp_handle_inspect (handles, self->priv->handle); + g_value_set_string (value, handle_name); + break; + case PROP_CONNECTION: + g_value_set_object (value, self->priv->connection); + break; + case PROP_INTERFACES: + g_value_set_boxed (value, _channel_interfaces); + break; + case PROP_INITIATOR_HANDLE: + g_value_set_uint (value, 0); + break; + case PROP_INITIATOR_ID: + g_value_set_static_string (value, ""); + break; + case PROP_REQUESTED: + g_value_set_boolean (value, FALSE); + break; + case PROP_CHANNEL_DESTROYED: + g_value_set_boolean (value, self->priv->closed); + break; + case PROP_CHANNEL_PROPERTIES: + g_value_take_boxed (value, + tp_dbus_properties_mixin_make_properties_hash + (object, + TP_IFACE_CHANNEL, "TargetHandle", + TP_IFACE_CHANNEL, "TargetHandleType", + TP_IFACE_CHANNEL, "ChannelType", + TP_IFACE_CHANNEL, "TargetID", + TP_IFACE_CHANNEL, "InitiatorHandle", + TP_IFACE_CHANNEL, "InitiatorID", + TP_IFACE_CHANNEL, "Requested", + TP_IFACE_CHANNEL, "Interfaces", + NULL)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +_set_property (GObject *object, + unsigned prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GruschlerFacebookContactList *self; + + self = GRUSCHLER_FACEBOOK_CONTACT_LIST (object); + + switch (prop_id) + { + case PROP_OBJECT_PATH: + g_free (self->priv->object_path); + self->priv->object_path = g_value_dup_string (value); + break; + case PROP_CHANNEL_TYPE: + break; /* ignore */ + case PROP_HANDLE_TYPE: + self->priv->handle_type = g_value_get_uint (value); + break; + case PROP_HANDLE: + self->priv->handle = g_value_get_uint (value); + break; + case PROP_INITIATOR_HANDLE: + break; /* ignore */ + case PROP_CONNECTION: + self->priv->connection = g_value_get_object (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +_dispose (GObject *object) +{ + GruschlerFacebookContactList *self; + + self = GRUSCHLER_FACEBOOK_CONTACT_LIST (object); + + if (!self->priv->disposed) + { + if (!self->priv->closed) + tp_svc_channel_emit_closed (self); + + self->priv->disposed = TRUE; + } + + G_OBJECT_CLASS (gruschler_facebook_contact_list_parent_class)->dispose (object); +} + +static void +_finalize (GObject *object) +{ + GruschlerFacebookContactList *self; + TpHandleRepoIface *handles; + + self = GRUSCHLER_FACEBOOK_CONTACT_LIST (object); + + handles = tp_base_connection_get_handles (self->priv->connection, + self->priv->handle_type); + + tp_handle_unref(handles, self->priv->handle); + + if (self->priv->object_path) + g_free (self->priv->object_path); + + G_OBJECT_CLASS (gruschler_facebook_contact_list_parent_class)->finalize (object); +} + +static gboolean +_group_add_member (GObject *object, + TpHandle handle, + const char *message, + GError **error) +{ + /* FIXME: implement this */ + g_debug ("%s: handle=%u, message=%s", G_STRFUNC, handle, message); + g_return_val_if_reached (FALSE); +} + +static gboolean +_group_remove_member (GObject *object, + TpHandle handle, + const char *message, + GError **error) +{ + /* FIXME: implement this */ + g_debug ("%s: handle=%u, message=%s", G_STRFUNC, handle, message); + g_return_val_if_reached (FALSE); +} + +static void +gruschler_facebook_contact_list_class_init (GruschlerFacebookContactListClass *class) +{ + static TpDBusPropertiesMixinPropImpl channel_props[] = { + { "TargetHandleType", "handle-type", NULL }, + { "TargetHandle", "handle", NULL }, + { "TargetID", "target-id", NULL }, + { "ChannelType", "channel-type", NULL }, + { "Interfaces", "interfaces", NULL }, + { "Requested", "requested", NULL }, + { "InitiatorHandle", "initiator-handle", NULL }, + { "InitiatorID", "initiator-id", NULL }, + { NULL, } + }; + + static TpDBusPropertiesMixinIfaceImpl prop_interfaces[] = { + { TP_IFACE_CHANNEL, + tp_dbus_properties_mixin_getter_gobject_properties, + NULL, channel_props, + }, + + { NULL, } + }; + + gsize offset; + GObjectClass *object_class; + GParamSpec *pspec; + + object_class = G_OBJECT_CLASS (class); + + object_class->constructor = _constructor; + object_class->get_property = _get_property; + object_class->set_property = _set_property; + object_class->dispose = _dispose; + object_class->finalize = _finalize; + + g_object_class_override_property (object_class, PROP_OBJECT_PATH, "object-path"); + g_object_class_override_property (object_class, PROP_CHANNEL_TYPE, "channel-type"); + g_object_class_override_property (object_class, PROP_HANDLE_TYPE, "handle-type"); + g_object_class_override_property (object_class, PROP_HANDLE, "handle"); + + g_object_class_override_property (object_class, PROP_CHANNEL_DESTROYED, "channel-destroyed"); + g_object_class_override_property (object_class, PROP_CHANNEL_PROPERTIES, "channel-properties"); + + pspec = g_param_spec_object ("connection", + "Connection", + "The connection that owns this channel.", + TP_TYPE_BASE_CONNECTION, + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + g_object_class_install_property (object_class, PROP_CONNECTION, pspec); + + pspec = g_param_spec_boxed ("interfaces", + "Interfaces", + "Additional interfaces implemented by channel", + G_TYPE_STRV, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + g_object_class_install_property (object_class, PROP_INTERFACES, pspec); + + pspec = g_param_spec_string ("target-id", + "Target ID", + "Inspected name of this channel's handle", + NULL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + g_object_class_install_property (object_class, PROP_TARGET_ID, pspec); + + pspec = g_param_spec_uint ("initiator-handle", + "Initiator handle", + "The contact who initiated the channel", + 0, G_MAXUINT32, 0, + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + g_object_class_install_property (object_class, PROP_INITIATOR_HANDLE, pspec); + + pspec = g_param_spec_string ("initiator-id", + "Initiator ID", + "Inspected name of the channel initiator handle", + NULL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + g_object_class_install_property (object_class, PROP_INITIATOR_ID, pspec); + + pspec = g_param_spec_boolean ("requested", + "Requested", + "Wheither this channel was requested by the local user", + FALSE, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + g_object_class_install_property (object_class, PROP_REQUESTED, pspec); + + class->properties_class.interfaces = prop_interfaces; + offset = G_STRUCT_OFFSET (GruschlerFacebookContactListClass, properties_class); + tp_dbus_properties_mixin_class_init (object_class, offset); + + offset = G_STRUCT_OFFSET (GruschlerFacebookContactListClass, group_class), + tp_group_mixin_class_init (object_class, offset, + _group_add_member, _group_remove_member); + tp_group_mixin_init_dbus_properties (object_class); + + g_type_class_add_private (class, sizeof (GruschlerFacebookContactListPrivate)); +} + +static void +_channel_close (TpSvcChannel *channel, + DBusGMethodInvocation *context) +{ + GruschlerFacebookContactList *self; + + self = GRUSCHLER_FACEBOOK_CONTACT_LIST (channel); + + if (TP_HANDLE_TYPE_LIST == self->priv->handle_type) + { + GError error = { + TP_ERRORS, TP_ERROR_NOT_IMPLEMENTED, + "you may not close contact list channels" + }; + + dbus_g_method_return_error (context, &error); + } + else if (tp_handle_set_size (self->group.members)) + { + GError error = { + TP_ERRORS, TP_ERROR_NOT_AVAILABLE, + "you may not close this group, because it's not empty" + }; + + dbus_g_method_return_error (context, &error); + } + else + { + /* TODO: figure out if friend lists can be modified */ + self->priv->closed = TRUE; + tp_svc_channel_emit_closed (self); + tp_svc_channel_return_from_close (context); + } +} + +static void +_channel_get_channel_type (TpSvcChannel *channel, + DBusGMethodInvocation *context) +{ + tp_svc_channel_return_from_get_channel_type (context, TP_IFACE_CHANNEL_TYPE_CONTACT_LIST); +} + + +static void +_channel_get_handle (TpSvcChannel *channel, + DBusGMethodInvocation *context) +{ + tp_svc_channel_return_from_get_handle (context, TP_HANDLE_TYPE_CONTACT, + GRUSCHLER_FACEBOOK_CONTACT_LIST (channel)->priv->handle); +} + +static void +_channel_get_interfaces (TpSvcChannel *channel, + DBusGMethodInvocation *context) +{ + tp_svc_channel_return_from_get_interfaces (context, _channel_interfaces); +} + +static void +_channel_iface_init (TpSvcChannelClass *iface) +{ +#define IMPLEMENT(x) \ + tp_svc_channel_implement_##x (iface, _channel_##x) + IMPLEMENT(close); + IMPLEMENT(get_channel_type); + IMPLEMENT(get_handle); + IMPLEMENT(get_interfaces); +#undef IMPLEMENT +} + diff --git a/src/facebook-contact-list.h b/src/facebook-contact-list.h new file mode 100644 index 0000000..a2c8f3c --- /dev/null +++ b/src/facebook-contact-list.h @@ -0,0 +1,62 @@ +/* telepathy-gruschler - A Telepathy connection manager for social networks. + * Copyright (C) 2009 Mathias Hasselmann + * + * 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 __GRUSCHLER_FACEBOOK_CONTACT_LIST_H__ +#define __GRUSCHLER_FACEBOOK_CONTACT_LIST_H__ + +#include <telepathy-glib/dbus-properties-mixin.h> +#include <telepathy-glib/group-mixin.h> + +G_BEGIN_DECLS + +#define GRUSCHLER_TYPE_FACEBOOK_CONTACT_LIST (gruschler_facebook_contact_list_get_type ()) +#define GRUSCHLER_FACEBOOK_CONTACT_LIST(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GRUSCHLER_TYPE_FACEBOOK_CONTACT_LIST, GruschlerFacebookContactList)) +#define GRUSCHLER_FACEBOOK_CONTACT_LIST_CLASS(cls) (G_TYPE_CHECK_CLASS_CAST ((cls), GRUSCHLER_TYPE_FACEBOOK_CONTACT_LIST, GruschlerFacebookContactListClass)) +#define GRUSCHLER_IS_FACEBOOK_CONTACT_LIST(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GRUSCHLER_TYPE_FACEBOOK_CONTACT_LIST)) +#define GRUSCHLER_IS_FACEBOOK_CONTACT_LIST_CLASS(obj) (G_TYPE_CHECK_CLASS_TYPE ((obj), GRUSCHLER_TYPE_FACEBOOK_CONTACT_LIST)) +#define GRUSCHLER_FACEBOOK_CONTACT_LIST_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GRUSCHLER_TYPE_FACEBOOK_CONTACT_LIST, GruschlerFacebookContactListClass)) + +typedef enum { + GRUSCHLER_FACEBOOK_LIST_HANDLE_PUBLISH = 1, + GRUSCHLER_FACEBOOK_LIST_HANDLE_SUBSCRIBE, + GRUSCHLER_FACEBOOK_LIST_HANDLE_STORED, + GRUSCHLER_FACEBOOK_LIST_HANDLE_DENY, +} GruschlerFacebookListHandle; + +typedef struct _GruschlerFacebookContactList GruschlerFacebookContactList; +typedef struct _GruschlerFacebookContactListClass GruschlerFacebookContactListClass; +typedef struct _GruschlerFacebookContactListPrivate GruschlerFacebookContactListPrivate; + +struct _GruschlerFacebookContactList { + GObject parent_instance; + TpGroupMixin group; + GruschlerFacebookContactListPrivate *priv; +}; + +struct _GruschlerFacebookContactListClass { + GObjectClass parent_class; + TpGroupMixinClass group_class; + TpDBusPropertiesMixinClass properties_class; +}; + +GType +gruschler_facebook_contact_list_get_type (void) G_GNUC_CONST; + +G_END_DECLS + +#endif /* __GRUSCHLER_FACEBOOK_CONTACT_LIST_H__ */ + diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..1feaf2e --- /dev/null +++ b/src/main.c @@ -0,0 +1,35 @@ +/* telepathy-gruschler - A Telepathy connection manager for social networks. + * Copyright (C) 2009 Mathias Hasselmann + * + * 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 + */ +#include "config.h" +#include "connection-manager.h" + +#include <telepathy-glib/debug.h> +#include <telepathy-glib/run.h> + +int +main (int argc, + char **argv) +{ + g_thread_init (NULL); + tp_debug_set_persistent (TRUE); + + return tp_run_connection_manager (PACKAGE, VERSION, + gruschler_connection_manager_new, + argc, argv); +} + diff --git a/tests/test-facebook.c b/tests/test-facebook.c new file mode 100644 index 0000000..d7cbd1d --- /dev/null +++ b/tests/test-facebook.c @@ -0,0 +1,299 @@ +/* telepathy-gruschler - A Telepathy connection manager for social networks. + * Copyright (C) 2009 Mathias Hasselmann + * + * 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 + */ +#include "config.h" + +#include <rest/rest-xml-parser.h> +#include <rest-extras/facebook-proxy.h> +#include <libsoup/soup.h> +#include <locale.h> +#include <string.h> + +static RestXmlNode * +get_xml (RestProxyCall *call) +{ + static RestXmlParser *parser = NULL; + RestXmlNode *root, *node; + + if (parser == NULL) + parser = rest_xml_parser_new (); + + root = rest_xml_parser_parse_from_data (parser, + rest_proxy_call_get_payload (call), + rest_proxy_call_get_payload_length (call)); + + if (!g_strcmp0 (root->name, "error_response")) + { + node = rest_xml_node_find (root, "error_msg"); + g_error ("Error from facebook: %s", node->content); + } + + g_object_unref (call); + + return root; +} + +static void +facebook_proxy_login (RestProxy *proxy, + const char *token, + const char *email, + const char *pass) +{ + char *url; + + url = facebook_proxy_build_login_url (FACEBOOK_PROXY (proxy), token); + + g_print ("login URL: %s\n", url); + + SoupSession *session; + SoupMessage *msg; + + session = soup_session_sync_new_with_options (SOUP_SESSION_USER_AGENT, "Firefox/3.0 ", + SOUP_SESSION_ADD_FEATURE, soup_cookie_jar_new (), + NULL); + + msg = soup_message_new (SOUP_METHOD_GET, url); + soup_session_send_message (session, msg); + + GMatchInfo *info, *info_params; + GRegex *regex, *re_parms; + const char *s; + int start, end; + + g_free (url); + url = NULL; + + regex = g_regex_new ("<form.*?action=\"(.*?)\".*?>(.*?)</form>", + G_REGEX_CASELESS | G_REGEX_DOTALL, 0, NULL); + + if (g_regex_match_full (regex, msg->response_body->data, + msg->response_body->length, 0, + 0, &info, NULL)) + { + url = g_match_info_fetch (info, 1); + g_match_info_fetch_pos (info, 2, &start, &end); + } + + g_match_info_free (info); + g_regex_unref (regex); + + regex = g_regex_new ("<input\\s+(.*?)>", G_REGEX_CASELESS | G_REGEX_DOTALL, 0, NULL); + re_parms = g_regex_new ("\\b(\\S+?)=\"(.*?)\"", G_REGEX_CASELESS | G_REGEX_DOTALL, 0, NULL); + + GHashTable *params = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); + + if (g_regex_match_full (regex, s = msg->response_body->data + start, end - start, 0, 0, &info, NULL)) + { + do + { + char *name = NULL, *value = NULL; + + g_match_info_fetch_pos (info, 1, &start, &end); + + if (g_regex_match_full (re_parms, s + start, end - start, 0, 0, &info_params, NULL)) + { + do + { + char *param = g_match_info_fetch (info_params, 1); + + if (!g_strcmp0 (param, "name")) + name = g_match_info_fetch (info_params, 2); + else if (!g_strcmp0 (param, "value")) + value = g_match_info_fetch (info_params, 2); + + g_free (param); + } + while (g_match_info_next (info_params, NULL)); + } + + if (name && value) + { + if (!g_strcmp0 (name, "charset_test")) + { + g_free (value); + g_free (name); + continue; + } + + if (!g_strcmp0 (name, "email")) + { + g_free (value); + value = g_strdup (email); + } + else if (!g_strcmp0 (name, "pass")) + { + g_free (value); + value = g_strdup (pass); + } + + g_hash_table_insert (params, name, value); + } + } + while (g_match_info_next (info, NULL)); + } + + g_match_info_free (info); + g_regex_unref (regex); + g_object_unref (msg); + + char *form = soup_form_encode_hash (params); + + msg = soup_message_new (SOUP_METHOD_POST, url); + soup_message_set_request (msg, "application/x-www-form-urlencoded", + SOUP_MEMORY_TAKE, form, strlen (form)); + soup_session_send_message (session, msg); + + g_file_set_contents ("blub.html", msg->response_body->data, msg->response_body->length, NULL); + + g_object_unref (msg); + g_object_unref (session); +} + +static char ** +facebook_proxy_friends_get (RestProxy *proxy) +{ + GError *error = NULL; + RestXmlNode *root, *node; + RestProxyCall *call; + GPtrArray *uids; + + call = rest_proxy_new_call (proxy); + rest_proxy_call_set_function (call, "friends.get"); + + if (!rest_proxy_call_run (call, NULL, &error)) + g_error ("Cannot get friends: %s", error->message); + + root = get_xml (call); + node = rest_xml_node_find (root, "uid"); + uids = g_ptr_array_new (); + + while (node) + { + if (!g_strcmp0 ("uid", node->name)) + g_ptr_array_add (uids, g_strdup (node->content)); + + node = node->next; + } + + rest_xml_node_unref (root); + g_ptr_array_add (uids, NULL); + + return (char **) g_ptr_array_free (uids, FALSE); +} + +int +main (int argc, + char **argv) +{ + GError *error = NULL; + RestProxyCall *call; + RestProxy *proxy; + RestXmlNode *root; + + char *token, *session_key, *secret, *uid; + + setlocale (LC_ALL, ""); + g_thread_init (NULL); + g_type_init (); + + proxy = facebook_proxy_new (GRUSCHLER_FACEBOOK_APIKEY, + GRUSCHLER_FACEBOOK_SECRET); + + call = rest_proxy_new_call (proxy); + rest_proxy_call_set_function (call, "auth.createToken"); + + if (!rest_proxy_call_run (call, NULL, &error)) + g_error ("cannot get token: %s", error->message); + + root = get_xml (call); + + if (g_strcmp0 (root->name, "auth_createToken_response")) + g_error ("Unexpected response to auto.createToken"); + + token = g_strdup (root->content); + rest_xml_node_unref (root); + + g_print ("token: %s\n", token); + + facebook_proxy_login (proxy, token, + GRUSCHLER_FACEBOOK_DEFAULT_EMAIL, + GRUSCHLER_FACEBOOK_DEFAULT_PASSWORD); + + call = rest_proxy_new_call (proxy); + rest_proxy_call_set_function (call, "auth.getSession"); + rest_proxy_call_add_param (call, "auth_token", token); + + if (!rest_proxy_call_run (call, NULL, &error)) + g_error ("Cannot get session: %s", error->message); + + root = get_xml (call); + + session_key = rest_xml_node_find (root, "session_key")->content; + secret = rest_xml_node_find (root, "secret")->content; + uid = rest_xml_node_find (root, "uid")->content; + + g_print ("Got new secret %s and session key %s for %s\n", secret, session_key, uid); + + facebook_proxy_set_session_key (FACEBOOK_PROXY (proxy), session_key); + facebook_proxy_set_app_secret (FACEBOOK_PROXY (proxy), secret); + + rest_xml_node_unref (root); + + char **uids = facebook_proxy_friends_get (proxy); + + call = rest_proxy_new_call (proxy); + rest_proxy_call_set_function (call, "users.getInfo"); + rest_proxy_call_add_param (call, "uids", g_strjoinv (",", uids)); + rest_proxy_call_add_param (call, "fields", "uid,about_me,birthday_date,current_location,first_name,hometown_location,last_name,name,pic,profile_update_time,profile_url,sex,status,username,website"); + rest_proxy_call_add_param (call, "locale", "en_US"); + + if (!rest_proxy_call_run (call, NULL, &error)) + g_error ("Cannot get user info: %s", error->message); + + root = get_xml (call); + + RestXmlNode *user, *detail; + + for (user = rest_xml_node_find (root, "user"); user; user = user->next) + { + if (g_strcmp0 ("user", user->name)) + continue; + + GHashTableIter iter; + + g_print ("---\n"); + + g_hash_table_iter_init (&iter, user->children); + while (g_hash_table_iter_next (&iter, NULL, (gpointer) &detail)) + { + g_print (" %s: %s\n", detail->name, detail->content); + } + } + + g_strfreev (uids); + + g_object_unref (proxy); +/* +fql.multiquery +queries= + { +"friends": "SELECT uid2 FROM friend WHERE uid1=667897485", +"profiles": "SELECT uid, name, profile_update_time FROM user WHERE profile_update_time > 0 AND uid IN (SELECT uid2 FROM friend WHERE uid1=667897485)" +} +*/ + return 0; +} |