# HG changeset patch # User Mark Huetsch # Date 1151290734 0 # Node ID 983fd420e86b01ae8bdea28c3097ccce5c77bc86 # Parent 5642f4658b5914ff323281e7db7db372fc703965 [gaim-migrate @ 16340] Performed minor cleanup of the OpenQ codebase and patched it into the Gaim trunk as a prpl, providing basic QQ functionality. committer: Tailor Script diff -r 5642f4658b59 -r 983fd420e86b configure.ac --- a/configure.ac Sun Jun 25 03:15:41 2006 +0000 +++ b/configure.ac Mon Jun 26 02:58:54 2006 +0000 @@ -401,7 +401,7 @@ fi if test "x$STATIC_PRPLS" = "xall" ; then - STATIC_PRPLS="bonjour gg irc jabber msn novell oscar sametime silc simple yahoo zephyr" + STATIC_PRPLS="bonjour gg irc jabber msn novell oscar qq sametime silc simple yahoo zephyr" fi if test "x$have_meanwhile" != "xyes" ; then STATIC_PRPLS=`echo $STATIC_PRPLS | $sedpath 's/sametime//'` @@ -436,6 +436,7 @@ msn) static_msn=yes ;; novell) static_novell=yes ;; oscar) static_oscar=yes ;; + qq) static_qq=yes ;; sametime) static_sametime=yes ;; silc) static_silc=yes ;; simple) static_simple=yes ;; @@ -452,6 +453,7 @@ AM_CONDITIONAL(STATIC_MSN, test "x$static_msn" = "xyes") AM_CONDITIONAL(STATIC_NOVELL, test "x$static_novell" = "xyes") AM_CONDITIONAL(STATIC_OSCAR, test "x$static_oscar" = "xyes") +AM_CONDITIONAL(STATIC_QQ, test "x$static_qq" = "xyes") AM_CONDITIONAL(STATIC_SAMETIME, test "x$static_sametime" = "xyes" -a "x$have_meanwhile" = "xyes") AM_CONDITIONAL(STATIC_SILC, test "x$static_silc" = "xyes" -a "x$silcincludes" = "xyes" -a "x$silcclient" = "xyes") AM_CONDITIONAL(STATIC_SIMPLE, test "x$static_simple" = "xyes") @@ -464,7 +466,7 @@ AC_ARG_WITH(dynamic_prpls, [AC_HELP_STRING([--with-dynamic-prpls], [specify which protocols to build dynamically])], [DYNAMIC_PRPLS=`echo $withval | $sedpath 's/,/ /g'`]) if test "x$DYNAMIC_PRPLS" = "xall" ; then - DYNAMIC_PRPLS="bonjour gg irc jabber msn novell oscar sametime silc simple yahoo zephyr" + DYNAMIC_PRPLS="bonjour gg irc jabber msn novell oscar qq sametime silc simple yahoo zephyr" fi if test "x$have_meanwhile" != "xyes"; then DYNAMIC_PRPLS=`echo $DYNAMIC_PRPLS | $sedpath 's/sametime//'` @@ -488,6 +490,7 @@ msn) dynamic_msn=yes ;; novell) dynamic_novell=yes ;; oscar) dynamic_oscar=yes ;; + qq) dynamic_qq=yes ;; sametime) dynamic_sametime=yes ;; silc) dynamic_silc=yes ;; simple) dynamic_simple=yes ;; @@ -504,6 +507,7 @@ AM_CONDITIONAL(DYNAMIC_MSN, test "x$dynamic_msn" = "xyes") AM_CONDITIONAL(DYNAMIC_NOVELL, test "x$dynamic_novell" = "xyes") AM_CONDITIONAL(DYNAMIC_OSCAR, test "x$dynamic_oscar" = "xyes") +AM_CONDITIONAL(DYNAMIC_QQ, test "x$dynamic_qq" = "xyes") AM_CONDITIONAL(DYNAMIC_SAMETIME, test "x$dynamic_sametime" = "xyes" -a "x$have_meanwhile" = "xyes") AM_CONDITIONAL(DYNAMIC_SILC, test "x$dynamic_silc" = "xyes" -a "x$silcincludes" = "xyes" -a "x$silcclient" = "xyes") AM_CONDITIONAL(DYNAMIC_SIMPLE, test "x$dynamic_simple" = "xyes") @@ -1772,6 +1776,7 @@ src/protocols/napster/Makefile src/protocols/novell/Makefile src/protocols/oscar/Makefile + src/protocols/qq/Makefile src/protocols/sametime/Makefile src/protocols/silc/Makefile src/protocols/simple/Makefile diff -r 5642f4658b59 -r 983fd420e86b pixmaps/smileys/default/Makefile.am --- a/pixmaps/smileys/default/Makefile.am Sun Jun 25 03:15:41 2006 +0000 +++ b/pixmaps/smileys/default/Makefile.am Mon Jun 26 02:58:54 2006 +0000 @@ -107,6 +107,104 @@ msn_wink.gif \ msn_xbox.png +QQ_SMILEYS = \ + qq_smiley_0.gif \ + qq_smiley_10.gif \ + qq_smiley_11.gif \ + qq_smiley_12.gif \ + qq_smiley_13.gif \ + qq_smiley_14.gif \ + qq_smiley_15.gif \ + qq_smiley_16.gif \ + qq_smiley_17.gif \ + qq_smiley_18.gif \ + qq_smiley_19.gif \ + qq_smiley_1.gif \ + qq_smiley_20.gif \ + qq_smiley_21.gif \ + qq_smiley_22.gif \ + qq_smiley_23.gif \ + qq_smiley_24.gif \ + qq_smiley_25.gif \ + qq_smiley_26.gif \ + qq_smiley_27.gif \ + qq_smiley_28.gif \ + qq_smiley_29.gif \ + qq_smiley_2.gif \ + qq_smiley_30.gif \ + qq_smiley_31.gif \ + qq_smiley_32.gif \ + qq_smiley_33.gif \ + qq_smiley_34.gif \ + qq_smiley_35.gif \ + qq_smiley_36.gif \ + qq_smiley_37.gif \ + qq_smiley_38.gif \ + qq_smiley_39.gif \ + qq_smiley_3.gif \ + qq_smiley_40.gif \ + qq_smiley_41.gif \ + qq_smiley_42.gif \ + qq_smiley_43.gif \ + qq_smiley_44.gif \ + qq_smiley_45.gif \ + qq_smiley_46.gif \ + qq_smiley_47.gif \ + qq_smiley_48.gif \ + qq_smiley_49.gif \ + qq_smiley_4.gif \ + qq_smiley_50.gif \ + qq_smiley_51.gif \ + qq_smiley_52.gif \ + qq_smiley_53.gif \ + qq_smiley_54.gif \ + qq_smiley_55.gif \ + qq_smiley_56.gif \ + qq_smiley_57.gif \ + qq_smiley_58.gif \ + qq_smiley_59.gif \ + qq_smiley_5.gif \ + qq_smiley_60.gif \ + qq_smiley_61.gif \ + qq_smiley_62.gif \ + qq_smiley_63.gif \ + qq_smiley_64.gif \ + qq_smiley_65.gif \ + qq_smiley_66.gif \ + qq_smiley_67.gif \ + qq_smiley_68.gif \ + qq_smiley_69.gif \ + qq_smiley_6.gif \ + qq_smiley_70.gif \ + qq_smiley_71.gif \ + qq_smiley_72.gif \ + qq_smiley_73.gif \ + qq_smiley_74.gif \ + qq_smiley_75.gif \ + qq_smiley_76.gif \ + qq_smiley_77.gif \ + qq_smiley_78.gif \ + qq_smiley_79.gif \ + qq_smiley_7.gif \ + qq_smiley_80.gif \ + qq_smiley_81.gif \ + qq_smiley_82.gif \ + qq_smiley_83.gif \ + qq_smiley_84.gif \ + qq_smiley_85.gif \ + qq_smiley_86.gif \ + qq_smiley_87.gif \ + qq_smiley_88.gif \ + qq_smiley_89.gif \ + qq_smiley_8.gif \ + qq_smiley_90.gif \ + qq_smiley_91.gif \ + qq_smiley_92.gif \ + qq_smiley_93.gif \ + qq_smiley_94.gif \ + qq_smiley_95.gif \ + qq_smiley_9.gif + YAHOO_SMILEYS = \ yahoo_alien.gif \ yahoo_alien2.gif \ @@ -194,6 +292,7 @@ $(AIM_SMILEYS) \ $(MSN_SMILEYS) \ $(YAHOO_SMILEYS) \ + $(QQ_SMILEYS) \ theme EXTRA_DIST = $(gaimsmileypix_DATA) diff -r 5642f4658b59 -r 983fd420e86b pixmaps/smileys/default/qq_smiley_0.gif Binary file pixmaps/smileys/default/qq_smiley_0.gif has changed diff -r 5642f4658b59 -r 983fd420e86b pixmaps/smileys/default/qq_smiley_1.gif Binary file pixmaps/smileys/default/qq_smiley_1.gif has changed diff -r 5642f4658b59 -r 983fd420e86b pixmaps/smileys/default/qq_smiley_10.gif Binary file pixmaps/smileys/default/qq_smiley_10.gif has changed diff -r 5642f4658b59 -r 983fd420e86b pixmaps/smileys/default/qq_smiley_11.gif Binary file pixmaps/smileys/default/qq_smiley_11.gif has changed diff -r 5642f4658b59 -r 983fd420e86b pixmaps/smileys/default/qq_smiley_12.gif Binary file pixmaps/smileys/default/qq_smiley_12.gif has changed diff -r 5642f4658b59 -r 983fd420e86b pixmaps/smileys/default/qq_smiley_13.gif Binary file pixmaps/smileys/default/qq_smiley_13.gif has changed diff -r 5642f4658b59 -r 983fd420e86b pixmaps/smileys/default/qq_smiley_14.gif Binary file pixmaps/smileys/default/qq_smiley_14.gif has changed diff -r 5642f4658b59 -r 983fd420e86b pixmaps/smileys/default/qq_smiley_15.gif Binary file pixmaps/smileys/default/qq_smiley_15.gif has changed diff -r 5642f4658b59 -r 983fd420e86b pixmaps/smileys/default/qq_smiley_16.gif Binary file pixmaps/smileys/default/qq_smiley_16.gif has changed diff -r 5642f4658b59 -r 983fd420e86b pixmaps/smileys/default/qq_smiley_17.gif Binary file pixmaps/smileys/default/qq_smiley_17.gif has changed diff -r 5642f4658b59 -r 983fd420e86b pixmaps/smileys/default/qq_smiley_18.gif Binary file pixmaps/smileys/default/qq_smiley_18.gif has changed diff -r 5642f4658b59 -r 983fd420e86b pixmaps/smileys/default/qq_smiley_19.gif Binary file pixmaps/smileys/default/qq_smiley_19.gif has changed diff -r 5642f4658b59 -r 983fd420e86b pixmaps/smileys/default/qq_smiley_2.gif Binary file pixmaps/smileys/default/qq_smiley_2.gif has changed diff -r 5642f4658b59 -r 983fd420e86b pixmaps/smileys/default/qq_smiley_20.gif Binary file pixmaps/smileys/default/qq_smiley_20.gif has changed diff -r 5642f4658b59 -r 983fd420e86b pixmaps/smileys/default/qq_smiley_21.gif Binary file pixmaps/smileys/default/qq_smiley_21.gif has changed diff -r 5642f4658b59 -r 983fd420e86b pixmaps/smileys/default/qq_smiley_22.gif Binary file pixmaps/smileys/default/qq_smiley_22.gif has changed diff -r 5642f4658b59 -r 983fd420e86b pixmaps/smileys/default/qq_smiley_23.gif Binary file pixmaps/smileys/default/qq_smiley_23.gif has changed diff -r 5642f4658b59 -r 983fd420e86b pixmaps/smileys/default/qq_smiley_24.gif Binary file pixmaps/smileys/default/qq_smiley_24.gif has changed diff -r 5642f4658b59 -r 983fd420e86b pixmaps/smileys/default/qq_smiley_25.gif Binary file pixmaps/smileys/default/qq_smiley_25.gif has changed diff -r 5642f4658b59 -r 983fd420e86b pixmaps/smileys/default/qq_smiley_26.gif Binary file pixmaps/smileys/default/qq_smiley_26.gif has changed diff -r 5642f4658b59 -r 983fd420e86b pixmaps/smileys/default/qq_smiley_27.gif Binary file pixmaps/smileys/default/qq_smiley_27.gif has changed diff -r 5642f4658b59 -r 983fd420e86b pixmaps/smileys/default/qq_smiley_28.gif Binary file pixmaps/smileys/default/qq_smiley_28.gif has changed diff -r 5642f4658b59 -r 983fd420e86b pixmaps/smileys/default/qq_smiley_29.gif Binary file pixmaps/smileys/default/qq_smiley_29.gif has changed diff -r 5642f4658b59 -r 983fd420e86b pixmaps/smileys/default/qq_smiley_3.gif Binary file pixmaps/smileys/default/qq_smiley_3.gif has changed diff -r 5642f4658b59 -r 983fd420e86b pixmaps/smileys/default/qq_smiley_30.gif Binary file pixmaps/smileys/default/qq_smiley_30.gif has changed diff -r 5642f4658b59 -r 983fd420e86b pixmaps/smileys/default/qq_smiley_31.gif Binary file pixmaps/smileys/default/qq_smiley_31.gif has changed diff -r 5642f4658b59 -r 983fd420e86b pixmaps/smileys/default/qq_smiley_32.gif Binary file pixmaps/smileys/default/qq_smiley_32.gif has changed diff -r 5642f4658b59 -r 983fd420e86b pixmaps/smileys/default/qq_smiley_33.gif Binary file pixmaps/smileys/default/qq_smiley_33.gif has changed diff -r 5642f4658b59 -r 983fd420e86b pixmaps/smileys/default/qq_smiley_34.gif Binary file pixmaps/smileys/default/qq_smiley_34.gif has changed diff -r 5642f4658b59 -r 983fd420e86b pixmaps/smileys/default/qq_smiley_35.gif Binary file pixmaps/smileys/default/qq_smiley_35.gif has changed diff -r 5642f4658b59 -r 983fd420e86b pixmaps/smileys/default/qq_smiley_36.gif Binary file pixmaps/smileys/default/qq_smiley_36.gif has changed diff -r 5642f4658b59 -r 983fd420e86b pixmaps/smileys/default/qq_smiley_37.gif Binary file pixmaps/smileys/default/qq_smiley_37.gif has changed diff -r 5642f4658b59 -r 983fd420e86b pixmaps/smileys/default/qq_smiley_38.gif Binary file pixmaps/smileys/default/qq_smiley_38.gif has changed diff -r 5642f4658b59 -r 983fd420e86b pixmaps/smileys/default/qq_smiley_39.gif Binary file pixmaps/smileys/default/qq_smiley_39.gif has changed diff -r 5642f4658b59 -r 983fd420e86b pixmaps/smileys/default/qq_smiley_4.gif Binary file pixmaps/smileys/default/qq_smiley_4.gif has changed diff -r 5642f4658b59 -r 983fd420e86b pixmaps/smileys/default/qq_smiley_40.gif Binary file pixmaps/smileys/default/qq_smiley_40.gif has changed diff -r 5642f4658b59 -r 983fd420e86b pixmaps/smileys/default/qq_smiley_41.gif Binary file pixmaps/smileys/default/qq_smiley_41.gif has changed diff -r 5642f4658b59 -r 983fd420e86b pixmaps/smileys/default/qq_smiley_42.gif Binary file pixmaps/smileys/default/qq_smiley_42.gif has changed diff -r 5642f4658b59 -r 983fd420e86b pixmaps/smileys/default/qq_smiley_43.gif Binary file pixmaps/smileys/default/qq_smiley_43.gif has changed diff -r 5642f4658b59 -r 983fd420e86b pixmaps/smileys/default/qq_smiley_44.gif Binary file pixmaps/smileys/default/qq_smiley_44.gif has changed diff -r 5642f4658b59 -r 983fd420e86b pixmaps/smileys/default/qq_smiley_45.gif Binary file pixmaps/smileys/default/qq_smiley_45.gif has changed diff -r 5642f4658b59 -r 983fd420e86b pixmaps/smileys/default/qq_smiley_46.gif Binary file pixmaps/smileys/default/qq_smiley_46.gif has changed diff -r 5642f4658b59 -r 983fd420e86b pixmaps/smileys/default/qq_smiley_47.gif Binary file pixmaps/smileys/default/qq_smiley_47.gif has changed diff -r 5642f4658b59 -r 983fd420e86b pixmaps/smileys/default/qq_smiley_48.gif Binary file pixmaps/smileys/default/qq_smiley_48.gif has changed diff -r 5642f4658b59 -r 983fd420e86b pixmaps/smileys/default/qq_smiley_49.gif Binary file pixmaps/smileys/default/qq_smiley_49.gif has changed diff -r 5642f4658b59 -r 983fd420e86b pixmaps/smileys/default/qq_smiley_5.gif Binary file pixmaps/smileys/default/qq_smiley_5.gif has changed diff -r 5642f4658b59 -r 983fd420e86b pixmaps/smileys/default/qq_smiley_50.gif Binary file pixmaps/smileys/default/qq_smiley_50.gif has changed diff -r 5642f4658b59 -r 983fd420e86b pixmaps/smileys/default/qq_smiley_51.gif Binary file pixmaps/smileys/default/qq_smiley_51.gif has changed diff -r 5642f4658b59 -r 983fd420e86b pixmaps/smileys/default/qq_smiley_52.gif Binary file pixmaps/smileys/default/qq_smiley_52.gif has changed diff -r 5642f4658b59 -r 983fd420e86b pixmaps/smileys/default/qq_smiley_53.gif Binary file pixmaps/smileys/default/qq_smiley_53.gif has changed diff -r 5642f4658b59 -r 983fd420e86b pixmaps/smileys/default/qq_smiley_54.gif Binary file pixmaps/smileys/default/qq_smiley_54.gif has changed diff -r 5642f4658b59 -r 983fd420e86b pixmaps/smileys/default/qq_smiley_55.gif Binary file pixmaps/smileys/default/qq_smiley_55.gif has changed diff -r 5642f4658b59 -r 983fd420e86b pixmaps/smileys/default/qq_smiley_56.gif Binary file pixmaps/smileys/default/qq_smiley_56.gif has changed diff -r 5642f4658b59 -r 983fd420e86b pixmaps/smileys/default/qq_smiley_57.gif Binary file pixmaps/smileys/default/qq_smiley_57.gif has changed diff -r 5642f4658b59 -r 983fd420e86b pixmaps/smileys/default/qq_smiley_58.gif Binary file pixmaps/smileys/default/qq_smiley_58.gif has changed diff -r 5642f4658b59 -r 983fd420e86b pixmaps/smileys/default/qq_smiley_59.gif Binary file pixmaps/smileys/default/qq_smiley_59.gif has changed diff -r 5642f4658b59 -r 983fd420e86b pixmaps/smileys/default/qq_smiley_6.gif Binary file pixmaps/smileys/default/qq_smiley_6.gif has changed diff -r 5642f4658b59 -r 983fd420e86b pixmaps/smileys/default/qq_smiley_60.gif Binary file pixmaps/smileys/default/qq_smiley_60.gif has changed diff -r 5642f4658b59 -r 983fd420e86b pixmaps/smileys/default/qq_smiley_61.gif Binary file pixmaps/smileys/default/qq_smiley_61.gif has changed diff -r 5642f4658b59 -r 983fd420e86b pixmaps/smileys/default/qq_smiley_62.gif Binary file pixmaps/smileys/default/qq_smiley_62.gif has changed diff -r 5642f4658b59 -r 983fd420e86b pixmaps/smileys/default/qq_smiley_63.gif Binary file pixmaps/smileys/default/qq_smiley_63.gif has changed diff -r 5642f4658b59 -r 983fd420e86b pixmaps/smileys/default/qq_smiley_64.gif Binary file pixmaps/smileys/default/qq_smiley_64.gif has changed diff -r 5642f4658b59 -r 983fd420e86b pixmaps/smileys/default/qq_smiley_65.gif Binary file pixmaps/smileys/default/qq_smiley_65.gif has changed diff -r 5642f4658b59 -r 983fd420e86b pixmaps/smileys/default/qq_smiley_66.gif Binary file pixmaps/smileys/default/qq_smiley_66.gif has changed diff -r 5642f4658b59 -r 983fd420e86b pixmaps/smileys/default/qq_smiley_67.gif Binary file pixmaps/smileys/default/qq_smiley_67.gif has changed diff -r 5642f4658b59 -r 983fd420e86b pixmaps/smileys/default/qq_smiley_68.gif Binary file pixmaps/smileys/default/qq_smiley_68.gif has changed diff -r 5642f4658b59 -r 983fd420e86b pixmaps/smileys/default/qq_smiley_69.gif Binary file pixmaps/smileys/default/qq_smiley_69.gif has changed diff -r 5642f4658b59 -r 983fd420e86b pixmaps/smileys/default/qq_smiley_7.gif Binary file pixmaps/smileys/default/qq_smiley_7.gif has changed diff -r 5642f4658b59 -r 983fd420e86b pixmaps/smileys/default/qq_smiley_70.gif Binary file pixmaps/smileys/default/qq_smiley_70.gif has changed diff -r 5642f4658b59 -r 983fd420e86b pixmaps/smileys/default/qq_smiley_71.gif Binary file pixmaps/smileys/default/qq_smiley_71.gif has changed diff -r 5642f4658b59 -r 983fd420e86b pixmaps/smileys/default/qq_smiley_72.gif Binary file pixmaps/smileys/default/qq_smiley_72.gif has changed diff -r 5642f4658b59 -r 983fd420e86b pixmaps/smileys/default/qq_smiley_73.gif Binary file pixmaps/smileys/default/qq_smiley_73.gif has changed diff -r 5642f4658b59 -r 983fd420e86b pixmaps/smileys/default/qq_smiley_74.gif Binary file pixmaps/smileys/default/qq_smiley_74.gif has changed diff -r 5642f4658b59 -r 983fd420e86b pixmaps/smileys/default/qq_smiley_75.gif Binary file pixmaps/smileys/default/qq_smiley_75.gif has changed diff -r 5642f4658b59 -r 983fd420e86b pixmaps/smileys/default/qq_smiley_76.gif Binary file pixmaps/smileys/default/qq_smiley_76.gif has changed diff -r 5642f4658b59 -r 983fd420e86b pixmaps/smileys/default/qq_smiley_77.gif Binary file pixmaps/smileys/default/qq_smiley_77.gif has changed diff -r 5642f4658b59 -r 983fd420e86b pixmaps/smileys/default/qq_smiley_78.gif Binary file pixmaps/smileys/default/qq_smiley_78.gif has changed diff -r 5642f4658b59 -r 983fd420e86b pixmaps/smileys/default/qq_smiley_79.gif Binary file pixmaps/smileys/default/qq_smiley_79.gif has changed diff -r 5642f4658b59 -r 983fd420e86b pixmaps/smileys/default/qq_smiley_8.gif Binary file pixmaps/smileys/default/qq_smiley_8.gif has changed diff -r 5642f4658b59 -r 983fd420e86b pixmaps/smileys/default/qq_smiley_80.gif Binary file pixmaps/smileys/default/qq_smiley_80.gif has changed diff -r 5642f4658b59 -r 983fd420e86b pixmaps/smileys/default/qq_smiley_81.gif Binary file pixmaps/smileys/default/qq_smiley_81.gif has changed diff -r 5642f4658b59 -r 983fd420e86b pixmaps/smileys/default/qq_smiley_82.gif Binary file pixmaps/smileys/default/qq_smiley_82.gif has changed diff -r 5642f4658b59 -r 983fd420e86b pixmaps/smileys/default/qq_smiley_83.gif Binary file pixmaps/smileys/default/qq_smiley_83.gif has changed diff -r 5642f4658b59 -r 983fd420e86b pixmaps/smileys/default/qq_smiley_84.gif Binary file pixmaps/smileys/default/qq_smiley_84.gif has changed diff -r 5642f4658b59 -r 983fd420e86b pixmaps/smileys/default/qq_smiley_85.gif Binary file pixmaps/smileys/default/qq_smiley_85.gif has changed diff -r 5642f4658b59 -r 983fd420e86b pixmaps/smileys/default/qq_smiley_86.gif Binary file pixmaps/smileys/default/qq_smiley_86.gif has changed diff -r 5642f4658b59 -r 983fd420e86b pixmaps/smileys/default/qq_smiley_87.gif Binary file pixmaps/smileys/default/qq_smiley_87.gif has changed diff -r 5642f4658b59 -r 983fd420e86b pixmaps/smileys/default/qq_smiley_88.gif Binary file pixmaps/smileys/default/qq_smiley_88.gif has changed diff -r 5642f4658b59 -r 983fd420e86b pixmaps/smileys/default/qq_smiley_89.gif Binary file pixmaps/smileys/default/qq_smiley_89.gif has changed diff -r 5642f4658b59 -r 983fd420e86b pixmaps/smileys/default/qq_smiley_9.gif Binary file pixmaps/smileys/default/qq_smiley_9.gif has changed diff -r 5642f4658b59 -r 983fd420e86b pixmaps/smileys/default/qq_smiley_90.gif Binary file pixmaps/smileys/default/qq_smiley_90.gif has changed diff -r 5642f4658b59 -r 983fd420e86b pixmaps/smileys/default/qq_smiley_91.gif Binary file pixmaps/smileys/default/qq_smiley_91.gif has changed diff -r 5642f4658b59 -r 983fd420e86b pixmaps/smileys/default/qq_smiley_92.gif Binary file pixmaps/smileys/default/qq_smiley_92.gif has changed diff -r 5642f4658b59 -r 983fd420e86b pixmaps/smileys/default/qq_smiley_93.gif Binary file pixmaps/smileys/default/qq_smiley_93.gif has changed diff -r 5642f4658b59 -r 983fd420e86b pixmaps/smileys/default/qq_smiley_94.gif Binary file pixmaps/smileys/default/qq_smiley_94.gif has changed diff -r 5642f4658b59 -r 983fd420e86b pixmaps/smileys/default/qq_smiley_95.gif Binary file pixmaps/smileys/default/qq_smiley_95.gif has changed diff -r 5642f4658b59 -r 983fd420e86b pixmaps/smileys/default/theme --- a/pixmaps/smileys/default/theme Sun Jun 25 03:15:41 2006 +0000 +++ b/pixmaps/smileys/default/theme Mon Jun 26 02:58:54 2006 +0000 @@ -37,7 +37,7 @@ msn_tongue.png :p :-P :P :-p msn_hot.png (h) (H) msn_angry.png :@ :-@ -msn_neutral.png :-| :| +msn_neutral.png :-| :| msn_weird.png :s :-S :-s :S msn_embarrassed.png :$ :-$ msn_sad.png :( :-( :-< @@ -110,6 +110,104 @@ msn_xbox.png (xx) msn_cigarette.gif (ci) +[QQ] +qq_smiley_0.gif /jy +qq_smiley_1.gif /se +qq_smiley_2.gif /pz +qq_smiley_3.gif /fd +qq_smiley_4.gif /dy +qq_smiley_5.gif /ll +qq_smiley_6.gif /hx +qq_smiley_7.gif /bz +qq_smiley_8.gif /shui +qq_smiley_9.gif /dk +qq_smiley_10.gif /gg +qq_smiley_11.gif /fn +qq_smiley_12.gif /tp +qq_smiley_13.gif /cy +qq_smiley_14.gif /wx +qq_smiley_15.gif /ng +qq_smiley_16.gif /kuk +qq_smiley_17.gif /feid +qq_smiley_18.gif /zk +qq_smiley_19.gif /tu +qq_smiley_20.gif /tx +qq_smiley_21.gif /ka +qq_smiley_22.gif /by +qq_smiley_23.gif /am +qq_smiley_24.gif /jie +qq_smiley_25.gif /kun +qq_smiley_26.gif /jk +qq_smiley_27.gif /lh +qq_smiley_28.gif /hanx +qq_smiley_29.gif /db +qq_smiley_30.gif /fendou +qq_smiley_31.gif /zhm +qq_smiley_32.gif /yiw +qq_smiley_33.gif /xu +qq_smiley_34.gif /yun +qq_smiley_35.gif /zhem +qq_smiley_36.gif /shuai +qq_smiley_37.gif /kl +qq_smiley_38.gif /qiao +qq_smiley_39.gif /zj +qq_smiley_40.gif /shan +qq_smiley_41.gif /fad +qq_smiley_42.gif /aiq +qq_smiley_43.gif /tiao +qq_smiley_44.gif /zhao +qq_smiley_45.gif /mm +qq_smiley_46.gif /zt +qq_smiley_47.gif /maom +qq_smiley_48.gif /xg +qq_smiley_49.gif /yb +qq_smiley_50.gif /qianc +qq_smiley_51.gif /dp +qq_smiley_52.gif /bei +qq_smiley_53.gif /dg +qq_smiley_54.gif /shd +qq_smiley_55.gif /zhd +qq_smiley_56.gif /dao +qq_smiley_57.gif /zq +qq_smiley_58.gif /yy +qq_smiley_59.gif /bb +qq_smiley_60.gif /gf +qq_smiley_61.gif /fan +qq_smiley_62.gif /yw +qq_smiley_63.gif /mg +qq_smiley_64.gif /dx +qq_smiley_65.gif /wen +qq_smiley_66.gif /xin +qq_smiley_67.gif /xs +qq_smiley_68.gif /hy +qq_smiley_69.gif /lw +qq_smiley_70.gif /dh +qq_smiley_71.gif /sj +qq_smiley_72.gif /yj +qq_smiley_73.gif /ds +qq_smiley_74.gif /ty +qq_smiley_75.gif /yl +qq_smiley_76.gif /qiang +qq_smiley_77.gif /ruo +qq_smiley_78.gif /ws +qq_smiley_79.gif /shl +qq_smiley_80.gif /dd +qq_smiley_81.gif /mn +qq_smiley_82.gif /hl +qq_smiley_83.gif /mamao +qq_smiley_84.gif /qz +qq_smiley_85.gif /fw +qq_smiley_86.gif /oh +qq_smiley_87.gif /bj +qq_smiley_88.gif /qsh +qq_smiley_89.gif /xig +qq_smiley_90.gif /xy +qq_smiley_91.gif /duoy +qq_smiley_92.gif /xr +qq_smiley_93.gif /xixing +qq_smiley_94.gif /nv +qq_smiley_95.gif /nan + [Yahoo] yahoo_angel.gif o:-) O:-) 0:-) yahoo_angry.gif X-( x-( X( x( diff -r 5642f4658b59 -r 983fd420e86b pixmaps/status/default/Makefile.am --- a/pixmaps/status/default/Makefile.am Sun Jun 25 03:15:41 2006 +0000 +++ b/pixmaps/status/default/Makefile.am Mon Jun 26 02:58:54 2006 +0000 @@ -34,6 +34,7 @@ offline.png \ op.png \ pending.png \ + qq.png \ secure.png \ silc.png \ simple.png \ diff -r 5642f4658b59 -r 983fd420e86b pixmaps/status/default/qq.png Binary file pixmaps/status/default/qq.png has changed diff -r 5642f4658b59 -r 983fd420e86b src/protocols/Makefile.am --- a/src/protocols/Makefile.am Sun Jun 25 03:15:41 2006 +0000 +++ b/src/protocols/Makefile.am Mon Jun 26 02:58:54 2006 +0000 @@ -1,3 +1,3 @@ -DIST_SUBDIRS = bonjour gg irc jabber msn napster novell oscar sametime silc toc simple yahoo zephyr +DIST_SUBDIRS = bonjour gg irc jabber msn napster novell oscar qq sametime silc toc simple yahoo zephyr SUBDIRS = $(DYNAMIC_PRPLS) $(STATIC_PRPLS) diff -r 5642f4658b59 -r 983fd420e86b src/protocols/qq/Makefile.am --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/protocols/qq/Makefile.am Mon Jun 26 02:58:54 2006 +0000 @@ -0,0 +1,108 @@ +EXTRA_DIST = \ + Makefile.mingw + +pkgdir = $(libdir)/gaim + +QQSOURCES = \ + utils.c \ + utils.h \ + packet_parse.c \ + packet_parse.h \ + buddy_info.c \ + buddy_info.h \ + buddy_list.c \ + buddy_list.h \ + buddy_opt.c \ + buddy_opt.h \ + buddy_status.c \ + buddy_status.h \ + qq.c \ + char_conv.c \ + char_conv.h \ + crypt.c \ + crypt.h \ + group_admindlg.c \ + group_admindlg.h \ + group.c \ + group_conv.c \ + group_conv.h \ + group_find.c \ + group_find.h \ + group_free.c \ + group_free.h \ + group.h \ + group_hash.c \ + group_hash.h \ + group_im.c \ + group_im.h \ + group_info.c \ + group_info.h \ + group_join.c \ + group_join.h \ + group_misc.c \ + group_misc.h \ + group_network.c \ + group_network.h \ + group_opt.c \ + group_opt.h \ + group_search.c \ + group_search.h \ + qq.h \ + header_info.c \ + header_info.h \ + im.c \ + im.h \ + infodlg.c \ + infodlg.h \ + ip_location.c \ + ip_location.h \ + keep_alive.c \ + keep_alive.h \ + login_logout.c \ + login_logout.h \ + qq_proxy.c \ + qq_proxy.h \ + recv_core.c \ + recv_core.h \ + send_core.c \ + send_core.h \ + sendqueue.c \ + sendqueue.h \ + show.c \ + show.h \ + sys_msg.c \ + sys_msg.h \ + udp_proxy_s5.c \ + udp_proxy_s5.h \ + send_file.c \ + send_file.h \ + file_trans.c \ + file_trans.h + +AM_CFLAGS = $(st) + +libqq_la_LDFLAGS = -module -avoid-version + +if STATIC_QQ + +st = -DGAIM_STATIC_PRPL +noinst_LIBRARIES = libqq.a +libqq_a_SOURCES = $(QQSOURCES) +libqq_a_CFLAGS = $(AM_CFLAGS) + +else + +st = +pkg_LTLIBRARIES = libqq.la +libqq_la_SOURCES = $(QQSOURCES) + +endif + +AM_CPPFLAGS = \ + -I$(top_srcdir)/src \ + -DDATADIR=\"${datadir}\" \ + -DVERSION=\"$(VERSION)\" \ + $(DEBUG_CFLAGS) \ + $(GTK_CFLAGS) \ + $(GLIB_CFLAGS) \ + $(GAIM_CFLAGS) diff -r 5642f4658b59 -r 983fd420e86b src/protocols/qq/Makefile.in --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/protocols/qq/Makefile.in Mon Jun 26 02:58:54 2006 +0000 @@ -0,0 +1,576 @@ +# Makefile.in generated automatically by automake 1.4-p6 from Makefile.am + +# Copyright (C) 1994, 1995-8, 1999, 2001 Free Software Foundation, Inc. +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + + +SHELL = @SHELL@ + +srcdir = @srcdir@ +top_srcdir = @top_srcdir@ +VPATH = @srcdir@ +prefix = @prefix@ +exec_prefix = @exec_prefix@ + +bindir = @bindir@ +sbindir = @sbindir@ +libexecdir = @libexecdir@ +datadir = @datadir@ +sysconfdir = @sysconfdir@ +sharedstatedir = @sharedstatedir@ +localstatedir = @localstatedir@ +libdir = @libdir@ +infodir = @infodir@ +mandir = @mandir@ +includedir = @includedir@ +oldincludedir = /usr/include + +DESTDIR = + +pkgdatadir = $(datadir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ + +top_builddir = ../../.. + +ACLOCAL = @ACLOCAL@ +AUTOCONF = @AUTOCONF@ +AUTOMAKE = @AUTOMAKE@ +AUTOHEADER = @AUTOHEADER@ + +INSTALL = @INSTALL@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ $(AM_INSTALL_PROGRAM_FLAGS) +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +transform = @program_transform_name@ + +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +build_alias = @build_alias@ +build_triplet = @build@ +host_alias = @host_alias@ +host_triplet = @host@ +target_alias = @target_alias@ +target_triplet = @target@ +ALL_LINGUAS = @ALL_LINGUAS@ +AO_CFLAGS = @AO_CFLAGS@ +AO_LIBS = @AO_LIBS@ +AR = @AR@ +AS = @AS@ +AUDIOFILE_CFLAGS = @AUDIOFILE_CFLAGS@ +AUDIOFILE_CONFIG = @AUDIOFILE_CONFIG@ +AUDIOFILE_LIBS = @AUDIOFILE_LIBS@ +BINRELOC_CFLAGS = @BINRELOC_CFLAGS@ +BINRELOC_LIBS = @BINRELOC_LIBS@ +BUILD_INCLUDED_LIBINTL = @BUILD_INCLUDED_LIBINTL@ +CATALOGS = @CATALOGS@ +CATOBJEXT = @CATOBJEXT@ +CC = @CC@ +CFLAGS = @CFLAGS@ +CXX = @CXX@ +CXXCPP = @CXXCPP@ +DATADIRNAME = @DATADIRNAME@ +DBUS_CFLAGS = @DBUS_CFLAGS@ +DBUS_LIBS = @DBUS_LIBS@ +DBUS_SERVICES_DIR = @DBUS_SERVICES_DIR@ +DEBUG_CFLAGS = @DEBUG_CFLAGS@ +DLLTOOL = @DLLTOOL@ +DOT = @DOT@ +DOXYGEN = @DOXYGEN@ +DYNALOADER_A = @DYNALOADER_A@ +DYNAMIC_PRPLS = @DYNAMIC_PRPLS@ +ECHO = @ECHO@ +EGREP = @EGREP@ +EVOLUTION_ADDRESSBOOK_CFLAGS = @EVOLUTION_ADDRESSBOOK_CFLAGS@ +EVOLUTION_ADDRESSBOOK_LIBS = @EVOLUTION_ADDRESSBOOK_LIBS@ +EXEEXT = @EXEEXT@ +F77 = @F77@ +GADU_CFLAGS = @GADU_CFLAGS@ +GADU_LIBS = @GADU_LIBS@ +GCJ = @GCJ@ +GCJFLAGS = @GCJFLAGS@ +GENCAT = @GENCAT@ +GETTEXT_PACKAGE = @GETTEXT_PACKAGE@ +GLIBC2 = @GLIBC2@ +GLIBC21 = @GLIBC21@ +GLIB_CFLAGS = @GLIB_CFLAGS@ +GLIB_GENMARSHAL = @GLIB_GENMARSHAL@ +GLIB_LIBS = @GLIB_LIBS@ +GLIB_MKENUMS = @GLIB_MKENUMS@ +GMOFILES = @GMOFILES@ +GMSGFMT = @GMSGFMT@ +GNUTLS_CFLAGS = @GNUTLS_CFLAGS@ +GNUTLS_LIBS = @GNUTLS_LIBS@ +GOBJECT_QUERY = @GOBJECT_QUERY@ +GSTREAMER_CFLAGS = @GSTREAMER_CFLAGS@ +GSTREAMER_LIBS = @GSTREAMER_LIBS@ +GTKSPELL_CFLAGS = @GTKSPELL_CFLAGS@ +GTKSPELL_LIBS = @GTKSPELL_LIBS@ +GTK_CFLAGS = @GTK_CFLAGS@ +GTK_LIBS = @GTK_LIBS@ +HAVE_ASPRINTF = @HAVE_ASPRINTF@ +HAVE_LIB = @HAVE_LIB@ +HAVE_POSIX_PRINTF = @HAVE_POSIX_PRINTF@ +HAVE_SNPRINTF = @HAVE_SNPRINTF@ +HAVE_WPRINTF = @HAVE_WPRINTF@ +HOWL_CFLAGS = @HOWL_CFLAGS@ +HOWL_LIBS = @HOWL_LIBS@ +INSTOBJEXT = @INSTOBJEXT@ +INTLBISON = @INTLBISON@ +INTLLIBS = @INTLLIBS@ +INTLOBJS = @INTLOBJS@ +INTLTOOL_CAVES_RULE = @INTLTOOL_CAVES_RULE@ +INTLTOOL_DESKTOP_RULE = @INTLTOOL_DESKTOP_RULE@ +INTLTOOL_DIRECTORY_RULE = @INTLTOOL_DIRECTORY_RULE@ +INTLTOOL_EXTRACT = @INTLTOOL_EXTRACT@ +INTLTOOL_ICONV = @INTLTOOL_ICONV@ +INTLTOOL_KBD_RULE = @INTLTOOL_KBD_RULE@ +INTLTOOL_KEYS_RULE = @INTLTOOL_KEYS_RULE@ +INTLTOOL_MERGE = @INTLTOOL_MERGE@ +INTLTOOL_MSGFMT = @INTLTOOL_MSGFMT@ +INTLTOOL_MSGMERGE = @INTLTOOL_MSGMERGE@ +INTLTOOL_OAF_RULE = @INTLTOOL_OAF_RULE@ +INTLTOOL_PERL = @INTLTOOL_PERL@ +INTLTOOL_PONG_RULE = @INTLTOOL_PONG_RULE@ +INTLTOOL_PROP_RULE = @INTLTOOL_PROP_RULE@ +INTLTOOL_SCHEMAS_RULE = @INTLTOOL_SCHEMAS_RULE@ +INTLTOOL_SERVER_RULE = @INTLTOOL_SERVER_RULE@ +INTLTOOL_SERVICE_RULE = @INTLTOOL_SERVICE_RULE@ +INTLTOOL_SHEET_RULE = @INTLTOOL_SHEET_RULE@ +INTLTOOL_SOUNDLIST_RULE = @INTLTOOL_SOUNDLIST_RULE@ +INTLTOOL_THEME_RULE = @INTLTOOL_THEME_RULE@ +INTLTOOL_UI_RULE = @INTLTOOL_UI_RULE@ +INTLTOOL_UPDATE = @INTLTOOL_UPDATE@ +INTLTOOL_XAM_RULE = @INTLTOOL_XAM_RULE@ +INTLTOOL_XGETTEXT = @INTLTOOL_XGETTEXT@ +INTLTOOL_XML_NOMERGE_RULE = @INTLTOOL_XML_NOMERGE_RULE@ +INTLTOOL_XML_RULE = @INTLTOOL_XML_RULE@ +INTL_LIBTOOL_SUFFIX_PREFIX = @INTL_LIBTOOL_SUFFIX_PREFIX@ +INTL_MACOSX_LIBS = @INTL_MACOSX_LIBS@ +KRB4_CFLAGS = @KRB4_CFLAGS@ +KRB4_LDFLAGS = @KRB4_LDFLAGS@ +KRB4_LIBS = @KRB4_LIBS@ +LDADD = @LDADD@ +LIB = @LIB@ +LIBICONV = @LIBICONV@ +LIBINTL = @LIBINTL@ +LIBPERL_A = @LIBPERL_A@ +LIBS = @LIBS@ +LIBTOOL = @LIBTOOL@ +LIBXML_CFLAGS = @LIBXML_CFLAGS@ +LIBXML_LIBS = @LIBXML_LIBS@ +LN_S = @LN_S@ +LTLIB = @LTLIB@ +LTLIBICONV = @LTLIBICONV@ +LTLIBINTL = @LTLIBINTL@ +MAKEINFO = @MAKEINFO@ +MEANWHILE_CFLAGS = @MEANWHILE_CFLAGS@ +MEANWHILE_LIBS = @MEANWHILE_LIBS@ +MKINSTALLDIRS = @MKINSTALLDIRS@ +MONO_CFLAGS = @MONO_CFLAGS@ +MONO_LIBS = @MONO_LIBS@ +NSS_CFLAGS = @NSS_CFLAGS@ +NSS_LIBS = @NSS_LIBS@ +OBJDUMP = @OBJDUMP@ +OBJEXT = @OBJEXT@ +PACKAGE = @PACKAGE@ +PERL = @PERL@ +PERL_CFLAGS = @PERL_CFLAGS@ +PERL_LIBS = @PERL_LIBS@ +PERL_MM_PARAMS = @PERL_MM_PARAMS@ +PKG_CONFIG = @PKG_CONFIG@ +POFILES = @POFILES@ +POSUB = @POSUB@ +PO_IN_DATADIR_FALSE = @PO_IN_DATADIR_FALSE@ +PO_IN_DATADIR_TRUE = @PO_IN_DATADIR_TRUE@ +PYTHON = @PYTHON@ +RANLIB = @RANLIB@ +RC = @RC@ +SASL_LIBS = @SASL_LIBS@ +SILC_CFLAGS = @SILC_CFLAGS@ +SILC_LIBS = @SILC_LIBS@ +SM_LIBS = @SM_LIBS@ +STARTUP_NOTIFICATION_CFLAGS = @STARTUP_NOTIFICATION_CFLAGS@ +STARTUP_NOTIFICATION_LIBS = @STARTUP_NOTIFICATION_LIBS@ +STATIC_LINK_LIBS = @STATIC_LINK_LIBS@ +STATIC_PRPLS = @STATIC_PRPLS@ +STRIP = @STRIP@ +TCL_CFLAGS = @TCL_CFLAGS@ +TCL_LIBS = @TCL_LIBS@ +TK_LIBS = @TK_LIBS@ +USE_INCLUDED_LIBINTL = @USE_INCLUDED_LIBINTL@ +USE_NLS = @USE_NLS@ +VERSION = @VERSION@ +XSS_LIBS = @XSS_LIBS@ +ZEPHYR_CFLAGS = @ZEPHYR_CFLAGS@ +ZEPHYR_LDFLAGS = @ZEPHYR_LDFLAGS@ +ZEPHYR_LIBS = @ZEPHYR_LIBS@ +enable_dbus = @enable_dbus@ +enable_dot = @enable_dot@ +enable_doxygen = @enable_doxygen@ +gaimpath = @gaimpath@ +perlpath = @perlpath@ +sedpath = @sedpath@ + +EXTRA_DIST = Makefile.mingw + + +pkgdir = $(libdir)/gaim + +QQSOURCES = utils.c utils.h packet_parse.c packet_parse.h buddy_info.c buddy_info.h buddy_list.c buddy_list.h buddy_opt.c buddy_opt.h buddy_status.c buddy_status.h qq.c char_conv.c char_conv.h crypt.c crypt.h group_admindlg.c group_admindlg.h group.c group_conv.c group_conv.h group_find.c group_find.h group_free.c group_free.h group.h group_hash.c group_hash.h group_im.c group_im.h group_info.c group_info.h group_join.c group_join.h group_misc.c group_misc.h group_network.c group_network.h group_opt.c group_opt.h group_search.c group_search.h qq.h header_info.c header_info.h im.c im.h infodlg.c infodlg.h ip_location.c ip_location.h keep_alive.c keep_alive.h login_logout.c login_logout.h qq_proxy.c qq_proxy.h recv_core.c recv_core.h send_core.c send_core.h sendqueue.c sendqueue.h show.c show.h sys_msg.c sys_msg.h udp_proxy_s5.c udp_proxy_s5.h send_file.c send_file.h file_trans.c file_trans.h + + +AM_CFLAGS = $(st) + +libqq_la_LDFLAGS = -module -avoid-version + +@STATIC_QQ_TRUE@st = -DGAIM_STATIC_PRPL +@STATIC_QQ_FALSE@st = +@STATIC_QQ_TRUE@noinst_LIBRARIES = libqq.a +@STATIC_QQ_TRUE@libqq_a_SOURCES = $(QQSOURCES) +@STATIC_QQ_TRUE@libqq_a_CFLAGS = $(AM_CFLAGS) +@STATIC_QQ_FALSE@pkg_LTLIBRARIES = libqq.la +@STATIC_QQ_FALSE@libqq_la_SOURCES = $(QQSOURCES) + +AM_CPPFLAGS = -I$(top_srcdir)/src -DDATADIR=\"${datadir}\" -DVERSION=\"$(VERSION)\" $(DEBUG_CFLAGS) $(GTK_CFLAGS) $(GLIB_CFLAGS) $(GAIM_CFLAGS) + +mkinstalldirs = $(SHELL) $(top_srcdir)/mkinstalldirs +CONFIG_HEADER = ../../../config.h +CONFIG_CLEAN_FILES = +LIBRARIES = $(noinst_LIBRARIES) + + +DEFS = @DEFS@ -I. -I$(srcdir) -I../../.. +CPPFLAGS = @CPPFLAGS@ +LDFLAGS = @LDFLAGS@ +X_CFLAGS = @X_CFLAGS@ +X_LIBS = @X_LIBS@ +X_EXTRA_LIBS = @X_EXTRA_LIBS@ +X_PRE_LIBS = @X_PRE_LIBS@ +libqq_a_LIBADD = +@STATIC_QQ_TRUE@libqq_a_OBJECTS = utils.$(OBJEXT) \ +@STATIC_QQ_TRUE@packet_parse.$(OBJEXT) buddy_info.$(OBJEXT) \ +@STATIC_QQ_TRUE@buddy_list.$(OBJEXT) buddy_opt.$(OBJEXT) \ +@STATIC_QQ_TRUE@buddy_status.$(OBJEXT) qq.$(OBJEXT) char_conv.$(OBJEXT) \ +@STATIC_QQ_TRUE@crypt.$(OBJEXT) group_admindlg.$(OBJEXT) \ +@STATIC_QQ_TRUE@group.$(OBJEXT) group_conv.$(OBJEXT) \ +@STATIC_QQ_TRUE@group_find.$(OBJEXT) group_free.$(OBJEXT) \ +@STATIC_QQ_TRUE@group_hash.$(OBJEXT) group_im.$(OBJEXT) \ +@STATIC_QQ_TRUE@group_info.$(OBJEXT) group_join.$(OBJEXT) \ +@STATIC_QQ_TRUE@group_misc.$(OBJEXT) group_network.$(OBJEXT) \ +@STATIC_QQ_TRUE@group_opt.$(OBJEXT) group_search.$(OBJEXT) \ +@STATIC_QQ_TRUE@header_info.$(OBJEXT) im.$(OBJEXT) infodlg.$(OBJEXT) \ +@STATIC_QQ_TRUE@ip_location.$(OBJEXT) keep_alive.$(OBJEXT) \ +@STATIC_QQ_TRUE@login_logout.$(OBJEXT) qq_proxy.$(OBJEXT) \ +@STATIC_QQ_TRUE@recv_core.$(OBJEXT) send_core.$(OBJEXT) \ +@STATIC_QQ_TRUE@sendqueue.$(OBJEXT) show.$(OBJEXT) sys_msg.$(OBJEXT) \ +@STATIC_QQ_TRUE@udp_proxy_s5.$(OBJEXT) send_file.$(OBJEXT) \ +@STATIC_QQ_TRUE@file_trans.$(OBJEXT) +LTLIBRARIES = $(pkg_LTLIBRARIES) + +libqq_la_LIBADD = +@STATIC_QQ_FALSE@libqq_la_OBJECTS = utils.lo packet_parse.lo \ +@STATIC_QQ_FALSE@buddy_info.lo buddy_list.lo buddy_opt.lo \ +@STATIC_QQ_FALSE@buddy_status.lo qq.lo char_conv.lo crypt.lo \ +@STATIC_QQ_FALSE@group_admindlg.lo group.lo group_conv.lo group_find.lo \ +@STATIC_QQ_FALSE@group_free.lo group_hash.lo group_im.lo group_info.lo \ +@STATIC_QQ_FALSE@group_join.lo group_misc.lo group_network.lo \ +@STATIC_QQ_FALSE@group_opt.lo group_search.lo header_info.lo im.lo \ +@STATIC_QQ_FALSE@infodlg.lo ip_location.lo keep_alive.lo \ +@STATIC_QQ_FALSE@login_logout.lo qq_proxy.lo recv_core.lo send_core.lo \ +@STATIC_QQ_FALSE@sendqueue.lo show.lo sys_msg.lo udp_proxy_s5.lo \ +@STATIC_QQ_FALSE@send_file.lo file_trans.lo +COMPILE = $(CC) $(DEFS) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) +LTCOMPILE = $(LIBTOOL) --mode=compile $(CC) $(DEFS) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) +CCLD = $(CC) +LINK = $(LIBTOOL) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(LDFLAGS) -o $@ +DIST_COMMON = Makefile.am Makefile.in + + +DISTFILES = $(DIST_COMMON) $(SOURCES) $(HEADERS) $(TEXINFOS) $(EXTRA_DIST) + +TAR = tar +GZIP_ENV = --best +DEP_FILES = .deps/buddy_info.P .deps/buddy_list.P .deps/buddy_opt.P \ +.deps/buddy_status.P .deps/char_conv.P .deps/crypt.P .deps/file_trans.P \ +.deps/group.P .deps/group_admindlg.P .deps/group_conv.P \ +.deps/group_find.P .deps/group_free.P .deps/group_hash.P \ +.deps/group_im.P .deps/group_info.P .deps/group_join.P \ +.deps/group_misc.P .deps/group_network.P .deps/group_opt.P \ +.deps/group_search.P .deps/header_info.P .deps/im.P .deps/infodlg.P \ +.deps/ip_location.P .deps/keep_alive.P .deps/login_logout.P \ +.deps/packet_parse.P .deps/qq.P .deps/qq_proxy.P .deps/recv_core.P \ +.deps/send_core.P .deps/send_file.P .deps/sendqueue.P .deps/show.P \ +.deps/sys_msg.P .deps/udp_proxy_s5.P .deps/utils.P +SOURCES = $(libqq_a_SOURCES) $(libqq_la_SOURCES) +OBJECTS = $(libqq_a_OBJECTS) $(libqq_la_OBJECTS) + +all: all-redirect +.SUFFIXES: +.SUFFIXES: .S .c .lo .o .obj .s +$(srcdir)/Makefile.in: Makefile.am $(top_srcdir)/configure.ac $(ACLOCAL_M4) + cd $(top_srcdir) && $(AUTOMAKE) --gnu src/protocols/qq/Makefile + +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status $(BUILT_SOURCES) + cd $(top_builddir) \ + && CONFIG_FILES=$(subdir)/$@ CONFIG_HEADERS= $(SHELL) ./config.status + + +mostlyclean-noinstLIBRARIES: + +clean-noinstLIBRARIES: + -test -z "$(noinst_LIBRARIES)" || rm -f $(noinst_LIBRARIES) + +distclean-noinstLIBRARIES: + +maintainer-clean-noinstLIBRARIES: + +# FIXME: We should only use cygpath when building on Windows, +# and only if it is available. +.c.obj: + $(COMPILE) -c `cygpath -w $<` + +.s.o: + $(COMPILE) -c $< + +.S.o: + $(COMPILE) -c $< + +mostlyclean-compile: + -rm -f *.o core *.core + -rm -f *.$(OBJEXT) + +clean-compile: + +distclean-compile: + -rm -f *.tab.c + +maintainer-clean-compile: + +.s.lo: + $(LIBTOOL) --mode=compile $(COMPILE) -c $< + +.S.lo: + $(LIBTOOL) --mode=compile $(COMPILE) -c $< + +mostlyclean-libtool: + -rm -f *.lo + +clean-libtool: + -rm -rf .libs _libs + +distclean-libtool: + +maintainer-clean-libtool: + +libqq.a: $(libqq_a_OBJECTS) $(libqq_a_DEPENDENCIES) + -rm -f libqq.a + $(AR) cru libqq.a $(libqq_a_OBJECTS) $(libqq_a_LIBADD) + $(RANLIB) libqq.a + +mostlyclean-pkgLTLIBRARIES: + +clean-pkgLTLIBRARIES: + -test -z "$(pkg_LTLIBRARIES)" || rm -f $(pkg_LTLIBRARIES) + +distclean-pkgLTLIBRARIES: + +maintainer-clean-pkgLTLIBRARIES: + +install-pkgLTLIBRARIES: $(pkg_LTLIBRARIES) + @$(NORMAL_INSTALL) + $(mkinstalldirs) $(DESTDIR)$(pkgdir) + @list='$(pkg_LTLIBRARIES)'; for p in $$list; do \ + if test -f $$p; then \ + echo "$(LIBTOOL) --mode=install $(INSTALL) $$p $(DESTDIR)$(pkgdir)/$$p"; \ + $(LIBTOOL) --mode=install $(INSTALL) $$p $(DESTDIR)$(pkgdir)/$$p; \ + else :; fi; \ + done + +uninstall-pkgLTLIBRARIES: + @$(NORMAL_UNINSTALL) + list='$(pkg_LTLIBRARIES)'; for p in $$list; do \ + $(LIBTOOL) --mode=uninstall rm -f $(DESTDIR)$(pkgdir)/$$p; \ + done + +libqq.la: $(libqq_la_OBJECTS) $(libqq_la_DEPENDENCIES) + $(LINK) -rpath $(pkgdir) $(libqq_la_LDFLAGS) $(libqq_la_OBJECTS) $(libqq_la_LIBADD) $(LIBS) + +tags: TAGS + +ID: $(HEADERS) $(SOURCES) $(LISP) + list='$(SOURCES) $(HEADERS)'; \ + unique=`for i in $$list; do echo $$i; done | \ + awk ' { files[$$0] = 1; } \ + END { for (i in files) print i; }'`; \ + here=`pwd` && cd $(srcdir) \ + && mkid -f$$here/ID $$unique $(LISP) + +TAGS: $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) $(LISP) + tags=; \ + here=`pwd`; \ + list='$(SOURCES) $(HEADERS)'; \ + unique=`for i in $$list; do echo $$i; done | \ + awk ' { files[$$0] = 1; } \ + END { for (i in files) print i; }'`; \ + test -z "$(ETAGS_ARGS)$$unique$(LISP)$$tags" \ + || (cd $(srcdir) && etags -o $$here/TAGS $(ETAGS_ARGS) $$tags $$unique $(LISP)) + +mostlyclean-tags: + +clean-tags: + +distclean-tags: + -rm -f TAGS ID + +maintainer-clean-tags: + +distdir = $(top_builddir)/$(PACKAGE)-$(VERSION)/$(subdir) + +subdir = src/protocols/qq + +distdir: $(DISTFILES) + here=`cd $(top_builddir) && pwd`; \ + top_distdir=`cd $(top_distdir) && pwd`; \ + distdir=`cd $(distdir) && pwd`; \ + cd $(top_srcdir) \ + && $(AUTOMAKE) --include-deps --build-dir=$$here --srcdir-name=$(top_srcdir) --output-dir=$$top_distdir --gnu src/protocols/qq/Makefile + @for file in $(DISTFILES); do \ + d=$(srcdir); \ + if test -d $$d/$$file; then \ + cp -pr $$d/$$file $(distdir)/$$file; \ + else \ + test -f $(distdir)/$$file \ + || ln $$d/$$file $(distdir)/$$file 2> /dev/null \ + || cp -p $$d/$$file $(distdir)/$$file || :; \ + fi; \ + done + +DEPS_MAGIC := $(shell mkdir .deps > /dev/null 2>&1 || :) + +-include $(DEP_FILES) + +mostlyclean-depend: + +clean-depend: + +distclean-depend: + -rm -rf .deps + +maintainer-clean-depend: + +%.o: %.c + @echo '$(COMPILE) -c $<'; \ + $(COMPILE) -Wp,-MD,.deps/$(*F).pp -c $< + @-cp .deps/$(*F).pp .deps/$(*F).P; \ + tr ' ' '\012' < .deps/$(*F).pp \ + | sed -e 's/^\\$$//' -e '/^$$/ d' -e '/:$$/ d' -e 's/$$/ :/' \ + >> .deps/$(*F).P; \ + rm .deps/$(*F).pp + +%.lo: %.c + @echo '$(LTCOMPILE) -c $<'; \ + $(LTCOMPILE) -Wp,-MD,.deps/$(*F).pp -c $< + @-sed -e 's/^\([^:]*\)\.o[ ]*:/\1.lo \1.o :/' \ + < .deps/$(*F).pp > .deps/$(*F).P; \ + tr ' ' '\012' < .deps/$(*F).pp \ + | sed -e 's/^\\$$//' -e '/^$$/ d' -e '/:$$/ d' -e 's/$$/ :/' \ + >> .deps/$(*F).P; \ + rm -f .deps/$(*F).pp +info-am: +info: info-am +dvi-am: +dvi: dvi-am +check-am: all-am +check: check-am +installcheck-am: +installcheck: installcheck-am +install-exec-am: +install-exec: install-exec-am + +install-data-am: install-pkgLTLIBRARIES +install-data: install-data-am + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am +install: install-am +uninstall-am: uninstall-pkgLTLIBRARIES +uninstall: uninstall-am +all-am: Makefile $(LIBRARIES) $(LTLIBRARIES) +all-redirect: all-am +install-strip: + $(MAKE) $(AM_MAKEFLAGS) AM_INSTALL_PROGRAM_FLAGS=-s install +installdirs: + $(mkinstalldirs) $(DESTDIR)$(pkgdir) + + +mostlyclean-generic: + +clean-generic: + +distclean-generic: + -rm -f Makefile $(CONFIG_CLEAN_FILES) + -rm -f config.cache config.log stamp-h stamp-h[0-9]* + +maintainer-clean-generic: +mostlyclean-am: mostlyclean-noinstLIBRARIES mostlyclean-compile \ + mostlyclean-libtool mostlyclean-pkgLTLIBRARIES \ + mostlyclean-tags mostlyclean-depend mostlyclean-generic + +mostlyclean: mostlyclean-am + +clean-am: clean-noinstLIBRARIES clean-compile clean-libtool \ + clean-pkgLTLIBRARIES clean-tags clean-depend \ + clean-generic mostlyclean-am + +clean: clean-am + +distclean-am: distclean-noinstLIBRARIES distclean-compile \ + distclean-libtool distclean-pkgLTLIBRARIES \ + distclean-tags distclean-depend distclean-generic \ + clean-am + -rm -f libtool + +distclean: distclean-am + +maintainer-clean-am: maintainer-clean-noinstLIBRARIES \ + maintainer-clean-compile maintainer-clean-libtool \ + maintainer-clean-pkgLTLIBRARIES maintainer-clean-tags \ + maintainer-clean-depend maintainer-clean-generic \ + distclean-am + @echo "This command is intended for maintainers to use;" + @echo "it deletes files that may require special tools to rebuild." + +maintainer-clean: maintainer-clean-am + +.PHONY: mostlyclean-noinstLIBRARIES distclean-noinstLIBRARIES \ +clean-noinstLIBRARIES maintainer-clean-noinstLIBRARIES \ +mostlyclean-compile distclean-compile clean-compile \ +maintainer-clean-compile mostlyclean-libtool distclean-libtool \ +clean-libtool maintainer-clean-libtool mostlyclean-pkgLTLIBRARIES \ +distclean-pkgLTLIBRARIES clean-pkgLTLIBRARIES \ +maintainer-clean-pkgLTLIBRARIES uninstall-pkgLTLIBRARIES \ +install-pkgLTLIBRARIES tags mostlyclean-tags distclean-tags clean-tags \ +maintainer-clean-tags distdir mostlyclean-depend distclean-depend \ +clean-depend maintainer-clean-depend info-am info dvi-am dvi check \ +check-am installcheck-am installcheck install-exec-am install-exec \ +install-data-am install-data install-am install uninstall-am uninstall \ +all-redirect all-am all installdirs mostlyclean-generic \ +distclean-generic clean-generic maintainer-clean-generic clean \ +mostlyclean distclean maintainer-clean + + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff -r 5642f4658b59 -r 983fd420e86b src/protocols/qq/Makefile.mingw --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/protocols/qq/Makefile.mingw Mon Jun 26 02:58:54 2006 +0000 @@ -0,0 +1,176 @@ +# +# Makefile.mingw +# +# Description: Makefile for win32 (mingw) version of OpenQ src +# + +# +# PATHS +# + +INCLUDE_DIR := . +OPENQ_TOP := ../ +GTK_TOP := ../../../../win32-dev/gtk_2_0 +GAIM_TOP := ../../.. +OPENQ_ROOT := . +GAIM_INSTALL_DIR := $(GAIM_TOP)/win32-install-dir + +## +## VARIABLE DEFINITIONS +## + +TARGET = openq + +TYPE = PLUGIN + +OPENQ_VERSION := $(shell cat $(OPENQ_TOP)/VERSION) + +# Compiler Options + +CFLAGS = + +DEFINES = -DOPENQ_VERSION=\"$(OPENQ_VERSION)\" + +# Static or Plugin... +ifeq ($(TYPE),STATIC) + DEFINES += -DSTATIC + DLL_INSTALL_DIR = $(GAIM_INSTALL_DIR) +else +ifeq ($(TYPE),PLUGIN) + DLL_INSTALL_DIR = $(GAIM_INSTALL_DIR)/plugins +endif +endif + + +## +## INCLUDE MAKEFILES +## + +include $(GAIM_TOP)/src/win32/global.mak + +## +## INCLUDE PATHS +## + +INCLUDE_PATHS += \ + -I$(OPENQ_ROOT) \ + -I$(GTK_TOP)/include \ + -I$(GTK_TOP)/include/gtk-2.0 \ + -I$(GTK_TOP)/include/glib-2.0 \ + -I$(GTK_TOP)/include/pango-1.0 \ + -I$(GTK_TOP)/include/atk-1.0 \ + -I$(GTK_TOP)/lib/glib-2.0/include \ + -I$(GTK_TOP)/lib/gtk-2.0/include \ + -I$(GAIM_TOP)/src \ + -I$(GAIM_TOP)/src/win32 \ + -I$(GAIM_TOP) + +LIB_PATHS = \ + -L$(GTK_TOP)/lib \ + -L$(GAIM_TOP)/src + +## +## SOURCES, OBJECTS +## + +C_SRC = \ + buddy_info.c \ + buddy_list.c \ + buddy_opt.c \ + buddy_status.c \ + char_conv.c \ + crypt.c \ + file_trans.c \ + group.c \ + group_admindlg.c \ + group_conv.c \ + group_find.c \ + group_free.c \ + group_hash.c \ + group_im.c \ + group_info.c \ + group_join.c \ + group_misc.c \ + group_network.c \ + group_opt.c \ + group_search.c \ + header_info.c \ + im.c \ + infodlg.c \ + ip_location.c \ + keep_alive.c \ + login_logout.c \ + packet_parse.c \ + qq.c \ + qq_proxy.c \ + recv_core.c \ + send_core.c \ + send_file.c \ + sendqueue.c \ + show.c \ + sys_msg.c \ + udp_proxy_s5.c \ + utils.c + +OBJECTS = $(C_SRC:%.c=%.o) + +## +## LIBRARIES +## + +LIBS = \ + -lgtk-win32-2.0 \ + -lglib-2.0 \ + -lgdk-win32-2.0 \ + -lgdk_pixbuf-2.0 \ + -lgmodule-2.0 \ + -lgobject-2.0 \ + -lws2_32 \ + -lintl \ + -lgaim + +## +## RULES +## + +# How to make a C file + +%.o: %.c + $(CC) $(CFLAGS) $(DEFINES) $(INCLUDE_PATHS) -o $@ -c $< + +## +## TARGET DEFINITIONS +## + +.PHONY: all clean + +all: $(TARGET).dll + +install: + mkdir -p $(DLL_INSTALL_DIR) + cp $(OPENQ_ROOT)/$(TARGET).dll $(DLL_INSTALL_DIR) + + +## +## BUILD Dependencies +## + +$(GAIM_TOP)/src/gaim.lib: + $(MAKE) -C $(GAIM_TOP)/src -f Makefile.mingw gaim.lib + +## +## BUILD DLL +## + +$(TARGET).dll: $(OBJECTS) $(GAIM_TOP)/src/gaim.lib + $(CC) -shared $(OBJECTS) $(LIB_PATHS) $(LIBS) $(DLL_LD_FLAGS) -o $(TARGET).dll + +## +## CLEAN RULES +## + +clean: + rm -rf *.o + rm -rf $(TARGET).dll + rm -rf $(TARGET).lib + diff -r 5642f4658b59 -r 983fd420e86b src/protocols/qq/TODO --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/protocols/qq/TODO Mon Jun 26 02:58:54 2006 +0000 @@ -0,0 +1,18 @@ +* Decide how to handle QQ status icons, which are customizable and legion. +* Give "Online Offline" and "My Offline" statuses appropriate titles. +* Handle emoticon at beginning of message via passthrough_unknown_commands. +* Fix file transfer. +* QQ protocol currently breaks Chinese localization of Gaim. Fix this. +* Fix ability to insert images into a conversation via the menubar. +* Fix _qq_menu_block_buddy in qq.c +* Eliminate all references to QQWry.dat and the geolocation lookup feature previously present OpenQ. +* Give smileys verbal instead of numerical titles. +* Clean up signedness warnings. +* Clean up mixed declaration and code warnings. +* Better decomposition: +** some of these functions are _really_ long +** buddy_status.c has a helper function which seems to belong in buddy_status.c +** consider cleaning up qq_encrypt (nested function declarations) +** eliminate group_misc.c +** investigate whether all of these externs are appropriate +* Check for memory leaks. diff -r 5642f4658b59 -r 983fd420e86b src/protocols/qq/buddy_info.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/protocols/qq/buddy_info.c Mon Jun 26 02:58:54 2006 +0000 @@ -0,0 +1,274 @@ +/** + * The QQ2003C protocol plugin + * + * for gaim + * + * Copyright (C) 2004 Puzzlebird + * + * 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 + */ + +// START OF FILE +/*****************************************************************************/ +#include "internal.h" // strlen, _("get_text) +#include "debug.h" // gaim_debug +#include "notify.h" // gaim_notify + +#include "utils.h" // uid_to_gaim_name +#include "packet_parse.h" // MAX_PACKET_SIZE +#include "buddy_info.h" // +#include "char_conv.h" // qq_to_utf8 +#include "crypt.h" // qq_crypt +#include "header_info.h" // cmd alias +#include "infodlg.h" // info_window +#include "keep_alive.h" // qq_update_buddy_contact +#include "send_core.h" // qq_send_cmd + +// amount of fiedls in user info +#define QQ_CONTACT_FIELDS 37 + +// There is no user id stored in the reply packet for information query +// we have to manually store the query, so that we know the query source +typedef struct _qq_info_query { + guint32 uid; + gboolean show_window; + contact_info *ret_info; +} qq_info_query; + +/*****************************************************************************/ +// send a packet to get detailed information of uid, +// if show_window, a info window will be display upon receiving a reply +void qq_send_packet_get_info(GaimConnection * gc, guint32 uid, gboolean show_window) +{ + qq_data *qd; + gchar *uid_str; + GList *list; + qq_info_query *query; + gboolean is_exist; + contact_info_window *info_window; + + g_return_if_fail(gc != NULL && gc->proto_data != NULL && uid != 0); + + qd = (qq_data *) gc->proto_data; + uid_str = g_strdup_printf("%d", uid); + qq_send_cmd(gc, QQ_CMD_GET_USER_INFO, TRUE, 0, TRUE, uid_str, strlen(uid_str)); + + if (show_window) { // prepare the window + is_exist = FALSE; // see if there is already a window for this uid + list = qd->contact_info_window; + while (list != NULL) { + info_window = (contact_info_window *) list->data; + if (uid == info_window->uid) { + is_exist = TRUE; + break; + } else + list = list->next; + } // while list + if (!is_exist) { // create a new one + info_window = g_new0(contact_info_window, 1); + info_window->uid = uid; + qd->contact_info_window = g_list_append(qd->contact_info_window, info_window); + } // if !is_exist + } // if show_window + + query = g_new0(qq_info_query, 1); + query->uid = uid; + query->show_window = show_window; + qd->info_query = g_list_append(qd->info_query, query); + + g_free(uid_str); +} // qq_send_packet_get_info + +/*****************************************************************************/ +// send packet to modify personal information, and/or change password +void qq_send_packet_modify_info(GaimConnection * gc, contact_info * info, gchar * new_passwd) +{ + GaimAccount *a; + gchar *old_passwd, *info_field[QQ_CONTACT_FIELDS]; + gint i; + guint8 *raw_data, *cursor, bar; + + g_return_if_fail(gc != NULL && info != NULL); + + a = gc->account; + old_passwd = a->password; + bar = 0x1f; + raw_data = g_newa(guint8, MAX_PACKET_SIZE - 128); + cursor = raw_data; + + g_memmove(info_field, info, sizeof(gchar *) * QQ_CONTACT_FIELDS); + + if (new_passwd == NULL || strlen(new_passwd) == 0) + create_packet_b(raw_data, &cursor, bar); + else { // we gonna change passwd + create_packet_data(raw_data, &cursor, old_passwd, strlen(old_passwd)); + create_packet_b(raw_data, &cursor, bar); + create_packet_data(raw_data, &cursor, new_passwd, strlen(new_passwd)); + } + + // important!, skip the first uid entry + for (i = 1; i < QQ_CONTACT_FIELDS; i++) { + create_packet_b(raw_data, &cursor, bar); + create_packet_data(raw_data, &cursor, info_field[i], strlen(info_field[i])); + } + create_packet_b(raw_data, &cursor, bar); + + qq_send_cmd(gc, QQ_CMD_UPDATE_INFO, TRUE, 0, TRUE, raw_data, cursor - raw_data); + +} // qq_send_packet_modify_info + +/*****************************************************************************/ +// process the reply of modidy_info packet +void qq_process_modify_info_reply(guint8 * buf, gint buf_len, GaimConnection * gc) +{ + qq_data *qd; + gint len; + guint8 *data; + + g_return_if_fail(gc != NULL && gc->proto_data != NULL); + g_return_if_fail(buf != NULL && buf_len != 0); + + qd = (qq_data *) gc->proto_data; + len = buf_len; + data = g_newa(guint8, len); + + if (qq_crypt(DECRYPT, buf, buf_len, qd->session_key, data, &len)) { + if (qd->uid == atoi(data)) { // return should be my uid + gaim_debug(GAIM_DEBUG_INFO, "QQ", "Update info ACK OK\n"); + gaim_notify_info(gc, NULL, _("You information have been updated"), NULL); + } + } else + gaim_debug(GAIM_DEBUG_ERROR, "QQ", "Error decrypt modify info reply\n"); + +} // qq_process_modify_info_reply + +/*****************************************************************************/ +// after getting info or modify myself, refresh the buddy list accordingly +void qq_refresh_buddy_and_myself(contact_info * info, GaimConnection * gc) +{ + GaimBuddy *b; + qq_data *qd; + qq_buddy *q_bud; + gchar *alias_utf8; + + g_return_if_fail(gc != NULL && gc->proto_data != NULL); + qd = (qq_data *) gc->proto_data; + + alias_utf8 = qq_to_utf8(info->nick, QQ_CHARSET_DEFAULT); + if (qd->uid == strtol(info->uid, NULL, 10)) { // it is me + qd->my_icon = strtol(info->face, NULL, 10); + if (alias_utf8 != NULL) + gaim_account_set_alias(gc->account, alias_utf8); + } + // update buddy list (including myself, if myself is the buddy) + b = gaim_find_buddy(gc->account, uid_to_gaim_name(strtol(info->uid, NULL, 10))); + q_bud = (b == NULL) ? NULL : (qq_buddy *) b->proto_data; + if (q_bud != NULL) { // I have this buddy + q_bud->age = strtol(info->age, NULL, 10); + q_bud->gender = strtol(info->gender, NULL, 10); + q_bud->icon = strtol(info->face, NULL, 10); + if (alias_utf8 != NULL) + q_bud->nickname = g_strdup(alias_utf8); + qq_update_buddy_contact(gc, q_bud); + } // if q_bud + g_free(alias_utf8); +} // qq_refresh_buddy_and_myself + +/*****************************************************************************/ +// process reply to get_info packet +void qq_process_get_info_reply(guint8 * buf, gint buf_len, GaimConnection * gc) +{ + gint len; + guint8 *data; + gchar **segments; + qq_info_query *query; + qq_data *qd; + contact_info *info; + contact_info_window *info_window; + gboolean show_window; + GList *list, *query_list; + + g_return_if_fail(gc != NULL && gc->proto_data != NULL); + g_return_if_fail(buf != NULL && buf_len != 0); + + qd = (qq_data *) gc->proto_data; + list = query_list = NULL; + len = buf_len; + data = g_newa(guint8, len); + info = NULL; + + if (qq_crypt(DECRYPT, buf, buf_len, qd->session_key, data, &len)) { + if (NULL == (segments = split_data(data, len, "\x1e", QQ_CONTACT_FIELDS))) + return; + + info = (contact_info *) segments; + qq_refresh_buddy_and_myself(info, gc); + + query_list = qd->info_query; + show_window = FALSE; + while (query_list != NULL) { + query = (qq_info_query *) query_list->data; + if (query->uid == atoi(info->uid)) { + show_window = query->show_window; + qd->info_query = g_list_remove(qd->info_query, qd->info_query->data); + g_free(query); + break; + } + query_list = query_list->next; + } // while query_list + + if (!show_window) { + g_strfreev(segments); + return; + } + // if not show_window, we can not find the window here either + list = qd->contact_info_window; + while (list != NULL) { + info_window = (contact_info_window *) (list->data); + if (info_window->uid == atoi(info->uid)) { + if (info_window->window) + qq_refresh_contact_info_dialog(info, gc, info_window); + else + qq_show_contact_info_dialog(info, gc, info_window); + break; + } else + list = list->next; + } // while list + g_strfreev(segments); + } else + gaim_debug(GAIM_DEBUG_ERROR, "QQ", "Error decrypt get info reply\n"); + +} // qq_process_get_info_reply + +/*****************************************************************************/ +void qq_info_query_free(qq_data * qd) +{ + gint i; + qq_info_query *p; + + g_return_if_fail(qd != NULL); + + i = 0; + while (qd->info_query != NULL) { + p = (qq_info_query *) (qd->info_query->data); + qd->info_query = g_list_remove(qd->info_query, p); + g_free(p); + i++; + } + gaim_debug(GAIM_DEBUG_INFO, "QQ", "%d info queries are freed!\n", i); +} // qq_add_buddy_request_free + +/*****************************************************************************/ +// END OF FILE diff -r 5642f4658b59 -r 983fd420e86b src/protocols/qq/buddy_info.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/protocols/qq/buddy_info.h Mon Jun 26 02:58:54 2006 +0000 @@ -0,0 +1,94 @@ +/** + * The QQ2003C protocol plugin + * + * for gaim + * + * Copyright (C) 2004 Puzzlebird + * + * 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 + */ + +// START OF FILE +/*****************************************************************************/ +#ifndef _QQ_BUDDY_INFO_H_ +#define _QQ_BUDDY_INFO_H_ + +#include +#include "connection.h" // GaimConnection +#include "buddy_opt.h" // gc_and_uid +#include "qq.h" // qq_data + +#define QQ_COMM_FLAG_QQ_MEMBER 0x02 +#define QQ_COMM_FLAG_TCP_MODE 0x10 +#define QQ_COMM_FLAG_MOBILE 0x20 +#define QQ_COMM_FLAG_BIND_MOBILE 0x40 +#define QQ_COMM_FLAG_VIDEO 0x80 + +#define QQ_BUDDY_GENDER_GG 0x00 +#define QQ_BUDDY_GENDER_MM 0x01 +#define QQ_BUDDY_GENDER_UNKNOWN 0xff + +typedef struct _contact_info contact_info; + +struct _contact_info { + gchar *uid; //0 + gchar *nick; //1 + gchar *country; //2 + gchar *province; //3 + gchar *zipcode; //4 + gchar *address; //5 + gchar *tel; //6 + gchar *age; //7 + gchar *gender; //8 + gchar *name; //9 + gchar *email; //10 + gchar *pager_sn; //11 + gchar *pager_num; //12 + gchar *pager_sp; //13 + gchar *pager_base_num; //14 + gchar *pager_type; //15 + gchar *occupation; //16 + gchar *homepage; //17 + gchar *auth_type; //18 + gchar *unknown1; //19 + gchar *unknown2; //20 + gchar *face; //21 + gchar *hp_num; //22 + gchar *hp_type; //23 + gchar *intro; //24 + gchar *city; //25 + gchar *unknown3; //26 + gchar *unknown4; //27 + gchar *unknown5; //28 + gchar *is_open_hp; //29 + gchar *is_open_contact; //30 + gchar *college; //31 + gchar *horoscope; //32 + gchar *zodiac; //33 sheng xiao + gchar *blood; //34 + gchar *qq_show; //35 + gchar *unknown6; //36, always 0x2D +}; + +void qq_refresh_buddy_and_myself(contact_info * info, GaimConnection * gc); +void qq_send_packet_get_info(GaimConnection * gc, guint32 uid, gboolean show_window); +void qq_send_packet_modify_info(GaimConnection * gc, contact_info * info, gchar * new_passwd); +void qq_process_modify_info_reply(guint8 * buf, gint buf_len, GaimConnection * gc); +void qq_process_get_info_reply(guint8 * buf, gint buf_len, GaimConnection * gc); +void qq_info_query_free(qq_data * qd); + +#endif +/*****************************************************************************/ +// END OF FILE diff -r 5642f4658b59 -r 983fd420e86b src/protocols/qq/buddy_list.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/protocols/qq/buddy_list.c Mon Jun 26 02:58:54 2006 +0000 @@ -0,0 +1,444 @@ +/** +* The QQ2003C protocol plugin + * + * for gaim + * + * Copyright (C) 2004 Puzzlebird + * + * 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 + */ + +// START OF FILE +/*****************************************************************************/ +#include // g_memmove, memmove +#include "debug.h" // gaim_debug + +#include "notify.h" // gaim_notify +#include "utils.h" // get_ip_str +#include "packet_parse.h" // create_packet, read_packet +#include "buddy_list.h" +#include "buddy_status.h" // qq_buddy_status +#include "buddy_opt.h" // qq_add_buddy_by_recv_packet +#include "char_conv.h" // qq_to_utf8 +#include "crypt.h" // qq_crypt +#include "header_info.h" // cmd alias +#include "keep_alive.h" // qq_refresh_all_buddy_status +#include "send_core.h" // qq_send_cmd +#include "qq.h" // qq_data +#include "group.h" // qq_group, by gfhuang +#include "group_find.h" // qq_group_find +#include "group_hash.h" //qq_group_create_by_id +#include "group_info.h" //qq_send_cmd_group_get_group_info + +#define QQ_GET_ONLINE_BUDDY_02 0x02 +#define QQ_GET_ONLINE_BUDDY_03 0x03 // unknown function + +#define QQ_ONLINE_BUDDY_ENTRY_LEN 38 + +typedef struct _qq_friends_online_entry { + qq_buddy_status *s; + guint16 unknown1; + guint8 flag1; + guint8 comm_flag; + guint16 unknown2; + guint8 ending; //0x00 +} qq_friends_online_entry; + +//TODO: defined in qq_buddy_status.c, but only used here. Check decomposition. +extern void // defined in qq_buddy_status.c + _qq_buddy_status_dump_unclear(qq_buddy_status * s); + +extern gint // defined in qq_buddy_status.c + _qq_buddy_status_read(guint8 * data, guint8 ** cursor, gint len, qq_buddy_status * s); + +/*****************************************************************************/ +// get a list of online_buddies +void qq_send_packet_get_buddies_online(GaimConnection * gc, guint8 position) +{ + qq_data *qd; + guint8 *raw_data, *cursor; + + g_return_if_fail(gc != NULL && gc->proto_data != NULL); + + qd = (qq_data *) gc->proto_data; + raw_data = g_newa(guint8, 5); + cursor = raw_data; + + // 000-000 get online friends cmd + // only 0x02 and 0x03 returns info from server, other valuse all return 0xff + // I can also only send the first byte (0x02, or 0x03) + // and the result is the same + create_packet_b(raw_data, &cursor, QQ_GET_ONLINE_BUDDY_02); + // 001-001 seems it supports 255 online buddies at most + create_packet_b(raw_data, &cursor, position); + // 002-002 + create_packet_b(raw_data, &cursor, 0x00); + // 003-004 + create_packet_w(raw_data, &cursor, 0x0000); + + qq_send_cmd(gc, QQ_CMD_GET_FRIENDS_ONLINE, TRUE, 0, TRUE, raw_data, 5); + qd->last_get_online = time(NULL); + +} // qq_send_packet_get_buddies_online + +/*****************************************************************************/ +// position starts with 0x0000, +// server may return a position tag if list is too long for one packet +void qq_send_packet_get_buddies_list(GaimConnection * gc, guint16 position) +{ + guint8 *raw_data, *cursor; + gint data_len; + + g_return_if_fail(gc != NULL); + + data_len = 3; + raw_data = g_newa(guint8, data_len); + cursor = raw_data; + // 000-001 starting position, can manually specify + create_packet_w(raw_data, &cursor, position); + // before Mar 18, 2004, any value can work, and we sent 00 + // I do not know what data QQ server is expecting, as QQ2003iii 0304 itself + // even can sending packets 00 and get no response. + // Now I tested that 00,00,00,00,00,01 work perfectly + // March 22, fount the 00,00,00 starts to work as well + create_packet_b(raw_data, &cursor, 0x00); + + qq_send_cmd(gc, QQ_CMD_GET_FRIENDS_LIST, TRUE, 0, TRUE, raw_data, data_len); + +} // qq_send_packet_get_buddies_list + +// get all list, buddies & Quns with groupsid support, written by gfhuang +void qq_send_packet_get_all_list_with_group(GaimConnection *gc, guint32 position) +{ + guint8 *raw_data, *cursor; + gint data_len; + + g_return_if_fail(gc != NULL); + + data_len = 10; + raw_data = g_newa(guint8, data_len); + cursor = raw_data; + // 0x01 download, 0x02, upload + create_packet_b(raw_data, &cursor, 0x01); + //unknown 0x02 + create_packet_b(raw_data, &cursor, 0x02); + //unknown 00 00 00 00 + create_packet_dw(raw_data, &cursor, 0x00000000); + create_packet_dw(raw_data, &cursor, position); + + qq_send_cmd(gc, QQ_CMD_GET_ALL_LIST_WITH_GROUP, TRUE, 0, TRUE, raw_data, data_len); +} + +/*****************************************************************************/ +static void _qq_buddies_online_reply_dump_unclear(qq_friends_online_entry * fe) +{ + GString *dump; + + g_return_if_fail(fe != NULL); + + _qq_buddy_status_dump_unclear(fe->s); + + dump = g_string_new(""); + g_string_append_printf(dump, "unclear fields for [%d]:\n", fe->s->uid); + g_string_append_printf(dump, "031-032: %04x (unknown)\n", fe->unknown1); + g_string_append_printf(dump, "033: %02x (flag1)\n", fe->flag1); + g_string_append_printf(dump, "034: %02x (comm_flag)\n", fe->comm_flag); + g_string_append_printf(dump, "035-036: %04x (unknown)\n", fe->unknown2); + + gaim_debug(GAIM_DEBUG_INFO, "QQ", "Online buddy entry, %s", dump->str); + g_string_free(dump, TRUE); +} // _qq_buddies_online_reply_dump_unknown + +/*****************************************************************************/ +// process the reply packet for get_buddies_online packet +void qq_process_get_buddies_online_reply(guint8 * buf, gint buf_len, GaimConnection * gc) { + + qq_data *qd; + gint len, bytes; + guint8 *data, *cursor, position; + GaimBuddy *b; + qq_buddy *q_bud; + qq_friends_online_entry *fe; + + g_return_if_fail(gc != NULL && gc->proto_data != NULL); + g_return_if_fail(buf != NULL && buf_len != 0); + + qd = (qq_data *) gc->proto_data; + len = buf_len; + data = g_newa(guint8, len); + cursor = data; + + if (qq_crypt(DECRYPT, buf, buf_len, qd->session_key, data, &len)) { + + read_packet_b(data, &cursor, len, &position); + fe = g_newa(qq_friends_online_entry, 1); + fe->s = g_newa(qq_buddy_status, 1); + + while (cursor < (data + len)) { + // based on one online buddy entry + bytes = 0; + // 000-030 qq_buddy_status + bytes += _qq_buddy_status_read(data, &cursor, len, fe->s); + // 031-032: unknown4 + bytes += read_packet_w(data, &cursor, len, &fe->unknown1); + // 033-033: flag1 + bytes += read_packet_b(data, &cursor, len, &fe->flag1); + // 034-034: comm_flag + bytes += read_packet_b(data, &cursor, len, &fe->comm_flag); + // 035-036: + bytes += read_packet_w(data, &cursor, len, &fe->unknown2); + // 037-037: + bytes += read_packet_b(data, &cursor, len, &fe->ending); // 0x00 + + if (fe->s->uid == 0 || bytes != QQ_ONLINE_BUDDY_ENTRY_LEN) { + gaim_debug(GAIM_DEBUG_ERROR, "QQ", "uid=0 or entry complete len(%d) != %d", bytes, QQ_ONLINE_BUDDY_ENTRY_LEN); + g_free(fe->s->ip); + g_free(fe->s->unknown_key); + continue; + } // check if it is a valid entry + +// if (QQ_DEBUG) +// _qq_buddies_online_reply_dump_unclear(fe); + + // update buddy information + b = gaim_find_buddy(gaim_connection_get_account(gc), uid_to_gaim_name(fe->s->uid)); + q_bud = (b == NULL) ? NULL : (qq_buddy *) b->proto_data; + + if (q_bud != NULL) { // we find one and update qq_buddy + if(0 != fe->s->client_version) + q_bud->client_version = fe->s->client_version; //by gfhuang + if(0 != *((guint32 *)fe->s->ip)) { // by gfhuang + g_memmove(q_bud->ip, fe->s->ip, 4); + q_bud->port = fe->s->port; + } + q_bud->status = fe->s->status; + q_bud->flag1 = fe->flag1; + q_bud->comm_flag = fe->comm_flag; + qq_update_buddy_contact(gc, q_bud); + } // if q_bud + else { + gaim_debug(GAIM_DEBUG_ERROR, "QQ", "Got an online buddy %d, but not in my buddy list", fe->s->uid); + } + + g_free(fe->s->ip); + g_free(fe->s->unknown_key); + } // while cursor + + if(cursor > (data + len)) { + gaim_debug(GAIM_DEBUG_ERROR, "QQ", "qq_process_get_buddies_online_reply: Dangerous error! maybe protocal changed, notify me!"); + } + + if (position != QQ_FRIENDS_ONLINE_POSITION_END) { + gaim_debug(GAIM_DEBUG_INFO, "QQ", "Has more online buddies, position from %d", position); + + qq_send_packet_get_buddies_online(gc, position); + } + else + qq_refresh_all_buddy_status(gc); + + } else + gaim_debug(GAIM_DEBUG_ERROR, "QQ", "Error decrypt buddies online"); + +} // qq_process_get_buddies_online_reply + +/*****************************************************************************/ +// process reply for get_buddies_list +void qq_process_get_buddies_list_reply(guint8 * buf, gint buf_len, GaimConnection * gc) { + qq_data *qd; + qq_buddy *q_bud; + gint len, bytes, bytes_expected, i; + guint16 position, unknown; + guint8 *data, *cursor, bar, pascal_len; + gchar *name; + GaimBuddy *b; + + g_return_if_fail(gc != NULL && gc->proto_data != NULL); + g_return_if_fail(buf != NULL && buf_len != 0); + + qd = (qq_data *) gc->proto_data; + len = buf_len; + data = g_newa(guint8, len); + cursor = data; + + if (qq_crypt(DECRYPT, buf, buf_len, qd->session_key, data, &len)) { + read_packet_w(data, &cursor, len, &position); + // the following data is buddy list in this packet + i = 0; + while (cursor < (data + len)) { + q_bud = g_new0(qq_buddy, 1); + bytes = 0; + // 000-003: uid + bytes += read_packet_dw(data, &cursor, len, &q_bud->uid); + // 004-004: 0xff if buddy is self, 0x00 otherwise + bytes += read_packet_b(data, &cursor, len, &bar); + // 005-005: icon index (1-255) + bytes += read_packet_b(data, &cursor, len, &q_bud->icon); + // 006-006: age + bytes += read_packet_b(data, &cursor, len, &q_bud->age); + // 007-007: gender + bytes += read_packet_b(data, &cursor, len, &q_bud->gender); + pascal_len = convert_as_pascal_string(cursor, &q_bud->nickname, QQ_CHARSET_DEFAULT); + cursor += pascal_len; + bytes += pascal_len; + bytes += read_packet_w(data, &cursor, len, &unknown); + /* flag1: (0-7) + * bit1 => qq show + * comm_flag: (0-7) + * bit1 => member + * bit4 => TCP mode + * bit5 => open mobile QQ + * bit6 => bind to mobile + * bit7 => whether having a video + */ + bytes += read_packet_b(data, &cursor, len, &q_bud->flag1); + bytes += read_packet_b(data, &cursor, len, &q_bud->comm_flag); + + bytes_expected = 12 + pascal_len; + + if (q_bud->uid == 0 || bytes != bytes_expected) { + gaim_debug(GAIM_DEBUG_INFO, "QQ", + "Buddy entry, expect %d bytes, read %d bytes\n", bytes_expected, bytes); + g_free(q_bud->nickname); + g_free(q_bud); + continue; + } else + i++; + + if (QQ_DEBUG) + gaim_debug(GAIM_DEBUG_INFO, "QQ", + "buddy [%09d]: flag1=0x%02x, comm_flag=0x%02x\n", + q_bud->uid, q_bud->flag1, q_bud->comm_flag); + + name = uid_to_gaim_name(q_bud->uid); + b = gaim_find_buddy(gc->account, name); + g_free(name); + + if (b == NULL) + b = qq_add_buddy_by_recv_packet(gc, q_bud->uid, TRUE, FALSE); + + b->proto_data = q_bud; + qd->buddies = g_list_append(qd->buddies, q_bud); + qq_update_buddy_contact(gc, q_bud); + } // while cursor + + if(cursor > (data + len)) { + gaim_debug(GAIM_DEBUG_ERROR, "QQ", "qq_process_get_buddies_list_reply: Dangerous error! maybe protocal changed, notify me!"); + } + if (position == QQ_FRIENDS_LIST_POSITION_END) { + gaim_debug(GAIM_DEBUG_INFO, "QQ", "Get friends list done, %d buddies\n", i); + qq_send_packet_get_buddies_online(gc, QQ_FRIENDS_ONLINE_POSITION_START); + } else + qq_send_packet_get_buddies_list(gc, position); + + } else + gaim_debug(GAIM_DEBUG_ERROR, "QQ", "Error decrypt buddies list"); + +} // qq_process_get_buddies_list_reply + + +// written by gfhuang +void qq_process_get_all_list_with_group_reply(guint8 * buf, gint buf_len, GaimConnection * gc) +{ + qq_data *qd; + gint len, i, j; + guint8 *data, *cursor; + guint8 sub_cmd, reply_code; + guint32 unknown, position; + guint32 uid; + guint8 type, groupid; + + qq_buddy *q_bud; + gchar *name; + GaimBuddy *b; + qq_group *group; + + g_return_if_fail(gc != NULL && gc->proto_data != NULL); + g_return_if_fail(buf != NULL && buf_len != 0); + + qd = (qq_data *) gc->proto_data; + len = buf_len; + data = g_newa(guint8, len); + cursor = data; + + if (qq_crypt(DECRYPT, buf, buf_len, qd->session_key, data, &len)) { + read_packet_b(data, &cursor, len, &sub_cmd); + g_return_if_fail(sub_cmd == 0x01); + read_packet_b(data, &cursor, len, &reply_code); + if(0 != reply_code) { + gaim_debug(GAIM_DEBUG_WARNING, "QQ", "Get all list with group reply, reply_code(%d) is not zero", reply_code); + } + read_packet_dw(data, &cursor, len, &unknown); + read_packet_dw(data, &cursor, len, &position); + // the following data is all list in this packet + i = 0; + j = 0; + while (cursor < (data + len)) { + // 00-03: uid + read_packet_dw(data, &cursor, len, &uid); + // 04: type 0x1:buddy 0x4:Qun + read_packet_b(data, &cursor, len, &type); + // 05: groupid*4 + read_packet_b(data, &cursor, len, &groupid); + groupid >>= 2; // these 2 bits might not be 0, faint! + if (uid == 0 || (type != 0x1 && type != 0x4)) { + gaim_debug(GAIM_DEBUG_WARNING, "QQ", + "Buddy entry, uid=%d, type=%d", uid, type); + continue; + } + if(0x1 == type) { // a buddy + name = uid_to_gaim_name(uid); + b = gaim_find_buddy(gc->account, name); + g_free(name); + + if (b == NULL) { + b = qq_add_buddy_by_recv_packet(gc, uid, TRUE, TRUE); + q_bud = b->proto_data; + } + else { + q_bud = NULL; + b->proto_data = q_bud; //wrong !!!! + } + qd->buddies = g_list_append(qd->buddies, q_bud); + qq_update_buddy_contact(gc, q_bud); + ++i; + } + else { //a group + group = qq_group_find_by_internal_group_id(gc, uid); + if(group == NULL) { + /* not working, gfhuang + group = qq_group_create_by_id(gc, uid, 0); + qq_send_cmd_group_get_group_info(gc, group); + */ + gaim_debug(GAIM_DEBUG_ERROR, "QQ", "Get a Qun with internel group %d\n", uid); + gaim_notify_info(gc, _("QQ Qun Operation"), _("Find one Qun in the server list, but i don't know its external id, please re-rejoin it manually"), NULL); + } + else { + group->my_status = QQ_GROUP_MEMBER_STATUS_IS_MEMBER; + qq_group_refresh(gc, group); + qq_send_cmd_group_get_group_info(gc, group); + } + ++j; + } + } + if(cursor > (data + len)) { + gaim_debug(GAIM_DEBUG_ERROR, "QQ", "qq_process_get_all_list_with_group_reply: Dangerous error! maybe protocal changed, notify me!"); + } + gaim_debug(GAIM_DEBUG_INFO, "QQ", "Get all list done, %d buddies and %d Quns\n", i, j); + } else + gaim_debug(GAIM_DEBUG_ERROR, "QQ", "Error decrypt all list with group"); + +} +/*****************************************************************************/ +// END OF FILE diff -r 5642f4658b59 -r 983fd420e86b src/protocols/qq/buddy_list.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/protocols/qq/buddy_list.h Mon Jun 26 02:58:54 2006 +0000 @@ -0,0 +1,47 @@ +/** +* The QQ2003C protocol plugin + * + * for gaim + * + * Copyright (C) 2004 Puzzlebird + * + * 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 + */ + +// START OF FILE +/*****************************************************************************/ +#ifndef _QQ_BUDDY_LIST_H_ +#define _QQ_BUDDY_LIST_H_ + +#include +#include "connection.h" // GaimConnection + +#define QQ_FRIENDS_LIST_POSITION_START 0x0000 +#define QQ_FRIENDS_LIST_POSITION_END 0xffff +#define QQ_FRIENDS_ONLINE_POSITION_START 0x00 +#define QQ_FRIENDS_ONLINE_POSITION_END 0xff + +void qq_send_packet_get_buddies_online(GaimConnection * gc, guint8 position); +void qq_process_get_buddies_online_reply(guint8 * buf, gint buf_len, GaimConnection * gc); +void qq_send_packet_get_buddies_list(GaimConnection * gc, guint16 position); +void qq_process_get_buddies_list_reply(guint8 * buf, gint buf_len, GaimConnection * gc); + +//added by gfhuang +void qq_send_packet_get_all_list_with_group(GaimConnection * gc, guint32 position); +void qq_process_get_all_list_with_group_reply(guint8 * buf, gint buf_len, GaimConnection * gc); + +#endif +/*****************************************************************************/ +// END OF FILE diff -r 5642f4658b59 -r 983fd420e86b src/protocols/qq/buddy_opt.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/protocols/qq/buddy_opt.c Mon Jun 26 02:58:54 2006 +0000 @@ -0,0 +1,617 @@ +/** +* The QQ2003C protocol plugin + * + * for gaim + * + * Copyright (C) 2004 Puzzlebird + * + * 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 + */ + +// START OF FILE +/*****************************************************************************/ +#include "debug.h" // gaim_debug +#include "internal.h" // strlen, _("get_text") +#include "notify.h" // gaim_notify +#include "request.h" // gaim_request_input + +#include "utils.h" // uid_to_gaim_name, gaim_name_to_uid +#include "packet_parse.h" // create_packet, read_packet +#include "buddy_info.h" // qq_send_packet_get_info +#include "buddy_list.h" // qq_send_packet_get_buddies_online +#include "buddy_opt.h" +#include "char_conv.h" // qq_to_utf8 +#include "crypt.h" // qq_crypt +#include "header_info.h" // cmd alias +#include "keep_alive.h" // qq_update_buddy_contact +#include "im.h" // QQ_MSG_IM_MAX +#include "send_core.h" // qq_send_cmd + +#define GAIM_GROUP_QQ_FORMAT "QQ (%s)" +#define GAIM_GROUP_QQ_UNKNOWN "QQ Unknown" +#define GAIM_GROUP_QQ_BLOCKED "QQ Blocked" + +#define QQ_REMOVE_BUDDY_REPLY_OK 0x00 +#define QQ_REMOVE_SELF_REPLY_OK 0x00 +#define QQ_ADD_BUDDY_AUTH_REPLY_OK 0x30 // ASCii value of "0" + +enum { + QQ_MY_AUTH_APPROVE = 0x30, // ASCii value of "0" + QQ_MY_AUTH_REJECT = 0x31, // ASCii value of "1" + QQ_MY_AUTH_REQUEST = 0x32, // ASCii value of "2" +}; + +typedef struct _qq_add_buddy_request { + guint32 uid; + guint16 seq; +} qq_add_buddy_request; + +/*****************************************************************************/ +// send packet to remove a buddy from my buddy list +static void _qq_send_packet_remove_buddy(GaimConnection * gc, guint32 uid) +{ + gchar *uid_str; + + g_return_if_fail(gc != NULL && uid > 0); + + uid_str = g_strdup_printf("%d", uid); + qq_send_cmd(gc, QQ_CMD_DEL_FRIEND, TRUE, 0, TRUE, uid_str, strlen(uid_str)); + + g_free(uid_str); +} // _qq_send_packet_remove_buddy + +/*****************************************************************************/ +// try to remove myself from someone's buddy list +static void _qq_send_packet_remove_self_from(GaimConnection * gc, guint32 uid) +{ + guint8 *raw_data, *cursor; + + g_return_if_fail(gc != NULL && gc->proto_data != NULL && uid > 0); + + raw_data = g_newa(guint8, 4); + cursor = raw_data; + create_packet_dw(raw_data, &cursor, uid); + + qq_send_cmd(gc, QQ_CMD_REMOVE_SELF, TRUE, 0, TRUE, raw_data, 4); + +} // _qq_send_packet_add_buddy + +/*****************************************************************************/ +// try to add a buddy without authentication +static void _qq_send_packet_add_buddy(GaimConnection * gc, guint32 uid) +{ + qq_data *qd; + qq_add_buddy_request *req; + gchar *uid_str; + + g_return_if_fail(gc != NULL && gc->proto_data != NULL && uid > 0); + + // we need to send the ascii code of this uid to qq server + uid_str = g_strdup_printf("%d", uid); + qq_send_cmd(gc, QQ_CMD_ADD_FRIEND_WO_AUTH, TRUE, 0, TRUE, uid_str, strlen(uid_str)); + g_free(uid_str); + + // must be set after sending packet to get the correct send_seq + qd = (qq_data *) gc->proto_data; + req = g_new0(qq_add_buddy_request, 1); + req->seq = qd->send_seq; + req->uid = uid; + qd->add_buddy_request = g_list_append(qd->add_buddy_request, req); +} // _qq_send_packet_add_buddy + +/*****************************************************************************/ +// this buddy needs authentication, text conversion is done at lowest level +static void _qq_send_packet_buddy_auth(GaimConnection * gc, guint32 uid, const gchar response, const gchar * text) +{ + gchar *text_qq, *uid_str; + guint8 bar, *cursor, *raw_data; + + g_return_if_fail(gc != NULL && uid != 0); + + uid_str = g_strdup_printf("%d", uid); + bar = 0x1f; + raw_data = g_newa(guint8, QQ_MSG_IM_MAX); + cursor = raw_data; + + create_packet_data(raw_data, &cursor, uid_str, strlen(uid_str)); + create_packet_b(raw_data, &cursor, bar); + create_packet_b(raw_data, &cursor, response); + + if (text != NULL) { + text_qq = utf8_to_qq(text, QQ_CHARSET_DEFAULT); + create_packet_b(raw_data, &cursor, bar); + create_packet_data(raw_data, &cursor, text_qq, strlen(text_qq)); + g_free(text_qq); + } + + qq_send_cmd(gc, QQ_CMD_BUDDY_AUTH, TRUE, 0, TRUE, raw_data, cursor - raw_data); + g_free(uid_str); +} // _qq_send_packet_buddy_auth + + +/*****************************************************************************/ +static void _qq_send_packet_add_buddy_auth_with_gc_and_uid(gc_and_uid * g, const gchar * text) { + GaimConnection *gc; + guint32 uid; + g_return_if_fail(g != NULL); + + gc = g->gc; + uid = g->uid; + g_return_if_fail(gc != NULL && uid != 0); + + _qq_send_packet_buddy_auth(gc, uid, QQ_MY_AUTH_REQUEST, text); + g_free(g); +} // qq_send_packet_add_buddy_auth + +/*****************************************************************************/ +// the real packet to reject and request is sent from here +static void _qq_reject_add_request_real(gc_and_uid * g, const gchar * reason) +{ + gint uid; + GaimConnection *gc; + + g_return_if_fail(g != NULL); + + gc = g->gc; + uid = g->uid; + g_return_if_fail(gc != NULL && uid != 0); + + _qq_send_packet_buddy_auth(gc, uid, QQ_MY_AUTH_REJECT, reason); + g_free(g); +} // _qq_reject_add_request_real + +/*****************************************************************************/ +// we approve other's request of adding me as friend +void qq_approve_add_request_with_gc_and_uid(gc_and_uid * g) +{ + gint uid; + GaimConnection *gc; + + g_return_if_fail(g != NULL); + + gc = g->gc; + uid = g->uid; + g_return_if_fail(gc != NULL && uid != 0); + + _qq_send_packet_buddy_auth(gc, uid, QQ_MY_AUTH_APPROVE, NULL); + g_free(g); +} // qq_approve_add_request_with_gc_and_uid + +/*****************************************************************************/ +void qq_do_nothing_with_gc_and_uid(gc_and_uid * g, const gchar * msg) +{ + g_free(g); +} // qq_do_nothing_with_gc_and_uid + +/*****************************************************************************/ +// we reject other's request of adding me as friend +void qq_reject_add_request_with_gc_and_uid(gc_and_uid * g) +{ + gint uid; + gchar *msg1, *msg2; + GaimConnection *gc; + gc_and_uid *g2; + + g_return_if_fail(g != NULL); + + gc = g->gc; + uid = g->uid; + g_return_if_fail(gc != NULL && uid != 0); + + g_free(g); + + g2 = g_new0(gc_and_uid, 1); + g2->gc = gc; + g2->uid = uid; + + msg1 = g_strdup_printf(_("You rejected %d's request"), uid); + msg2 = g_strdup(_("Input your reason:")); + + gaim_request_input(gc, _("Reject request"), msg1, msg2, + _("Sorry, you are not my type..."), TRUE, FALSE, + NULL, _("Reject"), G_CALLBACK(_qq_reject_add_request_real), _("Cancel"), NULL, g2); + +} // qq_reject_add_request_with_gc_and_uid + +/*****************************************************************************/ +void qq_add_buddy_with_gc_and_uid(gc_and_uid * g) +{ + gint uid; + GaimConnection *gc; + + g_return_if_fail(g != NULL); + + gc = g->gc; + uid = g->uid; + g_return_if_fail(gc != NULL && uid != 0); + + _qq_send_packet_add_buddy(gc, uid); + g_free(g); +} // qq_add_buddy_with_gc_and_uid + +/*****************************************************************************/ +void qq_block_buddy_with_gc_and_uid(gc_and_uid * g) +{ + guint32 uid; + GaimConnection *gc; + GaimBuddy buddy; + GaimGroup group; + + g_return_if_fail(g != NULL); + + gc = g->gc; + uid = g->uid; + g_return_if_fail(gc != NULL && uid > 0); + + buddy.name = uid_to_gaim_name(uid); + group.name = GAIM_GROUP_QQ_BLOCKED; + + qq_remove_buddy(gc, &buddy, &group); + _qq_send_packet_remove_self_from(gc, uid); + +} // qq_block_buddy_with_gc_and_uid + +/*****************************************************************************/ +// process reply to add_buddy_auth request +void qq_process_add_buddy_auth_reply(guint8 * buf, gint buf_len, GaimConnection * gc) { + qq_data *qd; + gint len; + guint8 *data, *cursor, reply; + gchar **segments, *msg_utf8; + + g_return_if_fail(gc != NULL && gc->proto_data != NULL); + g_return_if_fail(buf != NULL && buf_len != 0); + + qd = (qq_data *) gc->proto_data; + len = buf_len; + data = g_newa(guint8, len); + cursor = data; + + if (qq_crypt(DECRYPT, buf, buf_len, qd->session_key, data, &len)) { + read_packet_b(data, &cursor, len, &reply); + if (reply != QQ_ADD_BUDDY_AUTH_REPLY_OK) { + gaim_debug(GAIM_DEBUG_WARNING, "QQ", "Add buddy with auth request fails\n"); + if (NULL == (segments = split_data(data, len, "\x1f", 2))) + return; + msg_utf8 = qq_to_utf8(segments[1], QQ_CHARSET_DEFAULT); + gaim_notify_error(gc, NULL, _("Add buddy with auth request fails"), msg_utf8); + g_free(msg_utf8); + } else + gaim_debug(GAIM_DEBUG_INFO, "QQ", "Add buddy with auth request OK\n"); + } else + gaim_debug(GAIM_DEBUG_ERROR, "QQ", "Error decrypt add buddy with auth reply\n"); + +} // qq_process_add_buddy_auth_reply + +/*****************************************************************************/ +// process the server reply for my request to remove a buddy +void qq_process_remove_buddy_reply(guint8 * buf, gint buf_len, GaimConnection * gc) { + qq_data *qd; + gint len; + guint8 *data, *cursor, reply; + + g_return_if_fail(gc != NULL && gc->proto_data != NULL); + g_return_if_fail(buf != NULL && buf_len != 0); + + qd = (qq_data *) gc->proto_data; + len = buf_len; + data = g_newa(guint8, len); + + if (qq_crypt(DECRYPT, buf, buf_len, qd->session_key, data, &len)) { + cursor = data; + read_packet_b(data, &cursor, len, &reply); + if (reply != QQ_REMOVE_BUDDY_REPLY_OK) + // there is no reason return from server + gaim_debug(GAIM_DEBUG_WARNING, "QQ", "Remove buddy fails\n"); + else { // if reply + gaim_debug(GAIM_DEBUG_INFO, "QQ", "Remove buddy OK\n"); + gaim_notify_info(gc, NULL, _("You have successfully removed a buddy"), NULL); + } + } else + gaim_debug(GAIM_DEBUG_ERROR, "QQ", "Error decrypt remove buddy reply\n"); + +} // qq_process_remove_buddy_reply + + +/*****************************************************************************/ +// process the server reply for my request to remove myself from a buddy +void qq_process_remove_self_reply(guint8 * buf, gint buf_len, GaimConnection * gc) { + qq_data *qd; + gint len; + guint8 *data, *cursor, reply; + + g_return_if_fail(gc != NULL && gc->proto_data != NULL); + g_return_if_fail(buf != NULL && buf_len != 0); + + qd = (qq_data *) gc->proto_data; + len = buf_len; + data = g_newa(guint8, len); + + if (qq_crypt(DECRYPT, buf, buf_len, qd->session_key, data, &len)) { + cursor = data; + read_packet_b(data, &cursor, len, &reply); + if (reply != QQ_REMOVE_SELF_REPLY_OK) + // there is no reason return from server + gaim_debug(GAIM_DEBUG_WARNING, "QQ", "Remove self fails\n"); + else { // if reply + gaim_debug(GAIM_DEBUG_INFO, "QQ", "Remove self from a buddy OK\n"); + gaim_notify_info(gc, NULL, _("You have successfully removed yourself from a buddy"), NULL); + } + } else + gaim_debug(GAIM_DEBUG_ERROR, "QQ", "Error decrypt remove self reply\n"); + +} // qq_process_remove_buddy_reply + +/*****************************************************************************/ +void qq_process_add_buddy_reply(guint8 * buf, gint buf_len, guint16 seq, GaimConnection * gc) { + qq_data *qd; + gint len, for_uid; + gchar *msg, *data, **segments, *uid, *reply; + GList *list; + GaimBuddy *b; + gc_and_uid *g; + qq_add_buddy_request *req; + + g_return_if_fail(gc != NULL && gc->proto_data != NULL); + g_return_if_fail(buf != NULL && buf_len != 0); + + for_uid = 0; + qd = (qq_data *) gc->proto_data; + len = buf_len; + + list = qd->add_buddy_request; + while (list != NULL) { + req = (qq_add_buddy_request *) list->data; + if (req->seq == seq) { // reply to this + for_uid = req->uid; + qd->add_buddy_request = g_list_remove(qd->add_buddy_request, qd->add_buddy_request->data); + g_free(req); + break; + } + list = list->next; + } // while list + + if (for_uid == 0) { // we have no record for this + gaim_debug(GAIM_DEBUG_ERROR, "QQ", "We have no record for add buddy reply [%d], discard\n", seq); + return; + } else + gaim_debug(GAIM_DEBUG_INFO, "QQ", "Add buddy reply [%d] is for id [%d]\n", seq, for_uid); + + data = g_newa(guint8, len); + + if (qq_crypt(DECRYPT, buf, buf_len, qd->session_key, data, &len)) { + if (NULL == (segments = split_data(data, len, "\x1f", 2))) + return; + uid = segments[0]; + reply = segments[1]; + if (strtol(uid, NULL, 10) != qd->uid) { // should not happen + gaim_debug(GAIM_DEBUG_ERROR, "QQ", "Add buddy reply is to [%s], not me!", uid); + g_strfreev(segments); + return; + } // if uid + + if (strtol(reply, NULL, 10) > 0) { // need auth + gaim_debug(GAIM_DEBUG_WARNING, "QQ", "Add buddy attempt fails, need authentication\n"); + b = gaim_find_buddy(gc->account, uid_to_gaim_name(for_uid)); + if (b != NULL) + gaim_blist_remove_buddy(b); + g = g_new0(gc_and_uid, 1); + g->gc = gc; + g->uid = for_uid; + msg = g_strdup_printf(_("User %d needs authentication"), for_uid); + gaim_request_input(gc, NULL, msg, + _("Input request here"), + _("Would you be my friend?"), + TRUE, FALSE, NULL, _("Send"), + G_CALLBACK + (_qq_send_packet_add_buddy_auth_with_gc_and_uid), + _("Cancel"), G_CALLBACK(qq_do_nothing_with_gc_and_uid), g); + g_free(msg); + } else { // add OK + qq_add_buddy_by_recv_packet(gc, for_uid, TRUE, TRUE); + msg = g_strdup_printf(_("You have added %d in buddy list"), for_uid); + gaim_notify_info(gc, NULL, msg, NULL); + g_free(msg); + } // if reply + g_strfreev(segments); + } else + gaim_debug(GAIM_DEBUG_ERROR, "QQ", "Error decrypt add buddy reply\n"); + +} // qq_process_add_buddy_reply + +/*****************************************************************************/ +GaimGroup *qq_get_gaim_group(const gchar * group_name) +{ + GaimGroup *g; + + g_return_val_if_fail(group_name != NULL, NULL); + + g = gaim_find_group(group_name); + if (g == NULL) { + g = gaim_group_new(group_name); + gaim_blist_add_group(g, NULL); + gaim_debug(GAIM_DEBUG_WARNING, "QQ", "Add new group: %s\n", group_name); + } // if (g == NULL) + + return g; +} // qq_get_gaim_group + +/*****************************************************************************/ +// we add new buddy, if the received packet is from someone not in my list +// return the GaimBuddy that is just created +GaimBuddy *qq_add_buddy_by_recv_packet(GaimConnection * gc, guint32 uid, gboolean is_known, gboolean create) { + GaimAccount *a; + GaimBuddy *b; + GaimGroup *g; + qq_data *qd; + qq_buddy *q_bud; + gchar *name, *group_name; + + g_return_val_if_fail(gc != NULL && gc->proto_data != NULL, NULL); + + a = gc->account; + qd = (qq_data *) gc->proto_data; + g_return_val_if_fail(a != NULL && uid != 0, NULL); + + group_name = is_known ? + g_strdup_printf(GAIM_GROUP_QQ_FORMAT, gaim_account_get_username(a)) : g_strdup(GAIM_GROUP_QQ_UNKNOWN); + + g = qq_get_gaim_group(group_name); + + name = uid_to_gaim_name(uid); + b = gaim_find_buddy(gc->account, name); + // remove old, we can not simply return here + // because there might be old local copy of this buddy + if (b != NULL) + gaim_blist_remove_buddy(b); + + b = gaim_buddy_new(a, name, NULL); + + if (!create) + b->proto_data = NULL; + else { + q_bud = g_new0(qq_buddy, 1); + q_bud->uid = uid; + b->proto_data = q_bud; + qd->buddies = g_list_append(qd->buddies, q_bud); + qq_send_packet_get_info(gc, q_bud->uid, FALSE); + qq_send_packet_get_buddies_online(gc, QQ_FRIENDS_ONLINE_POSITION_START); + } // if !create + + gaim_blist_add_buddy(b, NULL, g, NULL); + gaim_debug(GAIM_DEBUG_WARNING, "QQ", "Add new buddy: [%s]\n", name); + + g_free(name); + g_free(group_name); + + return b; +} // qq_add_buddy_by_recv_packet + +/*****************************************************************************/ +// add a buddy and send packet to QQ server +// note that when gaim load local cached buddy list into its blist +// it also calls this funtion, so we have to +// define qd->logged_in=TRUE AFTER serv_finish_login(gc) +void qq_add_buddy(GaimConnection * gc, GaimBuddy * buddy, GaimGroup * group) { + qq_data *qd; + guint32 uid; + GaimBuddy *b; + + g_return_if_fail(gc != NULL && gc->proto_data != NULL); + + qd = (qq_data *) gc->proto_data; + if (!qd->logged_in) + return; // IMPORTANT ! + + uid = gaim_name_to_uid(buddy->name); + if (uid > 0) + _qq_send_packet_add_buddy(gc, uid); + else { + b = gaim_find_buddy(gc->account, buddy->name); + if (b != NULL) + gaim_blist_remove_buddy(b); + gaim_notify_error(gc, NULL, + _("QQid Error"), + _("Invalid QQid, to add buddy 1234567, \nyou should input qq-1234567")); + } +} // _qq_add_buddy + +/*****************************************************************************/ +// remove a buddy and send packet to QQ server accordingly +void qq_remove_buddy(GaimConnection * gc, GaimBuddy * buddy, GaimGroup * group) { + qq_data *qd; + GaimBuddy *b; + qq_buddy *q_bud; + guint32 uid; + + g_return_if_fail(gc != NULL && gc->proto_data != NULL); + + qd = (qq_data *) gc->proto_data; + uid = gaim_name_to_uid(buddy->name); + + if (!qd->logged_in) + return; + + if (uid > 0) + _qq_send_packet_remove_buddy(gc, uid); + + b = gaim_find_buddy(gc->account, buddy->name); + if (b != NULL) { + q_bud = (qq_buddy *) b->proto_data; + if (q_bud != NULL) + qd->buddies = g_list_remove(qd->buddies, q_bud); + else + gaim_debug(GAIM_DEBUG_WARNING, "QQ", "We have no qq_buddy record for %s\n", buddy->name); + // remove buddy on blist, this does not trigger qq_remove_buddy again + // do this only if the request comes from block request, + // otherwise gaim segmentation fault + if (g_ascii_strcasecmp(group->name, GAIM_GROUP_QQ_BLOCKED) + == 0) + gaim_blist_remove_buddy(b); + } // if b != NULL +} // _qq_remove_buddy + +/*****************************************************************************/ +// free add buddy request queue +void qq_add_buddy_request_free(qq_data * qd) +{ + gint i; + qq_add_buddy_request *p; + + g_return_if_fail(qd != NULL); + + i = 0; + while (qd->add_buddy_request) { + p = (qq_add_buddy_request *) (qd->add_buddy_request->data); + qd->add_buddy_request = g_list_remove(qd->add_buddy_request, p); + g_free(p); + i++; + } + gaim_debug(GAIM_DEBUG_INFO, "QQ", "%d add buddy requests are freed!\n", i); +} // qq_add_buddy_request_free + +/*****************************************************************************/ +// free up all qq_buddy +void qq_buddies_list_free(GaimAccount *account, qq_data * qd) +{ + gint i; + qq_buddy *p; + gchar *name; + GaimBuddy *b; + + g_return_if_fail(qd != NULL); + + i = 0; + while (qd->buddies) { + p = (qq_buddy *) (qd->buddies->data); + qd->buddies = g_list_remove(qd->buddies, p); + // added by gfhuang, for relogin crash bug + name = uid_to_gaim_name(p->uid); + b = gaim_find_buddy(account, name); + if(b != NULL) b->proto_data = NULL; + else { + gaim_debug(GAIM_DEBUG_INFO, "QQ", "qq_buddy %s not found in gaim proto_data\n", name); + } + g_free(name); + + g_free(p); + i++; + } + gaim_debug(GAIM_DEBUG_INFO, "QQ", "%d qq_buddy structures are freed!\n", i); + +} // qq_buddies_list_free + +/*****************************************************************************/ +// END OF FILE diff -r 5642f4658b59 -r 983fd420e86b src/protocols/qq/buddy_opt.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/protocols/qq/buddy_opt.h Mon Jun 26 02:58:54 2006 +0000 @@ -0,0 +1,65 @@ +/** +* The QQ2003C protocol plugin + * + * for gaim + * + * Copyright (C) 2004 Puzzlebird + * + * 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 + */ + +// START OF FILE +/*****************************************************************************/ +#ifndef _QQ_BUDDY_OPT_H_ +#define _QQ_BUDDY_OPT_H_ + +#include +#include "connection.h" // GaimConnection + +#include "qq.h" // qq_buddy + +typedef struct _gc_and_uid gc_and_uid; + +struct _gc_and_uid { + guint32 uid; + GaimConnection *gc; +}; + +void qq_approve_add_request_with_gc_and_uid(gc_and_uid * g); + +void qq_reject_add_request_with_gc_and_uid(gc_and_uid * g); + +void qq_add_buddy_with_gc_and_uid(gc_and_uid * g); + +void qq_block_buddy_with_gc_and_uid(gc_and_uid * g); + +void qq_do_nothing_with_gc_and_uid(gc_and_uid * g, const gchar * msg); + +void qq_process_remove_buddy_reply(guint8 * buf, gint buf_len, GaimConnection * gc); +void qq_process_remove_self_reply(guint8 * buf, gint buf_len, GaimConnection * gc); +void qq_process_add_buddy_reply(guint8 * buf, gint buf_len, guint16 seq, GaimConnection * gc); +void qq_process_add_buddy_auth_reply(guint8 * buf, gint buf_len, GaimConnection * gc); +GaimBuddy *qq_add_buddy_by_recv_packet(GaimConnection * gc, guint32 uid, gboolean is_known, gboolean create); +void qq_add_buddy(GaimConnection * gc, GaimBuddy * buddy, GaimGroup * group); +GaimGroup *qq_get_gaim_group(const gchar * group_name); + +void qq_remove_buddy(GaimConnection * gc, GaimBuddy * buddy, GaimGroup * group); +void qq_add_buddy_request_free(qq_data * qd); + +void qq_buddies_list_free(GaimAccount *account, qq_data * qd); + +#endif +/*****************************************************************************/ +// END OF FILE diff -r 5642f4658b59 -r 983fd420e86b src/protocols/qq/buddy_status.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/protocols/qq/buddy_status.c Mon Jun 26 02:58:54 2006 +0000 @@ -0,0 +1,272 @@ +/** + * The QQ2003C protocol plugin + * + * for gaim + * + * Copyright (C) 2004 Puzzlebird + * + * 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 + */ + +// START OF FILE +/*****************************************************************************/ +#include // g_memmove +#include "debug.h" // gaim_debug +#include "prefs.h" // gaim_prefs_get_bool + +#include "utils.h" // get_ip_str +#include "packet_parse.h" // create_packet, +#include "buddy_status.h" +#include "crypt.h" // qq_crypt.h +#include "header_info.h" // cmd alias +#include "keep_alive.h" // qq_update_buddy_contact +#include "send_core.h" // qq_send_cmd + +#define QQ_MISC_STATUS_HAVING_VIIDEO 0x00000001 + +#define QQ_ICON_SUFFIX_DEFAULT QQ_ICON_SUFFIX_OFFLINE +#define QQ_CHANGE_ONLINE_STATUS_REPLY_OK 0x30 // ASCii value of "0" + +enum { + QQ_ICON_SUFFIX_NORMAL = 1, + QQ_ICON_SUFFIX_OFFLINE = 2, + QQ_ICON_SUFFIX_AWAY = 3, +}; + +/*****************************************************************************/ +static void _qq_buddy_status_dump_unclear(qq_buddy_status * s) +{ + GString *dump; + + g_return_if_fail(s != NULL); + + dump = g_string_new(""); + g_string_append_printf(dump, "unclear fields for [%d]:\n", s->uid); + g_string_append_printf(dump, "004: %02x (unknown)\n", s->unknown1); + g_string_append_printf(dump, "011: %02x (unknown)\n", s->unknown2); + gaim_debug(GAIM_DEBUG_INFO, "QQ", "Buddy status entry, %s", dump->str); + g_string_free(dump, TRUE); +} // _qq_buddy_status_dump_unclear + +/*****************************************************************************/ +// parse the data into qq_buddy_status +static gint _qq_buddy_status_read(guint8 * data, guint8 ** cursor, gint len, qq_buddy_status * s) { + gint bytes; + + g_return_val_if_fail(data != NULL && *cursor != NULL && s != NULL, -1); + + bytes = 0; + + // 000-003: uid + bytes += read_packet_dw(data, cursor, len, &s->uid); + // 004-004: 0x01 + bytes += read_packet_b(data, cursor, len, &s->unknown1); + // 005-008: ip + s->ip = g_new0(guint8, 4); + bytes += read_packet_data(data, cursor, len, s->ip, 4); + // 009-010: port + bytes += read_packet_w(data, cursor, len, &s->port); + // 011-011: 0x00 + bytes += read_packet_b(data, cursor, len, &s->unknown2); + // 012-012: status + bytes += read_packet_b(data, cursor, len, &s->status); + // 013-014: client_version + bytes += read_packet_w(data, cursor, len, &s->client_version); + // 015-030: unknown key + s->unknown_key = g_new0(guint8, QQ_KEY_LENGTH); + bytes += read_packet_data(data, cursor, len, s->unknown_key, QQ_KEY_LENGTH); + + if (s->uid == 0 || bytes != 31) + return -1; + + return bytes; + +} // _qq_buddy_status_read + +/*****************************************************************************/ +// check if status means online or offline +gboolean is_online(guint8 status) +{ +// return (status == QQ_BUDDY_ONLINE_NORMAL ? TRUE : (status == QQ_BUDDY_ONLINE_AWAY ? TRUE : FALSE)); + //rewritten by gfhuang + switch(status) { + case QQ_BUDDY_ONLINE_NORMAL: + case QQ_BUDDY_ONLINE_AWAY: + case QQ_BUDDY_ONLINE_INVISIBLE: + return TRUE; + case QQ_BUDDY_ONLINE_OFFLINE: + return FALSE; + } + return FALSE; +} // is_online + +/*****************************************************************************/ +// the icon suffix is detemined by status +// although QQ server may return the right icon, I set it here myself +gchar get_suffix_from_status(guint8 status) +{ + switch (status) { + case QQ_BUDDY_ONLINE_NORMAL: + return QQ_ICON_SUFFIX_NORMAL; + case QQ_BUDDY_ONLINE_AWAY: + return QQ_ICON_SUFFIX_AWAY; + case QQ_BUDDY_ONLINE_INVISIBLE: + case QQ_BUDDY_ONLINE_OFFLINE: + return QQ_ICON_SUFFIX_OFFLINE; + default: + return QQ_ICON_SUFFIX_DEFAULT; + } // switch +} // get_suffix_from_status + +/*****************************************************************************/ +// send a packet to change my online status +void qq_send_packet_change_status(GaimConnection * gc) +{ + qq_data *qd; + guint8 *raw_data, *cursor, away_cmd; + guint32 misc_status; + gboolean fake_video; + + g_return_if_fail(gc != NULL && gc->proto_data != NULL); + + qd = (qq_data *) gc->proto_data; + if (!qd->logged_in) + return; + + switch (qd->status) { + case QQ_SELF_STATUS_AVAILABLE: + away_cmd = QQ_BUDDY_ONLINE_NORMAL; + break; + case QQ_SELF_STATUS_INVISIBLE: + away_cmd = QQ_BUDDY_ONLINE_INVISIBLE; + break; + case QQ_SELF_STATUS_AWAY: + case QQ_SELF_STATUS_IDLE: + case QQ_SELF_STATUS_CUSTOM: + away_cmd = QQ_BUDDY_ONLINE_AWAY; + break; + default: + away_cmd = QQ_BUDDY_ONLINE_NORMAL; + } // switch + + raw_data = g_new0(guint8, 5); + cursor = raw_data; + misc_status = 0x00000000; + + fake_video = gaim_prefs_get_bool("/plugins/prpl/qq/show_fake_video"); + if (fake_video) + misc_status |= QQ_MISC_STATUS_HAVING_VIIDEO; + + create_packet_b(raw_data, &cursor, away_cmd); + create_packet_dw(raw_data, &cursor, misc_status); + + qq_send_cmd(gc, QQ_CMD_CHANGE_ONLINE_STATUS, TRUE, 0, TRUE, raw_data, 5); + + g_free(raw_data); +} // qq_send_packet_change_status + +/*****************************************************************************/ +// parse the reply packet for change_status +void qq_process_change_status_reply(guint8 * buf, gint buf_len, GaimConnection * gc) { + qq_data *qd; + gint len; + guint8 *data, *cursor, reply; + + g_return_if_fail(gc != NULL && gc->proto_data != NULL); + g_return_if_fail(buf != NULL && buf_len != 0); + + qd = (qq_data *) gc->proto_data; + len = buf_len; + data = g_newa(guint8, len); + + if (qq_crypt(DECRYPT, buf, buf_len, qd->session_key, data, &len)) { + cursor = data; + read_packet_b(data, &cursor, len, &reply); + if (reply != QQ_CHANGE_ONLINE_STATUS_REPLY_OK) { + gaim_debug(GAIM_DEBUG_WARNING, "QQ", "Change status fail\n"); + } else + gaim_debug(GAIM_DEBUG_INFO, "QQ", "Change status OK\n"); + } else + gaim_debug(GAIM_DEBUG_ERROR, "QQ", "Error decrypt chg status reply\n"); + +} // qq_process_change_status_reply + +/*****************************************************************************/ +// it is a server message +// indicating that one of my buddies changes its status +void qq_process_friend_change_status(guint8 * buf, gint buf_len, GaimConnection * gc) { + qq_data *qd; + gint len, bytes; + guint32 my_uid; + guint8 *data, *cursor; + GaimBuddy *b; + qq_buddy *q_bud; + qq_buddy_status *s; + gchar *name; + + g_return_if_fail(gc != NULL && gc->proto_data != NULL); + g_return_if_fail(buf != NULL && buf_len != 0); + + qd = (qq_data *) gc->proto_data; + len = buf_len; + data = g_newa(guint8, len); + cursor = data; + + if (qq_crypt(DECRYPT, buf, buf_len, qd->session_key, data, &len)) { + s = g_new0(qq_buddy_status, 1); + bytes = 0; + // 000-030: qq_buddy_status; + bytes += _qq_buddy_status_read(data, &cursor, len, s); + // 031-034: my uid + bytes += read_packet_dw(data, &cursor, len, &my_uid); + + if (my_uid == 0 || bytes != 35) { + gaim_debug(GAIM_DEBUG_ERROR, "QQ", "my_uid == 0 || bytes(%d) != 35\n", bytes); + g_free(s->ip); + g_free(s->unknown_key); + g_free(s); + return; + } +// if (QQ_DEBUG) gfhuang +// _qq_buddy_status_dump_unclear(s); + + name = uid_to_gaim_name(s->uid); //by gfhuang + b = gaim_find_buddy(gc->account, name); + g_free(name); + q_bud = (b == NULL) ? NULL : (qq_buddy *) b->proto_data; + if (q_bud) { + gaim_debug(GAIM_DEBUG_INFO, "QQ", "s->uid = %d, q_bud->uid = %d\n", s->uid , q_bud->uid); + if(0 != *((guint32 *)s->ip)) { //by gfhuang + g_memmove(q_bud->ip, s->ip, 4); + q_bud->port = s->port; + } + q_bud->status = s->status; + if(0 != s->client_version) + q_bud->client_version = s->client_version; //gfhuang + qq_update_buddy_contact(gc, q_bud); + } + else + gaim_debug(GAIM_DEBUG_ERROR, "QQ", "got information of unknown buddy by gfhuang %d\n", s->uid); + + g_free(s->ip); + g_free(s->unknown_key); + g_free(s); + } else + gaim_debug(GAIM_DEBUG_ERROR, "QQ", "Error decrypt buddy status change packet\n"); + +} // qq_process_friend_change_status + +/*****************************************************************************/ +// END OF FILE diff -r 5642f4658b59 -r 983fd420e86b src/protocols/qq/buddy_status.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/protocols/qq/buddy_status.h Mon Jun 26 02:58:54 2006 +0000 @@ -0,0 +1,70 @@ +/** +* The QQ2003C protocol plugin + * + * for gaim + * + * Copyright (C) 2004 Puzzlebird + * + * 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 + * + */ + +// START OF FILE +/*****************************************************************************/ +#ifndef _QQ_BUDDY_STATUS_H_ +#define _QQ_BUDDY_STATUS_H_ + +#include +#include "connection.h" // GaimConnection +#include "qq.h" // QQ_KEY_LENGTH + +typedef struct _qq_buddy_status { + guint32 uid; + guint8 unknown1; + guint8 *ip; + guint16 port; + guint8 unknown2; + guint8 status; + guint16 client_version; + guint8 *unknown_key; +} qq_buddy_status; + +enum { + QQ_BUDDY_OFFLINE = 0x00, // by gfhuang + QQ_BUDDY_ONLINE_NORMAL = 0x0a, //10 + QQ_BUDDY_ONLINE_OFFLINE = 0x14, //20 + QQ_BUDDY_ONLINE_AWAY = 0x1e, //30 + QQ_BUDDY_ONLINE_INVISIBLE = 0x28, // 40 not 0x40!, bug by gfhuang +}; + +enum { + QQ_SELF_STATUS_AVAILABLE = 0x11, // I determined value + QQ_SELF_STATUS_AWAY = 0x12, + QQ_SELF_STATUS_INVISIBLE = 0x13, + QQ_SELF_STATUS_CUSTOM = 0x14, + QQ_SELF_STATUS_IDLE = 0x15, +}; + +gboolean is_online(guint8 status); + +gchar get_suffix_from_status(guint8 status); + +void qq_send_packet_change_status(GaimConnection * gc); + +void qq_process_change_status_reply(guint8 * buf, gint buf_len, GaimConnection * gc); +void qq_process_friend_change_status(guint8 * buf, gint buf_len, GaimConnection * gc); +#endif +/*****************************************************************************/ +// END OF FILE diff -r 5642f4658b59 -r 983fd420e86b src/protocols/qq/char_conv.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/protocols/qq/char_conv.c Mon Jun 26 02:58:54 2006 +0000 @@ -0,0 +1,285 @@ +/** + * The QQ2003C protocol plugin + * + * for gaim + * + * Copyright (C) 2004 Puzzlebird + * + * 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 + */ + +// START OF FILE +/*****************************************************************************/ +#include "debug.h" // gaim_debug +#include "internal.h" // strlen +// #include + +#include "utils.h" // hex_dump_to_str +#include "packet_parse.h" // read_packet +#include "char_conv.h" +#include "qq.h" // QQ_CHARSET_DEFAULT + +#define QQ_SMILEY_AMOUNT 96 + +#define UTF8 "UTF-8" +#define QQ_CHARSET_ZH_CN "GBK" +#define QQ_CHARSET_ENG "ISO-8859-1" + +#define QQ_NULL_MSG "(NULL)" // return this if conversion fail +#define QQ_NULL_SMILEY "(SM)" // return this if smiley conversion fails + +// a debug function +void _qq_show_packet(gchar * desc, gchar * buf, gint len); + +const gchar qq_smiley_map[QQ_SMILEY_AMOUNT] = { + 0x41, 0x43, 0x42, 0x44, 0x45, 0x46, 0x47, 0x48, + 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x73, + 0x74, 0x75, 0x76, 0x77, 0x8a, 0x8b, 0x8c, 0x8d, + 0x8e, 0x8f, 0x78, 0x79, 0x7a, 0x7b, 0x90, 0x91, + 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, + 0x59, 0x5a, 0x5c, 0x58, 0x57, 0x55, 0x7c, 0x7d, + 0x7e, 0x7f, 0x9a, 0x9b, 0x60, 0x67, 0x9c, 0x9d, + 0x9e, 0x5e, 0x9f, 0x89, 0x80, 0x81, 0x82, 0x62, + 0x63, 0x64, 0x65, 0x66, 0x83, 0x68, 0x84, 0x85, + 0x86, 0x87, 0x6b, 0x6e, 0x6f, 0x70, 0x88, 0xa0, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x56, 0x5b, 0x5d, + 0x5f, 0x61, 0x69, 0x6a, 0x6c, 0x6d, 0x71, 0x72, +}; + + +// change from \\ to / by gfhuang, for gaim2beta2 +// change the pixmaps/smiley/theme file as well +const gchar *gaim_smiley_map[QQ_SMILEY_AMOUNT] = { + "/jy", "/pz", "/se", "/fd", "/dy", "/ll", "/hx", "/bz", + "/shui", "/dk ", "/gg", "/fn", "/tp", "/cy", "/wx", "/ng", + "/kuk", "/feid", "/zk", "/tu", "/tx", "/ka", "/by", "/am", + "/jie", "/kun", "/jk", "/lh", "/hanx", "/db", "/fendou", + "/zhm", + "/yiw", "/xu", "/yun", "/zhem", "/shuai", "/kl", "/qiao", + "/zj", + "/shan", "/fad", "/aiq", "/tiao", "/zhao", "/mm", "/zt", + "/maom", + "/xg", "/yb", "/qianc", "/dp", "/bei", "/dg", "/shd", + "/zhd", + "/dao", "/zq", "/yy", "/bb", "/gf", "/fan", "/yw", "/mg", + "/dx", "/wen", "/xin", "/xs", "/hy", "/lw", "/dh", "/sj", + "/yj", "/ds", "/ty", "/yl", "/qiang", "/ruo", "/ws", + "/shl", + "/dd", "/mn", "/hl", "/mamao", "/qz", "/fw", "/oh", "/bj", + "/qsh", "/xig", "/xy", "/duoy", "/xr", "/xixing", "/nv", + "/nan" +}; + +/*****************************************************************************/ +// these functions parses font-attr +static gchar _get_size(gchar font_attr) +{ + return font_attr & 0x1f; +} + +static gboolean _check_bold(gchar font_attr) +{ + return (font_attr & 0x20) ? TRUE : FALSE; +} + +static gboolean _check_italic(gchar font_attr) +{ + return (font_attr & 0x40) ? TRUE : FALSE; +} + +static gboolean _check_underline(gchar font_attr) +{ + return (font_attr & 0x80) ? TRUE : FALSE; +} + +/*****************************************************************************/ +// convert a string from from_charset to to_charset, using g_convert +static gchar *_my_convert(const gchar * str, gssize len, const gchar * to_charset, const gchar * from_charset) { + + GError *error = NULL; + gchar *ret; + gsize byte_read, byte_write; + + g_return_val_if_fail(str != NULL && to_charset != NULL && from_charset != NULL, g_strdup(QQ_NULL_MSG)); + + ret = g_convert(str, len, to_charset, from_charset, &byte_read, &byte_write, &error); + + if (error == NULL) + return ret; // conversion is OK + else { // conversion error + gaim_debug(GAIM_DEBUG_ERROR, "QQ", "%s\n", error->message); + gaim_debug(GAIM_DEBUG_WARNING, "QQ", + "Dump failed text\n%s", hex_dump_to_str(str, (len == -1) ? strlen(str) : len)); + g_error_free(error); + return g_strdup(QQ_NULL_MSG); + } // if error +} // _my_convert + +/*****************************************************************************/ +// take the input as a pascal string and return a converted c-string in UTF-8 +// returns the number of bytes read, return -1 if fatal error +// the converted UTF-8 will be save in ret, +gint convert_as_pascal_string(guint8 * data, gchar ** ret, const gchar * from_charset) { + guint8 len; + + g_return_val_if_fail(data != NULL && from_charset != NULL, -1); + + len = data[0]; + *ret = _my_convert(data + 1, (gssize) len, UTF8, from_charset); + + return len + 1; +} // convert_as_pascal_string + +/*****************************************************************************/ +// convert QQ formatted msg to GAIM formatted msg (and UTF-8) +gchar *qq_encode_to_gaim(guint8 * data, gint len, const gchar * msg) +{ + GString *encoded; + guint8 font_attr, font_size, color[3], bar, *cursor; + gboolean is_bold, is_italic, is_underline; + guint16 charset_code; + gchar *font_name, *color_code, *msg_utf8, *ret; + + cursor = data; + _qq_show_packet("QQ_MESG recv for font style", data, len); + + read_packet_b(data, &cursor, len, &font_attr); + read_packet_data(data, &cursor, len, color, 3); // red,green,blue + color_code = g_strdup_printf("#%02x%02x%02x", color[0], color[1], color[2]); + + read_packet_b(data, &cursor, len, &bar); // skip, not sure of its use + read_packet_w(data, &cursor, len, &charset_code); + + font_name = g_strndup(cursor, data + len - cursor); + + font_size = _get_size(font_attr); + is_bold = _check_bold(font_attr); + is_italic = _check_italic(font_attr); + is_underline = _check_underline(font_attr); + + // although there is charset returned from QQ msg, it is can not be used + // for example, if a user send a Chinese message from English windows + // the charset_code in QQ msg is 0x0000, not 0x8602 + // therefore, it is better to use uniform conversion. + // by default, we use GBK, which includes all character of SC, TC, and EN + msg_utf8 = qq_to_utf8(msg, QQ_CHARSET_DEFAULT); + encoded = g_string_new(""); + + // Henry: The range QQ sends rounds from 8 to 22, where a font size + // of 10 is equal to 3 in html font tag + g_string_append_printf(encoded, + "", + color_code, font_name, font_size / 3); + gaim_debug(GAIM_DEBUG_INFO, "QQ_MESG", + "recv \n", + color_code, font_name, font_size / 3); + g_string_append(encoded, msg_utf8); + + if (is_bold) { + g_string_prepend(encoded, ""); + g_string_append(encoded, ""); + } + if (is_italic) { + g_string_prepend(encoded, ""); + g_string_append(encoded, ""); + } + if (is_underline) { + g_string_prepend(encoded, ""); + g_string_append(encoded, ""); + } + + g_string_append(encoded, ""); + ret = encoded->str; + + g_free(msg_utf8); + g_free(font_name); + g_free(color_code); + g_string_free(encoded, FALSE); + + return ret; +} // qq_encode_to_gaim + +/*****************************************************************************/ +// two convenient methods, using _my_convert +gchar *utf8_to_qq(const gchar * str, const gchar * to_charset) +{ + return _my_convert(str, -1, to_charset, UTF8); +} // utf8_to_qq + +gchar *qq_to_utf8(const gchar * str, const gchar * from_charset) +{ + return _my_convert(str, -1, UTF8, from_charset); +} // qq_to_utf8 + +/*****************************************************************************/ +// QQ uses binary code for smiley, while gaim uses strings. +// there is a mapping relations between these two. +gchar *qq_smiley_to_gaim(gchar * text) +{ + gint index; + gchar qq_smiley, *cur_seg, **segments, *ret; + GString *converted; + + converted = g_string_new(""); + segments = split_data(text, strlen(text), "\x14", 0); + g_string_append(converted, segments[0]); + + while ((*(++segments)) != NULL) { + cur_seg = *segments; + qq_smiley = cur_seg[0]; + for (index = 0; index < QQ_SMILEY_AMOUNT; index++) + if (qq_smiley_map[index] == qq_smiley) + break; + if (index >= QQ_SMILEY_AMOUNT) + g_string_append(converted, QQ_NULL_SMILEY); + else { + g_string_append(converted, gaim_smiley_map[index]); + g_string_append(converted, (cur_seg + 1)); + } // if index + } // while + + ret = converted->str; + g_string_free(converted, FALSE); + return ret; +} // qq_smiley_to_gaim + +/*****************************************************************************/ +// convert smiley from gaim style to qq binary code +gchar *gaim_smiley_to_qq(gchar * text) +{ + gchar *begin, *cursor, *ret; + gint index; + GString *converted; + + converted = g_string_new(text); + + for (index = 0; index < QQ_SMILEY_AMOUNT; index++) { + begin = cursor = converted->str; + while ((cursor = g_strstr_len(cursor, -1, gaim_smiley_map[index]))) { + g_string_erase(converted, (cursor - begin), strlen(gaim_smiley_map[index])); + g_string_insert_c(converted, (cursor - begin), 0x14); + g_string_insert_c(converted, (cursor - begin + 1), qq_smiley_map[index]); + cursor++; + } // while + } // for + g_string_append_c(converted, 0x20); // important for last smiiley + + ret = converted->str; + g_string_free(converted, FALSE); + return ret; +} // gaim_smiley_to_qq + +/*****************************************************************************/ +// END OF FILE diff -r 5642f4658b59 -r 983fd420e86b src/protocols/qq/char_conv.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/protocols/qq/char_conv.h Mon Jun 26 02:58:54 2006 +0000 @@ -0,0 +1,46 @@ +/** + * The QQ2003C protocol plugin + * + * for gaim + * + * Copyright (C) 2004 Puzzlebird + * + * 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 + */ + +// START OF FILE +/*****************************************************************************/ +#ifndef _QQ_CHAR_CONV_H_ +#define _QQ_CHAR_CONV_H_ + +#include + +#define QQ_CHARSET_DEFAULT "GBK" + +gint convert_as_pascal_string(guint8 * data, gchar ** ret, const gchar * from_charset); + +gchar *qq_smiley_to_gaim(gchar * text); + +gchar *gaim_smiley_to_qq(gchar * text); + +gchar *utf8_to_qq(const gchar * str, const gchar * to_charset); +gchar *qq_to_utf8(const gchar * str, const gchar * from_charset); +gchar *qq_encode_to_gaim(guint8 * font_attr_data, gint len, const gchar * msg); + +gchar *qq_im_filter_html(const gchar * text); + +#endif +/*****************************************************************************/ +// END OF FILE diff -r 5642f4658b59 -r 983fd420e86b src/protocols/qq/crypt.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/protocols/qq/crypt.c Mon Jun 26 02:58:54 2006 +0000 @@ -0,0 +1,289 @@ +/** + * The QQ2003C protocol plugin + * + * for gaim + * + * Copyright (C) 2004 Puzzlebird + * + * 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 + * + * + * OICQ encryption algorithm + * Convert from ASM code provided by PerlOICQ + * + * Puzzlebird, Nov-Dec 2002 + */ + +// START OF FILE +/*****************************************************************************/ +/*Notes: (OICQ uses 0x10 iterations, and modified something...) + +IN : 64 bits of data in v[0] - v[1]. +OUT: 64 bits of data in w[0] - w[1]. +KEY: 128 bits of key in k[0] - k[3]. + +delta is chosen to be the real part of +the golden ratio: Sqrt(5/4) - 1/2 ~ 0.618034 multiplied by 2^32. + +0x61C88647 is what we can track on the ASM codes.!! +*/ + +#ifndef _WIN32 +#include +#else +#include "win32dep.h" +#endif + +#include + +#include "crypt.h" +#include "debug.h" // gaim_debug, by gfhuang + +/*****************************************************************************/ +static void qq_encipher(unsigned long *const v, const unsigned long *const k, unsigned long *const w) +{ + register unsigned long y = ntohl(v[0]), z = ntohl(v[1]), a = ntohl(k[0]), b = ntohl(k[1]), c = ntohl(k[2]), d = ntohl(k[3]), n = 0x10, sum = 0, delta = 0x9E3779B9; /* 0x9E3779B9 - 0x100000000 = -0x61C88647 */ + + while (n-- > 0) { + sum += delta; + y += ((z << 4) + a) ^ (z + sum) ^ ((z >> 5) + b); + z += ((y << 4) + c) ^ (y + sum) ^ ((y >> 5) + d); + } // while + + w[0] = htonl(y); + w[1] = htonl(z); +} // qq_enciper + +/*****************************************************************************/ +static void qq_decipher(unsigned long *const v, const unsigned long *const k, unsigned long *const w) +{ + register unsigned long y = ntohl(v[0]), z = ntohl(v[1]), a = ntohl(k[0]), b = ntohl(k[1]), c = ntohl(k[2]), d = ntohl(k[3]), n = 0x10, sum = 0xE3779B90, // why this ? must be related with n value + delta = 0x9E3779B9; + + /* sum = delta<<5, in general sum = delta * n */ + while (n-- > 0) { + z -= ((y << 4) + c) ^ (y + sum) ^ ((y >> 5) + d); + y -= ((z << 4) + a) ^ (z + sum) ^ ((z >> 5) + b); + sum -= delta; + } + + w[0] = htonl(y); + w[1] = htonl(z); +} // qq_decipher + +/******************************************************************** + * encrypt part + *******************************************************************/ + +static void qq_encrypt(unsigned char *instr, int instrlen, unsigned char *key, unsigned char *outstr, int *outstrlen_prt) +{ + unsigned char plain[8], // plain text buffer + plain_pre_8[8], // plain text buffer, previous 8 bytes + *crypted, // crypted text + *crypted_pre_8, // crypted test, previous 8 bytes + *inp; // current position in instr + int pos_in_byte = 1, // loop in the byte + is_header = 1, // header is one byte + count = 0, // number of bytes being crypted + padding = 0; // number of padding stuff + + int rand(void) { // it can be the real random seed function + return 0xdead; + } // override with number, convenient for debug + + /*** we encrypt every eight byte ***/ + void encrypt_every_8_byte(void) { + for (pos_in_byte = 0; pos_in_byte < 8; pos_in_byte++) { + if (is_header) { + plain[pos_in_byte] ^= plain_pre_8[pos_in_byte]; + } else { + plain[pos_in_byte] ^= crypted_pre_8[pos_in_byte]; + } + } // prepare plain text + qq_encipher((unsigned long *) plain, (unsigned long *) key, (unsigned long *) crypted); // encrypt it + + for (pos_in_byte = 0; pos_in_byte < 8; pos_in_byte++) { + crypted[pos_in_byte] ^= plain_pre_8[pos_in_byte]; + } + memcpy(plain_pre_8, plain, 8); // prepare next + + crypted_pre_8 = crypted; // store position of previous 8 byte + crypted += 8; // prepare next output + count += 8; // outstrlen increase by 8 + pos_in_byte = 0; // back to start + is_header = 0; // and exit header + } // encrypt_every_8_byte + + pos_in_byte = (instrlen + 0x0a) % 8; // header padding decided by instrlen + if (pos_in_byte) { + pos_in_byte = 8 - pos_in_byte; + } + plain[0] = (rand() & 0xf8) | pos_in_byte; + + memset(plain + 1, rand() & 0xff, pos_in_byte++); + memset(plain_pre_8, 0x00, sizeof(plain_pre_8)); + + crypted = crypted_pre_8 = outstr; + + padding = 1; // pad some stuff in header + while (padding <= 2) { // at most two byte + if (pos_in_byte < 8) { + plain[pos_in_byte++] = rand() & 0xff; + padding++; + } + if (pos_in_byte == 8) { + encrypt_every_8_byte(); + } + } + + inp = instr; + while (instrlen > 0) { + if (pos_in_byte < 8) { + plain[pos_in_byte++] = *(inp++); + instrlen--; + } + if (pos_in_byte == 8) { + encrypt_every_8_byte(); + } + } + + padding = 1; // pad some stuff in tailer + while (padding <= 7) { // at most sever byte + if (pos_in_byte < 8) { + plain[pos_in_byte++] = 0x00; + padding++; + } + if (pos_in_byte == 8) { + encrypt_every_8_byte(); + } + } + + *outstrlen_prt = count; +} // qq_encrypt + + +/******************************************************************** + * [decrypt part] + * return 0 if failed, otherwise return 1 + ********************************************************************/ + +static int qq_decrypt(unsigned char *instr, int instrlen, unsigned char *key, unsigned char *outstr, int *outstrlen_ptr) +{ + unsigned char decrypted[8], m[8], *crypt_buff, *crypt_buff_pre_8, *outp; + int count, context_start, pos_in_byte, padding; + + int decrypt_every_8_byte(void) { + for (pos_in_byte = 0; pos_in_byte < 8; pos_in_byte++) { + if (context_start + pos_in_byte >= instrlen) + return 1; + decrypted[pos_in_byte] ^= crypt_buff[pos_in_byte]; + } + qq_decipher((unsigned long *) decrypted, (unsigned long *) key, (unsigned long *) decrypted); + + context_start += 8; + crypt_buff += 8; + pos_in_byte = 0; + return 1; + } // decrypt_every_8_byte + + // at least 16 bytes and %8 == 0 + if ((instrlen % 8) || (instrlen < 16)) { + //debug info by gfhuang + gaim_debug(GAIM_DEBUG_ERROR, "QQ", "Packet len is not times of 8 bytes, read %d bytes\n", instrlen); + return 0; + } + // get information from header + qq_decipher((unsigned long *) instr, (unsigned long *) key, (unsigned long *) decrypted); + pos_in_byte = decrypted[0] & 0x7; + count = instrlen - pos_in_byte - 10; // this is the plaintext length + // return if outstr buffer is not large enought or error plaintext length + if (*outstrlen_ptr < count || count < 0) { + gaim_debug(GAIM_DEBUG_ERROR, "QQ", "Buffer len %d is less than real len %d", *outstrlen_ptr, count); + return 0; + } + + memset(m, 0, 8); + crypt_buff_pre_8 = m; + *outstrlen_ptr = count; // everything is ok! set return string length + + crypt_buff = instr + 8; // address of real data start + context_start = 8; // context is at the second 8 byte + pos_in_byte++; // start of paddng stuff + + padding = 1; // at least one in header + while (padding <= 2) { // there are 2 byte padding stuff in header + if (pos_in_byte < 8) { // bypass the padding stuff, none sense data + pos_in_byte++; + padding++; + } + if (pos_in_byte == 8) { + crypt_buff_pre_8 = instr; + if (!decrypt_every_8_byte()) { + gaim_debug(GAIM_DEBUG_ERROR, "QQ", "decrypt every 8 bytes error A"); + return 0; + } + } + } // while + + outp = outstr; + while (count != 0) { + if (pos_in_byte < 8) { + *outp = crypt_buff_pre_8[pos_in_byte] ^ decrypted[pos_in_byte]; + outp++; + count--; + pos_in_byte++; + } + if (pos_in_byte == 8) { + crypt_buff_pre_8 = crypt_buff - 8; + if (!decrypt_every_8_byte()) { + gaim_debug(GAIM_DEBUG_ERROR, "QQ", "decrypt every 8 bytes error B"); + return 0; + } + } + } // while + + for (padding = 1; padding < 8; padding++) { + if (pos_in_byte < 8) { + if (crypt_buff_pre_8[pos_in_byte] ^ decrypted[pos_in_byte]) + return 0; + pos_in_byte++; + } + if (pos_in_byte == 8) { + crypt_buff_pre_8 = crypt_buff; + if (!decrypt_every_8_byte()) { + gaim_debug(GAIM_DEBUG_ERROR, "QQ", "decrypt every 8 bytes error C"); + return 0; + } + } + } // for + return 1; +} // qq_decrypt + +/*****************************************************************************/ +/* This is the Public Function */ +// return 1 is succeed, otherwise return 0 +int qq_crypt(unsigned char flag, + unsigned char *instr, int instrlen, unsigned char *key, unsigned char *outstr, int *outstrlen_ptr) +{ + if (flag == DECRYPT) + return qq_decrypt(instr, instrlen, key, outstr, outstrlen_ptr); + else if (flag == ENCRYPT) + qq_encrypt(instr, instrlen, key, outstr, outstrlen_ptr); + + return 1; // flag must be DECRYPT or ENCRYPT +} // qq_crypt + +/*****************************************************************************/ +// END OF FILE diff -r 5642f4658b59 -r 983fd420e86b src/protocols/qq/crypt.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/protocols/qq/crypt.h Mon Jun 26 02:58:54 2006 +0000 @@ -0,0 +1,36 @@ +/** + * The QQ2003C protocol plugin + * + * for gaim + * + * Copyright (C) 2004 Puzzlebird + * + * 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 + */ + +// START OF FILE +/*****************************************************************************/ +#ifndef _QQ_CRYPT_H_ +#define _QQ_CRYPT_H_ + +#define DECRYPT 0x00 +#define ENCRYPT 0x01 + +int qq_crypt(unsigned char flag, + unsigned char *instr, int instrlen, unsigned char *key, unsigned char *outstr, int *outstrlen_ptr); + +#endif +/*****************************************************************************/ +// END OF FILE diff -r 5642f4658b59 -r 983fd420e86b src/protocols/qq/file_trans.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/protocols/qq/file_trans.c Mon Jun 26 02:58:54 2006 +0000 @@ -0,0 +1,868 @@ +/** + * The QQ2003C protocol plugin + * + * for gaim + * + * Author: Henry Ou + * + * Copyright (C) 2004 Puzzlebird + * + * 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 + */ + +#ifdef _WIN32 +#define random rand +#endif + +#include "debug.h" // gaim_debug +#include "ft.h" // gaim_xfer +//#include "md5.h" +#include "cipher.h" //by gfhuang + +#include "file_trans.h" +#include "send_file.h" // ft_info +#include "packet_parse.h" //read_packet +#include "send_core.h" +#include "header_info.h" +#include "im.h" //gen_session_md5 +#include "crypt.h" //qq_crypt +#include "proxy.h" //qq_proxy_write + +extern gchar* +hex_dump_to_str (const guint8 * buffer, gint bytes); + +struct _qq_file_header { + guint8 tag; + guint16 client_ver; + guint8 file_key; + guint32 sender_uid; + guint32 receiver_uid; +}; + +typedef struct _qq_file_header qq_file_header; + +static guint32 _get_file_key(guint8 seed) +{ + guint32 key; + key = seed | (seed << 8) | (seed << 16) | (seed << 24); + return key; +} + +static guint32 _gen_file_key() +{ + guint8 seed; + + seed = random(); + return _get_file_key(seed); +} + +static guint32 _decrypt_qq_uid(guint32 uid, guint32 key) +{ + return ~(uid ^ key); +} + +static guint32 _encrypt_qq_uid(guint32 uid, guint32 key) +{ + return (~uid) ^ key; +} + +static void _fill_filename_md5(const gchar *filename, gchar *md5) +{ +// md5_state_t ctx; //gfhuang + GaimCipher *cipher; + GaimCipherContext *context; + + g_return_if_fail(filename != NULL && md5 != NULL); + + cipher = gaim_ciphers_find_cipher("md5"); + context = gaim_cipher_context_new(cipher, NULL); + gaim_cipher_context_append(context, filename, strlen(filename)); + gaim_cipher_context_digest(context, 16, md5, NULL); + gaim_cipher_context_destroy(context); +/* gfhuang + md5_init(&ctx); + md5_append(&ctx, filename, strlen(filename)); + md5_finish(&ctx, md5); +*/ +} + +static void _fill_file_md5(const gchar *filename, gint filelen, gchar *md5) +{ + FILE *fp; + gchar *buffer; +// md5_state_t ctx; //gfhuang + GaimCipher *cipher; + GaimCipherContext *context; + + const gint QQ_MAX_FILE_MD5_LENGTH = 10002432; + + g_return_if_fail(filename != NULL && md5 != NULL); + if (filelen > QQ_MAX_FILE_MD5_LENGTH) + filelen = QQ_MAX_FILE_MD5_LENGTH; + + fp = fopen(filename, "rb"); + g_return_if_fail(fp != NULL); + + buffer = g_newa(gchar, filelen); + g_return_if_fail(buffer != NULL); + fread(buffer, filelen, 1, fp); + + cipher = gaim_ciphers_find_cipher("md5"); + context = gaim_cipher_context_new(cipher, NULL); + gaim_cipher_context_append(context, buffer, filelen); + gaim_cipher_context_digest(context, 16, md5, NULL); + gaim_cipher_context_destroy(context); +/* by gfhuang + md5_init(&ctx); + md5_append(&ctx, buffer, filelen); + md5_finish(&ctx, md5); +*/ + fclose(fp); +} + +static void _qq_get_file_header(guint8 *buf, guint8 **cursor, gint buflen, qq_file_header *fh) +{ + read_packet_b(buf, cursor, buflen, &(fh->tag)); + read_packet_w(buf, cursor, buflen, &(fh->client_ver)); + read_packet_b(buf, cursor, buflen, &fh->file_key); + read_packet_dw(buf, cursor, buflen, &(fh->sender_uid)); + read_packet_dw(buf, cursor, buflen, &(fh->receiver_uid)); + + fh->sender_uid = _decrypt_qq_uid(fh->sender_uid, _get_file_key(fh->file_key)); + fh->receiver_uid = _decrypt_qq_uid(fh->receiver_uid, _get_file_key(fh->file_key)); +} + +static const gchar *qq_get_file_cmd_desc(gint type) +{ + switch (type) { + case QQ_FILE_CMD_SENDER_SAY_HELLO: + return "QQ_FILE_CMD_SENDER_SAY_HELLO"; + case QQ_FILE_CMD_SENDER_SAY_HELLO_ACK: + return "QQ_FILE_CMD_SENDER_SAY_HELLO_ACK"; + case QQ_FILE_CMD_RECEIVER_SAY_HELLO: + return "QQ_FILE_CMD_RECEIVER_SAY_HELLO"; + case QQ_FILE_CMD_RECEIVER_SAY_HELLO_ACK: + return "QQ_FILE_CMD_RECEIVER_SAY_HELLO_ACK"; + case QQ_FILE_CMD_NOTIFY_IP_ACK: + return "QQ_FILE_CMD_NOTIFY_IP_ACK"; + case QQ_FILE_CMD_PING: + return "QQ_FILE_CMD_PING"; + case QQ_FILE_CMD_PONG: + return "QQ_FILE_CMD_PONG"; + case QQ_FILE_CMD_INITATIVE_CONNECT: + return "QQ_FILE_CMD_INITATIVE_CONNECT"; + case QQ_FILE_CMD_FILE_OP: + return "QQ_FILE_CMD_FILE_OP"; + case QQ_FILE_CMD_FILE_OP_ACK: + return "QQ_FILE_CMD_FILE_OP_ACK"; + case QQ_FILE_BASIC_INFO: + return "QQ_FILE_BASIC_INFO"; + case QQ_FILE_DATA_INFO: + return "QQ_FILE_DATA_INFO"; + case QQ_FILE_EOF: + return "QQ_FILE_EOF"; + default: + return "UNKNOWN_TYPE"; + } +} + +/* The memmap version has better performance for big files transfering + * but it will spend plenty of memory, so do not use it in a low-memory host + */ +#ifdef USE_MMAP +#include + +static int _qq_xfer_open_file(const gchar *filename, const gchar *method, GaimXfer *xfer) +{ + ft_info *info = xfer->data; + int fd; + if (method[0] == 'r') { + fd = open(gaim_xfer_get_local_filename(xfer), O_RDONLY); + info->buffer = mmap(0, gaim_xfer_get_size(xfer), PROT_READ, MAP_PRIVATE, fd, 0); + } + else + { + fd = open(gaim_xfer_get_local_filename(xfer), O_RDWR|O_CREAT, 0644); + info->buffer = mmap(0, gaim_xfer_get_size(xfer), PROT_READ | PROT_WRITE, MAP_SHARED | MAP_FILE, fd, 0); + } + + if (info->buffer == NULL) { + return - 1; + } + return 0; +} + +static gint +_qq_xfer_read_file(guint8 *buffer, guint index, guint len, GaimXfer *xfer) +{ + ft_info *info = xfer->data; + gint readbytes; + + buffer = info->buffer + len * index; + readbytes = gaim_xfer_get_size(xfer) - (buffer - info->buffer); + if (readbytes > info->fragment_len) readbytes = info->fragment_len; + return readbytes; +} + +static gint +_qq_xfer_write_file(guint8 *buffer, guint index, guint len, GaimXfer *xfer) +{ + ft_info *info = xfer->data; + + memcpy(info->buffer + index * len, buffer, len); + return 0; +} + +void qq_xfer_close_file(GaimXfer *xfer) +{ + ft_info *info = xfer->data; + + if (info->buffer) munmap(info->buffer, gaim_xfer_get_size(xfer)); +} +#else +static int +_qq_xfer_open_file(const gchar *filename, const gchar *method, GaimXfer *xfer) +{ + ft_info *info = xfer->data; + info->dest_fp = fopen(gaim_xfer_get_local_filename(xfer), method); + if (info->dest_fp == NULL) { + return -1; + } + return 0; +} + +static gint +_qq_xfer_read_file(guint8 *buffer, guint index, guint len, GaimXfer *xfer) +{ + ft_info *info = xfer->data; + + fseek(info->dest_fp, index * len, SEEK_SET); + return fread(buffer, 1, len, info->dest_fp); +} + +static gint +_qq_xfer_write_file(guint8 *buffer, guint index, guint len, GaimXfer *xfer) +{ + ft_info *info = xfer->data; + fseek(info->dest_fp, index * len, SEEK_SET); + return fwrite(buffer, 1, len, info->dest_fp); +} + +void qq_xfer_close_file(GaimXfer *xfer) +{ + ft_info *info = xfer->data; + + if (info->dest_fp) fclose(info->dest_fp); +} +#endif + +static gint +_qq_send_file(GaimConnection *gc, guint8 *data, gint len, guint16 packet_type, guint32 to_uid) +{ + gint bytes; + guint8 *cursor, *buf; + guint32 file_key; + qq_data *qd; + ft_info *info; + + g_return_val_if_fail(gc != NULL && gc->proto_data != NULL, -1); + qd = (qq_data *) gc->proto_data; + g_return_val_if_fail(qd != NULL && qd->session_key != NULL, -1); + info = (ft_info *) qd->xfer->data; + bytes = 0; + + buf = g_newa(guint8, MAX_PACKET_SIZE); + cursor = buf; + file_key = _gen_file_key(); + + bytes += create_packet_b(buf, &cursor, packet_type); + bytes += create_packet_w(buf, &cursor, QQ_CLIENT); + bytes += create_packet_b(buf, &cursor, file_key & 0xff); + bytes += create_packet_dw(buf, &cursor, _encrypt_qq_uid(qd->uid, file_key)); + bytes += create_packet_dw(buf, &cursor, _encrypt_qq_uid(to_uid, file_key)); + bytes += create_packet_data(buf, &cursor, data, len); + + ssize_t _qq_xfer_write(const char *buf, size_t len, GaimXfer *xfer); + if (bytes == len + 12) { + //gaim_xfer_write(qd->xfer, buf, bytes); + _qq_xfer_write(buf, bytes, qd->xfer); + } else + gaim_debug(GAIM_DEBUG_INFO, "QQ", "send_file: want %d but got %d\n", len + 12, bytes); + return bytes; +} + + +extern gchar *_gen_session_md5(gint uid, gchar *session_key); + +/********************************************************************************/ +// send a file to udp channel with QQ_FILE_CONTROL_PACKET_TAG +void qq_send_file_ctl_packet(GaimConnection *gc, guint16 packet_type, guint32 to_uid, guint8 hellobyte) +{ + qq_data *qd; + gint bytes, bytes_expected, encrypted_len; + guint8 *raw_data, *cursor, *encrypted_data; + gchar *md5; + time_t now; + ft_info *info; + + g_return_if_fail(gc != NULL && gc->proto_data != NULL); + qd = (qq_data *) gc->proto_data; + info = (ft_info *) qd->xfer->data; + + raw_data = g_new0 (guint8, 61); + cursor = raw_data; + + bytes = 0; + now = time(NULL); + md5 = _gen_session_md5(qd->uid, qd->session_key); + + bytes += create_packet_data(raw_data, &cursor, md5, 16); + bytes += create_packet_w(raw_data, &cursor, packet_type); + switch (packet_type) { + case QQ_FILE_CMD_SENDER_SAY_HELLO: + case QQ_FILE_CMD_SENDER_SAY_HELLO_ACK: + case QQ_FILE_CMD_RECEIVER_SAY_HELLO_ACK: + case QQ_FILE_CMD_NOTIFY_IP_ACK: + case QQ_FILE_CMD_RECEIVER_SAY_HELLO: + bytes += create_packet_w(raw_data, &cursor, info->send_seq); + break; + default: + bytes += create_packet_w(raw_data, &cursor, ++qd->send_seq); + } + bytes += create_packet_dw(raw_data, &cursor, (guint32) now); + bytes += create_packet_b(raw_data, &cursor, 0x00); + bytes += create_packet_b(raw_data, &cursor, qd->my_icon); + bytes += create_packet_dw(raw_data, &cursor, 0x00000000); + bytes += create_packet_dw(raw_data, &cursor, 0x00000000); + bytes += create_packet_dw(raw_data, &cursor, 0x00000000); + bytes += create_packet_dw(raw_data, &cursor, 0x00000000); + bytes += create_packet_w(raw_data, &cursor, 0x0000); + bytes += create_packet_b(raw_data, &cursor, 0x00); + // 0x65: send a file, 0x6b: send a custom face, by gfhuang + bytes += create_packet_b(raw_data, &cursor, QQ_FILE_TRANSFER_FILE); // FIXME temp by gfhuang + switch (packet_type) + { + case QQ_FILE_CMD_SENDER_SAY_HELLO: + case QQ_FILE_CMD_RECEIVER_SAY_HELLO: + case QQ_FILE_CMD_SENDER_SAY_HELLO_ACK: + case QQ_FILE_CMD_RECEIVER_SAY_HELLO_ACK: + bytes += create_packet_b(raw_data, &cursor, 0x00); + bytes += create_packet_b(raw_data, &cursor, hellobyte); + bytes_expected = 48; + break; + case QQ_FILE_CMD_PING: + case QQ_FILE_CMD_PONG: + case QQ_FILE_CMD_NOTIFY_IP_ACK: + bytes += qq_fill_conn_info(raw_data, &cursor, info); + bytes_expected = 61; + break; + default: + gaim_debug(GAIM_DEBUG_INFO, "QQ", "qq_send_file_ctl_packet: Unknown packet type[%d]\n", + packet_type); + bytes_expected = 0; + } + + if (bytes == bytes_expected) { + gaim_debug(GAIM_DEBUG_INFO, "QQ", "sending packet[%s]: \n%s", qq_get_file_cmd_desc(packet_type), + hex_dump_to_str(raw_data, bytes)); + encrypted_len = bytes + 16; + encrypted_data = g_newa(guint8, encrypted_len); + qq_crypt(ENCRYPT, raw_data, bytes, info->file_session_key, encrypted_data, &encrypted_len); + //debug: try to decrypt it + /* + if (QQ_DEBUG) { + gaim_debug(GAIM_DEBUG_INFO, "QQ", "encrypted packet: \n%s", + hex_dump_to_str(encrypted_data, encrypted_len)); + guint8 *buf; + int buflen; + buf = g_newa(guint8, MAX_PACKET_SIZE); + buflen = encrypted_len; + if (qq_crypt(DECRYPT, encrypted_data, encrypted_len, info->file_session_key, buf, &buflen)) { + gaim_debug(GAIM_DEBUG_INFO, "QQ", "decrypt success\n"); + if (buflen == bytes && memcmp(raw_data, buf, buflen) == 0) + gaim_debug(GAIM_DEBUG_INFO, "QQ", "checksum ok\n"); + gaim_debug(GAIM_DEBUG_INFO, "QQ", "decrypted packet: \n%s", + hex_dump_to_str(buf, buflen)); + } else { + gaim_debug(GAIM_DEBUG_INFO, "QQ", "decrypt fail\n"); + } + } + */ + + gaim_debug(GAIM_DEBUG_INFO, "QQ", "<== send %s packet\n", qq_get_file_cmd_desc(packet_type)); + _qq_send_file(gc, encrypted_data, encrypted_len, QQ_FILE_CONTROL_PACKET_TAG, info->to_uid); + } + else + gaim_debug(GAIM_DEBUG_ERROR, "QQ", "qq_send_file_ctl_packet: Expected to get %d bytes, but get %d", + bytes_expected, bytes); + + g_free(md5); +} + +/********************************************************************************/ +// send a file to udp channel with QQ_FILE_DATA_PACKET_TAG +static void +_qq_send_file_data_packet(GaimConnection *gc, guint16 packet_type, guint8 sub_type, guint32 fragment_index, + guint16 seq, guint8 *data, gint len) +{ + gint bytes; + guint8 *raw_data, *cursor; + guint32 fragment_size = 1000; + gchar file_md5[16], filename_md5[16], *filename; + gint filename_len, filesize; + qq_data *qd; + + g_return_if_fail(gc != NULL && gc->proto_data != NULL); + qd = (qq_data *) gc->proto_data; + ft_info *info = (ft_info *) qd->xfer->data; + + filename = (gchar *) gaim_xfer_get_filename(qd->xfer); + filesize = gaim_xfer_get_size(qd->xfer); + + raw_data = g_newa(guint8, MAX_PACKET_SIZE); + cursor = raw_data; + bytes = 0; + + bytes += create_packet_b(raw_data, &cursor, 0x00); + bytes += create_packet_w(raw_data, &cursor, packet_type); + switch (packet_type) { + case QQ_FILE_BASIC_INFO: + case QQ_FILE_DATA_INFO: + case QQ_FILE_EOF: + bytes += create_packet_w(raw_data, &cursor, 0x0000); + bytes += create_packet_b(raw_data, &cursor, 0x00); + break; + case QQ_FILE_CMD_FILE_OP: + switch(sub_type) + { + case QQ_FILE_BASIC_INFO: + filename_len = strlen(filename); + _fill_filename_md5(filename, filename_md5); + _fill_file_md5(gaim_xfer_get_local_filename(qd->xfer), + gaim_xfer_get_size(qd->xfer), + file_md5); + + info->fragment_num = (filesize - 1) / QQ_FILE_FRAGMENT_MAXLEN + 1; + info->fragment_len = QQ_FILE_FRAGMENT_MAXLEN; + + gaim_debug(GAIM_DEBUG_INFO, "QQ", "start transfering data, %d fragments with %d length each\n", + info->fragment_num, info->fragment_len); + /* Unknown */ + bytes += create_packet_w(raw_data, &cursor, 0x0000); + /* Sub-operation type */ + bytes += create_packet_b(raw_data, &cursor, sub_type); + /* Length of file */ + bytes += create_packet_dw(raw_data, &cursor, filesize); + /* Number of fragments */ + bytes += create_packet_dw(raw_data, &cursor, info->fragment_num); + /* Length of a single fragment */ + bytes += create_packet_dw(raw_data, &cursor, info->fragment_len); + bytes += create_packet_data(raw_data, &cursor, file_md5, 16); + bytes += create_packet_data(raw_data, &cursor, filename_md5, 16); + /* Length of filename */ + bytes += create_packet_w(raw_data, &cursor, filename_len); + /* 8 unknown bytes */ + bytes += create_packet_dw(raw_data, &cursor, 0x00000000); + bytes += create_packet_dw(raw_data, &cursor, 0x00000000); + /* filename */ + bytes += create_packet_data(raw_data, &cursor, (guint8 *) filename, + filename_len); + break; + case QQ_FILE_DATA_INFO: + gaim_debug(GAIM_DEBUG_INFO, "QQ", "sending %dth fragment with length %d, offset %d\n", + fragment_index, len, (fragment_index-1)*fragment_size); + //bytes += create_packet_w(raw_data, &cursor, ++(qd->send_seq)); + bytes += create_packet_w(raw_data, &cursor, info->send_seq); + bytes += create_packet_b(raw_data, &cursor, sub_type); + //bytes += create_packet_dw(raw_data, &cursor, fragment_index); + bytes += create_packet_dw(raw_data, &cursor, fragment_index - 1); + bytes += create_packet_dw(raw_data, &cursor, (fragment_index - 1) * fragment_size); + bytes += create_packet_w(raw_data, &cursor, len); + bytes += create_packet_data(raw_data, &cursor, data, len); + break; + case QQ_FILE_EOF: + gaim_debug(GAIM_DEBUG_INFO, "QQ", "end of sending data\n"); + //bytes += create_packet_w(raw_data, &cursor, info->fragment_num + 1); + bytes += create_packet_w(raw_data, &cursor, info->fragment_num); + bytes += create_packet_b(raw_data, &cursor, sub_type); + //gaim_xfer_set_completed(qd->xfer, TRUE); + } + break; + case QQ_FILE_CMD_FILE_OP_ACK: + switch (sub_type) + { + case QQ_FILE_BASIC_INFO: + bytes += create_packet_w(raw_data, &cursor, 0x0000); + bytes += create_packet_b(raw_data, &cursor, sub_type); + bytes += create_packet_dw(raw_data, &cursor, 0x00000000); + break; + case QQ_FILE_DATA_INFO: + bytes += create_packet_w(raw_data, &cursor, seq); + bytes += create_packet_b(raw_data, &cursor, sub_type); + bytes += create_packet_dw(raw_data, &cursor, fragment_index); + break; + case QQ_FILE_EOF: + bytes += create_packet_w(raw_data, &cursor, filesize / QQ_FILE_FRAGMENT_MAXLEN + 2); + bytes += create_packet_b(raw_data, &cursor, sub_type); + break; + } + } + gaim_debug(GAIM_DEBUG_INFO, "QQ", "<== send %s packet\n", qq_get_file_cmd_desc(packet_type)); + _qq_send_file(gc, raw_data, bytes, QQ_FILE_DATA_PACKET_TAG, info->to_uid); +} + +/* An conversation starts like this + * Sender ==> Receiver [QQ_FILE_CMD_PING] + * Sender <== Receiver [QQ_FILE_CMD_PONG] + * Sender ==> Receiver [QQ_FILE_CMD_SENDER_SAY_HELLO] + * Sender <== Receiver [QQ_FILE_CMD_SENDER_SAY_HELLO_ACK] + * Sender <== Receiver [QQ_FILE_CMD_RECEIVER_SAY_HELLO] + * Sender ==> Receiver [QQ_FILE_CMD_RECEIVER_SAY_HELLO_ACK] + * Sender ==> Receiver [QQ_FILE_CMD_FILE_OP, QQ_FILE_BASIC_INFO] + * Sender <== Receiver [QQ_FILE_CMD_FILE_OP_ACK, QQ_FILE_BASIC_INFO] + * Sender ==> Receiver [QQ_FILE_CMD_FILE_OP, QQ_FILE_DATA_INFO] + * Sender <== Receiver [QQ_FILE_CMD_FILE_OP_ACK, QQ_FILE_DATA_INFO] + * Sender ==> Receiver [QQ_FILE_CMD_FILE_OP, QQ_FILE_DATA_INFO] + * Sender <== Receiver [QQ_FILE_CMD_FILE_OP_ACK, QQ_FILE_DATA_INFO] + * ...... + * Sender ==> Receiver [QQ_FILE_CMD_FILE_OP, QQ_FILE_EOF] + * Sender <== Receiver [QQ_FILE_CMD_FILE_OP_ACK, QQ_FILE_EOF] + */ + + +static void +_qq_process_recv_file_ctl_packet(GaimConnection *gc, guint8 *data, guint8 *cursor, + gint len, qq_file_header *fh) +{ + guint8 *decrypted_data; + gint decrypted_len; + qq_data *qd = (qq_data *) gc->proto_data; + guint16 packet_type; + guint16 seq; + guint8 hellobyte; + gchar *md5; + ft_info *info = (ft_info *) qd->xfer->data; + + decrypted_data = g_newa(guint8, len); + decrypted_len = len; + + md5 = _gen_session_md5(qd->uid, qd->session_key); + if (qq_crypt(DECRYPT, cursor, len - (cursor - data), md5, decrypted_data, &decrypted_len)) { + cursor = decrypted_data + 16; //skip md5 section + read_packet_w(decrypted_data, &cursor, decrypted_len, &packet_type); + read_packet_w(decrypted_data, &cursor, decrypted_len, &seq); + cursor += 4+1+1+19+1; + gaim_debug(GAIM_DEBUG_INFO, "QQ", "==> [%d] receive %s packet\n", seq, qq_get_file_cmd_desc(packet_type)); + gaim_debug(GAIM_DEBUG_INFO, "QQ", "decrypted control packet received: \n%s", + hex_dump_to_str(decrypted_data, decrypted_len)); + switch (packet_type) { + case QQ_FILE_CMD_NOTIFY_IP_ACK: + cursor = decrypted_data; + qq_get_conn_info(decrypted_data, &cursor, decrypted_len, info); +// qq_send_file_ctl_packet(gc, QQ_FILE_CMD_PING, fh->sender_uid, 0); + qq_send_file_ctl_packet(gc, QQ_FILE_CMD_SENDER_SAY_HELLO, fh->sender_uid, 0); + break; + case QQ_FILE_CMD_SENDER_SAY_HELLO: + /* I'm receiver, if we receive SAY_HELLO from sender, we send back the ACK */ + cursor += 47; + read_packet_b(decrypted_data, &cursor, + decrypted_len, &hellobyte); + + qq_send_file_ctl_packet(gc, QQ_FILE_CMD_SENDER_SAY_HELLO_ACK, fh->sender_uid, hellobyte); + qq_send_file_ctl_packet(gc, QQ_FILE_CMD_RECEIVER_SAY_HELLO, fh->sender_uid, 0); + break; + case QQ_FILE_CMD_SENDER_SAY_HELLO_ACK: + /* I'm sender, do nothing */ + break; + case QQ_FILE_CMD_RECEIVER_SAY_HELLO: + /* I'm sender, ack the hello packet and send the first data */ + cursor += 47; + read_packet_b(decrypted_data, &cursor, + decrypted_len, &hellobyte); + qq_send_file_ctl_packet(gc, QQ_FILE_CMD_RECEIVER_SAY_HELLO_ACK, fh->sender_uid, hellobyte); + _qq_send_file_data_packet(gc, QQ_FILE_CMD_FILE_OP, QQ_FILE_BASIC_INFO, 0, 0, NULL, 0); + break; + case QQ_FILE_CMD_RECEIVER_SAY_HELLO_ACK: + /* I'm receiver, do nothing */ + break; + case QQ_FILE_CMD_PING: + /* I'm receiver, ack the PING */ + qq_send_file_ctl_packet(gc, QQ_FILE_CMD_PONG, fh->sender_uid, 0); + break; + case QQ_FILE_CMD_PONG: + qq_send_file_ctl_packet(gc, QQ_FILE_CMD_SENDER_SAY_HELLO, fh->sender_uid, 0); + break; + default: + gaim_debug(GAIM_DEBUG_INFO, "QQ", "unprocess file command %d\n", packet_type); + } + } + g_free(md5); +} + +static void +_qq_recv_file_progess(GaimConnection *gc, guint8 *buffer, guint16 len, guint32 index, guint32 offset) +{ + qq_data *qd = (qq_data *) gc->proto_data; + GaimXfer *xfer = qd->xfer; + ft_info *info = (ft_info *) xfer->data; + guint32 mask; + + gaim_debug(GAIM_DEBUG_INFO, "QQ", "receiving %dth fragment with length %d, slide window status %o, max_fragment_index %d\n", + index, len, info->window, info->max_fragment_index); + if (info->window == 0 && info->max_fragment_index == 0) + { + if (_qq_xfer_open_file(gaim_xfer_get_local_filename(xfer), "wb", xfer) == -1) { + gaim_xfer_cancel_local(xfer); + return; + } + gaim_debug(GAIM_DEBUG_INFO, "QQ", "object file opened for writing\n"); + } + mask = 0x1 << (index % sizeof(info->window)); + if (index < info->max_fragment_index || (info->window & mask)) { + gaim_debug(GAIM_DEBUG_INFO, "QQ", "duplicate %dth fragment, drop it!\n", index+1); + return; + } + + info->window |= mask; + + _qq_xfer_write_file(buffer, index, len, xfer); + + xfer->bytes_sent += len; + xfer->bytes_remaining -= len; + gaim_xfer_update_progress(xfer); + + mask = 0x1 << (info->max_fragment_index % sizeof(info->window)); + while (info->window & mask) + { + info->window &= ~mask; + info->max_fragment_index ++; + if (mask & 0x8000) mask = 0x0001; + else mask = mask << 1; + } + gaim_debug(GAIM_DEBUG_INFO, "QQ", "procceed %dth fragment, slide window status %o, max_fragment_index %d\n", + index, info->window, info->max_fragment_index); +} + +static void +_qq_send_file_progess(GaimConnection *gc) +{ + qq_data *qd = (qq_data *) gc->proto_data; + GaimXfer *xfer = qd->xfer; + ft_info *info = (ft_info *) xfer->data; + guint32 mask; + guint8 *buffer; + guint i; + gint readbytes; + + if (gaim_xfer_get_bytes_remaining(xfer) <= 0) return; + if (info->window == 0 && info->max_fragment_index == 0) + { + if (_qq_xfer_open_file(gaim_xfer_get_local_filename(xfer), "rb", xfer) == -1) { + gaim_xfer_cancel_local(xfer); + return; + } + } + buffer = g_newa(guint8, info->fragment_len); + mask = 0x1 << (info->max_fragment_index % sizeof(info->window)); + for (i = 0; i < sizeof(info->window); i++) { + if ((info->window & mask) == 0) { + readbytes = _qq_xfer_read_file(buffer, info->max_fragment_index + i, info->fragment_len, xfer); + if (readbytes > 0) + _qq_send_file_data_packet(gc, QQ_FILE_CMD_FILE_OP, QQ_FILE_DATA_INFO, + info->max_fragment_index + i + 1, 0, buffer, readbytes); + } + if (mask & 0x8000) mask = 0x0001; + else mask = mask << 1; + } +} + +static void +_qq_update_send_progess(GaimConnection *gc, guint32 fragment_index) +{ + qq_data *qd = (qq_data *) gc->proto_data; + GaimXfer *xfer = qd->xfer; + ft_info *info = (ft_info *) xfer->data; + guint32 mask; + + gaim_debug(GAIM_DEBUG_INFO, "QQ", "receiving %dth fragment ack, slide window status %o, max_fragment_index %d\n", + fragment_index, info->window, info->max_fragment_index); + if (fragment_index < info->max_fragment_index || + fragment_index >= info->max_fragment_index + sizeof(info->window)) { + gaim_debug(GAIM_DEBUG_INFO, "QQ", "duplicate %dth fragment, drop it!\n", fragment_index+1); + return; + } + mask = 0x1 << (fragment_index % sizeof(info->window)); + if ((info->window & mask) == 0) + { + info->window |= mask; + if (fragment_index + 1 != info->fragment_num) { + xfer->bytes_sent += info->fragment_len; + } else { + xfer->bytes_sent += gaim_xfer_get_size(xfer) % info->fragment_len; + } + xfer->bytes_remaining = gaim_xfer_get_size(xfer) - gaim_xfer_get_bytes_sent(xfer); + gaim_xfer_update_progress(xfer); + if (gaim_xfer_get_bytes_remaining(xfer) <= 0) { + /* We have finished sending the file */ + gaim_xfer_set_completed(xfer, TRUE); + return; + } + mask = 0x1 << (info->max_fragment_index % sizeof(info->window)); + while (info->window & mask) + { + //move the slide window + info->window &= ~mask; + guint8 *buffer; + gint readbytes; + + buffer = g_newa(guint8, info->fragment_len); + readbytes = _qq_xfer_read_file(buffer, info->max_fragment_index + sizeof(info->window), + info->fragment_len, xfer); + if (readbytes > 0) + _qq_send_file_data_packet(gc, QQ_FILE_CMD_FILE_OP, QQ_FILE_DATA_INFO, + info->max_fragment_index + sizeof(info->window) + 1, 0, buffer, readbytes); + + info->max_fragment_index ++; + if (mask & 0x8000) mask = 0x0001; + else mask = mask << 1; + } + } + gaim_debug(GAIM_DEBUG_INFO, "QQ", "procceed %dth fragment ack, slide window status %o, max_fragment_index %d\n", + fragment_index, info->window, info->max_fragment_index); +} + +static void +_qq_process_recv_file_data(GaimConnection *gc, guint8 *data, guint8 *cursor, + gint len, guint32 to_uid) +{ + guint16 packet_type; + guint16 packet_seq; + guint8 sub_type; + guint32 fragment_index; + guint16 fragment_len; + guint32 fragment_offset; + qq_data *qd = (qq_data *) gc->proto_data; + ft_info *info = (ft_info *) qd->xfer->data; + + cursor += 1;//skip an unknown byte + read_packet_w(data, &cursor, len, &packet_type); + switch(packet_type) + { + case QQ_FILE_CMD_FILE_OP: + read_packet_w(data, &cursor, len, &packet_seq); + read_packet_b(data, &cursor, len, &sub_type); + switch (sub_type) + { + case QQ_FILE_BASIC_INFO: + cursor += 4; //file length, we have already known it from xfer + read_packet_dw(data, &cursor, len, &info->fragment_num); + read_packet_dw(data, &cursor, len, &info->fragment_len); + + /* FIXME: We must check the md5 here, if md5 doesn't match + * we will ignore the packet or send sth as error number + */ + + info->max_fragment_index = 0; + info->window = 0; + gaim_debug(GAIM_DEBUG_INFO, "QQ", "start receiving data, %d fragments with %d length each\n", + info->fragment_num, info->fragment_len); + _qq_send_file_data_packet(gc, QQ_FILE_CMD_FILE_OP_ACK, sub_type, + 0, 0, NULL, 0); + break; + case QQ_FILE_DATA_INFO: + read_packet_dw(data, &cursor, len, &fragment_index); + read_packet_dw(data, &cursor, len, &fragment_offset); + read_packet_w(data, &cursor, len, &fragment_len); + gaim_debug(GAIM_DEBUG_INFO, "QQ", "received %dth fragment with length %d, offset %d\n", + fragment_index, fragment_len, fragment_offset); + + _qq_send_file_data_packet(gc, QQ_FILE_CMD_FILE_OP_ACK, sub_type, + fragment_index, packet_seq, NULL, 0); + _qq_recv_file_progess(gc, cursor, fragment_len, fragment_index, fragment_offset); + break; + case QQ_FILE_EOF: + gaim_debug(GAIM_DEBUG_INFO, "QQ", "end of receiving\n"); + _qq_send_file_data_packet(gc, QQ_FILE_CMD_FILE_OP_ACK, sub_type, + 0, 0, NULL, 0); + break; + } + break; + case QQ_FILE_CMD_FILE_OP_ACK: + read_packet_w(data, &cursor, len, &packet_seq); + read_packet_b(data, &cursor, len, &sub_type); + switch (sub_type) + { + case QQ_FILE_BASIC_INFO: + info->max_fragment_index = 0; + info->window = 0; + /* It is ready to send file data */ + _qq_send_file_progess(gc); + break; + case QQ_FILE_DATA_INFO: + read_packet_dw(data, &cursor, len, &fragment_index); + _qq_update_send_progess(gc, fragment_index); + if (gaim_xfer_is_completed(qd->xfer)) + _qq_send_file_data_packet(gc, QQ_FILE_CMD_FILE_OP, QQ_FILE_EOF, 0, 0, NULL, 0); + // else + // _qq_send_file_progess(gc); + break; + case QQ_FILE_EOF: + /* FIXME: OK, we can end the connection successfully */ + + _qq_send_file_data_packet(gc, QQ_FILE_EOF, 0, 0, 0, NULL, 0); + gaim_xfer_set_completed(qd->xfer, TRUE); + break; + } + break; + case QQ_FILE_EOF: + _qq_send_file_data_packet(gc, QQ_FILE_EOF, 0, 0, 0, NULL, 0); + gaim_xfer_set_completed(qd->xfer, TRUE); + gaim_xfer_end(qd->xfer); + break; + case QQ_FILE_BASIC_INFO: + gaim_debug(GAIM_DEBUG_INFO, "QQ", "here\n"); + _qq_send_file_data_packet(gc, QQ_FILE_DATA_INFO, 0, 0, 0, NULL, 0); + break; + default: + gaim_debug(GAIM_DEBUG_INFO, "QQ", "_qq_process_recv_file_data: unknown packet type [%d]\n", + packet_type); + break; + } +} + +void qq_process_recv_file(GaimConnection *gc, guint8 *data, gint len) +{ + guint8 *cursor; + qq_file_header fh; + qq_data *qd; + + g_return_if_fail(gc != NULL && gc->proto_data != NULL); + qd = (qq_data *) gc->proto_data; + + cursor = data; + _qq_get_file_header(data, &cursor, len, &fh); + + switch (fh.tag) { + case QQ_FILE_CONTROL_PACKET_TAG: + _qq_process_recv_file_ctl_packet(gc, data, cursor, len, &fh); + break; + case QQ_FILE_DATA_PACKET_TAG: + _qq_process_recv_file_data(gc, data, cursor, len, fh.sender_uid); + break; + default: + gaim_debug(GAIM_DEBUG_INFO, "QQ", "unknown packet tag"); + } +} + diff -r 5642f4658b59 -r 983fd420e86b src/protocols/qq/file_trans.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/protocols/qq/file_trans.h Mon Jun 26 02:58:54 2006 +0000 @@ -0,0 +1,68 @@ +/** + * The QQ2003C protocol plugin + * + * for gaim + * + * Author: Henry Ou + * + * Copyright (C) 2004 Puzzlebird + * + * 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 _QQ_QQ_FILE_TRANS_H_ +#define _QQ_QQ_FILE_TRANS_H_ + +#include "server.h" + +enum { + QQ_FILE_CMD_SENDER_SAY_HELLO = 0x31, + QQ_FILE_CMD_SENDER_SAY_HELLO_ACK = 0x32, + QQ_FILE_CMD_RECEIVER_SAY_HELLO = 0x33, + QQ_FILE_CMD_RECEIVER_SAY_HELLO_ACK = 0x34, + QQ_FILE_CMD_NOTIFY_IP_ACK = 0x3c, + QQ_FILE_CMD_PING = 0x3d, + QQ_FILE_CMD_PONG = 0x3e, + QQ_FILE_CMD_INITATIVE_CONNECT = 0x40 +}; + +enum { + QQ_FILE_TRANSFER_FILE = 0x65, + QQ_FILE_TRANSFER_FACE = 0x6b +}; + +enum { + QQ_FILE_BASIC_INFO = 0x01, + QQ_FILE_DATA_INFO = 0x02, + QQ_FILE_EOF = 0x03, + QQ_FILE_CMD_FILE_OP = 0x07, + QQ_FILE_CMD_FILE_OP_ACK = 0x08 +}; + +#define QQ_FILE_FRAGMENT_MAXLEN 1000 + +#define QQ_FILE_CONTROL_PACKET_TAG 0x00 +//#define QQ_PACKET_TAG 0x02 // all QQ text packets starts with it +#define QQ_FILE_DATA_PACKET_TAG 0x03 +#define QQ_FILE_AGENT_PACKET_TAG 0x04 +//#define QQ_PACKET_TAIL 0x03 // all QQ text packets end with it + + +void qq_send_file_ctl_packet(GaimConnection *gc, guint16 packet_type, guint32 to_uid, guint8 hellobyte); +void qq_process_recv_file(GaimConnection *gc, guint8 *data, gint len); +//void qq_send_file_data_packet(GaimConnection *gc, guint16 packet_type, guint8 sub_type, guint32 fragment_index, guint16 seq, +// guint8 *data, gint len); +void qq_xfer_close_file(GaimXfer *xfer); +#endif diff -r 5642f4658b59 -r 983fd420e86b src/protocols/qq/group.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/protocols/qq/group.c Mon Jun 26 02:58:54 2006 +0000 @@ -0,0 +1,179 @@ +/** +* The QQ2003C protocol plugin + * + * for gaim + * + * Copyright (C) 2004 Puzzlebird + * + * 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 + */ + +// START OF FILE +/*****************************************************************************/ +#include "internal.h" + +#include "debug.h" // gaim_debug +#include "prpl.h" // struct proto_chat_entry +#include "request.h" // gaim_request_input + +#include "utils.h" // qq_string_to_dec_value +#include "group_hash.h" // QQ_GROUP_KEY_EXTERNAL_ID +#include "group_info.h" // qq_send_cmd_group_get_group_info +#include "group_search.h" // qq_send_cmd_group_search_group + +#include "group.h" + +/*****************************************************************************/ +static void _qq_group_search_callback(GaimConnection * gc, const gchar * input) +{ + guint32 external_group_id; + + g_return_if_fail(gc != NULL && input != NULL); + external_group_id = qq_string_to_dec_value(input); + // 0x00000000 means search for demo group + qq_send_cmd_group_search_group(gc, external_group_id); +} // _qq_group_search_callback + +/*****************************************************************************/ +// This is needed for GaimChat node to be valid +GList *qq_chat_info(GaimConnection * gc) +{ + GList *m; + struct proto_chat_entry *pce; + + m = NULL; + + pce = g_new0(struct proto_chat_entry, 1); + pce->label = _("ID: "); + pce->identifier = QQ_GROUP_KEY_EXTERNAL_ID; + m = g_list_append(m, pce); + + pce = g_new0(struct proto_chat_entry, 1); + pce->label = _("Admin: "); + pce->identifier = QQ_GROUP_KEY_CREATOR_UID; + m = g_list_append(m, pce); + + pce = g_new0(struct proto_chat_entry, 1); + pce->label = _("Status: "); + pce->identifier = QQ_GROUP_KEY_MEMBER_STATUS_DESC; + m = g_list_append(m, pce); + + return m; +} // qq_chat_info + +/*****************************************************************************/ +// get a list of qq groups +GaimRoomlist *qq_roomlist_get_list(GaimConnection * gc) +{ + GList *fields; + qq_data *qd; + GaimRoomlist *rl; + GaimRoomlistField *f; + + g_return_val_if_fail(gc != NULL && gc->proto_data != NULL, NULL); + qd = (qq_data *) gc->proto_data; + + fields = NULL; + rl = gaim_roomlist_new(gaim_connection_get_account(gc)); + qd->roomlist = rl; + + f = gaim_roomlist_field_new(GAIM_ROOMLIST_FIELD_STRING, _("Group ID"), QQ_GROUP_KEY_EXTERNAL_ID, FALSE); + fields = g_list_append(fields, f); + f = gaim_roomlist_field_new(GAIM_ROOMLIST_FIELD_STRING, _("Creator"), QQ_GROUP_KEY_CREATOR_UID, FALSE); + fields = g_list_append(fields, f); + f = gaim_roomlist_field_new(GAIM_ROOMLIST_FIELD_STRING, + _("Group Description"), QQ_GROUP_KEY_GROUP_DESC_UTF8, FALSE); + fields = g_list_append(fields, f); + f = gaim_roomlist_field_new(GAIM_ROOMLIST_FIELD_STRING, "", QQ_GROUP_KEY_INTERNAL_ID, TRUE); + fields = g_list_append(fields, f); + f = gaim_roomlist_field_new(GAIM_ROOMLIST_FIELD_STRING, "", QQ_GROUP_KEY_GROUP_TYPE, TRUE); + fields = g_list_append(fields, f); + f = gaim_roomlist_field_new(GAIM_ROOMLIST_FIELD_STRING, _("Auth"), QQ_GROUP_KEY_AUTH_TYPE, TRUE); + fields = g_list_append(fields, f); + f = gaim_roomlist_field_new(GAIM_ROOMLIST_FIELD_STRING, "", QQ_GROUP_KEY_GROUP_CATEGORY, TRUE); + fields = g_list_append(fields, f); + f = gaim_roomlist_field_new(GAIM_ROOMLIST_FIELD_STRING, "", QQ_GROUP_KEY_GROUP_NAME_UTF8, TRUE); + + fields = g_list_append(fields, f); + gaim_roomlist_set_fields(rl, fields); + gaim_roomlist_set_in_progress(qd->roomlist, TRUE); + + gaim_request_input(gc, _("QQ Qun"), + _("Please input external group ID"), + _ + ("You can only search for permanent QQ group\nInput 0 or leave it blank to search for demo groups"), + NULL, FALSE, FALSE, NULL, _("Search"), + G_CALLBACK(_qq_group_search_callback), _("Cancel"), NULL, gc); + + return qd->roomlist; +} // qq_roomlist_get_list + +/*****************************************************************************/ +// free roomlist space, I have no idea when this one is called ... +void qq_roomlist_cancel(GaimRoomlist * list) +{ + qq_data *qd; + GaimConnection *gc; + + g_return_if_fail(list != NULL); + gc = gaim_account_get_connection(list->account); + + g_return_if_fail(gc != NULL && gc->proto_data != NULL); + qd = (qq_data *) gc->proto_data; + + gaim_roomlist_set_in_progress(list, FALSE); + gaim_roomlist_unref(list); +} // qq_roomlist_cancel + +/*****************************************************************************/ +// this should be called upon signin, even we did not open group chat window +void qq_group_init(GaimConnection * gc) +{ + gint i; + GaimAccount *account; + GaimChat *chat; + GaimGroup *gaim_group; + GaimBlistNode *node; + qq_group *group; + + g_return_if_fail(gc != NULL); + account = gaim_connection_get_account(gc); + + + gaim_group = gaim_find_group(GAIM_GROUP_QQ_QUN); + if (gaim_group == NULL) { + gaim_debug(GAIM_DEBUG_INFO, "QQ", "We have no QQ Qun\n"); + return; + } // if group + + i = 0; + for (node = ((GaimBlistNode *) gaim_group)->child; node != NULL; node = node->next) + if (GAIM_BLIST_NODE_IS_CHAT(node)) { // got one + chat = (GaimChat *) node; + if (account != chat->account) + continue; // very important here ! + group = qq_group_from_hashtable(gc, chat->components); + if (group != NULL) { + i++; + qq_send_cmd_group_get_group_info(gc, group); // get group info and members + } // if group + } // if is chat + + gaim_debug(GAIM_DEBUG_INFO, "QQ", "Load %d QQ Qun configurations\n", i); + +} // qq_group_init + +/*****************************************************************************/ +// END OF FILE diff -r 5642f4658b59 -r 983fd420e86b src/protocols/qq/group.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/protocols/qq/group.h Mon Jun 26 02:58:54 2006 +0000 @@ -0,0 +1,70 @@ +/** +* The QQ2003C protocol plugin + * + * for gaim + * + * Copyright (C) 2004 Puzzlebird + * + * 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 + */ + +// START OF FILE +/*****************************************************************************/ +#ifndef _QQ_GROUP_H_ +#define _QQ_GROUP_H_ + +#include +#include "account.h" +#include "connection.h" // GaimConnection +#include "roomlist.h" // GaimRoomlist +#include "qq.h" // qq_data + +#define GAIM_GROUP_QQ_QUN "QQ 群" + +typedef enum { + QQ_GROUP_MEMBER_STATUS_NOT_MEMBER = 0x00, // default 0x00 means not member + QQ_GROUP_MEMBER_STATUS_IS_MEMBER, + QQ_GROUP_MEMBER_STATUS_APPLYING, + QQ_GROUP_MEMBER_STATUS_IS_ADMIN, +} qq_group_member_status; + +typedef struct _qq_group { + // all these will be saved when exit GAIM + qq_group_member_status my_status; // my status for this group + gchar *my_status_desc; // my status description + guint32 internal_group_id; + guint32 external_group_id; + guint8 group_type; // permanent or temporory + guint32 creator_uid; + guint32 group_category; + guint8 auth_type; + gchar *group_name_utf8; + gchar *group_desc_utf8; + // all these will loaded from netowrk only + gchar *notice_utf8; // group notice by admin + GList *members; // those evert appear in the group +} qq_group; + +GList *qq_chat_info(GaimConnection * gc); + +void qq_group_init(GaimConnection * gc); + +GaimRoomlist *qq_roomlist_get_list(GaimConnection * gc); + +void qq_roomlist_cancel(GaimRoomlist * list); + +#endif +/*****************************************************************************/ +// END OF FILE diff -r 5642f4658b59 -r 983fd420e86b src/protocols/qq/group_admindlg.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/protocols/qq/group_admindlg.c Mon Jun 26 02:58:54 2006 +0000 @@ -0,0 +1,659 @@ +/** +* The QQ2003C protocol plugin + * + * for gaim + * + * Copyright (C) 2004 Puzzlebird + * + * 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 + */ + +// START OF FILE +/*****************************************************************************/ +#include "debug.h" // gaim_debug +#include "blist.h" // GAIM_BLIST_NODE_IS_BUDDY +#include "notify.h" // gaim_notify_warning + +#include "utils.h" // gaim_name_to_uid +#include "group_admindlg.h" +#include "group_find.h" // qq_group_find_by_internal_group_id +#include "group_join.h" // auth_type +#include "group_opt.h" // QQ_GROUP_TYPE_PERMANENT + +enum { + COLUMN_SELECTED = 0, + COLUMN_UID, + COLUMN_NICKNAME, + NUM_COLUMNS +}; + +enum { + PAGE_INFO = 0, + PAGE_MEMBER, +}; + +typedef struct _qun_info_window { + guint32 internal_group_id; + GaimConnection *gc; + GtkWidget *window; + GtkWidget *notebook; + GtkWidget *lbl_external_group_id; + GtkWidget *lbl_admin_uid; + GtkWidget *ent_group_name; + GtkWidget *cmb_group_category; + GtkWidget *txt_group_desc; + GtkWidget *txt_group_notice; + GtkWidget *rad_auth[3]; + GtkWidget *btn_mod; + GtkWidget *btn_close; + GtkWidget *tre_members; +} qun_info_window; + +const gchar *qq_group_category[] = { + "åŒå­¦", "朋å‹", "åŒäº‹", "其他", +}; // qq_group_category + +const gchar *qq_group_auth_type_desc[] = { + "无须认è¯", "需è¦è®¤è¯", "ä¸å¯æ·»åŠ ", +}; // qq_group_auth_type_desc + +/*****************************************************************************/ +static void _qq_group_info_window_deleteevent(GtkWidget * widget, GdkEvent * event, gpointer data) { + gtk_widget_destroy(widget); // this will call _window_destroy +} // _window_deleteevent + +/*****************************************************************************/ +static void _qq_group_info_window_close(GtkWidget * widget, gpointer data) +{ + // this will call _info_window_destroy if it is info-window + gtk_widget_destroy(GTK_WIDGET(data)); +} // _window_close + +/*****************************************************************************/ +static void _qq_group_info_window_destroy(GtkWidget * widget, gpointer data) +{ + GaimConnection *gc; + GList *list; + qq_data *qd; + qun_info_window *info_window; + + gc = (GaimConnection *) data; + g_return_if_fail(gc != NULL && gc->proto_data != NULL); + gaim_debug(GAIM_DEBUG_INFO, "QQ", "Group info is destoryed\n"); + + qd = (qq_data *) gc->proto_data; + list = qd->qun_info_window; + + while (list) { + info_window = (qun_info_window *) (list->data); + if (info_window->window != widget) + list = list->next; + else { + qd->qun_info_window = g_list_remove(qd->qun_info_window, info_window); + g_free(info_window); + break; + } // if info_window + } // while +} // _window_destroy + +/*****************************************************************************/ +void qq_qun_info_window_free(qq_data * qd) +{ + gint i; + qun_info_window *info_window; + + i = 0; + while (qd->qun_info_window) { + info_window = (qun_info_window *) qd->qun_info_window->data; + qd->qun_info_window = g_list_remove(qd->qun_info_window, info_window); + if (info_window->window) + gtk_widget_destroy(info_window->window); + g_free(info_window); + i++; + } // while + + gaim_debug(GAIM_DEBUG_INFO, "QQ", "%d Qun info windows are freed\n", i); +} // qq_qun_info_window_free + +/*****************************************************************************/ +static void _qq_group_info_window_modify(GtkWidget * widget, gpointer data) +{ + GaimConnection *gc; + qun_info_window *info_window; + + g_return_if_fail(data != NULL); + info_window = (qun_info_window *) data; + + gc = info_window->gc; + g_return_if_fail(gc != NULL && gc->proto_data != NULL); + + //henry: This function contains some codes only supported by gtk-2.4 or later +//#if !GTK_CHECK_VERSION(2,4,0) +// gaim_notify_info(gc, _("QQ Qun Operation"), +// _("This version of GTK-2 does not support this function"), NULL); +// return; +//#else + gint page, group_category, i = 0; + qq_group *group; + qq_data *qd; + GtkTextIter start, end; + GtkTreeModel *model; + GtkTreeIter iter; + GValue value = { 0, }; + guint32 *new_members; + guint32 uid; + gboolean selected; + + qd = (qq_data *) gc->proto_data; + + // we assume the modification can succeed + // maybe it needs some tweak here + group = qq_group_find_by_internal_group_id(gc, info_window->internal_group_id); + g_return_if_fail(group != NULL); + + new_members = g_newa(guint32, QQ_QUN_MEMBER_MAX); + + page = gtk_notebook_get_current_page(GTK_NOTEBOOK(info_window->notebook)); + switch (page) { + case PAGE_INFO: + gaim_debug(GAIM_DEBUG_INFO, "QQ", "Gonna change Qun detailed information\n"); + // get the group_category +#if GTK_CHECK_VERSION(2,4,0) + group_category = gtk_combo_box_get_active(GTK_COMBO_BOX(info_window->cmb_group_category)); +#else + group_category = gtk_option_menu_get_history(GTK_OPTION_MENU(info_window->cmb_group_category)); +#endif + + if (group_category >= 0) + group->group_category = group_category; + else { + g_free(group); + gaim_debug(GAIM_DEBUG_ERROR, "QQ", "Invalid group_category: %d\n", group_category); + return; + } // if group_category + // get auth_type + if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(info_window->rad_auth[0]))) + group->auth_type = QQ_GROUP_AUTH_TYPE_NO_AUTH; + else if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(info_window->rad_auth[1]))) + group->auth_type = QQ_GROUP_AUTH_TYPE_NEED_AUTH; + else + group->auth_type = QQ_GROUP_AUTH_TYPE_NO_ADD; + // MUST use g_strdup, otherwise core dump after info_window is closed + group->group_name_utf8 = g_strdup(gtk_entry_get_text(GTK_ENTRY(info_window->ent_group_name))); + gtk_text_buffer_get_bounds(gtk_text_view_get_buffer + (GTK_TEXT_VIEW(info_window->txt_group_desc)), &start, &end); + group->group_desc_utf8 = + g_strdup(gtk_text_buffer_get_text + (gtk_text_view_get_buffer + (GTK_TEXT_VIEW(info_window->txt_group_desc)), &start, &end, FALSE)); + gtk_text_buffer_get_bounds(gtk_text_view_get_buffer + (GTK_TEXT_VIEW(info_window->txt_group_notice)), &start, &end); + group->notice_utf8 = + g_strdup(gtk_text_buffer_get_text + (gtk_text_view_get_buffer + (GTK_TEXT_VIEW(info_window->txt_group_notice)), &start, &end, FALSE)); + // finally we can modify it with new information + qq_group_modify_info(gc, group); + break; + case PAGE_MEMBER: + if (info_window->tre_members == NULL) { + gaim_debug(GAIM_DEBUG_INFO, "QQ", "Member list is not ready, cannot modify!\n"); + } else { + gaim_debug(GAIM_DEBUG_INFO, "QQ", "Gonna change Qun member list\n"); + model = gtk_tree_view_get_model(GTK_TREE_VIEW(info_window->tre_members)); + if (gtk_tree_model_get_iter_first(model, &iter)) { + gtk_tree_model_get_value(model, &iter, COLUMN_UID, &value); + uid = g_value_get_uint(&value); + g_value_unset(&value); + gtk_tree_model_get_value(model, &iter, COLUMN_SELECTED, &value); + selected = g_value_get_boolean(&value); + g_value_unset(&value); + if (!selected) + new_members[i++] = uid; + while (gtk_tree_model_iter_next(model, &iter)) { + gtk_tree_model_get_value(model, &iter, COLUMN_UID, &value); + uid = g_value_get_uint(&value); + g_value_unset(&value); + gtk_tree_model_get_value(model, &iter, COLUMN_SELECTED, &value); + selected = g_value_get_boolean(&value); + g_value_unset(&value); + if (!selected) + new_members[i++] = uid; + } // while + new_members[i] = 0xffffffff; // this labels the end + } else + new_members[0] = 0xffffffff; + qq_group_modify_members(gc, group, new_members); + } // if info_window->tre_members + break; + default: + gaim_debug(GAIM_DEBUG_INFO, "QQ", "Invalid page number: %d\n", page); + } // switch + + _qq_group_info_window_close(NULL, info_window->window); + +//#endif /* GTK_CHECK_VERSION */ +} // _qq_group_info_window_modify + +/*****************************************************************************/ +static void _qq_group_member_list_deleted_toggled(GtkCellRendererToggle * cell, gchar * path_str, gpointer data) { + qun_info_window *info_window; + GaimConnection *gc; + qq_group *group; + + info_window = (qun_info_window *) data; + g_return_if_fail(info_window != NULL); + + gc = info_window->gc; + g_return_if_fail(gc != NULL); + + group = qq_group_find_by_internal_group_id(gc, info_window->internal_group_id); + g_return_if_fail(group != NULL); + + GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(info_window->tre_members)); + GtkTreeIter iter; + GtkTreePath *path = gtk_tree_path_new_from_string(path_str); + gboolean selected; + guint32 uid; + + gtk_tree_model_get_iter(model, &iter, path); + gtk_tree_model_get(model, &iter, COLUMN_SELECTED, &selected, -1); + gtk_tree_model_get(model, &iter, COLUMN_UID, &uid, -1); + + if (uid != group->creator_uid) { // do not allow delete admin + selected ^= 1; + gtk_list_store_set(GTK_LIST_STORE(model), &iter, COLUMN_SELECTED, selected, -1); + gtk_tree_path_free(path); + } else + gaim_notify_error(gc, NULL, _("Qun creator cannot be removed"), NULL); +} // _qq_group_member_list_deleted_toggled + +/*****************************************************************************/ +static void _qq_group_member_list_drag_data_rcv_cb + (GtkWidget * widget, GdkDragContext * dc, guint x, guint y, + GtkSelectionData * sd, guint info, guint t, gpointer data) { + + GaimConnection *gc; + GaimAccount *account; + GaimBlistNode *n = NULL; + GaimContact *c = NULL; + GaimBuddy *b = NULL; + GtkWidget *treeview; + GtkTreeModel *model; + GtkListStore *store; + GtkTreeIter iter; + GValue value = { 0, }; + guint32 uid, input_uid; + + treeview = widget; + gc = (GaimConnection *) data; + g_return_if_fail(gc != NULL); + account = gaim_connection_get_account(gc); + + if (sd->target != gdk_atom_intern("GAIM_BLIST_NODE", FALSE) || sd->data == NULL) { + gaim_debug(GAIM_DEBUG_ERROR, "QQ", "Invalid drag data received, discard...\n"); + return; + } // if (sd->target + + memcpy(&n, sd->data, sizeof(n)); + + // we expect GAIM_BLIST_CONTACT_NODE and GAIM_BLIST_BUDDY_NODE + if (GAIM_BLIST_NODE_IS_CONTACT(n)) { + c = (GaimContact *) n; + b = c->priority; // we get the first buddy only + } else if (GAIM_BLIST_NODE_IS_BUDDY(n)) + b = (GaimBuddy *) n; + + if (b == NULL) { + gaim_debug(GAIM_DEBUG_ERROR, "QQ", "No valid GaimBuddy is passed from DnD\n"); + return; + } // if b == NULL + + gaim_debug(GAIM_DEBUG_INFO, "QQ", "We get a GaimBuddy: %s\n", b->name); + input_uid = gaim_name_to_uid(b->name); + g_return_if_fail(input_uid > 0); + + model = gtk_tree_view_get_model(GTK_TREE_VIEW(treeview)); + // we need to check if the user id in the member list is unique + // possibly a tree transverse is necessary to achieve this + if (gtk_tree_model_get_iter_first(model, &iter)) { + gtk_tree_model_get_value(model, &iter, COLUMN_UID, &value); + uid = g_value_get_uint(&value); + g_value_unset(&value); + while (uid != input_uid && gtk_tree_model_iter_next(model, &iter)) { + gtk_tree_model_get_value(model, &iter, COLUMN_UID, &value); + uid = g_value_get_uint(&value); + g_value_unset(&value); + } // while + } else + uid = 0; // if gtk_tree_model_get_iter_first + + if (uid == input_uid) { + gaim_debug(GAIM_DEBUG_WARNING, "QQ", "Qun already has this buddy %s\n", b->name); + return; + } else { // we add it to list + store = GTK_LIST_STORE(model); + gtk_list_store_append(store, &iter); + gtk_list_store_set(store, &iter, + COLUMN_SELECTED, FALSE, COLUMN_UID, input_uid, COLUMN_NICKNAME, b->alias, -1); + // re-sort the list + gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(store), COLUMN_UID, GTK_SORT_ASCENDING); + } // if uid + +} // _qq_group_member_list_drag_data_rcv_cb + +/*****************************************************************************/ +static GtkWidget *_create_page_info(GaimConnection * gc, qq_group * group, gboolean do_manage, qun_info_window * info_window) { + GtkWidget *vbox, *hbox; + GtkWidget *frame_info, *frame_auth; + GtkWidget *tbl_info; + GtkWidget *label, *entry, *combo, *text, *scrolled_window; + gint i; + + g_return_val_if_fail(gc != NULL && group != NULL, NULL); + + vbox = gtk_vbox_new(FALSE, 5); + + frame_info = gtk_frame_new(NULL); + gtk_box_pack_start(GTK_BOX(vbox), frame_info, TRUE, TRUE, 0); + + tbl_info = gtk_table_new(6, 4, FALSE); + gtk_table_set_row_spacings(GTK_TABLE(tbl_info), 4); + gtk_table_set_col_spacing(GTK_TABLE(tbl_info), 1, 10); + gtk_container_add(GTK_CONTAINER(frame_info), tbl_info); + + label = gtk_label_new(_("Group ID: ")); + gtk_table_attach(GTK_TABLE(tbl_info), label, 0, 1, 0, 1, GTK_FILL, GTK_FILL, 0, 0); + label = gtk_label_new(g_strdup_printf("%d", group->external_group_id)); + gtk_table_attach(GTK_TABLE(tbl_info), label, 1, 2, 0, 1, GTK_FILL, GTK_FILL, 0, 0); + info_window->lbl_external_group_id = label; + + label = gtk_label_new(_("Group Name")); + gtk_table_attach(GTK_TABLE(tbl_info), label, 2, 3, 0, 1, GTK_FILL, GTK_FILL, 0, 0); + entry = gtk_entry_new(); + gtk_widget_set_size_request(entry, 100, -1); + if (group->group_name_utf8 != NULL) + gtk_entry_set_text(GTK_ENTRY(entry), group->group_name_utf8); + gtk_table_attach(GTK_TABLE(tbl_info), entry, 3, 4, 0, 1, GTK_FILL, GTK_FILL, 0, 0); + info_window->ent_group_name = entry; + + label = gtk_label_new(_("Admin: ")); + gtk_table_attach(GTK_TABLE(tbl_info), label, 0, 1, 1, 2, GTK_FILL, GTK_FILL, 0, 0); + label = gtk_label_new(g_strdup_printf("%d", group->creator_uid)); + gtk_table_attach(GTK_TABLE(tbl_info), label, 1, 2, 1, 2, GTK_FILL, GTK_FILL, 0, 0); + info_window->lbl_admin_uid = label; + + label = gtk_label_new(_("Category")); + gtk_table_attach(GTK_TABLE(tbl_info), label, 2, 3, 1, 2, GTK_FILL, GTK_FILL, 0, 0); + + //henry: these codes are supported only in GTK-2.4 or later +#if GTK_CHECK_VERSION(2, 4, 0) + combo = gtk_combo_box_new_text(); + for (i = 0; i < 4; i++) + gtk_combo_box_append_text(GTK_COMBO_BOX(combo), qq_group_category[i]); + gtk_combo_box_set_active(GTK_COMBO_BOX(combo), group->group_category); +#else + GtkWidget *menu; + GtkWidget *item; + + combo = gtk_option_menu_new(); + menu = gtk_menu_new(); + for (i = 0; i < 4; i++) { + item = gtk_menu_item_new_with_label(qq_group_category[i]); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), item); + gtk_widget_show(item); + } + gtk_option_menu_set_menu(GTK_OPTION_MENU(combo), menu); + gtk_option_menu_set_history(GTK_OPTION_MENU(combo), group->group_category); +#endif /* GTK_CHECK_VERSION */ + + gtk_table_attach(GTK_TABLE(tbl_info), combo, 3, 4, 1, 2, GTK_FILL, GTK_FILL, 0, 0); + info_window->cmb_group_category = combo; + + label = gtk_label_new(_("Description")); + gtk_table_attach(GTK_TABLE(tbl_info), label, 0, 1, 2, 3, GTK_FILL, GTK_FILL, 0, 0); + text = gtk_text_view_new(); + gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text), GTK_WRAP_WORD); + gtk_widget_set_size_request(text, -1, 50); + if (group->group_desc_utf8 != NULL) + gtk_text_buffer_set_text(gtk_text_view_get_buffer(GTK_TEXT_VIEW(text)), group->group_desc_utf8, -1); + info_window->txt_group_desc = text; + + scrolled_window = gtk_scrolled_window_new(NULL, NULL); + gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_window), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC); + gtk_container_add(GTK_CONTAINER(scrolled_window), text); + gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolled_window), GTK_SHADOW_IN); + gtk_text_view_set_left_margin(GTK_TEXT_VIEW(text), 2); + gtk_text_view_set_right_margin(GTK_TEXT_VIEW(text), 2); + gtk_table_attach(GTK_TABLE(tbl_info), scrolled_window, 0, 4, 3, 4, GTK_FILL, GTK_FILL, 0, 0); + + label = gtk_label_new(_("Group Notice")); + gtk_table_attach(GTK_TABLE(tbl_info), label, 0, 1, 4, 5, GTK_FILL, GTK_FILL, 0, 0); + text = gtk_text_view_new(); + gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text), GTK_WRAP_WORD); + gtk_widget_set_size_request(text, -1, 50); + if (group->notice_utf8 != NULL) + gtk_text_buffer_set_text(gtk_text_view_get_buffer(GTK_TEXT_VIEW(text)), group->notice_utf8, -1); + info_window->txt_group_notice = text; + + scrolled_window = gtk_scrolled_window_new(NULL, NULL); + gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_window), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC); + gtk_container_add(GTK_CONTAINER(scrolled_window), text); + gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolled_window), GTK_SHADOW_IN); + gtk_text_view_set_left_margin(GTK_TEXT_VIEW(text), 2); + gtk_text_view_set_right_margin(GTK_TEXT_VIEW(text), 2); + gtk_table_attach(GTK_TABLE(tbl_info), scrolled_window, 0, 4, 5, 6, GTK_FILL, GTK_FILL, 0, 0); + + frame_auth = gtk_frame_new(_("Authentication")); + hbox = gtk_hbox_new(FALSE, 5); + gtk_container_add(GTK_CONTAINER(frame_auth), hbox); + info_window->rad_auth[0] = gtk_radio_button_new_with_label(NULL, qq_group_auth_type_desc[0]); + info_window->rad_auth[1] = + gtk_radio_button_new_with_label_from_widget(GTK_RADIO_BUTTON + (info_window->rad_auth[0]), qq_group_auth_type_desc[1]); + info_window->rad_auth[2] = + gtk_radio_button_new_with_label_from_widget(GTK_RADIO_BUTTON + (info_window->rad_auth[0]), qq_group_auth_type_desc[2]); + for (i = 0; i < 3; i++) + gtk_box_pack_start(GTK_BOX(hbox), info_window->rad_auth[i], FALSE, FALSE, 0); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(info_window->rad_auth[group->auth_type - 1]), TRUE); + gtk_box_pack_start(GTK_BOX(vbox), frame_auth, FALSE, FALSE, 0); + + if (!do_manage) { + gtk_widget_set_sensitive(frame_info, FALSE); + gtk_widget_set_sensitive(frame_auth, FALSE); + } // if ! do_manage + + return vbox; +} // _create_info_page + +/*****************************************************************************/ +static GtkWidget *_create_page_members + (GaimConnection * gc, qq_group * group, gboolean do_manage, qun_info_window * info_window) { + GtkWidget *vbox, *sw, *treeview; + GtkTreeModel *model; + GtkListStore *store; + GtkTreeIter iter; + GtkCellRenderer *renderer; + GtkTreeViewColumn *column; + GList *list; + qq_buddy *q_bud; + GtkTargetEntry gte = { "GAIM_BLIST_NODE", GTK_TARGET_SAME_APP, 0 }; + + g_return_val_if_fail(gc != NULL && group != NULL, NULL); + + vbox = gtk_vbox_new(FALSE, 0); + + if (group->members == NULL) { // if NULL, not ready + sw = gtk_label_new(_ + ("OpenQ is collecting member information.\nPlease close this window and open again")); + gtk_box_pack_start(GTK_BOX(vbox), sw, TRUE, TRUE, 0); + return vbox; + } // if group->members + + sw = gtk_scrolled_window_new(NULL, NULL); + gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(sw), GTK_SHADOW_ETCHED_IN); + gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(sw), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC); + gtk_box_pack_start(GTK_BOX(vbox), sw, TRUE, TRUE, 0); + + store = gtk_list_store_new(NUM_COLUMNS, G_TYPE_BOOLEAN, G_TYPE_UINT, G_TYPE_STRING); + + list = group->members; + while (list != NULL) { + q_bud = (qq_buddy *) list->data; + gtk_list_store_append(store, &iter); + gtk_list_store_set(store, &iter, + COLUMN_SELECTED, FALSE, + COLUMN_UID, q_bud->uid, COLUMN_NICKNAME, q_bud->nickname, -1); + list = list->next; + } // for + + model = GTK_TREE_MODEL(store); + treeview = gtk_tree_view_new_with_model(model); + info_window->tre_members = treeview; + + gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(treeview), TRUE); + gtk_tree_view_set_search_column(GTK_TREE_VIEW(treeview), COLUMN_UID); + g_object_unref(model); + + // set up drag & drop ONLY for managable Qun + if (do_manage) { + gtk_tree_view_enable_model_drag_dest(GTK_TREE_VIEW(treeview), >e, 1, GDK_ACTION_COPY); + g_signal_connect(G_OBJECT(treeview), "drag-data-received", + G_CALLBACK(_qq_group_member_list_drag_data_rcv_cb), gc); + } // if do manage + + gtk_container_add(GTK_CONTAINER(sw), treeview); + + model = gtk_tree_view_get_model(GTK_TREE_VIEW(treeview)); + renderer = gtk_cell_renderer_toggle_new(); + + // it seems this signal has to be handled + // otherwise, the checkbox in the column does not reponse to user action + if (do_manage) + g_signal_connect(renderer, "toggled", G_CALLBACK(_qq_group_member_list_deleted_toggled), info_window); + + column = gtk_tree_view_column_new_with_attributes(_("Del"), renderer, "active", COLUMN_SELECTED, NULL); + + gtk_tree_view_column_set_sizing(GTK_TREE_VIEW_COLUMN(column), GTK_TREE_VIEW_COLUMN_FIXED); + gtk_tree_view_column_set_fixed_width(GTK_TREE_VIEW_COLUMN(column), 30); + gtk_tree_view_append_column(GTK_TREE_VIEW(treeview), column); + + renderer = gtk_cell_renderer_text_new(); + column = gtk_tree_view_column_new_with_attributes(_("UID"), renderer, "text", COLUMN_UID, NULL); + gtk_tree_view_column_set_sort_column_id(column, COLUMN_UID); + gtk_tree_view_append_column(GTK_TREE_VIEW(treeview), column); + // default sort by UID + gtk_tree_view_column_set_sort_order(column, GTK_SORT_ASCENDING); + gtk_tree_view_column_set_sort_indicator(column, TRUE); + + renderer = gtk_cell_renderer_text_new(); + column = gtk_tree_view_column_new_with_attributes(_("Nickname"), renderer, "text", COLUMN_NICKNAME, NULL); + gtk_tree_view_append_column(GTK_TREE_VIEW(treeview), column); + + return vbox; +} // _create_page_members + +/*****************************************************************************/ +void qq_group_detail_window_show(GaimConnection * gc, qq_group * group) +{ + GtkWidget *vbox, *notebook; + GtkWidget *label, *bbox; + GList *list; + qq_data *qd; + qun_info_window *info_window = NULL; + gboolean do_manage, do_show, do_exist; + + g_return_if_fail(gc != NULL && gc->proto_data != NULL && group != NULL); + qd = (qq_data *) gc->proto_data; + + do_manage = group->my_status == QQ_GROUP_MEMBER_STATUS_IS_ADMIN; + do_show = do_manage || (group->my_status == QQ_GROUP_MEMBER_STATUS_IS_MEMBER); + + if (!do_show) { + gaim_notify_error(gc, _("QQ Qun Operation"), + _("You can not view Qun details"), + _("Only Qun admin or Qun member can view details")); + return; + } // if ! do_show + + list = qd->qun_info_window; + do_exist = FALSE; + while (list != NULL) { + info_window = (qun_info_window *) list->data; + if (info_window->internal_group_id == group->internal_group_id) { + break; + do_exist = TRUE; + } else + list = list->next; + } // while list + + if (!do_exist) { + info_window = g_new0(qun_info_window, 1); + info_window->gc = gc; + info_window->internal_group_id = group->internal_group_id; + g_list_append(qd->qun_info_window, info_window); + + info_window->window = gtk_window_new(GTK_WINDOW_TOPLEVEL); + g_signal_connect(GTK_WINDOW(info_window->window), + "delete_event", G_CALLBACK(_qq_group_info_window_deleteevent), NULL); + g_signal_connect(G_OBJECT(info_window->window), "destroy", + G_CALLBACK(_qq_group_info_window_destroy), gc); + + gtk_window_set_title(GTK_WINDOW(info_window->window), _("Manage Qun")); + gtk_window_set_resizable(GTK_WINDOW(info_window->window), FALSE); + gtk_container_set_border_width(GTK_CONTAINER(info_window->window), 5); + + vbox = gtk_vbox_new(FALSE, 0); + gtk_container_add(GTK_CONTAINER(info_window->window), vbox); + + notebook = gtk_notebook_new(); + info_window->notebook = notebook; + gtk_notebook_set_tab_pos(GTK_NOTEBOOK(notebook), GTK_POS_TOP); + gtk_box_pack_start(GTK_BOX(vbox), notebook, TRUE, TRUE, 0); + + label = gtk_label_new(_("Qun Information")); + gtk_notebook_append_page(GTK_NOTEBOOK(notebook), + _create_page_info(gc, group, do_manage, info_window), label); + label = gtk_label_new(_("Members")); + gtk_notebook_append_page(GTK_NOTEBOOK(notebook), + _create_page_members(gc, group, do_manage, info_window), label); + + bbox = gtk_hbutton_box_new(); + gtk_button_box_set_layout(GTK_BUTTON_BOX(bbox), GTK_BUTTONBOX_SPREAD); + gtk_box_set_spacing(GTK_BOX(bbox), 10); + + info_window->btn_mod = gtk_button_new_with_label(_("Modify")); + gtk_container_add(GTK_CONTAINER(bbox), info_window->btn_mod); + g_signal_connect(G_OBJECT(info_window->btn_mod), "clicked", + G_CALLBACK(_qq_group_info_window_modify), info_window); + + info_window->btn_close = gtk_button_new_with_label(_("Close")); + gtk_container_add(GTK_CONTAINER(bbox), info_window->btn_close); + g_signal_connect(G_OBJECT(info_window->btn_close), + "clicked", G_CALLBACK(_qq_group_info_window_close), info_window->window); + + if (!do_manage) + gtk_widget_set_sensitive(info_window->btn_mod, FALSE); + gtk_box_pack_start(GTK_BOX(vbox), bbox, TRUE, TRUE, 5); + gtk_widget_show_all(info_window->window); + + } else // we already have this + gtk_widget_grab_focus(info_window->window); + +} // qq_group_manage_window_show + +/*****************************************************************************/ +// END OF FILE diff -r 5642f4658b59 -r 983fd420e86b src/protocols/qq/group_admindlg.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/protocols/qq/group_admindlg.h Mon Jun 26 02:58:54 2006 +0000 @@ -0,0 +1,39 @@ +/** +* The QQ2003C protocol plugin + * + * for gaim + * + * Copyright (C) 2004 Puzzlebird + * + * 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 + */ + +// START OF FILE +/*****************************************************************************/ +#ifndef _QQ_GROUP_ADMINDLG_H_ +#define _QQ_GROUP_ADMINDLG_H_ + +#include +#include +#include "connection.h" +#include "group.h" +#include "qq.h" + +void qq_group_detail_window_show(GaimConnection * gc, qq_group * group); + +void qq_qun_info_window_free(qq_data * qd); +#endif +/*****************************************************************************/ +// END OF FILE diff -r 5642f4658b59 -r 983fd420e86b src/protocols/qq/group_conv.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/protocols/qq/group_conv.c Mon Jun 26 02:58:54 2006 +0000 @@ -0,0 +1,102 @@ +/** +* The QQ2003C protocol plugin + * + * for gaim + * + * Copyright (C) 2004 Puzzlebird + * + * 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 + */ + +// START OF FILE +/*****************************************************************************/ +#include // GList +#include "conversation.h" // GaimConversation + +#include "utils.h" // uid_to_gaim_name +#include "buddy_status.h" // is_online +#include "group_conv.h" +#include "qq.h" // qq_buddy + +/*****************************************************************************/ +// show group conversation window +void qq_group_conv_show_window(GaimConnection * gc, qq_group * group) +{ + GaimConversation *conv; + qq_data *qd; + + g_return_if_fail(gc != NULL && gc->proto_data != NULL && group != NULL); + qd = (qq_data *) gc->proto_data; + + conv = gaim_find_conversation_with_account(GAIM_CONV_TYPE_CHAT, /* add by gfhuang */group->group_name_utf8, gaim_connection_get_account(gc)); + if (conv == NULL) // show only one window per group + serv_got_joined_chat(gc, qd->channel++, group->group_name_utf8); +} // qq_group_conv_show_window + +/*****************************************************************************/ +// refresh online member in group conversation window +void qq_group_conv_refresh_online_member(GaimConnection * gc, qq_group * group) +{ + GList *names, *list, *flags; + qq_buddy *member; + gchar *member_name; + GaimConversation *conv; + gint flag; + g_return_if_fail(gc != NULL && group != NULL); + + names = NULL; + flags = NULL; + conv = gaim_find_conversation_with_account(GAIM_CONV_TYPE_CHAT /*gfhuang*/, group->group_name_utf8, gaim_connection_get_account(gc)); + if (conv != NULL && group->members != NULL) { + list = group->members; + while (list != NULL) { + member = (qq_buddy *) list->data; + //always put it even offline, by gfhuang + names = g_list_append(names, + (member->nickname != + NULL) ? + g_strdup(member->nickname) : uid_to_gaim_name(member->uid)); + + flag = 0; + if (is_online(member->status)) flag |= (GAIM_CBFLAGS_TYPING | GAIM_CBFLAGS_VOICE); // TYPING to put online above OP and FOUNDER + if(1 == (member->role & 1)) flag |= GAIM_CBFLAGS_OP; + //if(4 == (member->role & 4)) flag |= GAIM_CBFLAGS_VOICE; //active, no use + if(member->uid == group->creator_uid) flag |= GAIM_CBFLAGS_FOUNDER; + flags = g_list_append(flags, GINT_TO_POINTER(flag)); + list = list->next; + } // while list + + gaim_conv_chat_clear_users(GAIM_CONV_CHAT(conv)); + gaim_conv_chat_add_users(GAIM_CONV_CHAT(conv), names, NULL /*gfhuang*/, flags, FALSE /*gfhuang*/); + } // if conv + // clean up names + while (names != NULL) { + member_name = (gchar *) names->data; + names = g_list_remove(names, member_name); + g_free(member_name); + } // while name + // clean up flags, NOOOOOOOOOOOOOOOOOOOOOOO!!!!!! bugs, flags is not name! got by gfhuang + /* + while (flags != NULL) { + member_name = (gchar *) flags->data; + flags = g_list_remove(flags, member_name); + g_free(member_name); + } + */ + g_list_free(flags); +} // qq_group_conv_show_window + +/*****************************************************************************/ +// END OF FILE diff -r 5642f4658b59 -r 983fd420e86b src/protocols/qq/group_conv.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/protocols/qq/group_conv.h Mon Jun 26 02:58:54 2006 +0000 @@ -0,0 +1,36 @@ +/** +* The QQ2003C protocol plugin + * + * for gaim + * + * Copyright (C) 2004 Puzzlebird + * + * 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 + */ + +// START OF FILE +/*****************************************************************************/ +#ifndef _QQ_GROUP_CONV_H_ +#define _QQ_GROUP_CONV_H_ + +#include "connection.h" // GaimConnection +#include "group.h" // qq_group + +void qq_group_conv_show_window(GaimConnection * gc, qq_group * group); +void qq_group_conv_refresh_online_member(GaimConnection * gc, qq_group * group); + +#endif +/*****************************************************************************/ +// END OF FILE diff -r 5642f4658b59 -r 983fd420e86b src/protocols/qq/group_find.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/protocols/qq/group_find.c Mon Jun 26 02:58:54 2006 +0000 @@ -0,0 +1,207 @@ +/** + * The QQ2003C protocol plugin + * + * for gaim + * + * Copyright (C) 2004 Puzzlebird + * + * 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 + */ + +// START OF FILE +/*****************************************************************************/ +#include "debug.h" // gaim_debug +#include "conversation.h" // GaimConversation + +#include "utils.h" // QQ_NAME_PREFIX, gaim_name_to_uid +#include "group_find.h" +#include "group_network.h" // group_packet +#include "qq.h" // qq_data + +/*****************************************************************************/ +// find a chat member's valid gaim_name of its nickname and chat room channel +gchar *qq_group_find_member_by_channel_and_nickname(GaimConnection * gc, gint channel, const gchar * who) { + qq_group *group; + qq_buddy *member; + GList *list; + + g_return_val_if_fail(gc != NULL && who != NULL, NULL); + + // if it starts with QQ_NAME_PREFIX, we think it is valid name already + // otherwise we think it is nickname and try to find the matching gaim_name + if (g_str_has_prefix(who, QQ_NAME_PREFIX) && gaim_name_to_uid(who) > 0) + return (gchar *) who; + + group = qq_group_find_by_channel(gc, channel); + g_return_val_if_fail(group != NULL, NULL); + + list = group->members; + member = NULL; + while (list != NULL) { + member = (qq_buddy *) list->data; + if (member->nickname != NULL && !g_ascii_strcasecmp(member->nickname, who)) + break; + list = list->next; + } // while list + + return (member == NULL) ? NULL : uid_to_gaim_name(member->uid); + +} // qq_group_find_member_by_channel_and_nickname + +/*****************************************************************************/ +// find the internal_group_id by the reply packet sequence +// return TRUE if we have a record of it, return FALSE if not +gboolean qq_group_find_internal_group_id_by_seq(GaimConnection * gc, guint16 seq, guint32 * internal_group_id) { + GList *list; + qq_data *qd; + group_packet *p; + + g_return_val_if_fail(gc != NULL && gc->proto_data != NULL && internal_group_id != NULL, FALSE); + qd = (qq_data *) gc->proto_data; + + list = qd->group_packets; + while (list != NULL) { + p = (group_packet *) (list->data); + if (p->send_seq == seq) { // found and remove + *internal_group_id = p->internal_group_id; + qd->group_packets = g_list_remove(qd->group_packets, p); + g_free(p); + return TRUE; + } // if + list = list->next; + } // while + + return FALSE; +} // qq_group_find_internal_group_id_by_seq + +/*****************************************************************************/ +// find a qq_buddy by uid, called by qq_im.c +qq_buddy *qq_group_find_member_by_uid(qq_group * group, guint32 uid) +{ + GList *list; + qq_buddy *member; + g_return_val_if_fail(group != NULL && uid > 0, NULL); + + list = group->members; + while (list != NULL) { + member = (qq_buddy *) list->data; + if (member->uid == uid) + return member; + else + list = list->next; + } // while list + + return NULL; +} // qq_group_find_member_by_uid + +/*****************************************************************************/ +// remove a qq_buddy by uid, called by qq_group_opt.c +void qq_group_remove_member_by_uid(qq_group * group, guint32 uid) +{ + GList *list; + qq_buddy *member; + g_return_if_fail(group != NULL && uid > 0); + + list = group->members; + while (list != NULL) { + member = (qq_buddy *) list->data; + if (member->uid == uid) { + group->members = g_list_remove(group->members, member); + return; + } else + list = list->next; + } // while list + +} // qq_group_remove_member_by_uid + +/*****************************************************************************/ +qq_buddy *qq_group_find_or_add_member(GaimConnection * gc, qq_group * group, guint32 member_uid) { + qq_buddy *member, *q_bud; + GaimBuddy *buddy; + g_return_val_if_fail(gc != NULL && group != NULL && member_uid > 0, NULL); + + member = qq_group_find_member_by_uid(group, member_uid); + if (member == NULL) { // first appear during my session + member = g_new0(qq_buddy, 1); + member->uid = member_uid; + buddy = gaim_find_buddy(gaim_connection_get_account(gc), uid_to_gaim_name(member_uid)); + if (buddy != NULL) { + q_bud = (qq_buddy *) buddy->proto_data; + if (q_bud != NULL) + member->nickname = g_strdup(q_bud->nickname); + else if (buddy->alias != NULL) + member->nickname = g_strdup(buddy->alias); + } // if buddy != NULL + group->members = g_list_append(group->members, member); + } // if member + + return member; +} // qq_group_find_or_add_member + +/*****************************************************************************/ +// find a qq_group by chatroom channel +qq_group *qq_group_find_by_channel(GaimConnection * gc, gint channel) +{ + GaimConversation *conv; + qq_data *qd; + qq_group *group; + GList *list; + + g_return_val_if_fail(gc != NULL && gc->proto_data != NULL, NULL); + qd = (qq_data *) gc->proto_data; + + conv = gaim_find_chat(gc, channel); + g_return_val_if_fail(conv != NULL, NULL); + + list = qd->groups; + group = NULL; + while (list != NULL) { + group = (qq_group *) list->data; + if (!g_ascii_strcasecmp(gaim_conversation_get_name(conv), group->group_name_utf8)) + break; + list = list->next; + } // while list + + return group; + +} // qq_group_find_by_name + +/*****************************************************************************/ +// find a qq_group by internal_group_id +qq_group *qq_group_find_by_internal_group_id(GaimConnection * gc, guint32 internal_group_id) { + + GList *list; + qq_group *group; + qq_data *qd; + + g_return_val_if_fail(gc != NULL && gc->proto_data != NULL && internal_group_id > 0, NULL); + + qd = (qq_data *) gc->proto_data; + if (qd->groups == NULL) + return NULL; + + list = qd->groups; + while (list != NULL) { + group = (qq_group *) list->data; + if (group->internal_group_id == internal_group_id) + return group; + list = list->next; + } // while + + return NULL; +} // qq_group_find_by_internal_group_id + +/*****************************************************************************/ +// END OF FILE diff -r 5642f4658b59 -r 983fd420e86b src/protocols/qq/group_find.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/protocols/qq/group_find.h Mon Jun 26 02:58:54 2006 +0000 @@ -0,0 +1,42 @@ +/** + * The QQ2003C protocol plugin + * + * for gaim + * + * Copyright (C) 2004 Puzzlebird + * + * 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 + */ + +// START OF FILE +/*****************************************************************************/ +#ifndef _QQ_GROUP_FIND_H_ +#define _QQ_GROUP_FIND_H_ + +#include +#include "connection.h" // GaimConnection +#include "group.h" // qq_group + +gchar *qq_group_find_member_by_channel_and_nickname(GaimConnection * gc, gint channel, const gchar * who); +qq_buddy *qq_group_find_member_by_uid(qq_group * group, guint32 uid); +void qq_group_remove_member_by_uid(qq_group * group, guint32 uid); +qq_buddy *qq_group_find_or_add_member(GaimConnection * gc, qq_group * group, guint32 member_uid); +gboolean qq_group_find_internal_group_id_by_seq(GaimConnection * gc, guint16 seq, guint32 * internal_group_id); +qq_group *qq_group_find_by_channel(GaimConnection * gc, gint channel); +qq_group *qq_group_find_by_internal_group_id(GaimConnection * gc, guint32 internal_group_id); + +#endif +/*****************************************************************************/ +// END OF FILE diff -r 5642f4658b59 -r 983fd420e86b src/protocols/qq/group_free.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/protocols/qq/group_free.c Mon Jun 26 02:58:54 2006 +0000 @@ -0,0 +1,120 @@ +/** +* The QQ2003C protocol plugin + * + * for gaim + * + * Copyright (C) 2004 Puzzlebird + * + * 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 + */ + +// START OF FILE +/*****************************************************************************/ +#include "debug.h" // gaim_debug + +#include "buddy_status.h" // QQ_BUDDY_ONLINE_OFFLINE +#include "group_free.h" +#include "group_network.h" // group_packet +#include "group.h" // qq_group + +/*****************************************************************************/ +// gracefully free all members in a group +static void _qq_group_free_member(qq_group * group) +{ + gint i; + GList *list; + qq_buddy *member; + + g_return_if_fail(group != NULL); + i = 0; + while (NULL != (list = group->members)) { + member = (qq_buddy *) list->data; + i++; + group->members = g_list_remove(group->members, member); + g_free(member->nickname); + g_free(member); + } // while + + group->members = NULL; + +} // _qq_group_free_member + +/*****************************************************************************/ +// gracefully free the memory for one qq_group +static void _qq_group_free(qq_group * group) +{ + g_return_if_fail(group != NULL); + _qq_group_free_member(group); + g_free(group->group_name_utf8); + g_free(group->group_desc_utf8); + g_free(group); +} // _qq_group_free + +/*****************************************************************************/ +// clean up group_packets and free all contents +void qq_group_packets_free(qq_data * qd) +{ + group_packet *p; + gint i; + + i = 0; + while (qd->group_packets != NULL) { + p = (group_packet *) (qd->group_packets->data); + qd->group_packets = g_list_remove(qd->group_packets, p); + g_free(p); + i++; + } // while + gaim_debug(GAIM_DEBUG_INFO, "QQ", "%d group packets are freed!\n", i); +} // qq_group_packets_free + +/*****************************************************************************/ +void qq_group_remove_by_internal_group_id(qq_data * qd, guint32 internal_group_id) { + qq_group *group; + GList *list; + g_return_if_fail(qd != NULL); + + list = qd->groups; + while (list != NULL) { + group = (qq_group *) qd->groups->data; + if (internal_group_id == group->internal_group_id) { + qd->groups = g_list_remove(qd->groups, group); + _qq_group_free(group); + break; + } else + list = list->next; + } // while + +} // qq_group_free_all + +/*****************************************************************************/ +void qq_group_free_all(qq_data * qd) +{ + qq_group *group; + gint i; + g_return_if_fail(qd != NULL); + + i = 0; + while (qd->groups != NULL) { + i++; + group = (qq_group *) qd->groups->data; + qd->groups = g_list_remove(qd->groups, group); + _qq_group_free(group); + } // while + + gaim_debug(GAIM_DEBUG_INFO, "QQ", "%d groups are freed\n", i); +} // qq_group_free_all + +/*****************************************************************************/ +// END OF FILE diff -r 5642f4658b59 -r 983fd420e86b src/protocols/qq/group_free.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/protocols/qq/group_free.h Mon Jun 26 02:58:54 2006 +0000 @@ -0,0 +1,39 @@ +/** +* The QQ2003C protocol plugin + * + * for gaim + * + * Copyright (C) 2004 Puzzlebird + * + * 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 + */ + +// START OF FILE +/*****************************************************************************/ +#ifndef _QQ_GROUP_FREE_H_ +#define _QQ_GROUP_FREE_H_ + +#include +#include "qq.h" // qq_data + +void qq_group_packets_free(qq_data * qd); + +void qq_group_free_all(qq_data * qd); + +void qq_group_remove_by_internal_group_id(qq_data * qd, guint32 internal_group_id); + +#endif +/*****************************************************************************/ +// END OF FILE diff -r 5642f4658b59 -r 983fd420e86b src/protocols/qq/group_hash.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/protocols/qq/group_hash.c Mon Jun 26 02:58:54 2006 +0000 @@ -0,0 +1,205 @@ +/** +* The QQ2003C protocol plugin + * + * for gaim + * + * Copyright (C) 2004 Puzzlebird + * + * 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 + */ + +// START OF FILE +/*****************************************************************************/ +#include "blist.h" // gaim_blist_find_chat +#include "debug.h" // gaim_debug + +#include "utils.h" // qq_string_to_dec_value +#include "buddy_opt.h" // qq_get_gaim_group +#include "group_hash.h" +#include "group_misc.h" // qq_group_get_auth_index_str + +/*****************************************************************************/ +static gchar *_qq_group_set_my_status_desc(qq_group * group) +{ + const char *status_desc; + g_return_val_if_fail(group != NULL, g_strdup("")); + + switch (group->my_status) { + case QQ_GROUP_MEMBER_STATUS_NOT_MEMBER: + status_desc = _("I am not member"); + break; + case QQ_GROUP_MEMBER_STATUS_IS_MEMBER: + status_desc = _("I am a member"); + break; + case QQ_GROUP_MEMBER_STATUS_APPLYING: + status_desc = _("I am applying to join"); + break; + case QQ_GROUP_MEMBER_STATUS_IS_ADMIN: + status_desc = _("I am the admin"); + break; + default: + status_desc = _("Unknown status"); + } // switch + + return g_strdup(status_desc); +} // _qq_group_set_my_status_desc + +/*****************************************************************************/ +static void _qq_group_add_to_blist(GaimConnection * gc, qq_group * group) +{ + GHashTable *components; + GaimGroup *g; + GaimChat *chat; + components = qq_group_to_hashtable(group); + chat = gaim_chat_new(gaim_connection_get_account(gc), group->group_name_utf8, components); + g = qq_get_gaim_group(GAIM_GROUP_QQ_QUN); + gaim_blist_add_chat(chat, g, NULL); + gaim_debug(GAIM_DEBUG_INFO, "QQ", "You have add group \"%s\" to blist locally\n", group->group_name_utf8); +} // _qq_group_add_to_blist + +/*****************************************************************************/ +// create a dummy qq_group, which includes only internal_id and external_id +// all other attributes should be set to empty. +// and we need to send a get_group_info to QQ server to update it right away +qq_group *qq_group_create_by_id(GaimConnection * gc, guint32 internal_id, guint32 external_id) { + qq_group *group; + qq_data *qd; + + g_return_val_if_fail(gc != NULL && gc->proto_data != NULL, NULL); + g_return_val_if_fail(internal_id > 0, NULL); + qd = (qq_data *) gc->proto_data; + + group = g_new0(qq_group, 1); + group->my_status = QQ_GROUP_MEMBER_STATUS_NOT_MEMBER; + group->my_status_desc = _qq_group_set_my_status_desc(group); + group->internal_group_id = internal_id; + group->external_group_id = external_id; + group->group_type = 0x01; // assume permanent Qun + group->creator_uid = 10000; // assume by QQ admin + group->group_category = 0x01; + group->auth_type = 0x02; // assume need auth + group->group_name_utf8 = g_strdup(""); + group->group_desc_utf8 = g_strdup(""); + group->notice_utf8 = g_strdup(""); + group->members = NULL; + + qd->groups = g_list_append(qd->groups, group); + _qq_group_add_to_blist(gc, group); + + return group; +} // qq_group_create_by_id + +/*****************************************************************************/ +// convert a qq_group to hash-table, which could be component of GaimChat +GHashTable *qq_group_to_hashtable(qq_group * group) +{ + GHashTable *components; + components = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); + g_hash_table_insert(components, g_strdup(QQ_GROUP_KEY_MEMBER_STATUS), g_strdup_printf("%d", group->my_status)); + group->my_status_desc = _qq_group_set_my_status_desc(group); + + g_hash_table_insert(components, + g_strdup(QQ_GROUP_KEY_INTERNAL_ID), g_strdup_printf("%d", group->internal_group_id)); + g_hash_table_insert(components, g_strdup(QQ_GROUP_KEY_EXTERNAL_ID), + g_strdup_printf("%d", group->external_group_id)); + g_hash_table_insert(components, g_strdup(QQ_GROUP_KEY_GROUP_TYPE), g_strdup_printf("%d", group->group_type)); + g_hash_table_insert(components, g_strdup(QQ_GROUP_KEY_CREATOR_UID), g_strdup_printf("%d", group->creator_uid)); + g_hash_table_insert(components, + g_strdup(QQ_GROUP_KEY_GROUP_CATEGORY), g_strdup_printf("%d", group->group_category)); + g_hash_table_insert(components, g_strdup(QQ_GROUP_KEY_AUTH_TYPE), g_strdup_printf("%d", group->auth_type)); + g_hash_table_insert(components, g_strdup(QQ_GROUP_KEY_MEMBER_STATUS_DESC), g_strdup(group->my_status_desc)); + g_hash_table_insert(components, g_strdup(QQ_GROUP_KEY_GROUP_NAME_UTF8), g_strdup(group->group_name_utf8)); + g_hash_table_insert(components, g_strdup(QQ_GROUP_KEY_GROUP_DESC_UTF8), g_strdup(group->group_desc_utf8)); + return components; +} // qq_group_to_hashtable + +/*****************************************************************************/ +// create a qq_group from hashtable +qq_group *qq_group_from_hashtable(GaimConnection * gc, GHashTable * data) +{ + qq_data *qd; + qq_group *group; + + g_return_val_if_fail(gc != NULL && gc->proto_data != NULL, NULL); + g_return_val_if_fail(data != NULL, NULL); + qd = (qq_data *) gc->proto_data; + + group = g_new0(qq_group, 1); + group->my_status = + qq_string_to_dec_value + (NULL == + g_hash_table_lookup(data, + QQ_GROUP_KEY_MEMBER_STATUS) ? + g_strdup_printf("%d", + QQ_GROUP_MEMBER_STATUS_NOT_MEMBER) : + g_hash_table_lookup(data, QQ_GROUP_KEY_MEMBER_STATUS)); + group->internal_group_id = qq_string_to_dec_value(g_hash_table_lookup(data, QQ_GROUP_KEY_INTERNAL_ID)); + group->external_group_id = qq_string_to_dec_value(g_hash_table_lookup(data, QQ_GROUP_KEY_EXTERNAL_ID)); + group->group_type = qq_string_to_dec_value(g_hash_table_lookup(data, QQ_GROUP_KEY_GROUP_TYPE)); + group->creator_uid = qq_string_to_dec_value(g_hash_table_lookup(data, QQ_GROUP_KEY_CREATOR_UID)); + group->group_category = qq_string_to_dec_value(g_hash_table_lookup(data, QQ_GROUP_KEY_GROUP_CATEGORY)); + group->auth_type = qq_string_to_dec_value(g_hash_table_lookup(data, QQ_GROUP_KEY_AUTH_TYPE)); + group->group_name_utf8 = g_strdup(g_hash_table_lookup(data, QQ_GROUP_KEY_GROUP_NAME_UTF8)); + group->group_desc_utf8 = g_strdup(g_hash_table_lookup(data, QQ_GROUP_KEY_GROUP_DESC_UTF8)); + group->my_status_desc = _qq_group_set_my_status_desc(group); + + qd->groups = g_list_append(qd->groups, group); + + return group; +} // qq_group_from_hashtable + +/*****************************************************************************/ +// refresh group local subscription +void qq_group_refresh(GaimConnection * gc, qq_group * group) +{ + GaimChat *chat; + g_return_if_fail(gc != NULL && group != NULL); + + chat = gaim_blist_find_chat(gaim_connection_get_account(gc), g_strdup_printf("%d", group->external_group_id)); + if (chat == NULL && group->my_status != QQ_GROUP_MEMBER_STATUS_NOT_MEMBER) + _qq_group_add_to_blist(gc, group); + else if (chat != NULL) { // we have a local record, update its info + // if there is group_name_utf8, we update the group name + if (group->group_name_utf8 != NULL && strlen(group->group_name_utf8) > 0) + gaim_blist_alias_chat(chat, group->group_name_utf8); + g_hash_table_replace(chat->components, + g_strdup(QQ_GROUP_KEY_MEMBER_STATUS), g_strdup_printf("%d", group->my_status)); + group->my_status_desc = _qq_group_set_my_status_desc(group); + g_hash_table_replace(chat->components, + g_strdup(QQ_GROUP_KEY_MEMBER_STATUS_DESC), g_strdup(group->my_status_desc)); + g_hash_table_replace(chat->components, + g_strdup(QQ_GROUP_KEY_INTERNAL_ID), + g_strdup_printf("%d", group->internal_group_id)); + g_hash_table_replace(chat->components, + g_strdup(QQ_GROUP_KEY_EXTERNAL_ID), + g_strdup_printf("%d", group->external_group_id)); + g_hash_table_replace(chat->components, + g_strdup(QQ_GROUP_KEY_GROUP_TYPE), g_strdup_printf("%d", group->group_type)); + g_hash_table_replace(chat->components, + g_strdup(QQ_GROUP_KEY_CREATOR_UID), g_strdup_printf("%d", group->creator_uid)); + g_hash_table_replace(chat->components, + g_strdup(QQ_GROUP_KEY_GROUP_CATEGORY), + g_strdup_printf("%d", group->group_category)); + g_hash_table_replace(chat->components, + g_strdup(QQ_GROUP_KEY_AUTH_TYPE), g_strdup_printf("%d", group->auth_type)); + g_hash_table_replace(chat->components, + g_strdup(QQ_GROUP_KEY_GROUP_NAME_UTF8), g_strdup(group->group_name_utf8)); + g_hash_table_replace(chat->components, + g_strdup(QQ_GROUP_KEY_GROUP_DESC_UTF8), g_strdup(group->group_desc_utf8)); + } // if chat +} // qq_group_refresh + +/*****************************************************************************/ +// END OF FILE diff -r 5642f4658b59 -r 983fd420e86b src/protocols/qq/group_hash.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/protocols/qq/group_hash.h Mon Jun 26 02:58:54 2006 +0000 @@ -0,0 +1,50 @@ +/** +* The QQ2003C protocol plugin + * + * for gaim + * + * Copyright (C) 2004 Puzzlebird + * + * 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 + */ + +// START OF FILE +/*****************************************************************************/ +#ifndef _QQ_GROUP_HASH_H_ +#define _QQ_GROUP_HASH_H_ + +#include // GHashTable +#include "group.h" // qq_group + +#define QQ_GROUP_KEY_MEMBER_STATUS "my_status_code" +#define QQ_GROUP_KEY_MEMBER_STATUS_DESC "my_status_desc" +#define QQ_GROUP_KEY_INTERNAL_ID "internal_group_id" +#define QQ_GROUP_KEY_EXTERNAL_ID "external_group_id" +#define QQ_GROUP_KEY_GROUP_TYPE "group_type" +#define QQ_GROUP_KEY_CREATOR_UID "creator_uid" +#define QQ_GROUP_KEY_GROUP_CATEGORY "group_category" +#define QQ_GROUP_KEY_AUTH_TYPE "auth_type" +#define QQ_GROUP_KEY_GROUP_NAME_UTF8 "group_name_utf8" +#define QQ_GROUP_KEY_GROUP_DESC_UTF8 "group_desc_utf8" + +qq_group *qq_group_create_by_id(GaimConnection * gc, guint32 internal_id, guint32 external_id); +GHashTable *qq_group_to_hashtable(qq_group * group); + +qq_group *qq_group_from_hashtable(GaimConnection * gc, GHashTable * data); +void qq_group_refresh(GaimConnection * gc, qq_group * group); + +#endif +/*****************************************************************************/ +// END OF FILE diff -r 5642f4658b59 -r 983fd420e86b src/protocols/qq/group_im.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/protocols/qq/group_im.c Mon Jun 26 02:58:54 2006 +0000 @@ -0,0 +1,413 @@ +/** +* The QQ2003C protocol plugin + * + * for gaim + * + * Copyright (C) 2004 Puzzlebird + * + * 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 + */ + +// START OF FILE +/*****************************************************************************/ +#include "debug.h" +#include "conversation.h" // GaimConversation +#include "notify.h" // gaim_notify_warning +#include "prefs.h" // gaim_prefs_get_bool +#include "request.h" // gaim_request_action +#include "util.h" + +#include "utils.h" // uid_to_gaim_name +#include "packet_parse.h" // create_packet_xx +#include "char_conv.h" // qq_smiley_to_gaim +#include "group_find.h" // qq_group_find_by_external_group_id +#include "group_hash.h" // qq_group_refresh +#include "group_info.h" // qq_send_cmd_group_get_group_info +#include "group_im.h" +#include "group_network.h" // qq_send_group_cmd +#include "group_opt.h" // add_group_member +#include "im.h" // QQ_SEND_IM_AFTER_MSG_LEN + +typedef struct _qq_recv_group_im { + guint32 external_group_id; + guint8 group_type; + guint32 member_uid; + guint16 msg_seq; + time_t send_time; + guint16 msg_len; + guint8 *msg; + guint8 *font_attr; + gint font_attr_len; +} qq_recv_group_im; + +/*****************************************************************************/ +// send IM to a group +void qq_send_packet_group_im(GaimConnection * gc, qq_group * group, const gchar * msg) { + gint data_len, bytes; + guint8 *raw_data, *cursor; + guint16 msg_len; + gchar *msg_filtered; + + g_return_if_fail(gc != NULL && group != NULL && msg != NULL); + + msg_filtered = gaim_markup_strip_html(msg); + msg_len = strlen(msg_filtered); + data_len = 7 + msg_len + QQ_SEND_IM_AFTER_MSG_LEN; + raw_data = g_newa(guint8, data_len); + cursor = raw_data; + + bytes = 0; + bytes += create_packet_b(raw_data, &cursor, QQ_GROUP_CMD_SEND_MSG); + bytes += create_packet_dw(raw_data, &cursor, group->internal_group_id); + bytes += create_packet_w(raw_data, &cursor, msg_len + QQ_SEND_IM_AFTER_MSG_LEN); + bytes += create_packet_data(raw_data, &cursor, (gchar *) msg_filtered, msg_len); + guint8 *send_im_tail = qq_get_send_im_tail(NULL, NULL, NULL, + FALSE, FALSE, FALSE, + QQ_SEND_IM_AFTER_MSG_LEN); + bytes += create_packet_data(raw_data, &cursor, (gchar *) send_im_tail, QQ_SEND_IM_AFTER_MSG_LEN); + g_free(send_im_tail); + g_free(msg_filtered); + + if (bytes == data_len) // create OK + qq_send_group_cmd(gc, group, raw_data, data_len); + else + gaim_debug(GAIM_DEBUG_ERROR, "QQ", + "Fail creating group_im packet, expect %d bytes, build %d bytes\n", data_len, bytes); + +} // qq_send_packet_group_im + +/*****************************************************************************/ +// this is the ACK +void qq_process_group_cmd_im(guint8 * data, guint8 ** cursor, gint len, GaimConnection * gc) { + // return should be the internal group id + // but we have nothing to do with it + return; +} // qq_process_group_cmd_im + +/*****************************************************************************/ +// receive an application to join the group +void qq_process_recv_group_im_apply_join + (guint8 * data, guint8 ** cursor, gint len, guint32 internal_group_id, GaimConnection * gc) { + guint32 external_group_id, user_uid; + guint8 group_type; + gchar *reason_utf8, *msg, *reason; + group_member_opt *g; + + g_return_if_fail(gc != NULL && internal_group_id > 0 && data != NULL && len > 0); + + if (*cursor >= (data + len - 1)) { + gaim_debug(GAIM_DEBUG_WARNING, "QQ", "Received group msg apply_join is empty\n"); + return; + } // if + + read_packet_dw(data, cursor, len, &external_group_id); + read_packet_b(data, cursor, len, &group_type); + read_packet_dw(data, cursor, len, &user_uid); + + g_return_if_fail(external_group_id > 0 && user_uid > 0); + + convert_as_pascal_string(*cursor, &reason_utf8, QQ_CHARSET_DEFAULT); + + msg = g_strdup_printf(_("User %d applied to join group %d"), user_uid, external_group_id); + reason = g_strdup_printf(_("Reason: %s"), reason_utf8); + + g = g_new0(group_member_opt, 1); + g->gc = gc; + g->internal_group_id = internal_group_id; + g->member = user_uid; + + gaim_request_action(gc, _("QQ Qun Operation"), + msg, reason, + 2, g, 3, + _("Approve"), + G_CALLBACK + (qq_group_approve_application_with_struct), + _("Reject"), + G_CALLBACK + (qq_group_reject_application_with_struct), + _("Search"), G_CALLBACK(qq_group_search_application_with_struct)); + + g_free(reason); + g_free(msg); + g_free(reason_utf8); + +} // qq_process_recv_group_im_apply_join + +/*****************************************************************************/ +// the request to join a group is rejected +void qq_process_recv_group_im_been_rejected + (guint8 * data, guint8 ** cursor, gint len, guint32 internal_group_id, GaimConnection * gc) { + guint32 external_group_id, admin_uid; + guint8 group_type; + gchar *reason_utf8, *msg, *reason; + qq_group *group; + + g_return_if_fail(gc != NULL && data != NULL && len > 0); + + if (*cursor >= (data + len - 1)) { + gaim_debug(GAIM_DEBUG_WARNING, "QQ", "Received group msg been_rejected is empty\n"); + return; + } // if + + read_packet_dw(data, cursor, len, &external_group_id); + read_packet_b(data, cursor, len, &group_type); + read_packet_dw(data, cursor, len, &admin_uid); + + g_return_if_fail(external_group_id > 0 && admin_uid > 0); + + convert_as_pascal_string(*cursor, &reason_utf8, QQ_CHARSET_DEFAULT); + + msg = g_strdup_printf + (_("You request to join group %d has been rejected by admin %d"), external_group_id, admin_uid); + reason = g_strdup_printf(_("Reason: %s"), reason_utf8); + + gaim_notify_warning(gc, _("QQ Qun Operation"), msg, reason); + + group = qq_group_find_by_internal_group_id(gc, internal_group_id); + if (group != NULL) { + group->my_status = QQ_GROUP_MEMBER_STATUS_NOT_MEMBER; + qq_group_refresh(gc, group); + } // if group + + g_free(reason); + g_free(msg); + g_free(reason_utf8); + +} // qq_process_group_im_being_rejected + +/*****************************************************************************/ +// the request to join a group is approved +void qq_process_recv_group_im_been_approved + (guint8 * data, guint8 ** cursor, gint len, guint32 internal_group_id, GaimConnection * gc) { + guint32 external_group_id, admin_uid; + guint8 group_type; + gchar *reason_utf8, *msg; + qq_group *group; + + g_return_if_fail(gc != NULL && data != NULL && len > 0); + + if (*cursor >= (data + len - 1)) { + gaim_debug(GAIM_DEBUG_WARNING, "QQ", "Received group msg been_approved is empty\n"); + return; + } // if + + read_packet_dw(data, cursor, len, &external_group_id); + read_packet_b(data, cursor, len, &group_type); + read_packet_dw(data, cursor, len, &admin_uid); + + g_return_if_fail(external_group_id > 0 && admin_uid > 0); + // it is also a "æ— " here, so do not display + convert_as_pascal_string(*cursor, &reason_utf8, QQ_CHARSET_DEFAULT); + + msg = g_strdup_printf + (_("You request to join group %d has been approved by admin %d"), external_group_id, admin_uid); + + gaim_notify_warning(gc, _("QQ Qun Operation"), msg, NULL); + + group = qq_group_find_by_internal_group_id(gc, internal_group_id); + if (group != NULL) { + group->my_status = QQ_GROUP_MEMBER_STATUS_IS_MEMBER; + qq_group_refresh(gc, group); + } // if group + + g_free(msg); + g_free(reason_utf8); +} // qq_process_group_im_being_approved + +/*****************************************************************************/ +// process the packet when reomved from a group +void qq_process_recv_group_im_been_removed + (guint8 * data, guint8 ** cursor, gint len, guint32 internal_group_id, GaimConnection * gc) { + guint32 external_group_id, uid; + guint8 group_type; + gchar *msg; + qq_group *group; + + g_return_if_fail(gc != NULL && data != NULL && len > 0); + + if (*cursor >= (data + len - 1)) { + gaim_debug(GAIM_DEBUG_WARNING, "QQ", "Received group msg been_removed is empty\n"); + return; + } // if + + read_packet_dw(data, cursor, len, &external_group_id); + read_packet_b(data, cursor, len, &group_type); + read_packet_dw(data, cursor, len, &uid); + + g_return_if_fail(external_group_id > 0 && uid > 0); + + msg = g_strdup_printf(_("You [%d] has exit group \"%d\""), uid, external_group_id); + gaim_notify_info(gc, _("QQ Qun Operation"), msg, NULL); + + group = qq_group_find_by_internal_group_id(gc, internal_group_id); + if (group != NULL) { + group->my_status = QQ_GROUP_MEMBER_STATUS_NOT_MEMBER; + qq_group_refresh(gc, group); + } // if group + + g_free(msg); +} // qq_process_recv_group_im_been_removed + +/*****************************************************************************/ +// process the packet when added to a group +void qq_process_recv_group_im_been_added + (guint8 * data, guint8 ** cursor, gint len, guint32 internal_group_id, GaimConnection * gc) { + guint32 external_group_id, uid; + guint8 group_type; + qq_group *group; + gchar *msg; + + g_return_if_fail(gc != NULL && data != NULL && len > 0); + + if (*cursor >= (data + len - 1)) { + gaim_debug(GAIM_DEBUG_WARNING, "QQ", "Received group msg been_added is empty\n"); + return; + } // if + + read_packet_dw(data, cursor, len, &external_group_id); + read_packet_b(data, cursor, len, &group_type); + read_packet_dw(data, cursor, len, &uid); + + g_return_if_fail(external_group_id > 0 && uid > 0); + + msg = g_strdup_printf(_("You [%d] has been added by group \"%d\""), uid, external_group_id); + gaim_notify_info(gc, _("QQ Qun Operation"), msg, _("OpenQ has added this group to your buddy list")); + + group = qq_group_find_by_internal_group_id(gc, internal_group_id); + if (group != NULL) { + group->my_status = QQ_GROUP_MEMBER_STATUS_IS_MEMBER; + qq_group_refresh(gc, group); + } else { // no such group, try to create a dummy first, and then update + group = qq_group_create_by_id(gc, internal_group_id, external_group_id); + group->my_status = QQ_GROUP_MEMBER_STATUS_IS_MEMBER; + qq_group_refresh(gc, group); + qq_send_cmd_group_get_group_info(gc, group); + // the return of this cmd will automatically update the group in blist + } // if group; + + g_free(msg); + +} // qq_process_recv_group_im_been_added + +/*****************************************************************************/ +// recv an IM from a group chat +void qq_process_recv_group_im + (guint8 * data, guint8 ** cursor, gint data_len, guint32 internal_group_id, GaimConnection * gc, guint16 im_type /* gfhuang */) +{ + gchar *msg_with_gaim_smiley, *msg_utf8_encoded, *im_src_name; + guint16 unknown; + guint32 unknown4; + GaimConversation *conv; + qq_data *qd; + qq_buddy *member; + qq_group *group; + qq_recv_group_im *im_group; + gint skip_len; + + g_return_if_fail(gc != NULL && gc->proto_data != NULL && data != NULL && data_len > 0); + qd = (qq_data *) gc->proto_data; + + gaim_debug(GAIM_DEBUG_INFO, "QQ", //by gfhuang + "group im hex dump\n%s\n", hex_dump_to_str(*cursor, data_len - (*cursor - data))); + + if (*cursor >= (data + data_len - 1)) { + gaim_debug(GAIM_DEBUG_WARNING, "QQ", "Received group im_group is empty\n"); + return; + } + + im_group = g_newa(qq_recv_group_im, 1); + + read_packet_dw(data, cursor, data_len, &(im_group->external_group_id)); + read_packet_b(data, cursor, data_len, &(im_group->group_type)); + + if(QQ_RECV_IM_TEMP_QUN_IM == im_type) { //by gfhuang, protocal changed + read_packet_dw(data, cursor, data_len, &(internal_group_id)); + } + + read_packet_dw(data, cursor, data_len, &(im_group->member_uid)); + read_packet_w(data, cursor, data_len, &unknown); // 0x0001? + read_packet_w(data, cursor, data_len, &(im_group->msg_seq)); + read_packet_dw(data, cursor, data_len, (guint32 *) & (im_group->send_time)); + read_packet_dw(data, cursor, data_len, &unknown4); // versionID, gfhuang + // length includes font_attr + // this msg_len includes msg and font_attr + ////////////////// the format is + // length of all + // 1. unknown 10 bytes + // 2. 0-ended string + // 3. font_attr + + read_packet_w(data, cursor, data_len, &(im_group->msg_len)); + g_return_if_fail(im_group->msg_len > 0); + + // 10 bytes from lumaqq + // contentType = buf.getChar(); + // totalFragments = buf.get() & 255; + // fragmentSequence = buf.get() & 255; + // messageId = buf.getChar(); + // buf.getInt(); + + if(im_type != QQ_RECV_IM_UNKNOWN_QUN_IM) // gfhuang, protocal changed + skip_len = 10; + else + skip_len = 0; + *cursor += skip_len; + + im_group->msg = g_strdup(*cursor); + *cursor += strlen(im_group->msg) + 1; + // there might not be any font_attr, check it + im_group->font_attr_len = im_group->msg_len - strlen(im_group->msg) - 1 - skip_len /* gfhuang */; + if (im_group->font_attr_len > 0) + im_group->font_attr = g_memdup(*cursor, im_group->font_attr_len); + else + im_group->font_attr = NULL; + + // group im_group has no flag to indicate whether it has font_attr or not + msg_with_gaim_smiley = qq_smiley_to_gaim(im_group->msg); + if (im_group->font_attr_len > 0) + msg_utf8_encoded = qq_encode_to_gaim(im_group->font_attr, + im_group->font_attr_len, msg_with_gaim_smiley); + else + msg_utf8_encoded = qq_to_utf8(msg_with_gaim_smiley, QQ_CHARSET_DEFAULT); + + group = qq_group_find_by_internal_group_id(gc, internal_group_id); + g_return_if_fail(group != NULL); + + conv = gaim_find_conversation_with_account(GAIM_CONV_TYPE_CHAT, /*gfhuang*/group->group_name_utf8, gaim_connection_get_account(gc)); + if (conv == NULL && gaim_prefs_get_bool("/plugins/prpl/qq/prompt_group_msg_on_recv")) { + serv_got_joined_chat(gc, qd->channel++, group->group_name_utf8); + conv = gaim_find_conversation_with_account(GAIM_CONV_TYPE_CHAT, /*gfhuang*/group->group_name_utf8, gaim_connection_get_account(gc)); + } // if conv + + if (conv != NULL) { + member = qq_group_find_member_by_uid(group, im_group->member_uid); + if (member == NULL || member->nickname == NULL) + im_src_name = uid_to_gaim_name(im_group->member_uid); + else + im_src_name = g_strdup(member->nickname); + serv_got_chat_in(gc, + gaim_conv_chat_get_id(GAIM_CONV_CHAT + (conv)), im_src_name, 0, msg_utf8_encoded, im_group->send_time); + g_free(im_src_name); + } // if conv + g_free(msg_with_gaim_smiley); + g_free(msg_utf8_encoded); + g_free(im_group->msg); + g_free(im_group->font_attr); +} // _qq_process_recv_group_im + + +/*****************************************************************************/ +// END OF FILE diff -r 5642f4658b59 -r 983fd420e86b src/protocols/qq/group_im.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/protocols/qq/group_im.h Mon Jun 26 02:58:54 2006 +0000 @@ -0,0 +1,54 @@ +/** +* The QQ2003C protocol plugin + * + * for gaim + * + * Copyright (C) 2004 Puzzlebird + * + * 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 + */ + +// START OF FILE +/*****************************************************************************/ +#ifndef _QQ_GROUP_IM_H_ +#define _QQ_GROUP_IM_H_ + +#include +#include "connection.h" // GaimConnection +#include "group.h" // qq_group + +void qq_send_packet_group_im(GaimConnection * gc, qq_group * group, const gchar * msg); +void qq_process_group_cmd_im(guint8 * data, guint8 ** cursor, gint len, GaimConnection * gc); +void +qq_process_recv_group_im(guint8 * data, + guint8 ** cursor, gint data_len, guint32 internal_group_id, GaimConnection * gc, guint16 im_type /* gfhuang */); +void +qq_process_recv_group_im_apply_join(guint8 * data, + guint8 ** cursor, gint len, guint32 internal_group_id, GaimConnection * gc); +void +qq_process_recv_group_im_been_rejected(guint8 * data, + guint8 ** cursor, gint len, guint32 internal_group_id, GaimConnection * gc); +void +qq_process_recv_group_im_been_approved(guint8 * data, + guint8 ** cursor, gint len, guint32 internal_group_id, GaimConnection * gc); +void +qq_process_recv_group_im_been_removed(guint8 * data, + guint8 ** cursor, gint len, guint32 internal_group_id, GaimConnection * gc); +void +qq_process_recv_group_im_been_added(guint8 * data, + guint8 ** cursor, gint len, guint32 internal_group_id, GaimConnection * gc); +#endif +/*****************************************************************************/ +// END OF FILE diff -r 5642f4658b59 -r 983fd420e86b src/protocols/qq/group_info.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/protocols/qq/group_info.c Mon Jun 26 02:58:54 2006 +0000 @@ -0,0 +1,330 @@ +/** +* The QQ2003C protocol plugin + * + * for gaim + * + * Copyright (C) 2004 Puzzlebird + * + * 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 + */ + +// START OF FILE +/*****************************************************************************/ +#include "debug.h" // gaim_debug +#include "conversation.h" // gaim_find_conversation_with_account + +#include "buddy_status.h" // QQ_BUDDY_ONLINE_NORMAL +#include "char_conv.h" // convert_as_pascal_string +#include "group_find.h" // qq_group_find_by_internal_group_id +#include "group_hash.h" // qq_group_refresh +#include "group_info.h" +#include "buddy_status.h" // is_online +#include "group_network.h" // qq_send_group_cmd + +// we check who needs to update member info every minutes +// this interval determines if their member info is outdated +#define QQ_GROUP_CHAT_REFRESH_NICKNAME_INTERNAL 180 + +/*****************************************************************************/ +static gboolean _is_group_member_need_update_info(qq_buddy * member) +{ + g_return_val_if_fail(member != NULL, FALSE); + return (member->nickname == NULL) || + (time(NULL) - member->last_refresh) > QQ_GROUP_CHAT_REFRESH_NICKNAME_INTERNAL; +} // _is_group_member_need_update_info + +/*****************************************************************************/ +// this is done when we receive the reply to get_online_member sub_cmd +// all member are set offline, and then only those in reply packets are online +static void _qq_group_set_members_all_offline(qq_group * group) +{ + GList *list; + qq_buddy *member; + g_return_if_fail(group != NULL); + + list = group->members; + while (list != NULL) { + member = (qq_buddy *) list->data; + member->status = QQ_BUDDY_ONLINE_OFFLINE; + list = list->next; + } // while list +} // _qq_group_set_members_all_offline + +/*****************************************************************************/ +// send packet to get detailed information of one group +void qq_send_cmd_group_get_group_info(GaimConnection * gc, qq_group * group) +{ + guint8 *raw_data, *cursor; + gint bytes, data_len; + + g_return_if_fail(gc != NULL && group != NULL); + + data_len = 5; + raw_data = g_newa(guint8, data_len); + cursor = raw_data; + + bytes = 0; + bytes += create_packet_b(raw_data, &cursor, QQ_GROUP_CMD_GET_GROUP_INFO); + bytes += create_packet_dw(raw_data, &cursor, group->internal_group_id); + + if (bytes != data_len) + gaim_debug(GAIM_DEBUG_ERROR, "QQ", + "Fail create packet for %s\n", qq_group_cmd_get_desc(QQ_GROUP_CMD_GET_GROUP_INFO)); + else + qq_send_group_cmd(gc, group, raw_data, data_len); +} // qq_send_cmd_group_get_group_info + +/*****************************************************************************/ +// send packet to get online group member, called by keep_alive +void qq_send_cmd_group_get_online_member(GaimConnection * gc, qq_group * group) { + guint8 *raw_data, *cursor; + gint bytes, data_len; + + g_return_if_fail(gc != NULL && group != NULL); + + // only get online members when conversation window is on + if (NULL == gaim_find_conversation_with_account(GAIM_CONV_TYPE_CHAT/*gfhuang*/,group->group_name_utf8, gaim_connection_get_account(gc))) { + gaim_debug(GAIM_DEBUG_WARNING, "QQ", + "Conv windows for \"%s\" is not on, do not get online members\n", group->group_name_utf8); + return; + } // if gaim_find_conversation_with_account + + data_len = 5; + raw_data = g_newa(guint8, data_len); + cursor = raw_data; + + bytes = 0; + bytes += create_packet_b(raw_data, &cursor, QQ_GROUP_CMD_GET_ONLINE_MEMBER); + bytes += create_packet_dw(raw_data, &cursor, group->internal_group_id); + + if (bytes != data_len) + gaim_debug(GAIM_DEBUG_ERROR, "QQ", + "Fail create packet for %s\n", qq_group_cmd_get_desc(QQ_GROUP_CMD_GET_ONLINE_MEMBER)); + else + qq_send_group_cmd(gc, group, raw_data, data_len); +} // qq_send_cmd_group_search_group + +/*****************************************************************************/ +// send packet to get group member info +void qq_send_cmd_group_get_member_info(GaimConnection * gc, qq_group * group) { + guint8 *raw_data, *cursor; + gint bytes, data_len, i; + GList *list; + qq_buddy *member; + + g_return_if_fail(gc != NULL && group != NULL); + for (i = 0, list = group->members; list != NULL; list = list->next) { + member = (qq_buddy *) list->data; + if (_is_group_member_need_update_info(member)) + i++; + } // for i + + if (i <= 0) { + gaim_debug(GAIM_DEBUG_INFO, "QQ", "No group member needs to to update info now.\n"); + return; + } // if i + + data_len = 5 + 4 * i; + raw_data = g_newa(guint8, data_len); + cursor = raw_data; + + bytes = 0; + bytes += create_packet_b(raw_data, &cursor, QQ_GROUP_CMD_GET_MEMBER_INFO); + bytes += create_packet_dw(raw_data, &cursor, group->internal_group_id); + + list = group->members; + while (list != NULL) { + member = (qq_buddy *) list->data; + if (_is_group_member_need_update_info(member)) + bytes += create_packet_dw(raw_data, &cursor, member->uid); + list = list->next; + } // while list + + if (bytes != data_len) + gaim_debug(GAIM_DEBUG_ERROR, "QQ", + "Fail create packet for %s\n", qq_group_cmd_get_desc(QQ_GROUP_CMD_GET_MEMBER_INFO)); + else + qq_send_group_cmd(gc, group, raw_data, data_len); +} // qq_send_cmd_group_get_member_info + +/*****************************************************************************/ +void qq_process_group_cmd_get_group_info(guint8 * data, guint8 ** cursor, gint len, GaimConnection * gc) { + qq_group *group; + qq_data *qd; + guint8 orgnization, role; //gfhuang + guint16 unknown; + guint32 member_uid, internal_group_id; + gint pascal_len, i; + guint32 unknown4; + guint8 unknown1; + + g_return_if_fail(gc != NULL && gc->proto_data != NULL); + g_return_if_fail(data != NULL && len > 0); + qd = (qq_data *) gc->proto_data; + + read_packet_dw(data, cursor, len, &(internal_group_id)); + g_return_if_fail(internal_group_id > 0); + + group = qq_group_find_by_internal_group_id(gc, internal_group_id); + g_return_if_fail(group != NULL); + + read_packet_dw(data, cursor, len, &(group->external_group_id)); + read_packet_b(data, cursor, len, &(group->group_type)); + read_packet_dw(data, cursor, len, &unknown4); //unknown 4 bytes, protocal changed by gfhuang + read_packet_dw(data, cursor, len, &(group->creator_uid)); + read_packet_b(data, cursor, len, &(group->auth_type)); + read_packet_dw(data, cursor, len, &unknown4); // oldCategory, by gfhuang + read_packet_w(data, cursor, len, &unknown); + read_packet_dw(data, cursor, len, &(group->group_category)); + read_packet_w(data, cursor, len, &(unknown)); // 0x0000 + read_packet_b(data, cursor, len, &unknown1); + read_packet_dw(data, cursor, len, &(unknown4)); // versionID, by gfhuang + + pascal_len = convert_as_pascal_string(*cursor, &(group->group_name_utf8), QQ_CHARSET_DEFAULT); + *cursor += pascal_len; + read_packet_w(data, cursor, len, &(unknown)); // 0x0000 + pascal_len = convert_as_pascal_string(*cursor, &(group->notice_utf8), QQ_CHARSET_DEFAULT); + *cursor += pascal_len; + pascal_len = convert_as_pascal_string(*cursor, &(group->group_desc_utf8), QQ_CHARSET_DEFAULT); + *cursor += pascal_len; + + i = 0; + // now comes the member list separated by 0x00 + while (*cursor < data + len) { + read_packet_dw(data, cursor, len, &member_uid); + i++; + read_packet_b(data, cursor, len, &orgnization); // protocal changed, gfhuang + read_packet_b(data, cursor, len, &role);// gfhuang + + if(orgnization != 0 || role != 0) { + gaim_debug(GAIM_DEBUG_INFO, "QQ", "group member %d: orgnizatio=%d, role=%d\n", member_uid, orgnization, role); + } + qq_buddy *member = qq_group_find_or_add_member(gc, group, member_uid); + member->role = role; + } // while *cursor + if(*cursor > (data + len)) { + gaim_debug(GAIM_DEBUG_ERROR, "QQ", "group_cmd_get_group_info: Dangerous error! maybe protocal changed, notify me!"); + } + + gaim_debug(GAIM_DEBUG_INFO, "QQ", "group \"%s\" has %d members\n", group->group_name_utf8, i); + + if (group->creator_uid == qd->uid) + group->my_status = QQ_GROUP_MEMBER_STATUS_IS_ADMIN; + + qq_group_refresh(gc, group); + + //added topic by gfhuang + GaimConversation *gaim_conv = gaim_find_conversation_with_account(GAIM_CONV_TYPE_CHAT,group->group_name_utf8, gaim_connection_get_account(gc)); + if(NULL == gaim_conv) { + gaim_debug(GAIM_DEBUG_WARNING, "QQ", + "Conv windows for \"%s\" is not on, do not set topic\n", group->group_name_utf8); + } + else { + gaim_conv_chat_set_topic(GAIM_CONV_CHAT(gaim_conv), NULL, group->notice_utf8); + } +} // qq_process_group_cmd_get_group_info + +/*****************************************************************************/ +void qq_process_group_cmd_get_online_member(guint8 * data, guint8 ** cursor, gint len, GaimConnection * gc) { + guint32 internal_group_id, member_uid; + guint8 unknown; + gint bytes, i; + qq_group *group; + qq_buddy *member; + + g_return_if_fail(gc != NULL && data != NULL && len > 0); + + if (data + len - *cursor < 4) { + gaim_debug(GAIM_DEBUG_ERROR, "QQ", "Invalid group online member reply, discard it!\n"); + return; + } // if data_len-*cursor + + bytes = 0; + i = 0; + bytes += read_packet_dw(data, cursor, len, &internal_group_id); + bytes += read_packet_b(data, cursor, len, &unknown); // 0x3c ?? + g_return_if_fail(internal_group_id > 0); + + group = qq_group_find_by_internal_group_id(gc, internal_group_id); + if (group == NULL) { + gaim_debug(GAIM_DEBUG_ERROR, "QQ", "We have no group info for internal id [%d]\n", internal_group_id); + return; + } // if group == NULL + + // set all offline first, then update those online + _qq_group_set_members_all_offline(group); + while (*cursor < data + len) { + bytes += read_packet_dw(data, cursor, len, &member_uid); + i++; + member = qq_group_find_or_add_member(gc, group, member_uid); + if (member != NULL) + member->status = QQ_BUDDY_ONLINE_NORMAL; + } // while + if(*cursor > (data + len)) { + gaim_debug(GAIM_DEBUG_ERROR, "QQ", "group_cmd_get_online_member: Dangerous error! maybe protocal changed, notify me!"); + } + + gaim_debug(GAIM_DEBUG_INFO, "QQ", "Group \"%s\" has %d online members\n", group->group_name_utf8, i); + +} // qq_process_group_cmd_get_online_member + +/*****************************************************************************/ +// process the reply to get_member_info packet +void qq_process_group_cmd_get_member_info(guint8 * data, guint8 ** cursor, gint len, GaimConnection * gc) { + guint32 internal_group_id, member_uid; + guint16 unknown; + guint8 bar; + gint pascal_len, i; + qq_group *group; + qq_buddy *member; + + g_return_if_fail(gc != NULL && data != NULL && len > 0); + + read_packet_dw(data, cursor, len, &internal_group_id); + g_return_if_fail(internal_group_id > 0); + + group = qq_group_find_by_internal_group_id(gc, internal_group_id); + g_return_if_fail(group != NULL); + + i = 0; + // now starts the member info, as get buddy list reply + while (*cursor < data + len) { + read_packet_dw(data, cursor, len, &member_uid); + g_return_if_fail(member_uid > 0); + member = qq_group_find_member_by_uid(group, member_uid); + g_return_if_fail(member != NULL); + + i++; + read_packet_b(data, cursor, len, &bar); + read_packet_b(data, cursor, len, &(member->icon)); + read_packet_b(data, cursor, len, &(member->age)); + read_packet_b(data, cursor, len, &(member->gender)); + pascal_len = convert_as_pascal_string(*cursor, &(member->nickname), QQ_CHARSET_DEFAULT); + *cursor += pascal_len; + read_packet_w(data, cursor, len, &unknown); + read_packet_b(data, cursor, len, &(member->flag1)); + read_packet_b(data, cursor, len, &(member->comm_flag)); + + member->last_refresh = time(NULL); + } // while + if(*cursor > (data + len)) { + gaim_debug(GAIM_DEBUG_ERROR, "QQ", "group_cmd_get_member_info: Dangerous error! maybe protocal changed, notify me!"); + } + gaim_debug(GAIM_DEBUG_INFO, "QQ", "Group \"%s\" obtained %d member info\n", group->group_name_utf8, i); + +} // qq_process_group_cmd_get_member_info + +/*****************************************************************************/ +// END OF FILE diff -r 5642f4658b59 -r 983fd420e86b src/protocols/qq/group_info.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/protocols/qq/group_info.h Mon Jun 26 02:58:54 2006 +0000 @@ -0,0 +1,41 @@ +/** +* The QQ2003C protocol plugin + * + * for gaim + * + * Copyright (C) 2004 Puzzlebird + * + * 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 + */ + +// START OF FILE +/*****************************************************************************/ +#ifndef _QQ_GROUP_INFO_H_ +#define _QQ_GROUP_INFO_H_ + +#include +#include "connection.h" // GaimConnection +#include "group.h" // qq_group + +void qq_send_cmd_group_get_group_info(GaimConnection * gc, qq_group * group); +void qq_send_cmd_group_get_online_member(GaimConnection * gc, qq_group * group); +void qq_send_cmd_group_get_member_info(GaimConnection * gc, qq_group * group); +void qq_process_group_cmd_get_group_info(guint8 * data, guint8 ** cursor, gint len, GaimConnection * gc); +void qq_process_group_cmd_get_online_member(guint8 * data, guint8 ** cursor, gint len, GaimConnection * gc); +void qq_process_group_cmd_get_member_info(guint8 * data, guint8 ** cursor, gint len, GaimConnection * gc); + +#endif +/*****************************************************************************/ +// END OF FILE diff -r 5642f4658b59 -r 983fd420e86b src/protocols/qq/group_join.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/protocols/qq/group_join.c Mon Jun 26 02:58:54 2006 +0000 @@ -0,0 +1,375 @@ +/** +* The QQ2003C protocol plugin + * + * for gaim + * + * Copyright (C) 2004 Puzzlebird + * + * 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 + */ + +// START OF FILE +/*****************************************************************************/ +#include "debug.h" // gaim_debug +#include "notify.h" // gaim_notify_xxx +#include "request.h" // gaim_request_input +#include "server.h" // serv_got_joined_chat + +#include "buddy_opt.h" // gc_and_uid +#include "char_conv.h" // QQ_CHARSET_DEFAULT +#include "group_conv.h" // qq_group_conv_show_window +#include "group_find.h" // qq_group_find_by_internal_group_id +#include "group_free.h" // qq_group_remove_by_internal_group_id +#include "group_hash.h" // qq_group_refresh +#include "group_info.h" // qq_send_cmd_group_get_group_info +#include "group_join.h" +#include "group_opt.h" // qq_send_cmd_group_auth +#include "group_network.h" // qq_send_group_cmd + +enum { + QQ_GROUP_JOIN_OK = 0x01, + QQ_GROUP_JOIN_NEED_AUTH = 0x02, +}; + +/*****************************************************************************/ +static void _qq_group_exit_with_gc_and_id(gc_and_uid * g) +{ + GaimConnection *gc; + guint32 internal_group_id; + qq_group *group; + + g_return_if_fail(g != NULL && g->gc != NULL && g->uid > 0); + gc = g->gc; + internal_group_id = g->uid; + + group = qq_group_find_by_internal_group_id(gc, internal_group_id); + g_return_if_fail(group != NULL); + + qq_send_cmd_group_exit_group(gc, group); +} // _qq_group_exist_with_gc_and_id + +/*****************************************************************************/ +// send packet to join a group without auth +static void _qq_send_cmd_group_join_group(GaimConnection * gc, qq_group * group) +{ + guint8 *raw_data, *cursor; + gint bytes, data_len; + + g_return_if_fail(gc != NULL && group != NULL); + if (group->my_status == QQ_GROUP_MEMBER_STATUS_NOT_MEMBER) { + group->my_status = QQ_GROUP_MEMBER_STATUS_APPLYING; + qq_group_refresh(gc, group); + } // if group->my_status + + data_len = 5; + raw_data = g_newa(guint8, data_len); + cursor = raw_data; + + bytes = 0; + bytes += create_packet_b(raw_data, &cursor, QQ_GROUP_CMD_JOIN_GROUP); + bytes += create_packet_dw(raw_data, &cursor, group->internal_group_id); + + if (bytes != data_len) + gaim_debug(GAIM_DEBUG_ERROR, "QQ", + "Fail create packet for %s\n", qq_group_cmd_get_desc(QQ_GROUP_CMD_JOIN_GROUP)); + else + qq_send_group_cmd(gc, group, raw_data, data_len); +} // _qq_send_cmd_group_join_group + +/*****************************************************************************/ +static void _qq_group_join_auth_with_gc_and_id(gc_and_uid * g, const gchar * reason_utf8) +{ + GaimConnection *gc; + qq_group *group; + guint32 internal_group_id; + + g_return_if_fail(g != NULL && g->gc != NULL && g->uid > 0); + gc = g->gc; + internal_group_id = g->uid; + + group = qq_group_find_by_internal_group_id(gc, internal_group_id); + if (group == NULL) { + gaim_debug(GAIM_DEBUG_ERROR, "QQ", "Can not find qq_group by internal_id: %d\n", internal_group_id); + return; + } else // everything is OK + qq_send_cmd_group_auth(gc, group, QQ_GROUP_AUTH_REQUEST_APPLY, 0, reason_utf8); + +} // _qq_group_join_auth_with_gc_and_id + +/*****************************************************************************/ +static void _qq_group_join_auth(GaimConnection * gc, qq_group * group) +{ + gchar *msg; + gc_and_uid *g; + g_return_if_fail(gc != NULL && group != NULL); + + gaim_debug(GAIM_DEBUG_INFO, "QQ", "Group (internal id: %d) needs authentication\n", group->internal_group_id); + + msg = g_strdup_printf("Group \"%s\" needs authentication\n", group->group_name_utf8); + g = g_new0(gc_and_uid, 1); + g->gc = gc; + g->uid = group->internal_group_id; + gaim_request_input(gc, NULL, msg, + _("Input request here"), + _("Would you be my friend?"), TRUE, FALSE, NULL, + _("Send"), + G_CALLBACK(_qq_group_join_auth_with_gc_and_id), + _("Cancel"), G_CALLBACK(qq_do_nothing_with_gc_and_uid), g); + g_free(msg); +} // _qq_group_join_auth + +/*****************************************************************************/ +void qq_send_cmd_group_auth(GaimConnection * gc, qq_group * group, guint8 opt, guint32 uid, const gchar * reason_utf8) { + guint8 *raw_data, *cursor; + gchar *reason_qq; + gint bytes, data_len; + + g_return_if_fail(gc != NULL && group != NULL); + + if (reason_utf8 == NULL || strlen(reason_utf8) == 0) + reason_qq = g_strdup(""); + else + reason_qq = utf8_to_qq(reason_utf8, QQ_CHARSET_DEFAULT); + + if (opt == QQ_GROUP_AUTH_REQUEST_APPLY) { + group->my_status = QQ_GROUP_MEMBER_STATUS_APPLYING; + qq_group_refresh(gc, group); + uid = 0; + } // if (opt == QQ_GROUP_AUTH_REQUEST_APPLY) + + data_len = 10 + strlen(reason_qq) + 1; + raw_data = g_newa(guint8, data_len); + cursor = raw_data; + + bytes = 0; + bytes += create_packet_b(raw_data, &cursor, QQ_GROUP_CMD_JOIN_GROUP_AUTH); + bytes += create_packet_dw(raw_data, &cursor, group->internal_group_id); + bytes += create_packet_b(raw_data, &cursor, opt); + bytes += create_packet_dw(raw_data, &cursor, uid); + bytes += create_packet_b(raw_data, &cursor, strlen(reason_qq)); + bytes += create_packet_data(raw_data, &cursor, reason_qq, strlen(reason_qq)); + + if (bytes != data_len) + gaim_debug(GAIM_DEBUG_ERROR, "QQ", + "Fail create packet for %s\n", qq_group_cmd_get_desc(QQ_GROUP_CMD_JOIN_GROUP_AUTH)); + else + qq_send_group_cmd(gc, group, raw_data, data_len); +} // qq_send_packet_group_auth + +/*****************************************************************************/ +// send packet to exit one group +// In fact, this will never be used for GAIM +// when we remove a GaimChat node, there is no user controlable callback +// so we only remove the GaimChat node, +// but we never use this cmd to update the server side +// anyway, it is function, as when we remove the GaimChat node, +// user has no way to start up the chat conversation window +// therefore even we are still in it, +// the group IM will not show up to bother us. (Limited by GAIM) +void qq_send_cmd_group_exit_group(GaimConnection * gc, qq_group * group) +{ + guint8 *raw_data, *cursor; + gint bytes, data_len; + + g_return_if_fail(gc != NULL && group != NULL); + + data_len = 5; + raw_data = g_newa(guint8, data_len); + cursor = raw_data; + + bytes = 0; + bytes += create_packet_b(raw_data, &cursor, QQ_GROUP_CMD_EXIT_GROUP); + bytes += create_packet_dw(raw_data, &cursor, group->internal_group_id); + + if (bytes != data_len) + gaim_debug(GAIM_DEBUG_ERROR, "QQ", + "Fail create packet for %s\n", qq_group_cmd_get_desc(QQ_GROUP_CMD_EXIT_GROUP)); + else + qq_send_group_cmd(gc, group, raw_data, data_len); +} // qq_send_cmd_group_get_group_info + +/*****************************************************************************/ +// If comes here, cmd is OK already +void qq_process_group_cmd_exit_group(guint8 * data, guint8 ** cursor, gint len, GaimConnection * gc) { + gint bytes, expected_bytes; + guint32 internal_group_id; + GaimChat *chat; + qq_group *group; + qq_data *qd; + + g_return_if_fail(gc != NULL && gc->proto_data != NULL); + g_return_if_fail(data != NULL && len > 0); + qd = (qq_data *) gc->proto_data; + + bytes = 0; + expected_bytes = 4; + bytes += read_packet_dw(data, cursor, len, &internal_group_id); + + if (bytes == expected_bytes) { + group = qq_group_find_by_internal_group_id(gc, internal_group_id); + if (group != NULL) { + chat = + gaim_blist_find_chat + (gaim_connection_get_account(gc), g_strdup_printf("%d", group->external_group_id)); + if (chat != NULL) + gaim_blist_remove_chat(chat); + qq_group_remove_by_internal_group_id(qd, internal_group_id); + } // if group + gaim_notify_info(gc, _("QQ Qun Operation"), _("You have successfully exit group"), NULL); + } else + gaim_debug(GAIM_DEBUG_ERROR, "QQ", + "Invalid exit group reply, expect %d bytes, read %d bytes\n", expected_bytes, bytes); + +} // qq_process_group_cmd_exit_group + +/*****************************************************************************/ +// Process the reply to group_auth subcmd +void qq_process_group_cmd_join_group_auth(guint8 * data, guint8 ** cursor, gint len, GaimConnection * gc) { + gint bytes, expected_bytes; + guint32 internal_group_id; + qq_data *qd; + + g_return_if_fail(gc != NULL && gc->proto_data != NULL); + g_return_if_fail(data != NULL && len > 0); + qd = (qq_data *) gc->proto_data; + + bytes = 0; + expected_bytes = 4; + bytes += read_packet_dw(data, cursor, len, &internal_group_id); + g_return_if_fail(internal_group_id > 0); + + if (bytes == expected_bytes) + gaim_notify_info + (gc, _("QQ Group Auth"), _("You authorization operation has been accepted by QQ server"), NULL); + else + gaim_debug(GAIM_DEBUG_ERROR, "QQ", + "Invalid join group reply, expect %d bytes, read %d bytes\n", expected_bytes, bytes); + +} // qq_process_group_cmd_group_auth + +/*****************************************************************************/ +// process group cmd reply "join group" +void qq_process_group_cmd_join_group(guint8 * data, guint8 ** cursor, gint len, GaimConnection * gc) { + gint bytes, expected_bytes; + guint32 internal_group_id; + guint8 reply; + qq_group *group; + + g_return_if_fail(gc != NULL && data != NULL && len > 0); + + bytes = 0; + expected_bytes = 5; + bytes += read_packet_dw(data, cursor, len, &internal_group_id); + bytes += read_packet_b(data, cursor, len, &reply); + + if (bytes != expected_bytes) { + gaim_debug(GAIM_DEBUG_ERROR, "QQ", + "Invalid join group reply, expect %d bytes, read %d bytes\n", expected_bytes, bytes); + return; + } else { // join group OK + group = qq_group_find_by_internal_group_id(gc, internal_group_id); + // need to check if group is NULL or not. + g_return_if_fail(group != NULL); + switch (reply) { + case QQ_GROUP_JOIN_OK: + gaim_debug(GAIM_DEBUG_INFO, "QQ", "Succeed joining group \"%s\"\n", group->group_name_utf8); + group->my_status = QQ_GROUP_MEMBER_STATUS_IS_MEMBER; + qq_group_refresh(gc, group); + // this must be show before getting online member + qq_group_conv_show_window(gc, group); + qq_send_cmd_group_get_group_info(gc, group); + break; + case QQ_GROUP_JOIN_NEED_AUTH: + gaim_debug(GAIM_DEBUG_INFO, "QQ", + "Fail joining group [%d] %s, needs authentication\n", + group->external_group_id, group->group_name_utf8); + group->my_status = QQ_GROUP_MEMBER_STATUS_NOT_MEMBER; + qq_group_refresh(gc, group); + _qq_group_join_auth(gc, group); + break; + default: + gaim_debug(GAIM_DEBUG_INFO, "QQ", + "Error joining group [%d] %s, unknown reply: 0x%02x\n", + group->external_group_id, group->group_name_utf8, reply); + } // switch reply + } // if bytes != expected_bytes +} // qq_process_group_cmd_join_group + +/*****************************************************************************/ +// Apply to join one group without auth +void qq_group_join(GaimConnection * gc, GHashTable * data) +{ + gchar *internal_group_id_ptr; + guint32 internal_group_id; + qq_group *group; + + g_return_if_fail(gc != NULL && data != NULL); + + internal_group_id_ptr = g_hash_table_lookup(data, "internal_group_id"); + internal_group_id = strtol(internal_group_id_ptr, NULL, 10); + + g_return_if_fail(internal_group_id > 0); + + // for those we have subscribed, they should have been put into + // qd->groups in qq_group_init subroutine + group = qq_group_find_by_internal_group_id(gc, internal_group_id); + if (group == NULL) + group = qq_group_from_hashtable(gc, data); + + g_return_if_fail(group != NULL); + + switch (group->auth_type) { + case QQ_GROUP_AUTH_TYPE_NO_AUTH: + case QQ_GROUP_AUTH_TYPE_NEED_AUTH: + _qq_send_cmd_group_join_group(gc, group); + break; + case QQ_GROUP_AUTH_TYPE_NO_ADD: + gaim_notify_warning(gc, NULL, _("This group does not allow others to join"), NULL); + break; + default: + gaim_debug(GAIM_DEBUG_ERROR, "QQ", "Unknown group auth type: %d\n", group->auth_type); + } // switch auth_type +} // qq_group_join + +/*****************************************************************************/ +void qq_group_exit(GaimConnection * gc, GHashTable * data) +{ + gchar *internal_group_id_ptr; + guint32 internal_group_id; + gc_and_uid *g; + + g_return_if_fail(gc != NULL && data != NULL); + + internal_group_id_ptr = g_hash_table_lookup(data, "internal_group_id"); + internal_group_id = strtol(internal_group_id_ptr, NULL, 10); + + g_return_if_fail(internal_group_id > 0); + + g = g_new0(gc_and_uid, 1); + g->gc = gc; + g->uid = internal_group_id; + + gaim_request_action(gc, _("QQ Qun Operation"), + _("Are you sure to exit this Qun?"), + _ + ("Note, if you are the creator, \nthis operation will eventually remove this Qun."), + 1, g, 2, _("Cancel"), + G_CALLBACK(qq_do_nothing_with_gc_and_uid), + _("Go ahead"), G_CALLBACK(_qq_group_exit_with_gc_and_id)); + +} // qq_group_exit + +/*****************************************************************************/ +// END OF FILE diff -r 5642f4658b59 -r 983fd420e86b src/protocols/qq/group_join.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/protocols/qq/group_join.h Mon Jun 26 02:58:54 2006 +0000 @@ -0,0 +1,54 @@ +/** +* The QQ2003C protocol plugin + * + * for gaim + * + * Copyright (C) 2004 Puzzlebird + * + * 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 + */ + +// START OF FILE +/*****************************************************************************/ +#ifndef _QQ_GROUP_JOIN_H_ +#define _QQ_GROUP_JOIN_H_ + +#include +#include "connection.h" +#include "group.h" + +enum { + QQ_GROUP_AUTH_TYPE_NO_AUTH = 0x01, + QQ_GROUP_AUTH_TYPE_NEED_AUTH = 0x02, + QQ_GROUP_AUTH_TYPE_NO_ADD = 0x03, +}; + +enum { + QQ_GROUP_AUTH_REQUEST_APPLY = 0x01, + QQ_GROUP_AUTH_REQUEST_APPROVE = 0x02, + QQ_GROUP_AUTH_REQUEST_REJECT = 0x03, +}; + +void qq_send_cmd_group_auth(GaimConnection * gc, qq_group * group, guint8 opt, guint32 uid, const gchar * reason_utf8); +void qq_group_join(GaimConnection * gc, GHashTable * data); +void qq_group_exit(GaimConnection * gc, GHashTable * data); +void qq_send_cmd_group_exit_group(GaimConnection * gc, qq_group * group); +void qq_process_group_cmd_exit_group(guint8 * data, guint8 ** cursor, gint len, GaimConnection * gc); +void qq_process_group_cmd_join_group_auth(guint8 * data, guint8 ** cursor, gint len, GaimConnection * gc); +void qq_process_group_cmd_join_group(guint8 * data, guint8 ** cursor, gint len, GaimConnection * gc); + +#endif +/*****************************************************************************/ +// END OF FILE diff -r 5642f4658b59 -r 983fd420e86b src/protocols/qq/group_misc.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/protocols/qq/group_misc.c Mon Jun 26 02:58:54 2006 +0000 @@ -0,0 +1,34 @@ +/** +* The QQ2003C protocol plugin + * + * for gaim + * + * Copyright (C) 2004 Puzzlebird + * + * 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 + */ + +// START OF FILE +/*****************************************************************************/ +#include "debug.h" + +#include "utils.h" +#include "buddy_status.h" +#include "group_misc.h" + + + +/*****************************************************************************/ +// END OF FILE diff -r 5642f4658b59 -r 983fd420e86b src/protocols/qq/group_misc.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/protocols/qq/group_misc.h Mon Jun 26 02:58:54 2006 +0000 @@ -0,0 +1,34 @@ +/** +* The QQ2003C protocol plugin + * + * for gaim + * + * Copyright (C) 2004 Puzzlebird + * + * 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 + */ + +// START OF FILE +/*****************************************************************************/ +#ifndef _QQ_GROUP_MISC_H_ +#define _QQ_GROUP_MISC_H_ + +#include +#include "group.h" // qq_group + + +#endif +/*****************************************************************************/ +// END OF FILE diff -r 5642f4658b59 -r 983fd420e86b src/protocols/qq/group_network.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/protocols/qq/group_network.c Mon Jun 26 02:58:54 2006 +0000 @@ -0,0 +1,241 @@ +/** +* The QQ2003C protocol plugin + * + * for gaim + * + * Copyright (C) 2004 Puzzlebird + * + * 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 + */ + +// START OF FILE +/*****************************************************************************/ +#include "debug.h" // gaim_debug +#include "notify.h" // gaim_notify_xxx + +#include "utils.h" // hex_dump_to_str +#include "char_conv.h" // qq_to_utf8 +#include "crypt.h" // qq_crypt +#include "group_conv.h" // qq_group_conv_refresh_online_member +#include "group_find.h" // qq_group_find_internal_group_id_by_seq +#include "group_hash.h" // qq_group_refresh +#include "group_im.h" // qq_process_group_cmd_im +#include "group_info.h" // qq_process_group_cmd_get_online_member +#include "group_join.h" // qq_process_group_cmd_join_group +#include "group_network.h" +#include "group_opt.h" // qq_group_process_modify_info_reply +#include "group_search.h" // qq_process_group_cmd_search_group +#include "header_info.h" // QQ_CMD_GROUP_CMD +#include "send_core.h" // qq_send_cmd + +enum { + QQ_GROUP_CMD_REPLY_OK = 0x00, + QQ_GROUP_CMD_REPLY_NOT_MEMBER = 0x0a, +}; + +/*****************************************************************************/ +const gchar *qq_group_cmd_get_desc(qq_group_cmd cmd) +{ + switch (cmd) { + case QQ_GROUP_CMD_CREATE_GROUP: + return "QQ_GROUP_CMD_CREATE_GROUP"; + case QQ_GROUP_CMD_MEMBER_OPT: + return "QQ_GROUP_CMD_MEMBER_OPT"; + case QQ_GROUP_CMD_MODIFY_GROUP_INFO: + return "QQ_GROUP_CMD_MODIFY_GROUP_INFO"; + case QQ_GROUP_CMD_GET_GROUP_INFO: + return "QQ_GROUP_CMD_GET_GROUP_INFO"; + case QQ_GROUP_CMD_ACTIVATE_GROUP: + return "QQ_GROUP_CMD_ACTIVATE_GROUP"; + case QQ_GROUP_CMD_SEARCH_GROUP: + return "QQ_GROUP_CMD_SEARCH_GROUP"; + case QQ_GROUP_CMD_JOIN_GROUP: + return "QQ_GROUP_CMD_JOIN_GROUP"; + case QQ_GROUP_CMD_JOIN_GROUP_AUTH: + return "QQ_GROUP_CMD_JOIN_GROUP_AUTH"; + case QQ_GROUP_CMD_EXIT_GROUP: + return "QQ_GROUP_CMD_EXIT_GROUP"; + case QQ_GROUP_CMD_SEND_MSG: + return "QQ_GROUP_CMD_SEND_MSG"; + case QQ_GROUP_CMD_GET_ONLINE_MEMBER: + return "QQ_GROUP_CMD_GET_ONLINE_MEMBER"; + case QQ_GROUP_CMD_GET_MEMBER_INFO: + return "QQ_GROUP_CMD_GET_MEMBER_INFO"; + default: + return "Unknown QQ Group Command"; + } // switch +} // qq_group_cmd_get_desc + +/*****************************************************************************/ +// default process of reply error +static void _qq_process_group_cmd_reply_error_default(guint8 reply, guint8 * cursor, gint len, GaimConnection * gc) { + gchar *msg, *msg_utf8; + g_return_if_fail(cursor != NULL && len > 0 && gc != NULL); + + msg = g_strndup(cursor, len); // it will append 0x00 + msg_utf8 = qq_to_utf8(msg, QQ_CHARSET_DEFAULT); + g_free(msg); + msg = g_strdup_printf(_("Code [0x%02X]: %s"), reply, msg_utf8); + gaim_notify_error(gc, NULL, _("Group Operation Error"), msg); + g_free(msg); + g_free(msg_utf8); + +} // _qq_process_group_cmd_reply_error_default + +/*****************************************************************************/ +// default process, dump only +static void _qq_process_group_cmd_reply_default(guint8 * data, guint8 ** cursor, gint len, GaimConnection * gc) { + g_return_if_fail(gc != NULL && data != NULL && len > 0); + gaim_debug(GAIM_DEBUG_INFO, "QQ", "Dump unprocessed group cmd reply:\n%s", hex_dump_to_str(data, len)); +} // _qq_process_group_cmd_reply_default + +/*****************************************************************************/ +// The lower layer command of send group cmd +void qq_send_group_cmd(GaimConnection * gc, qq_group * group, guint8 * raw_data, gint data_len) { + qq_data *qd; + group_packet *p; + + g_return_if_fail(gc != NULL); + g_return_if_fail(raw_data != NULL && data_len > 0); + + qd = (qq_data *) gc->proto_data; + g_return_if_fail(qd != NULL); + + qq_send_cmd(gc, QQ_CMD_GROUP_CMD, TRUE, 0, TRUE, raw_data, data_len); + + p = g_new0(group_packet, 1); + + p->send_seq = qd->send_seq; + if (group == NULL) + p->internal_group_id = 0; + else + p->internal_group_id = group->internal_group_id; + + qd->group_packets = g_list_append(qd->group_packets, p); + +} // qq_send_group_cmd + +/*****************************************************************************/ +// the main entry of group cmd processing, called by qq_recv_core.c +void qq_process_group_cmd_reply(guint8 * buf, gint buf_len, guint16 seq, GaimConnection * gc) { + qq_group *group; + qq_data *qd; + gint len, bytes; + guint32 internal_group_id; + guint8 *data, *cursor, sub_cmd, reply; + + g_return_if_fail(gc != NULL && gc->proto_data != NULL); + g_return_if_fail(buf != NULL && buf_len != 0); + + qd = (qq_data *) gc->proto_data; + len = buf_len; + data = g_newa(guint8, len); + + if (!qq_group_find_internal_group_id_by_seq(gc, seq, &internal_group_id)) { + gaim_debug(GAIM_DEBUG_WARNING, "QQ", "We have no record of group cmd, seq [%d]\n", seq); + return; + } // if ! qq_group_find_internal_group_id_by_seq + + if (qq_crypt(DECRYPT, buf, buf_len, qd->session_key, data, &len)) { + if (len <= 2) { + gaim_debug(GAIM_DEBUG_ERROR, "QQ", "Group cmd reply is too short, only %d bytes\n", len); + return; + } // if len + + bytes = 0; + cursor = data; + bytes += read_packet_b(data, &cursor, len, &sub_cmd); + bytes += read_packet_b(data, &cursor, len, &reply); + + group = qq_group_find_by_internal_group_id(gc, internal_group_id); + + if (reply != QQ_GROUP_CMD_REPLY_OK) { + gaim_debug(GAIM_DEBUG_WARNING, "QQ", + "Group cmd reply says cmd %s fails\n", qq_group_cmd_get_desc(sub_cmd)); + switch (reply) { // this should be all errors + case QQ_GROUP_CMD_REPLY_NOT_MEMBER: + if (group != NULL) { + gaim_debug(GAIM_DEBUG_WARNING, + "QQ", + "You are not a member of group \"%s\"\n", group->group_name_utf8); + group->my_status = QQ_GROUP_MEMBER_STATUS_NOT_MEMBER; + qq_group_refresh(gc, group); + } // if group + break; + default: + _qq_process_group_cmd_reply_error_default(reply, cursor, len - bytes, gc); + } // switch reply + return; + } // if reply != QQ_GROUP_CMD_REPLY_OK + + // seems to ok so far, so we process the reply according to sub_cmd + switch (sub_cmd) { + case QQ_GROUP_CMD_GET_GROUP_INFO: + qq_process_group_cmd_get_group_info(data, &cursor, len, gc); + if (group != NULL) { + qq_send_cmd_group_get_member_info(gc, group); + qq_send_cmd_group_get_online_member(gc, group); + } + break; + case QQ_GROUP_CMD_CREATE_GROUP: + qq_group_process_create_group_reply(data, &cursor, len, gc); + break; + case QQ_GROUP_CMD_MODIFY_GROUP_INFO: + qq_group_process_modify_info_reply(data, &cursor, len, gc); + break; + case QQ_GROUP_CMD_MEMBER_OPT: + qq_group_process_modify_members_reply(data, &cursor, len, gc); + break; + case QQ_GROUP_CMD_ACTIVATE_GROUP: + qq_group_process_activate_group_reply(data, &cursor, len, gc); + break; + case QQ_GROUP_CMD_SEARCH_GROUP: + qq_process_group_cmd_search_group(data, &cursor, len, gc); + break; + case QQ_GROUP_CMD_JOIN_GROUP: + qq_process_group_cmd_join_group(data, &cursor, len, gc); + break; + case QQ_GROUP_CMD_JOIN_GROUP_AUTH: + qq_process_group_cmd_join_group_auth(data, &cursor, len, gc); + break; + case QQ_GROUP_CMD_EXIT_GROUP: + qq_process_group_cmd_exit_group(data, &cursor, len, gc); + break; + case QQ_GROUP_CMD_SEND_MSG: + qq_process_group_cmd_im(data, &cursor, len, gc); + break; + case QQ_GROUP_CMD_GET_ONLINE_MEMBER: + qq_process_group_cmd_get_online_member(data, &cursor, len, gc); + if (group != NULL) + qq_group_conv_refresh_online_member(gc, group); + break; + case QQ_GROUP_CMD_GET_MEMBER_INFO: + qq_process_group_cmd_get_member_info(data, &cursor, len, gc); + if (group != NULL) + qq_group_conv_refresh_online_member(gc, group); + break; + default: + gaim_debug(GAIM_DEBUG_WARNING, "QQ", + "Group cmd %s is processed by default\n", qq_group_cmd_get_desc(sub_cmd)); + _qq_process_group_cmd_reply_default(data, &cursor, len, gc); + } // switch sub_cmd + + } else + gaim_debug(GAIM_DEBUG_ERROR, "QQ", "Error decrypt group cmd reply\n"); + +} // qq_process_group_cmd_reply + +/*****************************************************************************/ +// END OF FILE diff -r 5642f4658b59 -r 983fd420e86b src/protocols/qq/group_network.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/protocols/qq/group_network.h Mon Jun 26 02:58:54 2006 +0000 @@ -0,0 +1,60 @@ +/** +* The QQ2003C protocol plugin + * + * for gaim + * + * Copyright (C) 2004 Puzzlebird + * + * 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 + */ + +// START OF FILE +/*****************************************************************************/ +#ifndef _QQ_GROUP_NETWORK_H_ +#define _QQ_GROUP_NETWORK_H_ + +#include +#include "connection.h" // GaimConnection +#include "packet_parse.h" // create_packet +#include "group.h" // qq_group + +typedef enum { + QQ_GROUP_CMD_CREATE_GROUP = 0x01, + QQ_GROUP_CMD_MEMBER_OPT = 0x02, + QQ_GROUP_CMD_MODIFY_GROUP_INFO = 0x03, + QQ_GROUP_CMD_GET_GROUP_INFO = 0x04, + QQ_GROUP_CMD_ACTIVATE_GROUP = 0x05, + QQ_GROUP_CMD_SEARCH_GROUP = 0x06, + QQ_GROUP_CMD_JOIN_GROUP = 0x07, + QQ_GROUP_CMD_JOIN_GROUP_AUTH = 0x08, + QQ_GROUP_CMD_EXIT_GROUP = 0x09, + QQ_GROUP_CMD_SEND_MSG = 0x0a, + QQ_GROUP_CMD_GET_ONLINE_MEMBER = 0x0b, + QQ_GROUP_CMD_GET_MEMBER_INFO = 0x0c, +} qq_group_cmd; + +typedef struct _group_packet { + guint16 send_seq; + guint32 internal_group_id; +} group_packet; + +const gchar *qq_group_cmd_get_desc(qq_group_cmd cmd); + +void qq_send_group_cmd(GaimConnection * gc, qq_group * group, guint8 * raw_data, gint data_len); +void qq_process_group_cmd_reply(guint8 * buf, gint buf_len, guint16 seq, GaimConnection * gc); + +#endif +/*****************************************************************************/ +// END OF FILE diff -r 5642f4658b59 -r 983fd420e86b src/protocols/qq/group_opt.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/protocols/qq/group_opt.c Mon Jun 26 02:58:54 2006 +0000 @@ -0,0 +1,479 @@ +/** +* The QQ2003C protocol plugin + * + * for gaim + * + * Copyright (C) 2004 Puzzlebird + * + * 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 + */ + +// START OF FILE +/*****************************************************************************/ +#include "debug.h" // gaim_debug +#include "notify.h" // gaim_notify_info +#include "request.h" // gaim_request_input + +#include "utils.h" // uid_to_gaim_name +#include "packet_parse.h" // create_packet_b +#include "buddy_info.h" // qq_send_packet_get_info +#include "char_conv.h" // utf8_to_qq +#include "group_admindlg.h" // qq_group_manage_window_show +#include "group_find.h" // qq_group_find_by_internal_group_id +#include "group_hash.h" // qq_group_refresh +#include "group_info.h" // qq_send_cmd_group_get_group_info +#include "group_join.h" // qq_send_cmd_group_auth +#include "group_network.h" // qq_send_group_cmd +#include "group_opt.h" +#include "qq.h" + +/*****************************************************************************/ +// This implement quick sort algorithm (low->high) +static void _quick_sort(gint * numbers, gint left, gint right) +{ + gint pivot, l_hold, r_hold; + + l_hold = left; + r_hold = right; + pivot = numbers[left]; + while (left < right) { + while ((numbers[right] >= pivot) && (left < right)) + right--; + if (left != right) { + numbers[left] = numbers[right]; + left++; + } // if + while ((numbers[left] <= pivot) && (left < right)) + left++; + if (left != right) { + numbers[right] = numbers[left]; + right--; + } // if + } // while + numbers[left] = pivot; + pivot = left; + left = l_hold; + right = r_hold; + if (left < pivot) + _quick_sort(numbers, left, pivot - 1); + if (right > pivot) + _quick_sort(numbers, pivot + 1, right); +} // _quick_sort + +/*****************************************************************************/ +static void _sort(guint32 * list) +{ + gint i; + for (i = 0; list[i] < 0xffffffff; i++) {; + } + _quick_sort(list, 0, i - 1); +} // _sort + +/*****************************************************************************/ +static void _qq_group_member_opt(GaimConnection * gc, qq_group * group, gint operation, guint32 * members) { + guint8 *data, *cursor; + gint i, count, data_len; + g_return_if_fail(gc != NULL && group != NULL && members != NULL); + + for (i = 0; members[i] != 0xffffffff; i++) {; + } + count = i; + data_len = 6 + count * 4; + data = g_newa(guint8, data_len); + cursor = data; + create_packet_b(data, &cursor, QQ_GROUP_CMD_MEMBER_OPT); + create_packet_dw(data, &cursor, group->internal_group_id); + create_packet_b(data, &cursor, operation); + for (i = 0; i < count; i++) + create_packet_dw(data, &cursor, members[i]); + qq_send_group_cmd(gc, group, data, data_len); +} // _qq_group_member_opt + +/*****************************************************************************/ +static void _qq_group_do_nothing_with_struct(group_member_opt * g) +{ + if (g != NULL) + g_free(g); +} // _qq_group_do_nothing_with_struct + +/*****************************************************************************/ +static void _qq_group_reject_application_real(group_member_opt * g, gchar * msg_utf8) +{ + qq_group *group; + g_return_if_fail(g != NULL && g->gc != NULL && g->internal_group_id > 0 && g->member > 0); + group = qq_group_find_by_internal_group_id(g->gc, g->internal_group_id); + g_return_if_fail(group != NULL); + qq_send_cmd_group_auth(g->gc, group, QQ_GROUP_AUTH_REQUEST_REJECT, g->member, msg_utf8); + g_free(g); +} // _qq_group_reject_application_real + +/*****************************************************************************/ +void qq_group_search_application_with_struct(group_member_opt * g) +{ + g_return_if_fail(g != NULL && g->gc != NULL && g->member > 0); + + qq_send_packet_get_info(g->gc, g->member, TRUE); // we wanna see window + gaim_request_action + (g->gc, NULL, _("Do you wanna approve the request?"), "", 2, g, + 2, _("Reject"), + G_CALLBACK(qq_group_reject_application_with_struct), + _("Approve"), G_CALLBACK(qq_group_approve_application_with_struct)); + +} // qq_group_search_application_with_struct + +/*****************************************************************************/ +void qq_group_reject_application_with_struct(group_member_opt * g) +{ + gchar *msg1, *msg2; + g_return_if_fail(g != NULL && g->gc != NULL && g->member > 0); + + msg1 = g_strdup_printf(_("You rejected %d's request"), g->member); + msg2 = g_strdup(_("Input your reason:")); + + gaim_request_input(g->gc, NULL, msg1, msg2, + _("Sorry, you are not my type..."), TRUE, FALSE, + NULL, _("Send"), + G_CALLBACK(_qq_group_reject_application_real), + _("Cancel"), G_CALLBACK(_qq_group_do_nothing_with_struct), g); + + g_free(msg1); + g_free(msg2); + +} // qq_group_do_nothing_with_struct + +/*****************************************************************************/ +void qq_group_approve_application_with_struct(group_member_opt * g) +{ + qq_group *group; + g_return_if_fail(g != NULL && g->gc != NULL && g->internal_group_id > 0 && g->member > 0); + group = qq_group_find_by_internal_group_id(g->gc, g->internal_group_id); + g_return_if_fail(group != NULL); + qq_send_cmd_group_auth(g->gc, group, QQ_GROUP_AUTH_REQUEST_APPROVE, g->member, ""); + qq_group_find_or_add_member(g->gc, group, g->member); + g_free(g); +} // qq_group_add_member_with_gc_and_uid + +/*****************************************************************************/ +void qq_group_modify_members(GaimConnection * gc, qq_group * group, guint32 * new_members) { + guint32 *old_members, *del_members, *add_members; + qq_buddy *q_bud; + qq_data *qd; + gint i = 0, old = 0, new = 0, del = 0, add = 0; + GList *list; + + g_return_if_fail(gc != NULL && gc->proto_data != NULL && group != NULL); + qd = (qq_data *) gc->proto_data; + if (new_members[0] == 0xffffffff) + return; + + old_members = g_newa(guint32, QQ_QUN_MEMBER_MAX); + del_members = g_newa(guint32, QQ_QUN_MEMBER_MAX); + add_members = g_newa(guint32, QQ_QUN_MEMBER_MAX); + + // construct the old member list + list = group->members; + while (list != NULL) { + q_bud = (qq_buddy *) list->data; + if (q_bud != NULL) + old_members[i++] = q_bud->uid; + list = list->next; + } // while + old_members[i] = 0xffffffff; // this is the end + + // sort to speed up making del_members and add_members list + _sort(old_members); + _sort(new_members); + + for (old = 0, new = 0; old_members[old] < 0xffffffff || new_members[new] < 0xffffffff;) { + if (old_members[old] > new_members[new]) + add_members[add++] = new_members[new++]; + else if (old_members[old] < new_members[new]) + del_members[del++] = old_members[old++]; + else { + if (old_members[old] < 0xffffffff) + old++; + if (new_members[new] < 0xffffffff) + new++; + } // if old_members, new_members + } // for old,new + del_members[del] = add_members[add] = 0xffffffff; + + for (i = 0; i < del; i++) + qq_group_remove_member_by_uid(group, del_members[i]); + for (i = 0; i < add; i++) + qq_group_find_or_add_member(gc, group, add_members[i]); + + if (del > 0) + _qq_group_member_opt(gc, group, QQ_GROUP_MEMBER_DEL, del_members); + if (add > 0) + _qq_group_member_opt(gc, group, QQ_GROUP_MEMBER_ADD, add_members); + +} // qq_group_modify_members + +/*****************************************************************************/ +void qq_group_process_modify_members_reply(guint8 * data, guint8 ** cursor, gint len, GaimConnection * gc) { + guint32 internal_group_id; + qq_group *group; + g_return_if_fail(data != NULL && gc != NULL); + + read_packet_dw(data, cursor, len, &internal_group_id); + g_return_if_fail(internal_group_id > 0); + + // we should have its info locally + group = qq_group_find_by_internal_group_id(gc, internal_group_id); + g_return_if_fail(group != NULL); + + gaim_debug(GAIM_DEBUG_INFO, "QQ", "Succeed in modify members for Qun %d\n", group->external_group_id); + + gaim_notify_info(gc, _("QQ Qun Operation"), _("You have successfully modify Qun member"), NULL); + +} // qq_group_process_modify_members_reply + +/*****************************************************************************/ +void qq_group_modify_info(GaimConnection * gc, qq_group * group) +{ + gint data_len, data_written; + guint8 *data, *cursor; + gchar *group_name, *group_desc, *notice; + + g_return_if_fail(gc != NULL && group != NULL); + + group_name = group->group_name_utf8 == NULL ? "" : utf8_to_qq(group->group_name_utf8, QQ_CHARSET_DEFAULT); + group_desc = group->group_desc_utf8 == NULL ? "" : utf8_to_qq(group->group_desc_utf8, QQ_CHARSET_DEFAULT); + notice = group->notice_utf8 == NULL ? "" : utf8_to_qq(group->notice_utf8, QQ_CHARSET_DEFAULT); + + data_len = 13 + 1 + strlen(group_name) + + 1 + strlen(group_desc) + + 1 + strlen(notice); + + data = g_newa(guint8, data_len); + cursor = data; + data_written = 0; + // 000-000 + data_written += create_packet_b(data, &cursor, QQ_GROUP_CMD_MODIFY_GROUP_INFO); + // 001-004 + data_written += create_packet_dw(data, &cursor, group->internal_group_id); + // 005-005 + data_written += create_packet_b(data, &cursor, 0x01); + // 006-006 + data_written += create_packet_b(data, &cursor, group->auth_type); + // 007-008 + data_written += create_packet_w(data, &cursor, 0x0000); + // 009-010 + data_written += create_packet_w(data, &cursor, group->group_category); + + data_written += create_packet_b(data, &cursor, strlen(group_name)); + data_written += create_packet_data(data, &cursor, group_name, strlen(group_name)); + + data_written += create_packet_w(data, &cursor, 0x0000); + + data_written += create_packet_b(data, &cursor, strlen(notice)); + data_written += create_packet_data(data, &cursor, notice, strlen(notice)); + + data_written += create_packet_b(data, &cursor, strlen(group_desc)); + data_written += create_packet_data(data, &cursor, group_desc, strlen(group_desc)); + + if (data_written != data_len) + gaim_debug(GAIM_DEBUG_ERROR, "QQ", + "Fail to create group_modify_info packet, expect %d bytes, wrote %d bytes\n", + data_len, data_written); + else + qq_send_group_cmd(gc, group, data, data_len); + +} // qq_group_modify_info + +/*****************************************************************************/ +void qq_group_process_modify_info_reply(guint8 * data, guint8 ** cursor, gint len, GaimConnection * gc) { + guint32 internal_group_id; + qq_group *group; + g_return_if_fail(data != NULL && gc != NULL); + + read_packet_dw(data, cursor, len, &internal_group_id); + g_return_if_fail(internal_group_id > 0); + + // we should have its info locally + group = qq_group_find_by_internal_group_id(gc, internal_group_id); + g_return_if_fail(group != NULL); + + gaim_debug(GAIM_DEBUG_INFO, "QQ", "Succeed in modify info for Qun %d\n", group->external_group_id); + qq_group_refresh(gc, group); + + gaim_notify_info(gc, _("QQ Qun Operation"), _("You have successfully modify Qun information"), NULL); + +} // qq_group_process_modify_info_reply + +/*****************************************************************************/ +// we create a very simple group first, and then let the user to modify +void qq_group_create_with_name(GaimConnection * gc, const gchar * name) +{ + gint data_len, data_written; + guint8 *data, *cursor; + qq_data *qd; + g_return_if_fail(gc != NULL && name != NULL); + + qd = (qq_data *) gc->proto_data; + data_len = 7 + 1 + strlen(name) + 2 + 1 + 1 + 4; + data = g_newa(guint8, data_len); + cursor = data; + + data_written = 0; + // we create the simpleset group, only group name is given + // 000 + data_written += create_packet_b(data, &cursor, QQ_GROUP_CMD_CREATE_GROUP); + // 001 + data_written += create_packet_b(data, &cursor, QQ_GROUP_TYPE_PERMANENT); + // 002 + data_written += create_packet_b(data, &cursor, QQ_GROUP_AUTH_TYPE_NEED_AUTH); + // 003-004 + data_written += create_packet_w(data, &cursor, 0x0000); + // 005-006 + data_written += create_packet_w(data, &cursor, 0x0003); + // 007 + data_written += create_packet_b(data, &cursor, strlen(name)); + data_written += create_packet_data(data, &cursor, (gchar *) name, strlen(name)); + data_written += create_packet_w(data, &cursor, 0x0000); + data_written += create_packet_b(data, &cursor, 0x00); // no group notice + data_written += create_packet_b(data, &cursor, 0x00); // no group desc + data_written += create_packet_dw(data, &cursor, qd->uid); // I am member of coz + + if (data_written != data_len) + gaim_debug(GAIM_DEBUG_ERROR, "QQ", + "Fail create create_group packet, expect %d bytes, written %d bytes\n", + data_len, data_written); + else + qq_send_group_cmd(gc, NULL, data, data_len); + +} // qq_group_create_with_name + +/*****************************************************************************/ +static void qq_group_setup_with_gc_and_uid(gc_and_uid * g) +{ + qq_group *group; + g_return_if_fail(g != NULL && g->gc != NULL && g->uid > 0); + + group = qq_group_find_by_internal_group_id(g->gc, g->uid); + g_return_if_fail(group != NULL); + + qq_group_detail_window_show(g->gc, group); + g_free(g); +} // qq_group_setup_with_gc_and_uid + +/*****************************************************************************/ +void qq_group_process_create_group_reply(guint8 * data, guint8 ** cursor, gint len, GaimConnection * gc) { + guint32 internal_group_id, external_group_id; + qq_group *group; + gc_and_uid *g; + qq_data *qd; + + g_return_if_fail(data != NULL && gc != NULL); + g_return_if_fail(gc->proto_data != NULL); + qd = (qq_data *) gc->proto_data; + + read_packet_dw(data, cursor, len, &internal_group_id); + read_packet_dw(data, cursor, len, &external_group_id); + g_return_if_fail(internal_group_id > 0 && external_group_id); + + group = qq_group_create_by_id(gc, internal_group_id, external_group_id); + group->my_status = QQ_GROUP_MEMBER_STATUS_IS_ADMIN; + group->creator_uid = qd->uid; + qq_group_refresh(gc, group); + + qq_group_activate_group(gc, internal_group_id); + qq_send_cmd_group_get_group_info(gc, group); + + gaim_debug(GAIM_DEBUG_INFO, "QQ", "Succeed in create Qun, external ID %d\n", group->external_group_id); + + g = g_new0(gc_and_uid, 1); + g->gc = gc; + g->uid = internal_group_id; + + gaim_request_action(gc, _("QQ Qun Operation"), + _("You have successfully created a Qun"), + _ + ("Would you like to set up the Qun details now?"), + 1, g, 2, _("Setup"), + G_CALLBACK(qq_group_setup_with_gc_and_uid), + _("Cancel"), G_CALLBACK(qq_do_nothing_with_gc_and_uid)); + +} // qq_group_process_modify_info_reply + +/*****************************************************************************/ +// we have to activate group after creation, otherwise the group can not be searched +void qq_group_activate_group(GaimConnection * gc, guint32 internal_group_id) +{ + gint data_len, data_written; + guint8 *data, *cursor; + g_return_if_fail(gc != NULL && internal_group_id > 0); + + data_len = 5; + data = g_newa(guint8, data_len); + cursor = data; + + data_written = 0; + // we create the simpleset group, only group name is given + // 000 + data_written += create_packet_b(data, &cursor, QQ_GROUP_CMD_ACTIVATE_GROUP); + // 001-005 + data_written += create_packet_dw(data, &cursor, internal_group_id); + + if (data_written != data_len) + gaim_debug(GAIM_DEBUG_ERROR, "QQ", + "Fail create activate_group packet, expect %d bytes, written %d bytes\n", + data_len, data_written); + else + qq_send_group_cmd(gc, NULL, data, data_len); + +} // qq_group_activate_group + +/*****************************************************************************/ +void qq_group_process_activate_group_reply(guint8 * data, guint8 ** cursor, gint len, GaimConnection * gc) { + guint32 internal_group_id; + qq_group *group; + g_return_if_fail(data != NULL && gc != NULL); + + read_packet_dw(data, cursor, len, &internal_group_id); + g_return_if_fail(internal_group_id > 0); + + // we should have its info locally + group = qq_group_find_by_internal_group_id(gc, internal_group_id); + g_return_if_fail(group != NULL); + + gaim_debug(GAIM_DEBUG_INFO, "QQ", "Succeed in activate Qun %d\n", group->external_group_id); + +} // qq_group_process_activate_group_reply + +/*****************************************************************************/ +void qq_group_manage_group(GaimConnection * gc, GHashTable * data) +{ + gchar *internal_group_id_ptr; + guint32 internal_group_id; + qq_group *group; + + g_return_if_fail(gc != NULL && data != NULL); + + internal_group_id_ptr = g_hash_table_lookup(data, "internal_group_id"); + internal_group_id = strtol(internal_group_id_ptr, NULL, 10); + g_return_if_fail(internal_group_id > 0); + + group = qq_group_find_by_internal_group_id(gc, internal_group_id); + g_return_if_fail(group != NULL); + + qq_group_detail_window_show(gc, group); + +} // qq_group_manage_members + + +/*****************************************************************************/ +// END OF FILE diff -r 5642f4658b59 -r 983fd420e86b src/protocols/qq/group_opt.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/protocols/qq/group_opt.h Mon Jun 26 02:58:54 2006 +0000 @@ -0,0 +1,67 @@ +/** +* The QQ2003C protocol plugin + * + * for gaim + * + * Copyright (C) 2004 Puzzlebird + * + * 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 + */ + +// START OF FILE +/*****************************************************************************/ +#ifndef _QQ_GROUP_OPT_H_ +#define _QQ_GROUP_OPT_H_ + +#include +#include "connection.h" // GaimConnection +#include "group.h" // qq_group + +#define QQ_QUN_MEMBER_MAX 80 // max number of the group + +typedef struct _group_member_opt { + GaimConnection *gc; + guint32 internal_group_id; + guint32 member; +} group_member_opt; + +enum { + QQ_GROUP_TYPE_PERMANENT = 0x01, + QQ_GROUP_TYPE_TEMPORARY, +}; + +enum { + QQ_GROUP_MEMBER_ADD = 0x01, + QQ_GROUP_MEMBER_DEL, +}; + +void qq_group_modify_members(GaimConnection * gc, qq_group * group, guint32 * new_members); +void qq_group_modify_info(GaimConnection * gc, qq_group * group); + +void qq_group_approve_application_with_struct(group_member_opt * g); +void qq_group_reject_application_with_struct(group_member_opt * g); +void qq_group_search_application_with_struct(group_member_opt * g); + +void qq_group_process_modify_info_reply(guint8 * data, guint8 ** cursor, gint len, GaimConnection * gc); +void qq_group_process_modify_members_reply(guint8 * data, guint8 ** cursor, gint len, GaimConnection * gc); +void qq_group_manage_group(GaimConnection * gc, GHashTable * data); +void qq_group_create_with_name(GaimConnection * gc, const gchar * name); +void qq_group_activate_group(GaimConnection * gc, guint32 internal_group_id); +void qq_group_process_activate_group_reply(guint8 * data, guint8 ** cursor, gint len, GaimConnection * gc); +void qq_group_process_create_group_reply(guint8 * data, guint8 ** cursor, gint len, GaimConnection * gc); + +#endif +/*****************************************************************************/ +// END OF FILE diff -r 5642f4658b59 -r 983fd420e86b src/protocols/qq/group_search.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/protocols/qq/group_search.c Mon Jun 26 02:58:54 2006 +0000 @@ -0,0 +1,125 @@ +/** + * The QQ2003C protocol plugin + * + * for gaim + * + * Copyright (C) 2004 Puzzlebird + * + * 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 + */ + +// START OF FILE +/*****************************************************************************/ +#include "debug.h" // gaim_debug + +#include "char_conv.h" // convert_as_pascal_string +#include "group_free.h" // qq_group_free_not_subscribed +#include "group_network.h" // qq_send_group_cmd +#include "group_search.h" +#include "utils.h" + +enum { + QQ_GROUP_SEARCH_TYPE_BY_ID = 0x01, + QQ_GROUP_SEARCH_TYPE_DEMO = 0x02, +}; + +/*****************************************************************************/ +// send packet to search for qq_group +void qq_send_cmd_group_search_group(GaimConnection * gc, guint32 external_group_id) { + guint8 *raw_data, *cursor, type; + gint bytes, data_len; + + g_return_if_fail(gc != NULL); + + data_len = 6; + raw_data = g_newa(guint8, data_len); + cursor = raw_data; + type = (external_group_id == 0x00000000) ? QQ_GROUP_SEARCH_TYPE_DEMO : QQ_GROUP_SEARCH_TYPE_BY_ID; + + bytes = 0; + bytes += create_packet_b(raw_data, &cursor, QQ_GROUP_CMD_SEARCH_GROUP); + bytes += create_packet_b(raw_data, &cursor, type); + bytes += create_packet_dw(raw_data, &cursor, external_group_id); + + if (bytes != data_len) + gaim_debug(GAIM_DEBUG_ERROR, "QQ", + "Fail create packet for %s\n", qq_group_cmd_get_desc(QQ_GROUP_CMD_SEARCH_GROUP)); + else + qq_send_group_cmd(gc, NULL, raw_data, data_len); +} // qq_send_cmd_group_search_group + +/*****************************************************************************/ +// process group cmd reply "search group" +void qq_process_group_cmd_search_group(guint8 * data, guint8 ** cursor, gint len, GaimConnection * gc) { + guint8 search_type; + guint16 unknown; + gint bytes, pascal_len, i; + qq_data *qd; + GaimRoomlistRoom *room; + qq_group *group; + + g_return_if_fail(gc != NULL && gc->proto_data != NULL); + g_return_if_fail(data != NULL && len > 0); + qd = (qq_data *) gc->proto_data; + + i = 0; + bytes += read_packet_b(data, cursor, len, &search_type); + group = g_newa(qq_group, 1); + + // now it starts with group_info_entry + while (*cursor < (data + len)) { // still have data to read + // begin of one qq_group + bytes = 0; + i++; + bytes += read_packet_dw(data, cursor, len, &(group->internal_group_id)); + bytes += read_packet_dw(data, cursor, len, &(group->external_group_id)); + bytes += read_packet_b(data, cursor, len, &(group->group_type)); + bytes += read_packet_w(data, cursor, len, &(unknown)); // protocal changed, gfhuang + bytes += read_packet_w(data, cursor, len, &(unknown)); // protocal changed, gfhuang + bytes += read_packet_dw(data, cursor, len, &(group->creator_uid)); + bytes += read_packet_w(data, cursor, len, &(unknown)); // protocal changed, gfhuang + bytes += read_packet_w(data, cursor, len, &(unknown)); // protocal changed, gfhuang + bytes += read_packet_w(data, cursor, len, &(unknown)); // protocal changed, gfhuang + bytes += read_packet_dw(data, cursor, len, &(group->group_category)); + pascal_len = convert_as_pascal_string(*cursor, &(group->group_name_utf8), QQ_CHARSET_DEFAULT); + bytes += pascal_len; + *cursor += pascal_len; + bytes += read_packet_w(data, cursor, len, &(unknown)); + bytes += read_packet_b(data, cursor, len, &(group->auth_type)); + pascal_len = convert_as_pascal_string(*cursor, &(group->group_desc_utf8), QQ_CHARSET_DEFAULT); + bytes += pascal_len; + *cursor += pascal_len; + // end of one qq_group + room = gaim_roomlist_room_new(GAIM_ROOMLIST_ROOMTYPE_ROOM, group->group_name_utf8, NULL); + gaim_roomlist_room_add_field(qd->roomlist, room, g_strdup_printf("%d", group->external_group_id)); + gaim_roomlist_room_add_field(qd->roomlist, room, g_strdup_printf("%d", group->creator_uid)); + gaim_roomlist_room_add_field(qd->roomlist, room, group->group_desc_utf8); + gaim_roomlist_room_add_field(qd->roomlist, room, g_strdup_printf("%d", group->internal_group_id)); + gaim_roomlist_room_add_field(qd->roomlist, room, g_strdup_printf("%d", group->group_type)); + gaim_roomlist_room_add_field(qd->roomlist, room, g_strdup_printf("%d", group->auth_type)); + gaim_roomlist_room_add_field(qd->roomlist, room, g_strdup_printf("%d", group->group_category)); + gaim_roomlist_room_add_field(qd->roomlist, room, group->group_name_utf8); + gaim_roomlist_room_add(qd->roomlist, room); + } // while + if(*cursor > (data + len)) { + gaim_debug(GAIM_DEBUG_ERROR, "QQ", "group_cmd_search_group: Dangerous error! maybe protocal changed, notify me!"); + } + gaim_roomlist_set_in_progress(qd->roomlist, FALSE); + gaim_debug(GAIM_DEBUG_INFO, "QQ", "Search group reply: %d groups\n", i); + +} // qq_process_group_cmd_search_group + +/*****************************************************************************/ +// END OF FILE diff -r 5642f4658b59 -r 983fd420e86b src/protocols/qq/group_search.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/protocols/qq/group_search.h Mon Jun 26 02:58:54 2006 +0000 @@ -0,0 +1,36 @@ +/** +* The QQ2003C protocol plugin + * + * for gaim + * + * Copyright (C) 2004 Puzzlebird + * + * 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 + */ + +// START OF FILE +/*****************************************************************************/ +#ifndef _QQ_GROUP_SEARCH_H_ +#define _QQ_GROUP_SEARCH_H_ + +#include +#include "connection.h" // GaimConnection + +void qq_send_cmd_group_search_group(GaimConnection * gc, guint32 external_group_id); +void qq_process_group_cmd_search_group(guint8 * data, guint8 ** cursor, gint len, GaimConnection * gc); + +#endif +/*****************************************************************************/ +// END OF FILE diff -r 5642f4658b59 -r 983fd420e86b src/protocols/qq/header_info.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/protocols/qq/header_info.c Mon Jun 26 02:58:54 2006 +0000 @@ -0,0 +1,118 @@ +/** +* The QQ2003C protocol plugin + * + * for gaim + * + * Copyright (C) 2004 Puzzlebird + * + * 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 + */ + +// START OF FILE +/*****************************************************************************/ +#include "header_info.h" + +#define QQ_CLIENT_062E 0x062e // GB QQ2000c build 0630 +#define QQ_CLIENT_072E 0x072e // En QQ2000c build 0305 +#define QQ_CLIENT_0801 0x0801 // En QQ2000c build 0630 +#define QQ_CLIENT_0A1D 0x0a1d // Gb QQ2003c build 0808 +#define QQ_CLIENT_0B07 0x0b07 // Gb QQ2003c build 0925 +#define QQ_CLIENT_0B2F 0x0b2f // GB QQ2003iii build 0117 +#define QQ_CLIENT_0B35 0x0b35 // GB QQ2003iii build 0304 (offical release) +#define QQ_CLIENT_0B37 0x0b37 // GB QQ2003iii build 0304 (April 05 updates) +#define QQ_SERVER_0100 0x0100 // server + +/*****************************************************************************/ +// given command alias, return the command name accordingly +const gchar *qq_get_cmd_desc(gint type) +{ + switch (type) { + case QQ_CMD_LOGOUT: + return "QQ_CMD_LOGOUT"; + case QQ_CMD_KEEP_ALIVE: + return "QQ_CMD_KEEP_ALIVE"; + case QQ_CMD_UPDATE_INFO: + return "QQ_CMD_UPDATE_INFO"; + case QQ_CMD_SEARCH_USER: + return "QQ_CMD_SEARCH_USER"; + case QQ_CMD_GET_USER_INFO: + return "QQ_CMD_GET_USER_INFO"; + case QQ_CMD_ADD_FRIEND_WO_AUTH: + return "QQ_CMD_ADD_FRIEND_WO_AUTH"; + case QQ_CMD_DEL_FRIEND: + return "QQ_CMD_DEL_FRIEND"; + case QQ_CMD_BUDDY_AUTH: + return "QQ_CMD_BUDDY_AUTH"; + case QQ_CMD_CHANGE_ONLINE_STATUS: + return "QQ_CMD_CHANGE_ONLINE_STATUS"; + case QQ_CMD_ACK_SYS_MSG: + return "QQ_CMD_ACK_SYS_MSG"; + case QQ_CMD_SEND_IM: + return "QQ_CMD_SEND_IM"; + case QQ_CMD_RECV_IM: + return "QQ_CMD_RECV_IM"; + case QQ_CMD_REMOVE_SELF: + return "QQ_CMD_REMOVE_SELF"; + case QQ_CMD_LOGIN: + return "QQ_CMD_LOGIN"; + case QQ_CMD_GET_FRIENDS_LIST: + return "QQ_CMD_GET_FRIENDS_LIST"; + case QQ_CMD_GET_FRIENDS_ONLINE: + return "QQ_CMD_GET_FRIENDS_ONLINE"; + case QQ_CMD_GROUP_CMD: + return "QQ_CMD_GROUP_CMD"; + case QQ_CMD_GET_ALL_LIST_WITH_GROUP: // by gfhuang + return "QQ_CMD_GET_ALL_LIST_WITH_GROUP"; + case QQ_CMD_REQUEST_LOGIN_TOKEN: + return "QQ_CMD_REQUEST_LOGIN_TOKEN"; // by gfhuang + case QQ_CMD_RECV_MSG_SYS: + return "QQ_CMD_RECV_MSG_SYS"; + case QQ_CMD_RECV_MSG_FRIEND_CHANGE_STATUS: + return "QQ_CMD_RECV_MSG_FRIEND_CHANGE_STATUS"; + default: + return "UNKNOWN_TYPE"; + } // switch (type) +} // qq_get_cmd_desc + +/*****************************************************************************/ +// given source tag, return its description accordingly +const gchar *qq_get_source_str(gint source) +{ + switch (source) { + case QQ_CLIENT_062E: + return "GB QQ2000c build 0630"; + case QQ_CLIENT_072E: + return "En QQ2000c build 0305"; + case QQ_CLIENT_0801: + return "En QQ2000c build 0630"; + case QQ_CLIENT_0A1D: + return "GB QQ2003ii build 0808"; + case QQ_CLIENT_0B07: + return "GB QQ2003ii build 0925"; + case QQ_CLIENT_0B2F: + return "GB QQ2003iii build 0117"; + case QQ_CLIENT_0B35: + return "GB QQ2003iii build 0304"; + case QQ_CLIENT_0B37: + return "GB QQ2003iii build 0304 (April 5 update)"; + case QQ_SERVER_0100: + return "QQ Server 0100"; + default: + return "QQ unknown version"; + } +} // qq_get_source_str + +/*****************************************************************************/ +// END OF FILE diff -r 5642f4658b59 -r 983fd420e86b src/protocols/qq/header_info.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/protocols/qq/header_info.h Mon Jun 26 02:58:54 2006 +0000 @@ -0,0 +1,72 @@ +/** +* The QQ2003C protocol plugin + * + * for gaim + * + * Copyright (C) 2004 Puzzlebird + * + * 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 + */ + +// START OF FILE +/*****************************************************************************/ +#ifndef _QQ_HEADER_INFO_H_ +#define _QQ_HEADER_INFO_H_ + +#include + +#define QQ_UDP_HEADER_LENGTH 7 +#define QQ_TCP_HEADER_LENGTH 9 + +#define QQ_PACKET_TAG 0x02 // all QQ text packets starts with it +#define QQ_PACKET_TAIL 0x03 // all QQ text packets end with it + +//#define QQ_CLIENT 0x0b37 // QQ2003iii build 0304, Aprili 05 update + +// list of known QQ commands +enum { + QQ_CMD_LOGOUT = 0x0001, // log out + QQ_CMD_KEEP_ALIVE = 0x0002, // get onlines from tencent + QQ_CMD_UPDATE_INFO = 0x0004, // update information + QQ_CMD_SEARCH_USER = 0x0005, // serach for user + QQ_CMD_GET_USER_INFO = 0x0006, // get user information + QQ_CMD_ADD_FRIEND_WO_AUTH = 0x0009, // add friend without auth + QQ_CMD_DEL_FRIEND = 0x000a, // delete a friend + QQ_CMD_BUDDY_AUTH = 0x000b, // buddy authentication + QQ_CMD_CHANGE_ONLINE_STATUS = 0x000d, // change my online status + QQ_CMD_ACK_SYS_MSG = 0x0012, // ack system message + QQ_CMD_SEND_IM = 0x0016, // send message + QQ_CMD_RECV_IM = 0x0017, // receive message + QQ_CMD_REMOVE_SELF = 0x001c, // remove self + QQ_CMD_REQUEST_KEY = 0x001d, // request key for file transfer + QQ_CMD_CELL_PHONE_1 = 0x0021, // cell phone 1 + QQ_CMD_LOGIN = 0x0022, // login + QQ_CMD_GET_FRIENDS_LIST = 0x0026, // retrieve my freinds list + QQ_CMD_GET_FRIENDS_ONLINE = 0x0027, // get my online friends list + QQ_CMD_CELL_PHONE_2 = 0x0029, // cell phone 2 + QQ_CMD_GROUP_CMD = 0x0030, // group command + QQ_CMD_GET_ALL_LIST_WITH_GROUP = 0x58, //by gfhuang + QQ_CMD_REQUEST_LOGIN_TOKEN = 0x62, //by gfhuang + QQ_CMD_RECV_MSG_SYS = 0x0080, // receive a system message + QQ_CMD_RECV_MSG_FRIEND_CHANGE_STATUS = 0x0081, // friends change status +}; + +const gchar *qq_get_cmd_desc(gint type); + +const gchar *qq_get_source_str(gint source); + +#endif +/*****************************************************************************/ +// END OF FILE diff -r 5642f4658b59 -r 983fd420e86b src/protocols/qq/im.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/protocols/qq/im.c Mon Jun 26 02:58:54 2006 +0000 @@ -0,0 +1,795 @@ +/** + * The QQ2003C protocol plugin + * + * for gaim + * + * Copyright (C) 2004 Puzzlebird + * + * 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 + */ + +// START OF FILE +/*****************************************************************************/ +#include "conversation.h" // GaimConeversation +#include "debug.h" // gaim_debug +#include "internal.h" // strlen, _("get_text") +#include "cipher.h" // md5 functions //md5.h gfhuang +#include "notify.h" // gaim_notify_warning +#include "server.h" // serv_got_im +#include "util.h" // gaim_markup_find_tag, gaim_base16_decode + +#include "utils.h" // gen_ip_str +#include "packet_parse.h" // create_packet +#include "buddy_info.h" // qq_send_packet_get_info +#include "buddy_list.h" // qq_send_packet_get_buddies_online +#include "buddy_opt.h" // qq_add_buddy_by_recv_packet +#include "char_conv.h" // qq_to_utf8, qq_encode_to_gaim +#include "crypt.h" // qq_crypt +#include "group_im.h" // qq_send_packet_group_im +#include "header_info.h" // cmd alias +#include "im.h" +#include "send_core.h" // qq_send_cmd +#include "send_core.h" // qq_data +#include "send_file.h" + +#define QQ_SEND_IM_REPLY_OK 0x00 +#define DEFAULT_FONT_NAME_LEN 4 + +// a debug function +static void _qq_show_packet(gchar * desc, gchar * buf, gint len); + +enum +{ + QQ_NORMAL_IM_TEXT = 0x000b, + QQ_NORMAL_IM_FILE_REQUEST_TCP = 0x0001, //gfhuang + QQ_NORMAL_IM_FILE_APPROVE_TCP = 0x0003, + QQ_NORMAL_IM_FILE_REJECT_TCP = 0x0005, + QQ_NORMAL_IM_FILE_REQUEST_UDP = 0x0035, + QQ_NORMAL_IM_FILE_APPROVE_UDP = 0x0037, + QQ_NORMAL_IM_FILE_REJECT_UDP = 0x0039, + QQ_NORMAL_IM_FILE_NOTIFY = 0x003b, + QQ_NORMAL_IM_FILE_PASV = 0x003f, // are you behind a firewall, gfhuang + QQ_NORMAL_IM_FILE_CANCEL = 0x0049, + QQ_NORMAL_IM_FILE_EX_REQUEST_UDP = 0x81, //gfhuang + QQ_NORMAL_IM_FILE_EX_REQUEST_ACCEPT = 0x83, //gfhuang + QQ_NORMAL_IM_FILE_EX_REQUEST_CANCEL = 0x85, //gfhuang + QQ_NORMAL_IM_FILE_EX_NOTIFY_IP = 0x87 //gfhuang +}; + + +enum { + QQ_RECV_SYS_IM_KICK_OUT = 0x01, +}; + +/* +guint8 send_im_tail[QQ_SEND_IM_AFTER_MSG_LEN] = { + 0x00, // end of msg + 0x0a, // font attr, size=10, no bold, no italic, no underline + 0x00, // font color red 00-ff + 0x00, // font color green + 0x00, // font color blue + 0x00, // unknown + 0x86, 0x22, // encoding, 0x8622=GB, 0x0000=EN, define BIG5 support here + 0xcb, 0xce, 0xcc, 0xe5, 0x0d // font name, related, not fix length +}; +*/ + +typedef struct _qq_recv_im_header qq_recv_im_header; +typedef struct _qq_recv_normal_im_text qq_recv_normal_im_text; +typedef struct _qq_recv_normal_im_common qq_recv_normal_im_common; +typedef struct _qq_recv_normal_im_unprocessed qq_recv_normal_im_unprocessed; + +struct _qq_recv_normal_im_common { + // this is the common part of normal_text + guint16 sender_ver; + guint32 sender_uid; + guint32 receiver_uid; + guint8 *session_md5; + guint16 normal_im_type; +}; + +struct _qq_recv_normal_im_text { + qq_recv_normal_im_common *common; + // now comes the part fo text only + guint16 msg_seq; + guint32 send_time; + guint8 unknown1; + guint8 sender_icon; + guint8 unknown2[3]; + guint8 is_there_font_attr; + guint8 unknown3[4]; + guint8 msg_type; + guint8 *msg; // no fixed length, ends with 0x00 + guint8 *font_attr; + gint font_attr_len; +}; + +struct _qq_recv_normal_im_unprocessed { + qq_recv_normal_im_common *common; + // now comes the part of unprocessed + guint8 *unknown; // no fixed length + gint length; +}; + +struct _qq_recv_im_header { + guint32 sender_uid; + guint32 receiver_uid; + guint32 server_im_seq; + guint8 sender_ip[4]; + guint16 sender_port; + guint16 im_type; +}; + +#define QQ_SEND_IM_AFTER_MSG_HEADER_LEN 8 +#define DEFAULT_FONT_NAME "\0xcb\0xce\0xcc\0xe5" + +guint8 *qq_get_send_im_tail(const gchar * font_color, + const gchar * font_size, + const gchar * font_name, + gboolean is_bold, gboolean is_italic, gboolean is_underline, guint tail_len) +{ + gchar *s1, *s2; + unsigned char *rgb; + guint font_name_len; + guint8 *send_im_tail; + const guint8 simsun[] = { 0xcb, 0xce, 0xcc, 0xe5 }; + + if (font_name) { + font_name_len = strlen(font_name); + } else { + font_name_len = DEFAULT_FONT_NAME_LEN; + font_name = simsun; + } + + send_im_tail = g_new0(guint8, tail_len); + + g_strlcpy(send_im_tail + QQ_SEND_IM_AFTER_MSG_HEADER_LEN, + font_name, tail_len - QQ_SEND_IM_AFTER_MSG_HEADER_LEN); + send_im_tail[tail_len - 1] = tail_len; + + send_im_tail[0] = 0x00; + if (font_size) { + send_im_tail[1] = (guint8) (atoi(font_size) * 3 + 1); + } else { + send_im_tail[1] = 10; + } + if (is_bold) + send_im_tail[1] |= 0x20; + if (is_italic) + send_im_tail[1] |= 0x40; + if (is_underline) + send_im_tail[1] |= 0x80; + + if (font_color) { + s1 = g_strndup(font_color + 1, 6); + /* Henry: maybe this is a bug of gaim, the string should have + * the length of odd number @_@ + */ + s2 = g_strdup_printf("%sH", s1); +// gaim_base16_decode(s2, &rgb); by gfhuang + rgb = gaim_base16_decode(s2, NULL); + g_free(s1); + g_free(s2); + memcpy(send_im_tail + 2, rgb, 3); + g_free(rgb); + } else { + send_im_tail[2] = send_im_tail[3] = send_im_tail[4] = 0; + } + + send_im_tail[5] = 0x00; + send_im_tail[6] = 0x86; + send_im_tail[7] = 0x22; // encoding, 0x8622=GB, 0x0000=EN, define BIG5 support here + _qq_show_packet("QQ_MESG", send_im_tail, tail_len); + return (guint8 *) send_im_tail; +} // qq_get_send_im_tail + +/*****************************************************************************/ +static const gchar *qq_get_recv_im_type_str(gint type) +{ + switch (type) { + case QQ_RECV_IM_TO_BUDDY: + return "QQ_RECV_IM_TO_BUDDY"; + case QQ_RECV_IM_TO_UNKNOWN: + return "QQ_RECV_IM_TO_UNKNOWN"; + case QQ_RECV_IM_UNKNOWN_QUN_IM: + return "QQ_RECV_IM_UNKNOWN_QUN_IM"; + case QQ_RECV_IM_ADD_TO_QUN: + return "QQ_RECV_IM_ADD_TO_QUN"; + case QQ_RECV_IM_DEL_FROM_QUN: + return "QQ_RECV_IM_DEL_FROM_QUN"; + case QQ_RECV_IM_APPLY_ADD_TO_QUN: + return "QQ_RECV_IM_APPLY_ADD_TO_QUN"; + case QQ_RECV_IM_CREATE_QUN: + return "QQ_RECV_IM_CREATE_QUN"; + case QQ_RECV_IM_SYS_NOTIFICATION: + return "QQ_RECV_IM_SYS_NOTIFICATION"; + case QQ_RECV_IM_APPROVE_APPLY_ADD_TO_QUN: + return "QQ_RECV_IM_APPROVE_APPLY_ADD_TO_QUN"; + case QQ_RECV_IM_REJCT_APPLY_ADD_TO_QUN: + return "QQ_RECV_IM_REJCT_APPLY_ADD_TO_QUN"; + case QQ_RECV_IM_TEMP_QUN_IM: + return "QQ_RECV_IM_TEMP_QUN_IM"; //gfhuang + case QQ_RECV_IM_QUN_IM: + return "QQ_RECV_IM_QUN_IM"; //gfhuang + default: + return "QQ_RECV_IM_UNKNOWN"; + } // switch type +} // qq_get_recv_im_type_str + +/*****************************************************************************/ +// generate a md5 key using uid and session_key +static gchar *_gen_session_md5(gint uid, gchar * session_key) +{ + gchar *src, md5_str[QQ_KEY_LENGTH]; + guint8 *cursor; +// md5_state_t ctx; //gfhuang + GaimCipher *cipher; + GaimCipherContext *context; + + + src = g_newa(gchar, 20); + cursor = src; + create_packet_dw(src, &cursor, uid); + create_packet_data(src, &cursor, session_key, QQ_KEY_LENGTH); + +/* gfhuang + md5_init(&ctx); + md5_append(&ctx, src, 20); + md5_finish(&ctx, (md5_byte_t *) md5_str); +*/ + cipher = gaim_ciphers_find_cipher("md5"); + context = gaim_cipher_context_new(cipher, NULL); + gaim_cipher_context_append(context, src, 20); + gaim_cipher_context_digest(context, sizeof(md5_str), md5_str, NULL); + gaim_cipher_context_destroy(context); + + return g_memdup(md5_str, QQ_KEY_LENGTH); +} // _gen_session_md5 + +/*****************************************************************************/ +// when we receive a message, +// we send an ACK which is the first 16 bytes of incoming packet +static void _qq_send_packet_recv_im_ack(GaimConnection * gc, guint16 seq, guint8 * data) { + qq_send_cmd(gc, QQ_CMD_RECV_IM, FALSE, seq, FALSE, data, 16); +} // _qq_send_packet_recv_im_ack + +/*****************************************************************************/ +// read the common parts of the normal_im, +// returns the bytes read if succeed, or -1 if there is any error +static gint _qq_normal_im_common_read(guint8 * data, guint8 ** cursor, gint len, qq_recv_normal_im_common * common) { + + gint bytes; + g_return_val_if_fail(data != NULL && len != 0 && common != NULL, -1); + + bytes = 0; + // now push data into common header + bytes += read_packet_w(data, cursor, len, &(common->sender_ver)); + bytes += read_packet_dw(data, cursor, len, &(common->sender_uid)); + bytes += read_packet_dw(data, cursor, len, &(common->receiver_uid)); + + common->session_md5 = g_memdup(*cursor, QQ_KEY_LENGTH); + bytes += QQ_KEY_LENGTH; + *cursor += QQ_KEY_LENGTH; + + bytes += read_packet_w(data, cursor, len, &(common->normal_im_type)); + + if (bytes != 28) { // read common place fail + gaim_debug(GAIM_DEBUG_ERROR, "QQ", "Expect 28 bytes, read %d bytes\n", bytes); + return -1; + } // if bytes + + return bytes; +} // _qq_normal_im_common_read + +/*****************************************************************************/ +// process received normal text IM +static void _qq_process_recv_normal_im_text + (guint8 * data, guint8 ** cursor, gint len, qq_recv_normal_im_common * common, GaimConnection * gc) { + guint16 gaim_msg_type; + gchar *name; + gchar *msg_with_gaim_smiley; + gchar *msg_utf8_encoded; + qq_data *qd; + qq_recv_normal_im_text *im_text; + + g_return_if_fail(gc != NULL && gc->proto_data != NULL && common != NULL); + qd = (qq_data *) gc->proto_data; + + // now it is QQ_NORMAL_IM_TEXT + if (*cursor >= (data + len - 1)) { + gaim_debug(GAIM_DEBUG_WARNING, "QQ", "Received normal IM text is empty\n"); + return; + } else + im_text = g_newa(qq_recv_normal_im_text, 1); + + im_text->common = common; + + // push data into im_text + read_packet_w(data, cursor, len, &(im_text->msg_seq)); + read_packet_dw(data, cursor, len, &(im_text->send_time)); + read_packet_b(data, cursor, len, &(im_text->unknown1)); + read_packet_b(data, cursor, len, &(im_text->sender_icon)); + read_packet_data(data, cursor, len, (guint8 *) & (im_text->unknown2), 3); + read_packet_b(data, cursor, len, &(im_text->is_there_font_attr)); + ////////////////////// + // from lumaqq for unknown3, gfhuang + // totalFragments = buf.get() & 255; + // fragmentSequence = buf.get() & 255; + // messageId = buf.getChar(); + read_packet_data(data, cursor, len, (guint8 *) & (im_text->unknown3), 4); + read_packet_b(data, cursor, len, &(im_text->msg_type)); + + // we need to check if this is auto-reply + // QQ2003iii build 0304, returns the msg without font_attr + // even the is_there_font_attr shows 0x01, and msg does not ends with 0x00 + if (im_text->msg_type == QQ_IM_AUTO_REPLY) { + im_text->is_there_font_attr = 0x00; // indeed there is no this flag + im_text->msg = g_strndup(*cursor, data + len - *cursor); + } else { // it is normal mesasge + if (im_text->is_there_font_attr) { + im_text->msg = g_strdup(*cursor); + *cursor += strlen(im_text->msg) + 1; + im_text->font_attr_len = data + len - *cursor; + im_text->font_attr = g_memdup(*cursor, im_text->font_attr_len); + } else // not im_text->is_there_font_attr + im_text->msg = g_strndup(*cursor, data + len - *cursor); + } // if im_text->msg_type + _qq_show_packet("QQ_MESG recv", data, *cursor - data); + + name = uid_to_gaim_name(common->sender_uid); + if (gaim_find_buddy(gc->account, name) == NULL) + qq_add_buddy_by_recv_packet(gc, common->sender_uid, FALSE, TRUE); + + gaim_msg_type = (im_text->msg_type == QQ_IM_AUTO_REPLY) ? GAIM_MESSAGE_AUTO_RESP : 0; + + msg_with_gaim_smiley = qq_smiley_to_gaim(im_text->msg); + msg_utf8_encoded = im_text->is_there_font_attr ? + qq_encode_to_gaim(im_text->font_attr, + im_text->font_attr_len, + msg_with_gaim_smiley) : qq_to_utf8(msg_with_gaim_smiley, QQ_CHARSET_DEFAULT); + + // send encoded to gaim, note that we use im_text->send_time, + // not the time we receive the message + // as it may have been dealyed when I am not online. + serv_got_im(gc, name, msg_utf8_encoded, gaim_msg_type, (time_t) im_text->send_time); + + g_free(msg_utf8_encoded); + g_free(msg_with_gaim_smiley); + g_free(name); + g_free(im_text->msg); + if (im_text->is_there_font_attr) + g_free(im_text->font_attr); +} // _qq_process_recv_normal_im_text + +/*****************************************************************************/ +// it is a normal IM, maybe text or video request +static void _qq_process_recv_normal_im(guint8 * data, guint8 ** cursor, gint len, GaimConnection * gc) +{ + gint bytes; + qq_recv_normal_im_common *common; + qq_recv_normal_im_unprocessed *im_unprocessed; + + g_return_if_fail (data != NULL && len != 0); + + if (*cursor >= (data + len - 1)) { + gaim_debug (GAIM_DEBUG_WARNING, "QQ", + "Received normal IM is empty\n"); + return; + } + else + common = g_newa (qq_recv_normal_im_common, 1); + + bytes = _qq_normal_im_common_read (data, cursor, len, common); + if (bytes < 0) { + gaim_debug (GAIM_DEBUG_ERROR, "QQ", + "Fail read the common part of normal IM\n"); + return; + } // if bytes + + switch (common->normal_im_type) { + case QQ_NORMAL_IM_TEXT: + gaim_debug (GAIM_DEBUG_INFO, + "QQ", + "Normal IM, text type:\n [%d] => [%d], src: %s\n", + common->sender_uid, common->receiver_uid, + qq_get_source_str (common->sender_ver)); + _qq_process_recv_normal_im_text (data, cursor, len, common, + gc); + break; + case QQ_NORMAL_IM_FILE_REJECT_UDP: + qq_process_recv_file_reject (data, cursor, len, + common->sender_uid, gc); + break; + case QQ_NORMAL_IM_FILE_APPROVE_UDP: + qq_process_recv_file_accept (data, cursor, len, + common->sender_uid, gc); + break; + case QQ_NORMAL_IM_FILE_REQUEST_UDP: + qq_process_recv_file_request (data, cursor, len, + common->sender_uid, gc); + break; + case QQ_NORMAL_IM_FILE_CANCEL: + qq_process_recv_file_cancel (data, cursor, len, + common->sender_uid, gc); + break; + case QQ_NORMAL_IM_FILE_NOTIFY: + qq_process_recv_file_notify (data, cursor, len, + common->sender_uid, gc); + break; + default: + im_unprocessed = g_newa (qq_recv_normal_im_unprocessed, 1); + im_unprocessed->common = common; + im_unprocessed->unknown = *cursor; + im_unprocessed->length = data + len - *cursor; + //  a simple process here, maybe more later + gaim_debug (GAIM_DEBUG_WARNING, "QQ", + "Normal IM, unprocessed type [0x%04x]\n", + common->normal_im_type); + gaim_debug (GAIM_DEBUG_WARNING, "QQ", + "Dump unknown part.\n%s", + hex_dump_to_str (im_unprocessed->unknown, + im_unprocessed->length)); + g_free (common->session_md5); + return; + } // normal_im_type + + g_free (common->session_md5); +} // qq_process_recv_normal_im + + +/* +static void _qq_process_recv_normal_im(guint8 * data, guint8 ** cursor, gint len, GaimConnection * gc) { + gint bytes; + qq_recv_normal_im_common *common; + qq_recv_normal_im_unprocessed *im_unprocessed; + + g_return_if_fail(data != NULL && len != 0); + + if (*cursor >= (data + len - 1)) { + gaim_debug(GAIM_DEBUG_WARNING, "QQ", "Received normal IM is empty\n"); + return; + } else + common = g_newa(qq_recv_normal_im_common, 1); + + bytes = _qq_normal_im_common_read(data, cursor, len, common); + if (bytes < 0) { + gaim_debug(GAIM_DEBUG_ERROR, "QQ", "Fail read the common part of normal IM\n"); + return; + } // if bytes + + if (common->normal_im_type != QQ_NORMAL_IM_TEXT) { + im_unprocessed = g_newa(qq_recv_normal_im_unprocessed, 1); + im_unprocessed->common = common; + im_unprocessed->unknown = *cursor; + im_unprocessed->length = data + len - *cursor; + // a simple process here, maybe more later + gaim_debug(GAIM_DEBUG_WARNING, "QQ", "Normal IM, unprocessed type [0x%04x]\n", common->normal_im_type); + gaim_debug(GAIM_DEBUG_WARNING, "QQ", + "Dump unknown part.\n%s", hex_dump_to_str(im_unprocessed->unknown, im_unprocessed->length)); + g_free(common->session_md5); + return; + } else { // normal_im_type == QQ_NORMAL_IM_TEXT + gaim_debug(GAIM_DEBUG_INFO, + "QQ", + "Normal IM, text type:\n [%d] => [%d], src: %s\n", + common->sender_uid, common->receiver_uid, qq_get_source_str(common->sender_ver)); + _qq_process_recv_normal_im_text(data, cursor, len, common, gc); + } // normal_im_type + + g_free(common->session_md5); +} // qq_process_recv_normal_im +*/ + +/*****************************************************************************/ +// process im from system administrator +static void _qq_process_recv_sys_im(guint8 * data, guint8 ** cursor, gint data_len, GaimConnection * gc) { + gint len; + guint8 reply; + gchar **segments, *msg_utf8; + + g_return_if_fail(gc != NULL && data != NULL && data_len != 0); + + if (*cursor >= (data + data_len - 1)) { + gaim_debug(GAIM_DEBUG_WARNING, "QQ", "Received sys IM is empty\n"); + return; + } + + len = data + data_len - *cursor; + + if (NULL == (segments = split_data(*cursor, len, "\x2f", 2))) + return; + + reply = strtol(segments[0], NULL, 10); + if (reply == QQ_RECV_SYS_IM_KICK_OUT) + gaim_debug(GAIM_DEBUG_WARNING, "QQ", "We are kicked out by QQ server\n"); + msg_utf8 = qq_to_utf8(segments[1], QQ_CHARSET_DEFAULT); + gaim_notify_warning(gc, NULL, _("System Message"), msg_utf8); + +} // _qq_process_recv_sys_im + +/*****************************************************************************/ +// send an IM to to_uid +void qq_send_packet_im(GaimConnection * gc, guint32 to_uid, gchar * msg, gint type) { + qq_data *qd; + guint8 *cursor, *raw_data; + guint16 client_tag, normal_im_type; + gint msg_len, raw_len, bytes; + time_t now; + gchar *md5, *msg_filtered; + GData *attribs; + gchar *font_size = NULL, *font_color = NULL, *font_name = NULL, *tmp; + gboolean is_bold = FALSE, is_italic = FALSE, is_underline = FALSE; + const gchar *start, *end, *last; + + g_return_if_fail(gc != NULL && gc->proto_data != NULL); + + qd = (qq_data *) gc->proto_data; + client_tag = QQ_CLIENT; + normal_im_type = QQ_NORMAL_IM_TEXT; + + last = msg; + while (gaim_markup_find_tag("font", last, &start, &end, &attribs)) { + tmp = g_datalist_get_data(&attribs, "size"); + if (tmp) { + if (font_size) + g_free(font_size); + font_size = g_strdup(tmp); + } + tmp = g_datalist_get_data(&attribs, "color"); + if (tmp) { + if (font_color) + g_free(font_color); + font_color = g_strdup(tmp); + } + tmp = g_datalist_get_data(&attribs, "face"); + if (tmp) { + if (font_name) + g_free(font_name); + font_name = g_strdup(tmp); + } + + g_datalist_clear(&attribs); + last = end + 1; + } + + if (gaim_markup_find_tag("b", msg, &start, &end, &attribs)) { + is_bold = TRUE; + g_datalist_clear(&attribs); + } + + if (gaim_markup_find_tag("i", msg, &start, &end, &attribs)) { + is_italic = TRUE; + g_datalist_clear(&attribs); + } + + if (gaim_markup_find_tag("u", msg, &start, &end, &attribs)) { + is_underline = TRUE; + g_datalist_clear(&attribs); + } + + gaim_debug(GAIM_DEBUG_INFO, "QQ_MESG", "send mesg: %s\n", msg); + msg_filtered = gaim_markup_strip_html(msg); + msg_len = strlen(msg_filtered); + now = time(NULL); + md5 = _gen_session_md5(qd->uid, qd->session_key); + + guint font_name_len, tail_len; + font_name_len = (font_name) ? strlen(font_name) : DEFAULT_FONT_NAME_LEN; + tail_len = font_name_len + QQ_SEND_IM_AFTER_MSG_HEADER_LEN + 1; + + raw_len = QQ_SEND_IM_BEFORE_MSG_LEN + msg_len + tail_len; + raw_data = g_newa(guint8, raw_len); + cursor = raw_data; + bytes = 0; + + //000-003: receiver uid + bytes += create_packet_dw(raw_data, &cursor, qd->uid); + //004-007: sender uid + bytes += create_packet_dw(raw_data, &cursor, to_uid); + //008-009: sender client version + bytes += create_packet_w(raw_data, &cursor, client_tag); + //010-013: receiver uid + bytes += create_packet_dw(raw_data, &cursor, qd->uid); + //014-017: sender uid + bytes += create_packet_dw(raw_data, &cursor, to_uid); + //018-033: md5 of (uid+session_key) + bytes += create_packet_data(raw_data, &cursor, md5, 16); + //034-035: message type + bytes += create_packet_w(raw_data, &cursor, normal_im_type); + //036-037: sequence number + bytes += create_packet_w(raw_data, &cursor, qd->send_seq); + //038-041: send time + bytes += create_packet_dw(raw_data, &cursor, (guint32) now); + //042-042: always 0x00 + bytes += create_packet_b(raw_data, &cursor, 0x00); + //043-043: sender icon + bytes += create_packet_b(raw_data, &cursor, qd->my_icon); + //044-046: always 0x00 + bytes += create_packet_w(raw_data, &cursor, 0x0000); + bytes += create_packet_b(raw_data, &cursor, 0x00); + //047-047: we use font attr + bytes += create_packet_b(raw_data, &cursor, 0x01); + //048-051: always 0x00 + bytes += create_packet_dw(raw_data, &cursor, 0x00000000); + //052-052: text message type (normal/auto-reply) + bytes += create_packet_b(raw_data, &cursor, type); + //053- : msg ends with 0x00 + bytes += create_packet_data(raw_data, &cursor, msg_filtered, msg_len); + guint8 *send_im_tail = qq_get_send_im_tail(font_color, font_size, font_name, is_bold, + is_italic, is_underline, tail_len); + _qq_show_packet("QQ_MESG debug", send_im_tail, tail_len); + bytes += create_packet_data(raw_data, &cursor, (gchar *) send_im_tail, tail_len); + + _qq_show_packet("QQ_MESG raw", raw_data, cursor - raw_data); + + if (bytes == raw_len) // create packet OK + qq_send_cmd(gc, QQ_CMD_SEND_IM, TRUE, 0, TRUE, raw_data, cursor - raw_data); + else + gaim_debug(GAIM_DEBUG_ERROR, "QQ", + "Fail creating send_im packet, expect %d bytes, build %d bytes\n", raw_len, bytes); + + if (font_color) + g_free(font_color); + if (font_size) + g_free(font_size); + g_free(send_im_tail); + g_free(msg_filtered); +} // qq_send_packet_im + +/*****************************************************************************/ +// parse the reply to send_im +void qq_process_send_im_reply(guint8 * buf, gint buf_len, GaimConnection * gc) +{ + qq_data *qd; + gint len; + guint8 *data, *cursor, reply; + + g_return_if_fail(gc != NULL && gc->proto_data != NULL); + g_return_if_fail(buf != NULL && buf_len != 0); + + qd = gc->proto_data; + len = buf_len; + data = g_newa(guint8, len); + + if (qq_crypt(DECRYPT, buf, buf_len, qd->session_key, data, &len)) { + cursor = data; + read_packet_b(data, &cursor, len, &reply); + if (reply != QQ_SEND_IM_REPLY_OK) { + gaim_debug(GAIM_DEBUG_WARNING, "QQ", "Send IM fail\n"); + gaim_notify_error(gc, _("Server ACK"), _("Send IM fail\n"), NULL); +// serv_got_im(gc, name, "Server ACK: Send IM fail\n", GAIM_MESSAGE_ERROR, time(NULL)); + } + else + gaim_debug(GAIM_DEBUG_INFO, "QQ", "IM ACK OK\n"); + } else { + gaim_debug(GAIM_DEBUG_ERROR, "QQ", "Error decrypt send im reply\n"); + } + +} // qq_process_send_im_reply + +/*****************************************************************************/ +// I receive a message, mainly it is text msg, +// but we need to proess other types (group etc) +void qq_process_recv_im(guint8 * buf, gint buf_len, guint16 seq, GaimConnection * gc) { + qq_data *qd; + gint len, bytes; + guint8 *data, *cursor; + qq_recv_im_header *im_header; + + g_return_if_fail(gc != NULL && gc->proto_data != NULL); + g_return_if_fail(buf != NULL && buf_len != 0); + + qd = (qq_data *) gc->proto_data; + len = buf_len; + data = g_newa(guint8, len); + + if (qq_crypt(DECRYPT, buf, buf_len, qd->session_key, data, &len)) { + if (len < 16) { // we need to ack with the first 16 bytes + gaim_debug(GAIM_DEBUG_ERROR, "QQ", "IM is too short\n"); + return; + } else + _qq_send_packet_recv_im_ack(gc, seq, data); + + cursor = data; + bytes = 0; + im_header = g_newa(qq_recv_im_header, 1); + bytes += read_packet_dw(data, &cursor, len, &(im_header->sender_uid)); + bytes += read_packet_dw(data, &cursor, len, &(im_header->receiver_uid)); + bytes += read_packet_dw(data, &cursor, len, &(im_header->server_im_seq)); + // if the message is delivered via server, it is server IP/port + bytes += read_packet_data(data, &cursor, len, (guint8 *) & (im_header->sender_ip), 4); + bytes += read_packet_w(data, &cursor, len, &(im_header->sender_port)); + bytes += read_packet_w(data, &cursor, len, &(im_header->im_type)); + + if (bytes != 20) { // length of im_header + gaim_debug(GAIM_DEBUG_ERROR, "QQ", + "Fail read recv IM header, expect 20 bytes, read %d bytes\n", bytes); + return; + } // if bytes + + if (im_header->receiver_uid != qd->uid) { // should not happen + gaim_debug(GAIM_DEBUG_ERROR, "QQ", "IM to [%d], NOT me\n", im_header->receiver_uid); + return; + } // if im_header->receiver_uid + + switch (im_header->im_type) { + case QQ_RECV_IM_TO_BUDDY: + gaim_debug(GAIM_DEBUG_INFO, "QQ", + "IM from buddy [%d], I am in his/her buddy list\n", im_header->sender_uid); + _qq_process_recv_normal_im(data, &cursor, len, gc); + break; + case QQ_RECV_IM_TO_UNKNOWN: + gaim_debug(GAIM_DEBUG_INFO, "QQ", + "IM from buddy [%d], I am a stranger to him/her\n", im_header->sender_uid); + _qq_process_recv_normal_im(data, &cursor, len, gc); + break; + case QQ_RECV_IM_UNKNOWN_QUN_IM: + case QQ_RECV_IM_TEMP_QUN_IM: + case QQ_RECV_IM_QUN_IM: + gaim_debug(GAIM_DEBUG_INFO, "QQ", "IM from group, internal_id [%d]\n", im_header->sender_uid); + // sender_uid is in fact internal_group_id + qq_process_recv_group_im(data, &cursor, len, im_header->sender_uid, gc, im_header->im_type); + break; + case QQ_RECV_IM_ADD_TO_QUN: + gaim_debug(GAIM_DEBUG_INFO, "QQ", + "IM from group, added by group internal_id [%d]\n", im_header->sender_uid); + // sender_uid is in fact internal_group_id + // we need this to create a dummy group and add to blist + qq_process_recv_group_im_been_added(data, &cursor, len, im_header->sender_uid, gc); + break; + case QQ_RECV_IM_DEL_FROM_QUN: + gaim_debug(GAIM_DEBUG_INFO, "QQ", + "IM from group, removed by group internal_ID [%d]\n", im_header->sender_uid); + // sender_uid is in fact internal_group_id + qq_process_recv_group_im_been_removed(data, &cursor, len, im_header->sender_uid, gc); + break; + case QQ_RECV_IM_APPLY_ADD_TO_QUN: + gaim_debug(GAIM_DEBUG_INFO, "QQ", + "IM from group, apply to join group internal_ID [%d]\n", im_header->sender_uid); + // sender_uid is in fact internal_group_id + qq_process_recv_group_im_apply_join(data, &cursor, len, im_header->sender_uid, gc); + break; + case QQ_RECV_IM_APPROVE_APPLY_ADD_TO_QUN: + gaim_debug(GAIM_DEBUG_INFO, "QQ", + "IM for group system info, approved by group internal_id [%d]\n", + im_header->sender_uid); + // sender_uid is in fact internal_group_id + qq_process_recv_group_im_been_approved(data, &cursor, len, im_header->sender_uid, gc); + break; + case QQ_RECV_IM_REJCT_APPLY_ADD_TO_QUN: + gaim_debug(GAIM_DEBUG_INFO, "QQ", + "IM for group system info, rejected by group internal_id [%d]\n", + im_header->sender_uid); + // sender_uid is in fact internal_group_id + qq_process_recv_group_im_been_rejected(data, &cursor, len, im_header->sender_uid, gc); + break; + case QQ_RECV_IM_SYS_NOTIFICATION: + gaim_debug(GAIM_DEBUG_INFO, "QQ", + "IM from [%d], should be a system administrator\n", im_header->sender_uid); + _qq_process_recv_sys_im(data, &cursor, len, gc); + break; + default: + gaim_debug(GAIM_DEBUG_WARNING, "QQ", + "IM from [%d], [0x%02x] %s is not processed\n", + im_header->sender_uid, + im_header->im_type, qq_get_recv_im_type_str(im_header->im_type)); + } // switch + } else + gaim_debug(GAIM_DEBUG_ERROR, "QQ", "Error decrypt rev im\n"); + +} // qq_process_recv_im + +/*****************************************************************************/ +// END OF FILE diff -r 5642f4658b59 -r 983fd420e86b src/protocols/qq/im.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/protocols/qq/im.h Mon Jun 26 02:58:54 2006 +0000 @@ -0,0 +1,68 @@ +/** +* The QQ2003C protocol plugin + * + * for gaim + * + * Copyright (C) 2004 Puzzlebird + * + * 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 + */ + +// START OF FILE +/*****************************************************************************/ +#ifndef _QQ_IM_H_ +#define _QQ_IM_H_ + +#include +#include "connection.h" // GaimConnection +#include "group.h" // qq_group + +#define QQ_MSG_IM_MAX 500 // max length of IM +#define QQ_SEND_IM_BEFORE_MSG_LEN 53 +#define QQ_SEND_IM_AFTER_MSG_LEN 13 // there is one 0x00 at the end + +enum { + QQ_IM_TEXT = 0x01, + QQ_IM_AUTO_REPLY = 0x02, +}; + + +enum { + QQ_RECV_IM_TO_BUDDY = 0x0009, + QQ_RECV_IM_TO_UNKNOWN = 0x000a, + QQ_RECV_IM_UNKNOWN_QUN_IM = 0x0020, //gfhuang + QQ_RECV_IM_ADD_TO_QUN = 0x0021, + QQ_RECV_IM_DEL_FROM_QUN = 0x0022, + QQ_RECV_IM_APPLY_ADD_TO_QUN = 0x0023, + QQ_RECV_IM_APPROVE_APPLY_ADD_TO_QUN = 0x0024, + QQ_RECV_IM_REJCT_APPLY_ADD_TO_QUN = 0x0025, + QQ_RECV_IM_CREATE_QUN = 0x0026, + QQ_RECV_IM_TEMP_QUN_IM = 0x002A, //gfhuang + QQ_RECV_IM_QUN_IM = 0x002B, //gfhuang + QQ_RECV_IM_SYS_NOTIFICATION = 0x0030, +}; + +guint8 *qq_get_send_im_tail(const gchar * font_color, + const gchar * font_size, + const gchar * font_name, + gboolean is_bold, gboolean is_italic, gboolean is_underline, guint len); + +void qq_send_packet_im(GaimConnection * gc, guint32 to_uid, gchar * msg, gint type); +void qq_process_recv_im(guint8 * buf, gint buf_len, guint16 seq, GaimConnection * gc); +void qq_process_send_im_reply(guint8 * buf, gint buf_len, GaimConnection * gc); + +#endif +/*****************************************************************************/ +// END OF FILE diff -r 5642f4658b59 -r 983fd420e86b src/protocols/qq/infodlg.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/protocols/qq/infodlg.c Mon Jun 26 02:58:54 2006 +0000 @@ -0,0 +1,1127 @@ +/** + * The QQ2003C protocol plugin + * + * for gaim + * + * Copyright (C) 2004 Puzzlebird + * + * 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 + */ + +// START OF FILE +/*****************************************************************************/ +#include "debug.h" // gaim_debug +#include "internal.h" // strlen, _("get_text") +#include "notify.h" // gaim_notify_error + +#include "utils.h" // get_face_gdkpixbuf +#include "char_conv.h" // qq_to_utf8 +#include "show.h" // qq_show_get_image +#include "infodlg.h" + +// must be defined after qq_infodlg.h to have +#include "gtkdialogs.h" // GAIM_DIALOG, mainwindow + + +// the follwoing definition is in UTF-8 +const gint blood_types_count = 5; +const gchar *blood_types[] = { + "其它", "A", "B", "O", "AB", +}; + +const gint country_names_count = 6; +const gchar *country_names[] = { + "中国", "中国香港", "中国澳门", "中国å°æ¹¾", + "新加å¡", "马æ¥è¥¿äºš", "美国", +}; + +const gint province_names_count = 34; +const gchar *province_names[] = { + "北京", "天津", "上海", "é‡åº†", "香港", + "河北", "山西", "内蒙å¤", "è¾½å®", "å‰æž—", + "黑龙江", "江西", "浙江", "江è‹", "安徽", + "ç¦å»º", "山东", "æ²³å—", "湖北", "æ¹–å—", + "广东", "广西", "æµ·å—", "å››å·", "贵州", + "云å—", "西è—", "陕西", "甘肃", "å®å¤", + "é’æµ·", "æ–°ç–†", "å°æ¹¾", "澳门", +}; + +const gint zodiac_names_count = 13; +const gchar *zodiac_names[] = { + "-", "é¼ ", "牛", "虎", "å…”", + "é¾™", "蛇", "马", "羊", "猴", + "鸡", "ç‹—", "猪", +}; + +const gint horoscope_names_count = 13; +const gchar *horoscope_names[] = { + "-", "水瓶座", "åŒé±¼åº§", "牡羊座", "金牛座", + "åŒå­åº§", "巨蟹座", "ç‹®å­åº§", "处女座", "天秤座", + "天èŽåº§", "射手座", "魔羯座", +}; + +const gint occupation_names_count = 15; +const gchar *occupation_names[] = { + "å…¨èŒ", "å…¼èŒ", "制造业", "商业", "失业中", + "学生", "工程师", "政府部门", "教育业", "æœåŠ¡è¡Œä¸š", + "è€æ¿", "计算机业", "退休", "金èžä¸š", + "销售/广告/市场", +}; + +enum { + QQ_CONTACT_OPEN = 0x00, + QQ_CONTACT_ONLY_FRIENDS = 0x01, + QQ_CONTACT_CLOSE = 0x02, +}; + + +enum { + QQ_AUTH_NO_AUTH = 0x00, + QQ_AUTH_NEED_AUTH = 0x01, + QQ_AUTH_NO_ADD = 0x02, +}; + +typedef struct _change_icon_widgets change_icon_widgets; + +struct _change_icon_widgets { + GtkWidget *dialog; // dialog that shows all icons + GtkWidget *face; // the image widget we are going to change +}; + +/*****************************************************************************/ +static void _window_deleteevent(GtkWidget * widget, GdkEvent * event, gpointer data) { + gtk_widget_destroy(widget); // this will call _window_destroy +} // _window_deleteevent + +/*****************************************************************************/ +static void _window_close(GtkWidget * widget, gpointer data) +{ + // this will call _info_window_destroy if it is info-window + gtk_widget_destroy(GTK_WIDGET(data)); +} // _window_close + +/*****************************************************************************/ +static void _no_edit(GtkWidget * w) +{ + gtk_editable_set_editable(GTK_EDITABLE(w), FALSE); +} // _no_edit + +/*****************************************************************************/ +static GtkWidget *_qq_entry_new() +{ + GtkWidget *entry; + entry = gtk_entry_new(); + gtk_entry_set_max_length(GTK_ENTRY(entry), 255); // set the max length + return entry; +} // _qq_entry_new + +/*****************************************************************************/ +static void _qq_set_entry(GtkWidget * entry, gchar * text) +{ + gchar *text_utf8; + + text_utf8 = qq_to_utf8(text, QQ_CHARSET_DEFAULT); + gtk_entry_set_text(GTK_ENTRY(entry), text_utf8); + + g_free(text_utf8); +} // _qq_set_entry + +/*****************************************************************************/ +static gchar *_qq_get_entry(GtkWidget * entry) +{ + return utf8_to_qq(gtk_entry_get_text(GTK_ENTRY(entry)), QQ_CHARSET_DEFAULT); +} // _qq_get_entry + +/*****************************************************************************/ +static void _qq_set_text(GtkWidget * entry, gchar * text) +{ + gchar *text_utf8; + + text_utf8 = qq_to_utf8(text, QQ_CHARSET_DEFAULT); + gtk_text_buffer_set_text(gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)), text_utf8, -1); + + g_free(text_utf8); +} // _qq_set_text + +/*****************************************************************************/ +static gchar *_qq_get_text(GtkWidget * entry) +{ + gchar *str, *ret; + GtkTextIter start, end; + + gtk_text_buffer_get_bounds(gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)), &start, &end); + str = gtk_text_buffer_get_text(gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)), &start, &end, FALSE); + ret = utf8_to_qq(str, QQ_CHARSET_DEFAULT); + + return ret; +} // _qq_get_text + +/*****************************************************************************/ +static void _qq_set_image(GtkWidget * entry, gint index) +{ + GdkPixbuf *pixbuf; + + g_return_if_fail(entry != NULL && index >= 0); + + pixbuf = get_face_gdkpixbuf(index); + gtk_image_set_from_pixbuf(GTK_IMAGE(entry), pixbuf); + g_object_unref(pixbuf); + g_object_set_data(G_OBJECT(entry), "user_data", GINT_TO_POINTER(index)); +} // _qq_set_image + + +/*****************************************************************************/ +static void _qq_change_face(GtkWidget * w, gpointer * user_data) +{ + gint index; + change_icon_widgets *change_icon; + + change_icon = (change_icon_widgets *) user_data; + + index = (gint) g_object_get_data(G_OBJECT(w), "user_data"); + + _qq_set_image(change_icon->face, index); + _window_close(NULL, change_icon->dialog); + + g_free(change_icon); + +} // _qq_change_face + +/*****************************************************************************/ +static GList *_get_list_by_array(gchar ** array, gint size) +{ + gint i; + GList *cbitems; + + cbitems = NULL; + for (i = 0; i < size; i++) + cbitems = g_list_append(cbitems, array[i]); + return cbitems; +} // _get_list_by_array + +/*****************************************************************************/ +static void _qq_set_open_contact_radio(contact_info_window * info_window, gchar * is_open_contact) { + gint open; + GtkWidget **radio; + + open = atoi(is_open_contact); + radio = info_window->open_contact_radio; + + if (open == QQ_CONTACT_OPEN) + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(radio[0]), TRUE); + else if (open == QQ_CONTACT_ONLY_FRIENDS) + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(radio[1]), TRUE); + else + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(radio[2]), TRUE); +} // _qq_set_open_contact_radio + +/*****************************************************************************/ +static void _qq_set_auth_type_radio(contact_info_window * info_window, gchar * auth_type_str) { + gint auth_type; + GtkWidget **radio; + + auth_type = atoi(auth_type_str); + radio = info_window->auth_radio; + + if (auth_type == QQ_AUTH_NO_AUTH) + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(radio[0]), TRUE); + else if (auth_type == QQ_AUTH_NEED_AUTH) + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(radio[1]), TRUE); + else + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(radio[2]), TRUE); +} // _qq_set_auth_type_radio + +/*****************************************************************************/ +static void _info_window_destroy(GtkWidget * widget, gpointer data) +{ + GList *list; + qq_data *qd; + GaimConnection *gc; + contact_info_window *info_window; + + gc = (GaimConnection *) data; + + g_return_if_fail(gc != NULL && gc->proto_data != NULL); + + if (QQ_DEBUG) + gaim_debug(GAIM_DEBUG_INFO, "QQ", "Destroy info window.\n"); + qd = (qq_data *) gc->proto_data; + list = qd->contact_info_window; + + while (list) { + info_window = (contact_info_window *) (list->data); + if (info_window->window != widget) + list = list->next; + else { + if (info_window->old_info) + g_strfreev((gchar **) info_window->old_info); + qd->contact_info_window = g_list_remove(qd->contact_info_window, info_window); + g_free(info_window); + break; + } + } // while +} // _info_window_destory + +/*****************************************************************************/ +void qq_contact_info_window_free(qq_data * qd) +{ + gint i; + contact_info_window *info; + + g_return_if_fail(qd != NULL); + + i = 0; + while (qd->contact_info_window) { + info = (contact_info_window *) qd->contact_info_window->data; + // remove the entry before free it + // in some rare cases, system might automatically (stupidly) + // remove the entry from the list when user free the entry, + // then, qd->contact_info_window becomes NULL and it causes crash + qd->contact_info_window = g_list_remove(qd->contact_info_window, info); + if (info->window) + gtk_widget_destroy(info->window); + g_free(info); + i++; + } + gaim_debug(GAIM_DEBUG_INFO, "QQ", "%d conatct_info_window are freed\n", i); +} // qq_contact_info_window_free + +/*****************************************************************************/ +static void _info_window_refresh(GtkWidget * widget, gpointer data) +{ + GaimConnection *gc; + contact_info_window *info_window; + + gc = (GaimConnection *) data; + info_window = (contact_info_window *) + g_object_get_data(G_OBJECT(widget), "user_data"); + + qq_send_packet_get_info(gc, info_window->uid, TRUE); + gtk_widget_set_sensitive(info_window->refresh_button, FALSE); +} // _info_window_refresh + +/*****************************************************************************/ +static void _info_window_change(GtkWidget * widget, gpointer data) +{ + gchar *passwd[3]; + gboolean is_modify_passwd; + guint8 icon, auth_type, open_contact; + qq_data *qd; + GaimAccount *a; + GaimConnection *gc; + contact_info *info; + contact_info_window *info_window; + + gc = (GaimConnection *) data; + a = gc->account; + qd = gc->proto_data; + info_window = (contact_info_window *) + g_object_get_data(G_OBJECT(widget), "user_data"); + info = info_window->old_info; + + is_modify_passwd = FALSE; + passwd[0] = passwd[1] = passwd[2] = NULL; + + if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(info_window->is_modify_passwd))) + is_modify_passwd = TRUE; + + if (is_modify_passwd) { + passwd[0] = _qq_get_entry(info_window->old_password); + passwd[1] = _qq_get_entry(info_window->password[0]); + passwd[2] = _qq_get_entry(info_window->password[1]); + + if (g_ascii_strcasecmp(a->password, passwd[0])) { + gaim_notify_error(gc, _("Error"), _("Old password is wrong"), NULL); + g_free(passwd[0]); + g_free(passwd[1]); + g_free(passwd[2]); + return; + } + if (passwd[1][0] == '\0') { + gaim_notify_error(gc, _("Error"), _("Password can not be empty"), NULL); + g_free(passwd[0]); + g_free(passwd[1]); + g_free(passwd[2]); + return; + } + if (g_ascii_strcasecmp(passwd[1], passwd[2])) { + gaim_notify_error(gc, _("Error"), + _("Confirmed password is not the same as new password"), NULL); + g_free(passwd[0]); + g_free(passwd[1]); + g_free(passwd[2]); + return; + } + if (strlen(passwd[1]) > 16) { + gaim_notify_error(gc, _("Error"), _("Password can not longer than 16 characters"), NULL); + g_free(passwd[0]); + g_free(passwd[1]); + g_free(passwd[2]); + return; + } + } // if (is_modify_passwd) + + icon = (gint) g_object_get_data(G_OBJECT(info_window->face), "user_data"); + + info->face = g_strdup_printf("%d", icon); + info->nick = _qq_get_entry(info_window->nick); + info->age = _qq_get_entry(info_window->age); + info->gender = _qq_get_entry(info_window->gender); + info->country = _qq_get_entry(info_window->country); + info->province = _qq_get_entry(info_window->province); + info->city = _qq_get_entry(info_window->city); + info->email = _qq_get_entry(info_window->email); + info->address = _qq_get_entry(info_window->address); + info->zipcode = _qq_get_entry(info_window->zipcode); + info->tel = _qq_get_entry(info_window->tel); + info->name = _qq_get_entry(info_window->name); + info->college = _qq_get_entry(info_window->college); + info->occupation = _qq_get_entry(info_window->occupation); + info->homepage = _qq_get_entry(info_window->homepage); + info->intro = _qq_get_text(info_window->intro); + + info->blood = + get_index_str_by_name((gchar **) blood_types, + gtk_entry_get_text(GTK_ENTRY(info_window->blood)), blood_types_count); + info->zodiac = + get_index_str_by_name((gchar **) zodiac_names, + gtk_entry_get_text(GTK_ENTRY(info_window->zodiac)), zodiac_names_count); + info->horoscope = + get_index_str_by_name((gchar **) horoscope_names, + gtk_entry_get_text(GTK_ENTRY(info_window->horoscope)), horoscope_names_count); + + if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(info_window->auth_radio[0]))) + auth_type = QQ_AUTH_NO_AUTH; + else if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(info_window->auth_radio[1]))) + auth_type = QQ_AUTH_NEED_AUTH; + else + auth_type = QQ_AUTH_NO_ADD; + + info->auth_type = g_strdup_printf("%d", auth_type); + + if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(info_window->open_contact_radio[0]))) + open_contact = QQ_CONTACT_OPEN; + else if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(info_window->open_contact_radio[1]))) + open_contact = QQ_CONTACT_ONLY_FRIENDS; + else + open_contact = QQ_CONTACT_CLOSE; + + info->is_open_contact = g_strdup_printf("%d", open_contact); + + qq_send_packet_modify_info(gc, info, passwd[1]); + qq_refresh_buddy_and_myself(info, gc); + + gtk_widget_destroy(info_window->window); //close window +} + +/*****************************************************************************/ +static void _info_window_change_face(GtkWidget * widget, GdkEvent * event, contact_info_window * info_window) { + gint i; + GdkPixbuf *pixbuf; + GtkWidget *dialog, *image, *button, *vbox, *smiley_box; + change_icon_widgets *change_icon; + + change_icon = g_new0(change_icon_widgets, 1); + + smiley_box = NULL; + GAIM_DIALOG(dialog); // learned from gtkimhtmltoolbar.c + gtk_window_set_position(GTK_WINDOW(dialog), GTK_WIN_POS_MOUSE); + gtk_window_set_modal(GTK_WINDOW(dialog), FALSE); + + g_signal_connect(G_OBJECT(dialog), "delete_event", G_CALLBACK(_window_deleteevent), NULL); + + gtk_window_set_resizable(GTK_WINDOW(dialog), FALSE); + gtk_window_set_position(GTK_WINDOW(dialog), GTK_WIN_POS_MOUSE); + + change_icon->dialog = dialog; + change_icon->face = info_window->face; + + vbox = gtk_vbox_new(TRUE, 5); + + for (i = 0; i < 85; i++) { + if (i % 8 == 0) { + smiley_box = gtk_toolbar_new(); + gtk_box_pack_start(GTK_BOX(vbox), smiley_box, TRUE, TRUE, 0); + } + pixbuf = get_face_gdkpixbuf(i * 3); + image = gtk_image_new_from_pixbuf(pixbuf); + g_object_unref(pixbuf); + button = gtk_toolbar_append_item(GTK_TOOLBAR(smiley_box), + NULL, NULL, NULL, image, G_CALLBACK(_qq_change_face), change_icon); + g_object_set_data(G_OBJECT(button), "user_data", GINT_TO_POINTER(i * 3)); + gtk_button_set_relief(GTK_BUTTON(button), GTK_RELIEF_NONE); + } // for i + + gtk_container_add(GTK_CONTAINER(dialog), vbox); + gtk_container_set_border_width(GTK_CONTAINER(dialog), 5); + gtk_window_set_title(GTK_WINDOW(dialog), _("Choose my head icon")); + gtk_widget_show_all(dialog); +} // _info_window_change_face + +/*****************************************************************************/ +static void _change_passwd_checkbutton_callback(GtkWidget * widget, contact_info_window * info_window) { + if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget))) { + gtk_widget_set_sensitive(info_window->old_password, TRUE); + gtk_widget_set_sensitive(info_window->password[0], TRUE); + gtk_widget_set_sensitive(info_window->password[1], TRUE); + } else { + gtk_widget_set_sensitive(info_window->old_password, FALSE); + gtk_widget_set_sensitive(info_window->password[0], FALSE); + gtk_widget_set_sensitive(info_window->password[1], FALSE); + } +} // _change_passwd_checkbutton_callback + +/*****************************************************************************/ +static GtkWidget *_create_page_basic + (gint is_myself, contact_info * info, GaimConnection * gc, contact_info_window * info_window) { + GdkPixbuf *pixbuf; + GList *cbitems; + GtkWidget *hbox, *pixmap, *frame, *label, *table; + GtkWidget *entry, *combo, *alignment; + GtkWidget *event_box, *qq_show; + GtkTooltips *tooltips; + + tooltips = gtk_tooltips_new(); + + cbitems = NULL; + + hbox = gtk_hbox_new(FALSE, 0); + frame = gtk_frame_new(""); + gtk_container_set_border_width(GTK_CONTAINER(frame), 5); + gtk_box_pack_start(GTK_BOX(hbox), frame, TRUE, TRUE, 0); + + table = gtk_table_new(7, 3, FALSE); + gtk_table_set_row_spacings(GTK_TABLE(table), 2); + gtk_table_set_col_spacings(GTK_TABLE(table), 5); + gtk_container_set_border_width(GTK_CONTAINER(table), 5); + gtk_container_add(GTK_CONTAINER(frame), table); + + pixbuf = get_face_gdkpixbuf((guint8) strtol(info->face, NULL, 10)); + pixmap = gtk_image_new_from_pixbuf(pixbuf); + info_window->face = pixmap; + + alignment = gtk_alignment_new(0.25, 0, 0, 0); + + // set up icon + if (is_myself) { + g_object_set_data(G_OBJECT(info_window->face), "user_data", + GINT_TO_POINTER(strtol(info->face, NULL, 10))); + event_box = gtk_event_box_new(); + g_signal_connect(G_OBJECT(event_box), "button_press_event", + G_CALLBACK(_info_window_change_face), info_window); + gtk_container_add(GTK_CONTAINER(event_box), pixmap); + gtk_container_add(GTK_CONTAINER(alignment), event_box); + gtk_tooltips_set_tip(GTK_TOOLTIPS(tooltips), event_box, _("Click to change icon"), NULL); + } else + gtk_container_add(GTK_CONTAINER(alignment), pixmap); + + gtk_table_attach(GTK_TABLE(table), alignment, 2, 3, 0, 2, GTK_FILL, 0, 0, 0); + + // set up qq_show image and event + qq_show = qq_show_default(info); + g_object_set_data(G_OBJECT(qq_show), "user_data", GINT_TO_POINTER((gint) atoi(info->uid))); + + frame = gtk_frame_new(""); + gtk_container_set_border_width(GTK_CONTAINER(frame), 0); + + if (strtol(info->qq_show, NULL, 10) != 0) { // buddy has qq_show + event_box = gtk_event_box_new(); + gtk_container_add(GTK_CONTAINER(frame), event_box); + gtk_container_add(GTK_CONTAINER(event_box), qq_show); + g_signal_connect(G_OBJECT(event_box), "button_press_event", G_CALLBACK(qq_show_get_image), qq_show); + gtk_tooltips_set_tip(GTK_TOOLTIPS(tooltips), event_box, _("Click to refresh"), NULL); + } else // no event_box + gtk_container_add(GTK_CONTAINER(frame), qq_show); + + gtk_table_attach(GTK_TABLE(table), frame, 2, 3, 2, 7, GTK_EXPAND, 0, 0, 0); + + label = gtk_label_new(_("QQid")); + gtk_misc_set_alignment(GTK_MISC(label), 0, .5); + gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, 1, GTK_FILL, 0, 0, 0); + + label = gtk_label_new(_("Nickname")); + gtk_misc_set_alignment(GTK_MISC(label), 0, .5); + gtk_table_attach(GTK_TABLE(table), label, 0, 1, 1, 2, GTK_FILL, 0, 0, 0); + + entry = _qq_entry_new(); + info_window->uid_entry = entry; + _qq_set_entry(info_window->uid_entry, info->uid); + gtk_widget_set_size_request(entry, 120, -1); + gtk_table_attach(GTK_TABLE(table), entry, 1, 2, 0, 1, GTK_FILL, 0, 0, 0); + + entry = _qq_entry_new(); + info_window->nick = entry; + _qq_set_entry(info_window->nick, info->nick); + gtk_widget_set_size_request(entry, 120, -1); + gtk_table_attach(GTK_TABLE(table), entry, 1, 2, 1, 2, GTK_FILL, 0, 0, 0); + + label = gtk_label_new(_("Age")); + gtk_misc_set_alignment(GTK_MISC(label), 0, .5); + gtk_table_attach(GTK_TABLE(table), label, 0, 1, 2, 3, GTK_FILL, 0, 0, 0); + + label = gtk_label_new(_("Gender")); + gtk_misc_set_alignment(GTK_MISC(label), 0, .5); + gtk_table_attach(GTK_TABLE(table), label, 0, 1, 3, 4, GTK_FILL, 0, 0, 0); + + label = gtk_label_new(_("Country/Region")); + gtk_misc_set_alignment(GTK_MISC(label), 0, .5); + gtk_table_attach(GTK_TABLE(table), label, 0, 1, 4, 5, GTK_FILL, 0, 0, 0); + + combo = gtk_combo_new(); + cbitems = _get_list_by_array((gchar **) country_names, country_names_count); + gtk_combo_set_popdown_strings(GTK_COMBO(combo), cbitems); + g_list_free(cbitems); + cbitems = NULL; + info_window->country = GTK_COMBO(combo)->entry; + _qq_set_entry(info_window->country, info->country); + gtk_widget_set_size_request(combo, 150, -1); + gtk_table_attach(GTK_TABLE(table), combo, 1, 2, 4, 5, GTK_FILL, 0, 0, 0); + if (!is_myself) + gtk_widget_set_sensitive(combo, FALSE); + + entry = _qq_entry_new(); + info_window->age = entry; + _qq_set_entry(info_window->age, info->age); + gtk_widget_set_size_request(entry, 40, -1); + gtk_table_attach(GTK_TABLE(table), entry, 1, 2, 2, 3, GTK_FILL, 0, 0, 0); + + combo = gtk_combo_new(); + cbitems = g_list_append(cbitems, (char *)_("Male")); + cbitems = g_list_append(cbitems, (char *)_("Female")); + gtk_combo_set_popdown_strings(GTK_COMBO(combo), cbitems); + g_list_free(cbitems); + cbitems = NULL; + info_window->gender = GTK_COMBO(combo)->entry; + gtk_widget_set_size_request(combo, 60, -1); + _qq_set_entry(info_window->gender, info->gender); + gtk_editable_set_editable(GTK_EDITABLE(GTK_COMBO(combo)->entry), FALSE); + gtk_table_attach(GTK_TABLE(table), combo, 1, 2, 3, 4, GTK_FILL, 0, 0, 0); + if (!is_myself) + gtk_widget_set_sensitive(combo, FALSE); + + label = gtk_label_new(_("Province")); + gtk_misc_set_alignment(GTK_MISC(label), 0, .5); + gtk_table_attach(GTK_TABLE(table), label, 0, 1, 5, 6, GTK_FILL, 0, 0, 0); + + label = gtk_label_new(_("City")); + gtk_misc_set_alignment(GTK_MISC(label), 0, .5); + gtk_table_attach(GTK_TABLE(table), label, 0, 1, 6, 7, GTK_FILL, 0, 0, 0); + + combo = gtk_combo_new(); + cbitems = _get_list_by_array((gchar **) province_names, province_names_count); + gtk_combo_set_popdown_strings(GTK_COMBO(combo), cbitems); + g_list_free(cbitems); + cbitems = NULL; + info_window->province = GTK_COMBO(combo)->entry; + _qq_set_entry(info_window->province, info->province); + gtk_widget_set_size_request(combo, 150, -1); + gtk_table_attach(GTK_TABLE(table), combo, 1, 2, 5, 6, GTK_FILL, 0, 0, 0); + if (!is_myself) + gtk_widget_set_sensitive(combo, FALSE); + + entry = _qq_entry_new(); + info_window->city = entry; + gtk_widget_set_size_request(entry, 120, -1); + _qq_set_entry(info_window->city, info->city); + gtk_table_attach(GTK_TABLE(table), entry, 1, 2, 6, 7, GTK_FILL, 0, 0, 0); + + return hbox; +} // _create_page_basic + +/*****************************************************************************/ +static GtkWidget *_create_page_contact + (int is_myself, contact_info * info, GaimConnection * gc, contact_info_window * info_window) { + GtkWidget *vbox1, *frame; + GtkWidget *hbox1, *entry, *label; + GtkWidget *hbox, *radio, *table; + + hbox = gtk_hbox_new(FALSE, 0); + vbox1 = gtk_vbox_new(FALSE, 0); + gtk_box_pack_start(GTK_BOX(hbox), vbox1, TRUE, TRUE, 0); + + frame = gtk_frame_new(""); + gtk_container_set_border_width(GTK_CONTAINER(frame), 5); + gtk_box_pack_start(GTK_BOX(vbox1), frame, TRUE, TRUE, 0); + + table = gtk_table_new(2, 5, FALSE); + gtk_table_set_row_spacings(GTK_TABLE(table), 2); + gtk_table_set_col_spacings(GTK_TABLE(table), 5); + gtk_container_set_border_width(GTK_CONTAINER(table), 5); + gtk_container_add(GTK_CONTAINER(frame), table); + + label = gtk_label_new(_("Email")); + gtk_misc_set_alignment(GTK_MISC(label), 0, .5); + gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, 1, GTK_FILL, 0, 0, 0); + + label = gtk_label_new(_("Address")); + gtk_misc_set_alignment(GTK_MISC(label), 0, .5); + gtk_table_attach(GTK_TABLE(table), label, 0, 1, 1, 2, GTK_FILL, 0, 0, 0); + + label = gtk_label_new(_("Zipcode")); + gtk_misc_set_alignment(GTK_MISC(label), 0, .5); + gtk_table_attach(GTK_TABLE(table), label, 0, 1, 2, 3, GTK_FILL, 0, 0, 0); + + label = gtk_label_new(_("Tel")); + gtk_misc_set_alignment(GTK_MISC(label), 0, .5); + gtk_table_attach(GTK_TABLE(table), label, 0, 1, 3, 4, GTK_FILL, 0, 0, 0); + + label = gtk_label_new(_("Homepage")); + gtk_misc_set_alignment(GTK_MISC(label), 0, .5); + gtk_table_attach(GTK_TABLE(table), label, 0, 1, 4, 5, GTK_FILL, 0, 0, 0); + + entry = _qq_entry_new(); + info_window->email = entry; + gtk_widget_set_size_request(entry, 240, -1); + _qq_set_entry(info_window->email, info->email); + gtk_table_attach(GTK_TABLE(table), entry, 1, 2, 0, 1, GTK_FILL, 0, 0, 0); + + entry = _qq_entry_new(); + info_window->address = entry; + _qq_set_entry(info_window->address, info->address); + gtk_table_attach(GTK_TABLE(table), entry, 1, 2, 1, 2, GTK_FILL, 0, 0, 0); + + entry = _qq_entry_new(); + info_window->zipcode = entry; + _qq_set_entry(info_window->zipcode, info->zipcode); + gtk_table_attach(GTK_TABLE(table), entry, 1, 2, 2, 3, GTK_FILL, 0, 0, 0); + + entry = _qq_entry_new(); + info_window->tel = entry; + _qq_set_entry(info_window->tel, info->tel); + gtk_table_attach(GTK_TABLE(table), entry, 1, 2, 3, 4, GTK_FILL, 0, 0, 0); + + entry = _qq_entry_new(); + info_window->homepage = entry; + _qq_set_entry(info_window->homepage, info->homepage); + gtk_table_attach(GTK_TABLE(table), entry, 1, 2, 4, 5, GTK_FILL, 0, 0, 0); + + frame = gtk_frame_new(_("Above infomation")); + gtk_container_set_border_width(GTK_CONTAINER(frame), 5); + if (!is_myself) + gtk_widget_set_sensitive(frame, FALSE); + gtk_box_pack_start(GTK_BOX(vbox1), frame, FALSE, FALSE, 0); + + hbox1 = gtk_hbox_new(FALSE, 5); + gtk_container_add(GTK_CONTAINER(frame), hbox1); + + radio = gtk_radio_button_new_with_label(NULL, _("Open to all")); + info_window->open_contact_radio[0] = radio; + gtk_box_pack_start(GTK_BOX(hbox1), radio, FALSE, FALSE, 0); + + radio = gtk_radio_button_new_with_label_from_widget + (GTK_RADIO_BUTTON(radio), _("Only accessible by my frineds")); + info_window->open_contact_radio[1] = radio; + gtk_box_pack_start(GTK_BOX(hbox1), radio, FALSE, FALSE, 0); + + radio = gtk_radio_button_new_with_label_from_widget(GTK_RADIO_BUTTON(radio), _("Close to all")); + info_window->open_contact_radio[2] = radio; + gtk_box_pack_start(GTK_BOX(hbox1), radio, FALSE, FALSE, 0); + + _qq_set_open_contact_radio(info_window, info->is_open_contact); + + return hbox; +} // _create_page_contact + +/*****************************************************************************/ +static GtkWidget *_create_page_details + (int is_myself, contact_info * info, GaimConnection * gc, contact_info_window * info_window) { + GList *cbitems; + GtkWidget *text, *hbox, *frame; + GtkWidget *combo, *table, *label, *entry, *scrolled_window; + + cbitems = NULL; + + hbox = gtk_hbox_new(FALSE, 0); + frame = gtk_frame_new(""); + gtk_container_set_border_width(GTK_CONTAINER(frame), 5); + gtk_box_pack_start(GTK_BOX(hbox), frame, TRUE, TRUE, 0); + + table = gtk_table_new(4, 4, FALSE); + gtk_table_set_row_spacings(GTK_TABLE(table), 2); + gtk_table_set_col_spacings(GTK_TABLE(table), 5); + gtk_container_set_border_width(GTK_CONTAINER(table), 5); + gtk_container_add(GTK_CONTAINER(frame), table); + + label = gtk_label_new(_("Real Name")); + gtk_misc_set_alignment(GTK_MISC(label), 0, .5); + gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, 1, GTK_FILL, 0, 0, 0); + + label = gtk_label_new(_(" Zodiac")); + gtk_misc_set_alignment(GTK_MISC(label), 0, .5); + gtk_table_attach(GTK_TABLE(table), label, 2, 3, 0, 1, GTK_FILL, 0, 0, 0); + + label = gtk_label_new(_(" Blood Type")); + gtk_misc_set_alignment(GTK_MISC(label), 0, .5); + gtk_table_attach(GTK_TABLE(table), label, 2, 3, 1, 2, GTK_FILL, 0, 0, 0); + + label = gtk_label_new(_(" Horoscope")); + gtk_misc_set_alignment(GTK_MISC(label), 0, .5); + gtk_table_attach(GTK_TABLE(table), label, 2, 3, 2, 3, GTK_FILL, 0, 0, 0); + + label = gtk_label_new(_("College")); + gtk_misc_set_alignment(GTK_MISC(label), 0, .5); + gtk_table_attach(GTK_TABLE(table), label, 0, 1, 1, 2, GTK_FILL, 0, 0, 0); + + label = gtk_label_new(_("Career")); + gtk_misc_set_alignment(GTK_MISC(label), 0, .5); + gtk_table_attach(GTK_TABLE(table), label, 0, 1, 2, 3, GTK_FILL, 0, 0, 0); + + label = gtk_label_new(_("Intro")); + gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); + gtk_table_attach(GTK_TABLE(table), label, 0, 1, 3, 4, GTK_FILL, 0, 0, 0); + + entry = _qq_entry_new(); + info_window->name = entry; + gtk_widget_set_size_request(entry, 120, -1); + _qq_set_entry(info_window->name, info->name); + gtk_table_attach(GTK_TABLE(table), entry, 1, 2, 0, 1, GTK_FILL, 0, 0, 0); + + entry = _qq_entry_new(); + info_window->college = entry; + gtk_widget_set_size_request(entry, 120, -1); + _qq_set_entry(info_window->college, info->college); + gtk_table_attach(GTK_TABLE(table), entry, 1, 2, 1, 2, GTK_FILL, 0, 0, 0); + + combo = gtk_combo_new(); + cbitems = _get_list_by_array((gchar **) zodiac_names, zodiac_names_count); + gtk_combo_set_popdown_strings(GTK_COMBO(combo), cbitems); + g_list_free(cbitems); + cbitems = NULL; + info_window->zodiac = GTK_COMBO(combo)->entry; + gtk_entry_set_text(GTK_ENTRY(info_window->zodiac), + get_name_by_index_str((gchar **) zodiac_names, info->zodiac, zodiac_names_count)); + gtk_widget_set_size_request(combo, 70, -1); + gtk_editable_set_editable(GTK_EDITABLE(GTK_COMBO(combo)->entry), FALSE); + gtk_table_attach(GTK_TABLE(table), combo, 3, 4, 0, 1, GTK_FILL, 0, 0, 0); + if (!is_myself) + gtk_widget_set_sensitive(combo, FALSE); + + combo = gtk_combo_new(); + cbitems = _get_list_by_array((gchar **) blood_types, blood_types_count); + gtk_combo_set_popdown_strings(GTK_COMBO(combo), cbitems); + g_list_free(cbitems); + cbitems = NULL; + info_window->blood = GTK_COMBO(combo)->entry; + gtk_entry_set_text(GTK_ENTRY(info_window->blood), + get_name_by_index_str((gchar **) blood_types, info->blood, blood_types_count)); + gtk_widget_set_size_request(combo, 70, -1); + gtk_editable_set_editable(GTK_EDITABLE(GTK_COMBO(combo)->entry), FALSE); + gtk_table_attach(GTK_TABLE(table), combo, 3, 4, 1, 2, GTK_FILL, 0, 0, 0); + if (!is_myself) + gtk_widget_set_sensitive(combo, FALSE); + + combo = gtk_combo_new(); + cbitems = _get_list_by_array((gchar **) horoscope_names, horoscope_names_count); + gtk_combo_set_popdown_strings(GTK_COMBO(combo), cbitems); + g_list_free(cbitems); + cbitems = NULL; + info_window->horoscope = GTK_COMBO(combo)->entry; + gtk_entry_set_text(GTK_ENTRY(info_window->horoscope), get_name_by_index_str((gchar **) + horoscope_names, + info->horoscope, + horoscope_names_count)); + gtk_widget_set_size_request(combo, 70, -1); + gtk_editable_set_editable(GTK_EDITABLE(GTK_COMBO(combo)->entry), FALSE); + gtk_table_attach(GTK_TABLE(table), combo, 3, 4, 2, 3, GTK_FILL, 0, 0, 0); + if (!is_myself) + gtk_widget_set_sensitive(combo, FALSE); + + combo = gtk_combo_new(); + cbitems = _get_list_by_array((gchar **) occupation_names, occupation_names_count); + gtk_combo_set_popdown_strings(GTK_COMBO(combo), cbitems); + g_list_free(cbitems); + cbitems = NULL; + info_window->occupation = GTK_COMBO(combo)->entry; + _qq_set_entry(info_window->occupation, info->occupation); + gtk_widget_set_size_request(combo, 120, -1); + gtk_table_attach(GTK_TABLE(table), combo, 1, 2, 2, 3, GTK_FILL, 0, 0, 0); + if (!is_myself) + gtk_widget_set_sensitive(combo, FALSE); + + text = gtk_text_view_new(); + gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text), GTK_WRAP_WORD); + info_window->intro = text; + gtk_widget_set_size_request(text, -1, 90); + _qq_set_text(info_window->intro, info->intro); + + scrolled_window = gtk_scrolled_window_new(NULL, NULL); + gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_window), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC); + gtk_container_add(GTK_CONTAINER(scrolled_window), text); + gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolled_window), GTK_SHADOW_IN); + gtk_text_view_set_left_margin(GTK_TEXT_VIEW(text), 2); + gtk_text_view_set_right_margin(GTK_TEXT_VIEW(text), 2); + + gtk_table_attach(GTK_TABLE(table), scrolled_window, 1, 4, 3, 4, GTK_FILL, 0, 0, 0); + return hbox; +} // _create_page_details + +/*****************************************************************************/ +static GtkWidget *_create_page_security + (int is_myself, contact_info * info, GaimConnection * gc, contact_info_window * info_window) { + GtkWidget *hbox, *frame, *vbox1, *vbox2; + GtkWidget *entry, *table, *label, *check_button; + GtkWidget *radio, *alignment; + + hbox = gtk_hbox_new(FALSE, 0); + vbox1 = gtk_vbox_new(FALSE, 0); + gtk_box_pack_start(GTK_BOX(hbox), vbox1, TRUE, TRUE, 0); + + frame = gtk_frame_new(_("Change Password")); + gtk_container_set_border_width(GTK_CONTAINER(frame), 5); + gtk_box_pack_start(GTK_BOX(vbox1), frame, FALSE, FALSE, 0); + + table = gtk_table_new(3, 3, FALSE); + gtk_table_set_row_spacings(GTK_TABLE(table), 2); + gtk_table_set_col_spacings(GTK_TABLE(table), 5); + gtk_container_set_border_width(GTK_CONTAINER(table), 5); + gtk_container_add(GTK_CONTAINER(frame), table); + + label = gtk_label_new(_("Old Passwd")); + gtk_misc_set_alignment(GTK_MISC(label), 0, .5); + gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, 1, GTK_FILL, 0, 0, 0); + + check_button = gtk_check_button_new_with_label(_("I wanna change")); + info_window->is_modify_passwd = check_button; + g_signal_connect(G_OBJECT(check_button), "toggled", + G_CALLBACK(_change_passwd_checkbutton_callback), info_window); + gtk_table_attach(GTK_TABLE(table), check_button, 2, 3, 0, 1, GTK_FILL, 0, 0, 0); + + label = gtk_label_new(_("New Passwd")); + gtk_misc_set_alignment(GTK_MISC(label), 0, .5); + gtk_table_attach(GTK_TABLE(table), label, 0, 1, 1, 2, GTK_FILL, 0, 0, 0); + + label = gtk_label_new(_("(less than 16 char)")); + gtk_misc_set_alignment(GTK_MISC(label), 0, .5); + gtk_table_attach(GTK_TABLE(table), label, 2, 3, 1, 2, GTK_FILL, 0, 0, 0); + + label = gtk_label_new(_("Confirm")); + gtk_misc_set_alignment(GTK_MISC(label), 0, .5); + gtk_table_attach(GTK_TABLE(table), label, 0, 1, 2, 3, GTK_FILL, 0, 0, 0); + + entry = _qq_entry_new(); + gtk_entry_set_visibility(GTK_ENTRY(entry), FALSE); + gtk_widget_set_sensitive(entry, FALSE); + info_window->old_password = entry; + gtk_widget_set_size_request(entry, 160, -1); + gtk_table_attach(GTK_TABLE(table), entry, 1, 2, 0, 1, GTK_FILL, 0, 0, 0); + + entry = _qq_entry_new(); + gtk_entry_set_visibility(GTK_ENTRY(entry), FALSE); + gtk_widget_set_sensitive(entry, FALSE); + info_window->password[0] = entry; + gtk_widget_set_size_request(entry, 160, -1); + gtk_table_attach(GTK_TABLE(table), entry, 1, 2, 1, 2, GTK_FILL, 0, 0, 0); + + entry = _qq_entry_new(); + gtk_entry_set_visibility(GTK_ENTRY(entry), FALSE); + gtk_widget_set_sensitive(entry, FALSE); + info_window->password[1] = entry; + gtk_widget_set_size_request(entry, 160, -1); + gtk_table_attach(GTK_TABLE(table), entry, 1, 2, 2, 3, GTK_FILL, 0, 0, 0); + + alignment = gtk_alignment_new(0, 0, 0, 0); + gtk_widget_set_size_request(alignment, -1, 5); + gtk_box_pack_start(GTK_BOX(vbox1), alignment, FALSE, FALSE, 0); + + frame = gtk_frame_new(_("Authentication")); + gtk_container_set_border_width(GTK_CONTAINER(frame), 5); + gtk_box_pack_start(GTK_BOX(vbox1), frame, FALSE, FALSE, 0); + + vbox2 = gtk_vbox_new(FALSE, 1); + gtk_container_add(GTK_CONTAINER(frame), vbox2); + + radio = gtk_radio_button_new_with_label(NULL, _("Anyone can add me")); + info_window->auth_radio[0] = radio; + gtk_box_pack_start(GTK_BOX(vbox2), radio, FALSE, FALSE, 0); + + radio = gtk_radio_button_new_with_label_from_widget + (GTK_RADIO_BUTTON(radio), _("User needs to be authenticated to add me")); + info_window->auth_radio[1] = radio; + gtk_box_pack_start(GTK_BOX(vbox2), radio, FALSE, FALSE, 0); + + radio = gtk_radio_button_new_with_label_from_widget(GTK_RADIO_BUTTON(radio), _("Nobody can add me")); + info_window->auth_radio[2] = radio; + gtk_box_pack_start(GTK_BOX(vbox2), radio, FALSE, FALSE, 0); + + _qq_set_auth_type_radio(info_window, info->auth_type); + + return hbox; +} // _create_page_security + +/*****************************************************************************/ +void qq_show_contact_info_dialog(contact_info * info, GaimConnection * gc, contact_info_window * info_window) { + + GaimAccount *a = gc->account; + gboolean is_myself; + GtkWidget *label, *window, *notebook, *vbox, *bbox, *button; + + is_myself = (!strcmp(info->uid, a->username)); + window = gtk_window_new(GTK_WINDOW_TOPLEVEL); +// gtk_window_set_transient_for(GTK_WINDOW(window), GTK_WINDOW(mainwindow)); //by gfhuang + info_window->window = window; + + // we need to duplicate here, + // as the passed-in info structure will be freed in his function + info_window->old_info = (contact_info *) g_strdupv((gchar **) info); + + /* When the window is given the "delete_event" signal (this is given + * by the window manager, usually by the "close" option, or on the + * titlebar), we ask it to call the delete_event () function + * as defined above. The data passed to the callback + * function is NULL and is ignored in the callback function. + */ + g_signal_connect(G_OBJECT(window), "delete_event", G_CALLBACK(_window_deleteevent), NULL); + + /* Here we connect the "destroy" event to a signal handler. + * This event occurs when we call gtk_widget_destroy() on the window, + * or if we return FALSE in the "delete_event" callback. + */ + g_signal_connect(G_OBJECT(window), "destroy", G_CALLBACK(_info_window_destroy), gc); + + gtk_window_set_resizable(GTK_WINDOW(window), FALSE); + + if (is_myself) + gtk_window_set_title(GTK_WINDOW(window), _("My Information")); + else + gtk_window_set_title(GTK_WINDOW(window), _("My Buddy's Information")); + gtk_container_set_border_width(GTK_CONTAINER(window), 5); + + vbox = gtk_vbox_new(FALSE, 0); + gtk_container_add(GTK_CONTAINER(window), vbox); + + notebook = gtk_notebook_new(); + gtk_notebook_set_tab_pos(GTK_NOTEBOOK(notebook), GTK_POS_LEFT); + + label = gtk_label_new(_("Basic")); + gtk_notebook_append_page(GTK_NOTEBOOK(notebook), _create_page_basic(is_myself, info, gc, info_window), label); + + label = gtk_label_new(_("Contact")); + gtk_notebook_append_page(GTK_NOTEBOOK(notebook), _create_page_contact(is_myself, info, gc, info_window), label); + + label = gtk_label_new(_("Details")); + gtk_notebook_append_page(GTK_NOTEBOOK(notebook), _create_page_details(is_myself, info, gc, info_window), label); + + if (is_myself) { + label = gtk_label_new(_("Security")); + gtk_notebook_append_page(GTK_NOTEBOOK(notebook), + _create_page_security(is_myself, info, gc, info_window), label); + } + + gtk_box_pack_start(GTK_BOX(vbox), notebook, TRUE, TRUE, 0); + + bbox = gtk_hbutton_box_new(); + gtk_button_box_set_layout(GTK_BUTTON_BOX(bbox), GTK_BUTTONBOX_END); + gtk_box_set_spacing(GTK_BOX(bbox), 10); + + button = gtk_button_new_with_label(_("Modify")); + g_object_set_data(G_OBJECT(button), "user_data", (gpointer) info_window); + g_signal_connect(G_OBJECT(button), "clicked", G_CALLBACK(_info_window_change), gc); + gtk_container_add(GTK_CONTAINER(bbox), button); + + if (is_myself) + gtk_widget_set_sensitive(button, TRUE); + else + gtk_widget_set_sensitive(button, FALSE); + + button = gtk_button_new_with_label(_("Refresh")); + info_window->refresh_button = button; + g_object_set_data(G_OBJECT(button), "user_data", (gpointer) info_window); + g_signal_connect(G_OBJECT(button), "clicked", G_CALLBACK(_info_window_refresh), gc); + gtk_container_add(GTK_CONTAINER(bbox), button); + + button = gtk_button_new_with_label(_("Close")); + g_signal_connect(G_OBJECT(button), "clicked", G_CALLBACK(_window_close), info_window->window); + gtk_container_add(GTK_CONTAINER(bbox), button); + + gtk_box_pack_start(GTK_BOX(vbox), bbox, TRUE, TRUE, 5); + + _no_edit(info_window->uid_entry); // uid entry cannot be changed + if (!is_myself) { + _no_edit(info_window->nick); + _no_edit(info_window->country); + _no_edit(info_window->age); + _no_edit(info_window->gender); + _no_edit(info_window->province); + _no_edit(info_window->city); + _no_edit(info_window->email); + _no_edit(info_window->address); + _no_edit(info_window->zipcode); + _no_edit(info_window->tel); + _no_edit(info_window->name); + _no_edit(info_window->blood); + _no_edit(info_window->college); + _no_edit(info_window->occupation); + _no_edit(info_window->zodiac); + _no_edit(info_window->horoscope); + _no_edit(info_window->homepage); + gtk_text_view_set_editable(GTK_TEXT_VIEW(info_window->intro), FALSE); + } + gtk_widget_show_all(window); +} // qq_show_contact_info_dialog + +/*****************************************************************************/ +void qq_refresh_contact_info_dialog(contact_info * new_info, GaimConnection * gc, contact_info_window * info_window) { + GaimAccount *a; + gboolean is_myself; + contact_info *info; + a = gc->account; + + if (info_window->old_info) + g_strfreev((gchar **) info_window->old_info); + + // we need to duplicate here, + // as the passed-in info structure will be freed in his function + info = (contact_info *) g_strdupv((gchar **) new_info); + info_window->old_info = info; + + is_myself = !(g_ascii_strcasecmp(info->uid, a->username)); + gtk_widget_set_sensitive(info_window->refresh_button, TRUE); + + if (is_myself) { + _qq_set_auth_type_radio(info_window, info->auth_type); + _qq_set_open_contact_radio(info_window, info->is_open_contact); + } + + _qq_set_entry(info_window->uid_entry, info->uid); + _qq_set_entry(info_window->nick, info->nick); + _qq_set_entry(info_window->country, info->country); + _qq_set_entry(info_window->age, info->age); + _qq_set_entry(info_window->gender, info->gender); + _qq_set_entry(info_window->province, info->province); + _qq_set_entry(info_window->city, info->city); + _qq_set_entry(info_window->email, info->email); + _qq_set_entry(info_window->address, info->address); + _qq_set_entry(info_window->zipcode, info->zipcode); + _qq_set_entry(info_window->tel, info->tel); + _qq_set_entry(info_window->name, info->name); + gtk_entry_set_text(GTK_ENTRY(info_window->zodiac), + get_name_by_index_str((gchar **) zodiac_names, info->zodiac, zodiac_names_count)); + gtk_entry_set_text(GTK_ENTRY(info_window->horoscope), get_name_by_index_str((gchar **) + horoscope_names, + info->horoscope, + horoscope_names_count)); + gtk_entry_set_text(GTK_ENTRY(info_window->blood), + get_name_by_index_str((gchar **) blood_types, info->blood, blood_types_count)); + _qq_set_entry(info_window->college, info->college); + _qq_set_entry(info_window->occupation, info->occupation); + _qq_set_entry(info_window->homepage, info->homepage); + + _qq_set_image(info_window->face, (guint8) atoi(info->face)); + + _qq_set_text(info_window->intro, info->intro); +} // qq_refresh_contact_info_dialog + +/*****************************************************************************/ +// END OF FILE diff -r 5642f4658b59 -r 983fd420e86b src/protocols/qq/infodlg.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/protocols/qq/infodlg.h Mon Jun 26 02:58:54 2006 +0000 @@ -0,0 +1,63 @@ +/** + * The QQ2003C protocol plugin + * + * for gaim + * + * Copyright (C) 2004 Puzzlebird + * + * 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 + */ + +// START OF FILE +/*****************************************************************************/ +#ifndef _QQ_INFODLG_H_ +#define _QQ_INFODLG_H_ + +#include +#include +#include "connection.h" // GaimConnection + +#include "buddy_info.h" // contact_info +#include "qq.h" // qq_data + +typedef struct _contact_info_window contact_info_window; + +struct _contact_info_window { + guint32 uid; + GtkWidget *window; + GtkWidget *refresh_button; + GtkWidget *uid_entry, + *face, + *nick, + *country, + *age, + *gender, + *province, + *city, + *email, + *address, *zipcode, *tel, *name, *blood, *college, *occupation, *zodiac, *horoscope, *homepage, *intro; + GtkWidget *open_contact_radio[3]; + GtkWidget *auth_radio[3], *is_modify_passwd, *old_password, *password[2]; + contact_info *old_info; +}; + +void qq_contact_info_window_free(qq_data * qd); + +void qq_show_contact_info_dialog(contact_info * info, GaimConnection * gc, contact_info_window * info_window); +void qq_refresh_contact_info_dialog(contact_info * info, GaimConnection * gc, contact_info_window * info_window); + +#endif +/*****************************************************************************/ +// END OF FILE diff -r 5642f4658b59 -r 983fd420e86b src/protocols/qq/ip_location.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/protocols/qq/ip_location.c Mon Jun 26 02:58:54 2006 +0000 @@ -0,0 +1,253 @@ +/** + * The QQ2003C protocol plugin + * + * for gaim + * + * Copyright (C) 2004 Puzzlebird + * + * 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 + * + * This code is based on + * * qqwry.c, by lvxiang + * * IpChecker.m, by Jeff_Ye + */ + +// START OF FILE +/*****************************************************************************/ +#include "internal.h" +#include // memset +#include "debug.h" +#include "prefs.h" // gaim_prefs_get_string + +#include "utils.h" +#include "ip_location.h" + +#define DEFAULT_IP_LOCATION_FILE "gaim/QQWry.dat" + +typedef struct _ip_finder ip_finder; + +// all offset is based the begining of the file +struct _ip_finder { + guint32 offset_first_start_ip; // first abs offset of start ip + guint32 offset_last_start_ip; // last abs offset of start ip + guint32 cur_start_ip; // start ip of current search range + guint32 cur_end_ip; // end ip of current search range + guint32 offset_cur_end_ip; // where is the current end ip saved + GIOChannel *io; // IO Channel to read file +}; // struct _ip_finder + +/*****************************************************************************/ +// convert 1-4 bytes array to integer. +// Small endian (higher bytes in lower place) +static guint32 _byte_array_to_int(guint8 * ip, gint count) +{ + guint32 ret, i; + g_return_val_if_fail(count >= 1 && count <= 4, 0); + ret = ip[0]; + for (i = 0; i < count; i++) + ret |= ((guint32) ip[i]) << (8 * i); + return ret; +} // _byte_array_to_int + +/*****************************************************************************/ +// read len of bytes to buf, from io at offset +static void _read_from(GIOChannel * io, guint32 offset, guint8 * buf, gint len) +{ + GError *err; + GIOStatus status; + + err = NULL; + status = g_io_channel_seek_position(io, offset, G_SEEK_SET, &err); + if (err != NULL) { + gaim_debug(GAIM_DEBUG_ERROR, "QQ", "Fail seek file @offset[%d]: %s", offset, err->message); + g_error_free(err); + memset(buf, 0, len); + return; + } // if err + + status = g_io_channel_read_chars(io, buf, len, NULL, &err); + if (err != NULL) { + gaim_debug(GAIM_DEBUG_ERROR, "QQ", "Fail read %d bytes from file: %s", len, err->message); + g_error_free(err); + memset(buf, 0, len); + return; + } // if err +} // _read_from + +/*****************************************************************************/ +// read len of bytes to buf, from io at offset +static gsize _read_line_from(GIOChannel * io, guint32 offset, gchar ** ret_str) +{ + gsize bytes_read; + GError *err; + GIOStatus status; + + err = NULL; + status = g_io_channel_seek_position(io, offset, G_SEEK_SET, &err); + if (err != NULL) { + gaim_debug(GAIM_DEBUG_ERROR, "QQ", "Fail seek file @offset[%d]: %s", offset, err->message); + g_error_free(err); + ret_str = NULL; + return -1; + } // if err + + status = g_io_channel_read_line(io, ret_str, &bytes_read, NULL, &err); + if (err != NULL) { + gaim_debug(GAIM_DEBUG_ERROR, "QQ", "Fail read from file: %s", err->message); + g_error_free(err); + ret_str = NULL; + return -1; + } // if err + + return bytes_read; +} // _read_from + +/*****************************************************************************/ +// read the string from io, at offset, it may jump several times +// returns the offset of next readable string for area +static guint32 _get_string(GIOChannel * io, guint32 offset, gchar ** ret) +{ + guint8 *buf; + g_return_val_if_fail(io != NULL, 0); + + buf = g_new0(guint8, 3); + _read_from(io, offset, buf, 1); + + switch (buf[0]) { /* fixed by bluestar11 at gmail dot com, 04/12/20 */ + case 0x01: /* jump together */ + _read_from(io, offset + 1, buf, 3); + return _get_string(io, _byte_array_to_int(buf, 3), ret); + case 0x02: /* jump separately */ + _read_from(io, offset + 1, buf, 3); + _get_string(io, _byte_array_to_int(buf, 3), ret); + return offset + 4; + default: + _read_line_from(io, offset, ret); + return offset + strlen(*ret) + 1; + } /* switch */ +} // _get_string + +/*****************************************************************************/ +// extract country and area starting from offset +static void _get_country_city(GIOChannel * io, guint32 offset, gchar ** country, gchar ** area) +{ + guint32 next_offset; + g_return_if_fail(io != NULL); + + next_offset = _get_string(io, offset, country); + if (next_offset == 0) + *area = g_strdup(""); + else + _get_string(io, next_offset, area); +} // _get_country_city + +/*****************************************************************************/ +// set start_ip and end_ip of current range +static void _set_ip_range(gint rec_no, ip_finder * f) +{ + guint8 *buf; + guint32 offset; + + g_return_if_fail(f != NULL); + + buf = g_newa(guint8, 7); + offset = f->offset_first_start_ip + rec_no * 7; + + _read_from(f->io, offset, buf, 7); + f->cur_start_ip = _byte_array_to_int(buf, 4); + f->offset_cur_end_ip = _byte_array_to_int(buf + 4, 3); + + _read_from(f->io, f->offset_cur_end_ip, buf, 4); + f->cur_end_ip = _byte_array_to_int(buf, 4); + +} // _set_ip_range + +/*****************************************************************************/ +// set the country and area for given IP. +// country and area needs to be freed later +gboolean qq_ip_get_location(guint32 ip, gchar ** country, gchar ** area) +{ + gint rec, record_count, B, E; + guint8 *buf; + gchar *addr_file; + ip_finder *f; + GError *err; + const char *ip_fn; + + if (ip == 0) + return FALSE; + + f = g_new0(ip_finder, 1); + err = NULL; + ip_fn = gaim_prefs_get_string("/plugins/prpl/qq/ipfile"); + if (ip_fn == NULL || strlen(ip_fn) == 0 || strncmp(ip_fn, "(null)", strlen("(null)")) == 0) { + addr_file = g_build_filename(DATADIR, DEFAULT_IP_LOCATION_FILE, NULL); + } else { + addr_file = g_build_filename(ip_fn, NULL); + } + + f->io = g_io_channel_new_file(addr_file, "r", &err); + g_free(addr_file); + if (err != NULL) { + gaim_debug(GAIM_DEBUG_ERROR, "QQ", "Unable to open (%s): %s\n", addr_file, err->message); + g_error_free(err); + return FALSE; + } else + g_io_channel_set_encoding(f->io, NULL, NULL); // set binary + + buf = g_newa(guint8, 4); + + _read_from(f->io, 0, buf, 4); + f->offset_first_start_ip = _byte_array_to_int(buf, 4); + _read_from(f->io, 4, buf, 4); + f->offset_last_start_ip = _byte_array_to_int(buf, 4); + + record_count = (f->offset_last_start_ip - f->offset_first_start_ip) / 7; + if (record_count <= 1) { + gaim_debug(GAIM_DEBUG_ERROR, "QQ", "File data error, no records found\n"); + g_io_channel_shutdown(f->io, FALSE, NULL); + return FALSE;; + } + // search for right range + B = 0; + E = record_count; + while (B < E - 1) { + rec = (B + E) / 2; + _set_ip_range(rec, f); + if (ip == f->cur_start_ip) { + B = rec; + break; + } + if (ip > f->cur_start_ip) + B = rec; + else + E = rec; + } // while + _set_ip_range(B, f); + + if (f->cur_start_ip <= ip && ip <= f->cur_end_ip) { + _get_country_city(f->io, f->offset_cur_end_ip + 4, country, area); + } else { // not in this range... miss + *country = g_strdup("unkown"); + *area = g_strdup(" "); + } // if ip_start<=ip<=ip_end + + g_io_channel_shutdown(f->io, FALSE, NULL); + return TRUE; + +} // qq_ip_get_location + +/*****************************************************************************/ +// END OF FILE diff -r 5642f4658b59 -r 983fd420e86b src/protocols/qq/ip_location.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/protocols/qq/ip_location.h Mon Jun 26 02:58:54 2006 +0000 @@ -0,0 +1,34 @@ +/** + * The QQ2003C protocol plugin + * + * for gaim + * + * Copyright (C) 2004 Puzzlebird + * + * 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 + */ + +// START OF FILE +/*****************************************************************************/ +#ifndef _QQ_IP_LOCATION_H_ +#define _QQ_IP_LOCATION_H_ + +#include "glib.h" + +gboolean qq_ip_get_location(guint32 ip, gchar ** country, gchar ** city); + +#endif +/*****************************************************************************/ +// END OF FILE diff -r 5642f4658b59 -r 983fd420e86b src/protocols/qq/keep_alive.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/protocols/qq/keep_alive.c Mon Jun 26 02:58:54 2006 +0000 @@ -0,0 +1,182 @@ +/** +* The QQ2003C protocol plugin + * + * for gaim + * + * Copyright (C) 2004 Puzzlebird + * + * 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 + * + * + * OICQ encryption algorithm + * Convert from ASM code provided by PerlOICQ + * + * Puzzlebird, Nov-Dec 2002 + */ + +// START OF FILE +/*****************************************************************************/ +#include "debug.h" // gaim_debug +#include "server.h" // serv_got_update + +#include "utils.h" // uid_to_gaim_name +#include "packet_parse.h" // create_packet +#include "buddy_list.h" // qq_send_packet_get_buddies_online +#include "buddy_status.h" // QQ_BUDDY_ONLINE_NORMAL +#include "crypt.h" // qq_crypt +#include "header_info.h" // cmd alias +#include "keep_alive.h" +#include "send_core.h" // qq_send_cmd + +#define QQ_UPDATE_ONLINE_INTERVAL 300 // in sec + +/*****************************************************************************/ +// send keep-alive packet to QQ server (it is a heart-beat) +void qq_send_packet_keep_alive(GaimConnection * gc) +{ + qq_data *qd; + guint8 *raw_data, *cursor; + + g_return_if_fail(gc != NULL && gc->proto_data != NULL); + + qd = (qq_data *) gc->proto_data; + raw_data = g_newa(guint8, 4); + cursor = raw_data; + + // In fact, we can send whatever we like to server + // with this command, server return the same result including + // the amount of online QQ users, my ip and port + create_packet_dw(raw_data, &cursor, qd->uid); + + qq_send_cmd(gc, QQ_CMD_KEEP_ALIVE, TRUE, 0, TRUE, raw_data, 4); + +} // qq_send_packet_keep_alive + +/*****************************************************************************/ +// parse the return of keep-alive packet, it includes some system information +void qq_process_keep_alive_reply(guint8 * buf, gint buf_len, GaimConnection * gc) { + qq_data *qd; + gint len; + gchar *data, **segments; // the returns are gchar, no need guint8 + + g_return_if_fail(gc != NULL && gc->proto_data != NULL); + g_return_if_fail(buf != NULL && buf_len != 0); + + qd = (qq_data *) gc->proto_data; + len = buf_len; + data = g_newa(guint8, len); + + if (qq_crypt(DECRYPT, buf, buf_len, qd->session_key, data, &len)) { + if (NULL == (segments = split_data(data, len, "\x1f", 6 /* 5->6, protocal changed by gfhuang*, the last one is 60, don't know what it is */))) + return; + // segments[0] and segment[1] are all 0x30 ("0") + qd->all_online = strtol(segments[2], NULL, 10); + if(0 == qd->all_online) //added by gfhuang + gaim_connection_error(gc, _("Keep alive error, seems connection lost!")); + g_free(qd->my_ip); + qd->my_ip = g_strdup(segments[3]); + qd->my_port = strtol(segments[4], NULL, 10); + g_strfreev(segments); + } else + gaim_debug(GAIM_DEBUG_ERROR, "QQ", "Error decrypt keep alive reply\n"); + + // we refresh buddies's online status periodically + // qd->lasat_get_online is updated when setting get_buddies_online packet + if ((time(NULL) - qd->last_get_online) >= QQ_UPDATE_ONLINE_INTERVAL) + qq_send_packet_get_buddies_online(gc, QQ_FRIENDS_ONLINE_POSITION_START); + +} // qq_process_keep_alive_reply + +/*****************************************************************************/ +// refresh all buddies online/offline, +// after receiving reply for get_buddies_online packet +void qq_refresh_all_buddy_status(GaimConnection * gc) +{ + time_t now; + GList *list; + qq_data *qd; + qq_buddy *q_bud; + + g_return_if_fail(gc != NULL && gc->proto_data != NULL); + + qd = (qq_data *) (gc->proto_data); + now = time(NULL); + list = qd->buddies; + g_return_if_fail(qd != NULL); + + while (list != NULL) { + q_bud = (qq_buddy *) list->data; + if (q_bud != NULL && now > q_bud->last_refresh + QQ_UPDATE_ONLINE_INTERVAL + && q_bud->status != QQ_BUDDY_ONLINE_INVISIBLE) { // by gfhuang + q_bud->status = QQ_BUDDY_ONLINE_OFFLINE; + qq_update_buddy_contact(gc, q_bud); + } + list = list->next; + } // while +} // qq_refresh_all_buddy_status + +/*****************************************************************************/ +void qq_update_buddy_contact(GaimConnection * gc, qq_buddy * q_bud) +{ + gchar *name; +// gboolean online; + GaimBuddy *bud; + g_return_if_fail(gc != NULL && q_bud != NULL); + + name = uid_to_gaim_name(q_bud->uid); + bud = gaim_find_buddy(gc->account, name); + g_return_if_fail(bud != NULL); + + if (bud != NULL) { + gaim_blist_server_alias_buddy(bud, q_bud->nickname); //server by gfhuang + q_bud->last_refresh = time(NULL); + + // gaim support signon and idle time + // but it is not much useful for QQ, I do not use them it +// serv_got_update(gc, name, online, 0, q_bud->signon, q_bud->idle, bud->uc); //disable by gfhuang + char *status_id = "available"; + switch(q_bud->status) { + case QQ_BUDDY_OFFLINE: + status_id = "offline"; + break; + case QQ_BUDDY_ONLINE_NORMAL: + status_id = "available"; + break; + case QQ_BUDDY_ONLINE_OFFLINE: + status_id = "offline"; + break; + case QQ_BUDDY_ONLINE_AWAY: + status_id = "away"; + break; + case QQ_BUDDY_ONLINE_INVISIBLE: + status_id = "invisible"; + break; + default: + status_id = "invisible"; + gaim_debug(GAIM_DEBUG_ERROR, "QQ", "unknown status by gfhuang: %x\n", q_bud->status); + break; + } + gaim_debug(GAIM_DEBUG_INFO, "QQ", "set buddy %d to %s\n", q_bud->uid, status_id); + gaim_prpl_got_user_status(gc->account, name, status_id, NULL); + } // if bud + else + gaim_debug(GAIM_DEBUG_ERROR, "QQ", "unknown buddy by gfhuang: %d\n", q_bud->uid); + + gaim_debug(GAIM_DEBUG_INFO, "QQ", "qq_update_buddy_contact, client=%04x\n", q_bud->client_version); + g_free(name); +} // qq_update_buddy_contact + +/*****************************************************************************/ +// END OF FILE diff -r 5642f4658b59 -r 983fd420e86b src/protocols/qq/keep_alive.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/protocols/qq/keep_alive.h Mon Jun 26 02:58:54 2006 +0000 @@ -0,0 +1,42 @@ +/** +* The QQ2003C protocol plugin + * + * for gaim + * + * Copyright (C) 2004 Puzzlebird + * + * 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 + * + */ + +// START OF FILE +/*****************************************************************************/ +#ifndef _QQ_KEEP_ALIVE_H_ +#define _QQ_KEEP_ALIVE_H_ + +#include +#include "connection.h" // GaimConnection +#include "qq.h" // qq_buddy + +void qq_send_packet_keep_alive(GaimConnection * gc); + +void qq_process_keep_alive_reply(guint8 * buf, gint buf_len, GaimConnection * gc); +void qq_refresh_all_buddy_status(GaimConnection * gc); + +void qq_update_buddy_contact(GaimConnection * gc, qq_buddy * q_bud); + +#endif +/*****************************************************************************/ +// END OF FILE diff -r 5642f4658b59 -r 983fd420e86b src/protocols/qq/login_logout.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/protocols/qq/login_logout.c Mon Jun 26 02:58:54 2006 +0000 @@ -0,0 +1,511 @@ +/** + * The QQ2003C protocol plugin + * + * for gaim + * + * Copyright (C) 2004 Puzzlebird + * + * 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 + */ + +// START OF FILE +/*****************************************************************************/ +#include "debug.h" // gaim_debug +#include "internal.h" // memcpy, _("get_text") +#include "server.h" // serv_finish_login + +#include "utils.h" // gen_ip_str +#include "packet_parse.h" // create_packet +#include "buddy_info.h" // qq_send_packet_get_info +#include "buddy_list.h" // qq_send_packet_get_buddies_list +#include "buddy_status.h" // QQ_SELF_STATUS_AVAILABLE +#include "char_conv.h" // qq_to_utf8 +#include "crypt.h" // qq_crypt +#include "group.h" // qq_group_init +#include "header_info.h" // QQ_CMD_LOGIN +#include "login_logout.h" +#include "qq_proxy.h" // qq_connect +#include "send_core.h" // qq_send_cmd +#include "qq.h" // qq_data + +//#define QQ_LOGIN_DATA_LENGTH 69 //length of plain login packet +#define QQ_LOGIN_DATA_LENGTH 416 //new length from eva, by gfhuang +#define QQ_LOGIN_REPLY_OK_PACKET_LEN 139 +#define QQ_LOGIN_REPLY_REDIRECT_PACKET_LEN 11 + +#define QQ_REQUEST_LOGIN_TOKEN_REPLY_OK 0x00 //added by gfhuang + +#define QQ_LOGIN_REPLY_OK 0x00 +#define QQ_LOGIN_REPLY_REDIRECT 0x01 +//#define QQ_LOGIN_REPLY_PWD_ERROR 0x02 +#define QQ_LOGIN_REPLY_PWD_ERROR 0x05 +#define QQ_LOGIN_REPLY_MISC_ERROR 0xff // defined by myself + +// for QQ 2003iii 0117, fixed value +/* static const guint8 login_23_51[29] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xbf, 0x14, 0x11, 0x20, + 0x03, 0x9d, 0xb2, 0xe6, 0xb3, 0x11, 0xb7, 0x13, + 0x95, 0x67, 0xda, 0x2c, 0x01 +}; */ + +// for QQ 2003iii 0304, fixed value +/* +static const guint8 login_23_51[29] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x9a, 0x93, 0xfe, 0x85, + 0xd3, 0xd9, 0x2a, 0x41, 0xc8, 0x0d, 0xff, 0xb6, + 0x40, 0xb8, 0xac, 0x32, 0x01 +}; +*/ + +//for QQ 2005? copy from lumqq +static const gint8 login_23_51[29] = { + 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, -122, + -52, 76, 53, 44, -45, 115, 108, 20, -10, -10, + -81, -61, -6, 51, -92, 1 +}; + +static const gint8 login_53_68[16] = { + -115, -117, -6, -20, -43, 82, 23, 74, -122, -7, + -89, 117, -26, 50, -47, 109 +}; + +static const gint8 login_100_bytes[100] = { + 64, + 11, 4, 2, 0, 1, 0, 0, 0, 0, 0, + 3, 9, 0, 0, 0, 0, 0, 0, 0, 1, + -23, 3, 1, 0, 0, 0, 0, 0, 1, -13, + 3, 0, 0, 0, 0, 0, 0, 1, -19, 3, + 0, 0, 0, 0, 0, 0, 1, -20, 3, 0, + 0, 0, 0, 0, 0, 3, 5, 0, 0, 0, + 0, 0, 0, 0, 3, 7, 0, 0, 0, 0, + 0, 0, 0, 1, -18, 3, 0, 0, 0, 0, + 0, 0, 1, -17, 3, 0, 0, 0, 0, 0, + 0, 1, -21, 3, 0, 0, 0, 0, 0 +}; + +// fixed value, not affected by version, or mac address +/* +static const guint8 login_53_68[16] = { + 0x82, 0x2a, 0x91, 0xfd, 0xa5, 0xca, 0x67, 0x4c, + 0xac, 0x81, 0x1f, 0x6f, 0x52, 0x05, 0xa7, 0xbf +}; +*/ + + +typedef struct _qq_login_reply_ok qq_login_reply_ok_packet; +typedef struct _qq_login_reply_redirect qq_login_reply_redirect_packet; + +struct _qq_login_reply_ok { + guint8 result; + guint8 *session_key; + guint32 uid; + guint8 client_ip[4]; // those detected by server + guint16 client_port; + guint8 server_ip[4]; + guint16 server_port; + time_t login_time; + guint8 unknown1[26]; + guint8 unknown_server1_ip[4]; + guint16 unknown_server1_port; + guint8 unknown_server2_ip[4]; + guint16 unknown_server2_port; + guint16 unknown2; // 0x0001 + guint16 unknown3; // 0x0000 + guint8 unknown4[32]; + guint8 unknown5[12]; + guint8 last_client_ip[4]; + time_t last_login_time; + guint8 unknown6[8]; +}; + +struct _qq_login_reply_redirect { + guint8 result; + guint32 uid; + guint8 new_server_ip[4]; + guint16 new_server_port; +}; + +extern gint // defined in send_core.c + _create_packet_head_seq(guint8 * buf, + guint8 ** cursor, GaimConnection * gc, guint16 cmd, gboolean is_auto_seq, guint16 * seq); +extern gint // defined in send_core.c + _qq_send_packet(GaimConnection * gc, guint8 * buf, gint len, guint16 cmd); + +/*****************************************************************************/ +// It is fixed to 16 bytes 0x01 for QQ2003, +// Any value works (or a random 16 bytes string) +static gchar *_gen_login_key(void) +{ + return g_strnfill(QQ_KEY_LENGTH, 0x01); +} // _gen_login_key + +/*****************************************************************************/ +// process login reply which says OK +static gint _qq_process_login_ok(GaimConnection * gc, guint8 * data, gint len) +{ + gint bytes; + guint8 *cursor; + qq_data *qd; + qq_login_reply_ok_packet lrop; + + g_return_val_if_fail(gc != NULL && gc->proto_data != NULL, QQ_LOGIN_REPLY_MISC_ERROR); + + qd = (qq_data *) gc->proto_data; + cursor = data; + bytes = 0; + + // 000-000: reply code + bytes += read_packet_b(data, &cursor, len, &lrop.result); + // 001-016: session key + lrop.session_key = g_memdup(cursor, QQ_KEY_LENGTH); + cursor += QQ_KEY_LENGTH; + bytes += QQ_KEY_LENGTH; + gaim_debug(GAIM_DEBUG_INFO, "QQ", "Get session_key done\n"); + // 017-020: login uid + bytes += read_packet_dw(data, &cursor, len, &lrop.uid); + // 021-024: server detected user public IP + bytes += read_packet_data(data, &cursor, len, (guint8 *) & lrop.client_ip, 4); + // 025-026: server detected user port + bytes += read_packet_w(data, &cursor, len, &lrop.client_port); + // 027-030: server detected itself ip 127.0.0.1 ? + bytes += read_packet_data(data, &cursor, len, (guint8 *) & lrop.server_ip, 4); + // 031-032: server listening port + bytes += read_packet_w(data, &cursor, len, &lrop.server_port); + // 033-036: login time for current session + bytes += read_packet_dw(data, &cursor, len, (guint32 *) & lrop.login_time); + // 037-062: 26 bytes, unknown + bytes += read_packet_data(data, &cursor, len, (guint8 *) & lrop.unknown1, 26); + // 063-066: unknown server1 ip address + bytes += read_packet_data(data, &cursor, len, (guint8 *) & lrop.unknown_server1_ip, 4); + // 067-068: unknown server1 port + bytes += read_packet_w(data, &cursor, len, &lrop.unknown_server1_port); + // 069-072: unknown server2 ip address + bytes += read_packet_data(data, &cursor, len, (guint8 *) & lrop.unknown_server2_ip, 4); + // 073-074: unknown server2 port + bytes += read_packet_w(data, &cursor, len, &lrop.unknown_server2_port); + // 075-076: 2 bytes unknown + bytes += read_packet_w(data, &cursor, len, &lrop.unknown2); + // 077-078: 2 bytes unknown + bytes += read_packet_w(data, &cursor, len, &lrop.unknown3); + // 079-110: 32 bytes unknown + bytes += read_packet_data(data, &cursor, len, (guint8 *) & lrop.unknown4, 32); + // 111-122: 12 bytes unknown + bytes += read_packet_data(data, &cursor, len, (guint8 *) & lrop.unknown5, 12); + // 123-126: login IP of last session + bytes += read_packet_data(data, &cursor, len, (guint8 *) & lrop.last_client_ip, 4); + // 127-130: login time of last session + bytes += read_packet_dw(data, &cursor, len, (guint32 *) & lrop.last_login_time); + // 131-138: 8 bytes unknown + bytes += read_packet_data(data, &cursor, len, (guint8 *) & lrop.unknown6, 8); + + if (bytes != QQ_LOGIN_REPLY_OK_PACKET_LEN) { // fail parsing login info + gaim_debug(GAIM_DEBUG_WARNING, "QQ", + "Fail parsing login info, expect %d bytes, read %d bytes\n", + QQ_LOGIN_REPLY_OK_PACKET_LEN, bytes); + } // but we still goes on as login OK + + qd->session_key = g_memdup(lrop.session_key, QQ_KEY_LENGTH); + qd->my_ip = gen_ip_str(lrop.client_ip); + qd->my_port = lrop.client_port; + qd->login_time = lrop.login_time; + qd->last_login_time = lrop.last_login_time; + qd->last_login_ip = gen_ip_str(lrop.last_client_ip); + + g_free(lrop.session_key); + + gaim_connection_set_state(gc, GAIM_CONNECTED); +// serv_finish_login(gc); //by gfhuang + qd->logged_in = TRUE; // must be defined after sev_finish_login + + // now initiate QQ Qun, do it first as it may take longer to finish + qq_group_init(gc); + + // Now goes on updating my icon/nickname, not showing info_window + qq_send_packet_get_info(gc, qd->uid, FALSE); + // change my status manually, even server may broadcast my online + qd->status = (qd->login_mode == QQ_LOGIN_MODE_HIDDEN) ? QQ_SELF_STATUS_INVISIBLE : QQ_SELF_STATUS_AVAILABLE; + qq_send_packet_change_status(gc); + // now refresh buddy list + + //changed by gfhuang, using With Qun version, error, not working still + qq_send_packet_get_buddies_list(gc, QQ_FRIENDS_LIST_POSITION_START); + //qq_send_packet_get_all_list_with_group(gc, QQ_FRIENDS_LIST_POSITION_START); + + return QQ_LOGIN_REPLY_OK; +} // _qq_process_login_ok + +/*****************************************************************************/ +// process login reply packet which includes redirected new server address +static gint _qq_process_login_redirect(GaimConnection * gc, guint8 * data, gint len) +{ + gint bytes, ret; + guint8 *cursor; + gchar *new_server_str; + qq_data *qd; + qq_login_reply_redirect_packet lrrp; + + g_return_val_if_fail(gc != NULL && gc->proto_data != NULL, QQ_LOGIN_REPLY_MISC_ERROR); + + qd = (qq_data *) gc->proto_data; + cursor = data; + bytes = 0; + // 000-000: reply code + bytes += read_packet_b(data, &cursor, len, &lrrp.result); + // 001-004: login uid + bytes += read_packet_dw(data, &cursor, len, &lrrp.uid); + // 005-008: redirected new server IP + bytes += read_packet_data(data, &cursor, len, lrrp.new_server_ip, 4); + // 009-010: redirected new server port + bytes += read_packet_w(data, &cursor, len, &lrrp.new_server_port); + + if (bytes != QQ_LOGIN_REPLY_REDIRECT_PACKET_LEN) { + gaim_debug(GAIM_DEBUG_ERROR, "QQ", + "Fail parsing login redirect packet, expect %d bytes, read %d bytes\n", + QQ_LOGIN_REPLY_REDIRECT_PACKET_LEN, bytes); + ret = QQ_LOGIN_REPLY_MISC_ERROR; + } else { // start new connection + new_server_str = gen_ip_str(lrrp.new_server_ip); + gaim_debug(GAIM_DEBUG_WARNING, "QQ", + "Redirected to new server: %s:%d\n", new_server_str, lrrp.new_server_port); + qq_connect(gc->account, new_server_str, lrrp.new_server_port, qd->use_tcp, TRUE); + g_free(new_server_str); + ret = QQ_LOGIN_REPLY_REDIRECT; + } // if bytes != QQ_LOGIN_REPLY_MISC_ERROR + + return ret; +} // _qq_process_login_redirect + +/*****************************************************************************/ +// process login reply which says wrong password +static gint _qq_process_login_wrong_pwd(GaimConnection * gc, guint8 * data, gint len) +{ + gchar *server_reply, *server_reply_utf8; + server_reply = g_new0(gchar, len); + g_memmove(server_reply, data + 1, len - 1); + server_reply_utf8 = qq_to_utf8(server_reply, QQ_CHARSET_DEFAULT); + gaim_debug(GAIM_DEBUG_ERROR, "QQ", "Wrong password, server msg in UTF8: %s\n", server_reply_utf8); + g_free(server_reply); + g_free(server_reply_utf8); + + return QQ_LOGIN_REPLY_PWD_ERROR; +} // _qq_process_login_wrong_pwd + + +// request before login, new protocal, by gfhuang +void qq_send_packet_request_login_token(GaimConnection *gc) +{ + qq_data *qd; + guint8 *buf, *cursor; + guint16 seq_ret; + gint bytes; + + g_return_if_fail(gc != NULL && gc->proto_data != NULL); + + qd = (qq_data *) gc->proto_data; + buf = g_newa(guint8, MAX_PACKET_SIZE); + + cursor = buf; + bytes = 0; + bytes += _create_packet_head_seq(buf, &cursor, gc, QQ_CMD_REQUEST_LOGIN_TOKEN, TRUE, &seq_ret); + bytes += create_packet_dw(buf, &cursor, qd->uid); + bytes += create_packet_b(buf, &cursor, 0); + bytes += create_packet_b(buf, &cursor, QQ_PACKET_TAIL); + + if (bytes == (cursor - buf)) // packet creation OK + _qq_send_packet(gc, buf, bytes, QQ_CMD_REQUEST_LOGIN_TOKEN); + else + gaim_debug(GAIM_DEBUG_ERROR, "QQ", "Fail create request login token packet\n"); +} + +/*****************************************************************************/ +// send login packet to QQ server +void qq_send_packet_login(GaimConnection * gc, guint8 token_length, guint8 *token) +{ + qq_data *qd; + guint8 *buf, *cursor, *raw_data, *encrypted_data; + guint16 seq_ret; + gint encrypted_len, bytes; + gint pos; + + g_return_if_fail(gc != NULL && gc->proto_data != NULL); + + qd = (qq_data *) gc->proto_data; + buf = g_newa(guint8, MAX_PACKET_SIZE); + raw_data = g_newa(guint8, QQ_LOGIN_DATA_LENGTH); + encrypted_data = g_newa(guint8, QQ_LOGIN_DATA_LENGTH + 16); // 16 bytes more + qd->inikey = _gen_login_key(); + + // now generate the encrypted data + // 000-015 use pwkey as key to encrypt empty string + qq_crypt(ENCRYPT, "", 0, qd->pwkey, raw_data, &encrypted_len); + // 016-016 + raw_data[16] = 0x00; + // 017-020, used to be IP, now zero + *((guint32 *) (raw_data + 17)) = 0x00000000; + // 021-022, used to be port, now zero + *((guint16 *) (raw_data + 21)) = 0x0000; + // 023-051, fixed value, unknown + g_memmove(raw_data + 23, login_23_51, 29); + // 052-052, login mode + raw_data[52] = qd->login_mode; + // 053-068, fixed value, maybe related to per machine + g_memmove(raw_data + 53, login_53_68, 16); + + // 069 , login token length, by gfhuang + raw_data[69] = token_length; + pos = 70; + // 070-093, login token //normally 24 bytes + g_memmove(raw_data + pos, token, token_length); + pos += token_length; + // 100 bytes unknown + g_memmove(raw_data + pos, login_100_bytes, 100); + pos += 100; + // all zero left + memset(raw_data+pos, 0, QQ_LOGIN_DATA_LENGTH - pos); + + qq_crypt(ENCRYPT, raw_data, QQ_LOGIN_DATA_LENGTH, qd->inikey, encrypted_data, &encrypted_len); + + cursor = buf; + bytes = 0; + bytes += _create_packet_head_seq(buf, &cursor, gc, QQ_CMD_LOGIN, TRUE, &seq_ret); + bytes += create_packet_dw(buf, &cursor, qd->uid); + bytes += create_packet_data(buf, &cursor, qd->inikey, QQ_KEY_LENGTH); + bytes += create_packet_data(buf, &cursor, encrypted_data, encrypted_len); + bytes += create_packet_b(buf, &cursor, QQ_PACKET_TAIL); + + if (bytes == (cursor - buf)) // packet creation OK + _qq_send_packet(gc, buf, bytes, QQ_CMD_LOGIN); + else + gaim_debug(GAIM_DEBUG_ERROR, "QQ", "Fail create login packet\n"); +} // qq_send_packet_login + + +//added by gfhuang +void qq_process_request_login_token_reply(guint8 * buf, gint buf_len, GaimConnection * gc) +{ + qq_data *qd; + + g_return_if_fail(gc != NULL && gc->proto_data != NULL); + g_return_if_fail(buf != NULL && buf_len != 0); + + qd = (qq_data *) gc->proto_data; + + if (buf[0] == QQ_REQUEST_LOGIN_TOKEN_REPLY_OK) { + gaim_debug(GAIM_DEBUG_INFO, "QQ", + "<<< got a token with %d bytes -> [default] decrypt and dump\n%s",buf[1], hex_dump_to_str(buf+2, buf[1])); + qq_send_packet_login(gc, buf[1], buf + 2); + } else { + gaim_debug(GAIM_DEBUG_ERROR, "QQ", "Unknown request login token reply code : %d\n", buf[0]); + gaim_debug(GAIM_DEBUG_WARNING, "QQ", + ">>> %d bytes -> [default] decrypt and dump\n%s", + buf_len, hex_dump_to_str(buf, buf_len)); + try_dump_as_gbk(buf, buf_len); + gaim_connection_error(gc, _("Request login token error!")); + } +} + +/*****************************************************************************/ +// send logout packets to QQ server +void qq_send_packet_logout(GaimConnection * gc) +{ + gint i; + qq_data *qd; + + g_return_if_fail(gc != NULL && gc->proto_data != NULL); + + qd = (qq_data *) gc->proto_data; + for (i = 0; i < 4; i++) + qq_send_cmd(gc, QQ_CMD_LOGOUT, FALSE, 0xffff, FALSE, qd->pwkey, QQ_KEY_LENGTH); + + qd->logged_in = FALSE; // update login status AFTER sending logout packets +} // qq_send_packet_logout + +/*****************************************************************************/ +// process the login reply packet +void qq_process_login_reply(guint8 * buf, gint buf_len, GaimConnection * gc) +{ + gint len, ret, bytes; + guint8 *data; + qq_data *qd; + + g_return_if_fail(gc != NULL && gc->proto_data != NULL); + g_return_if_fail(buf != NULL && buf_len != 0); + + qd = (qq_data *) gc->proto_data; + len = buf_len; + data = g_newa(guint8, len); // no need to be freed in the future + + if (qq_crypt(DECRYPT, buf, buf_len, qd->pwkey, data, &len)) { + // should be able to decrypt with pwkey + gaim_debug(GAIM_DEBUG_INFO, "QQ", "Decrypt login reply packet with pwkey, %d bytes\n", len); + if (data[0] == QQ_LOGIN_REPLY_OK) { + ret = _qq_process_login_ok(gc, data, len); + } else { + gaim_debug(GAIM_DEBUG_ERROR, "QQ", "Unknown login reply code : %d\n", data[0]); + ret = QQ_LOGIN_REPLY_MISC_ERROR; + } // if QQ_LOGIN_REPLY_OK + } else { // decrypt with pwkey error + len = buf_len; // reset len, decrypt will fail if len is too short + if (qq_crypt(DECRYPT, buf, buf_len, qd->inikey, data, &len)) { + // decrypt ok with inipwd, it might be password error + gaim_debug(GAIM_DEBUG_WARNING, "QQ", "Decrypt login reply packet with inikey, %d bytes\n", len); + bytes = 0; + switch (data[0]) { + case QQ_LOGIN_REPLY_REDIRECT: + ret = _qq_process_login_redirect(gc, data, len); + break; + case QQ_LOGIN_REPLY_PWD_ERROR: + ret = _qq_process_login_wrong_pwd(gc, data, len); + break; + default: + gaim_debug(GAIM_DEBUG_ERROR, "QQ", "Unknown reply code: %d\n", data[0]); + // dump by gfhuang + gaim_debug(GAIM_DEBUG_WARNING, "QQ", + ">>> %d bytes -> [default] decrypt and dump\n%s", + buf_len, hex_dump_to_str(data, len)); + try_dump_as_gbk(data, len); + + ret = QQ_LOGIN_REPLY_MISC_ERROR; + } // switch data[0] + } else { // no idea how to decrypt + gaim_debug(GAIM_DEBUG_ERROR, "QQ", "No idea how to decrypt login reply\n"); + ret = QQ_LOGIN_REPLY_MISC_ERROR; + } // if qq_crypt with qd->inikey + } // if qq_crypt with qd->pwkey + + switch (ret) { + case QQ_LOGIN_REPLY_PWD_ERROR: + gc->wants_to_die = TRUE; + gaim_connection_error(gc, _("Wrong password!")); + break; + case QQ_LOGIN_REPLY_MISC_ERROR: + gaim_connection_error(gc, _("Unable to login, check debug log")); + break; + case QQ_LOGIN_REPLY_OK: + gaim_debug(GAIM_DEBUG_INFO, "QQ", "Login replys OK, everything is fine\n"); + break; + case QQ_LOGIN_REPLY_REDIRECT: + // the redirect has been done in _qq_process_login_reply + break; + default:{; + } + } // switch ret +} // qq_process_login_reply + +/*****************************************************************************/ +// END OF FILE diff -r 5642f4658b59 -r 983fd420e86b src/protocols/qq/login_logout.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/protocols/qq/login_logout.h Mon Jun 26 02:58:54 2006 +0000 @@ -0,0 +1,44 @@ +/** + * The QQ2003C protocol plugin + * + * for gaim + * + * Copyright (C) 2004 Puzzlebird + * + * 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 + */ + +// START OF FILE +/*****************************************************************************/ +#ifndef _QQ_LOGIN_LOGOUT_H_ +#define _QQ_LOGIN_LOGOUT_H_ + +#include +#include "connection.h" // GaimConnection + +#define QQ_LOGIN_MODE_NORMAL 0x0a +#define QQ_LOGIN_MODE_HIDDEN 0x28 + +//void qq_send_packet_login(GaimConnection * gc); //for internal usage, by gfhuang +void qq_send_packet_request_login_token(GaimConnection *gc); //by gfhuang + +void qq_send_packet_logout(GaimConnection * gc); + +void qq_process_login_reply(guint8 * buf, gint buf_len, GaimConnection * gc); +void qq_process_request_login_token_reply(guint8 *buf, gint buf_len, GaimConnection *gc); // by gfhuang + +#endif +/*****************************************************************************/ +// END OF FILE diff -r 5642f4658b59 -r 983fd420e86b src/protocols/qq/packet_parse.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/protocols/qq/packet_parse.c Mon Jun 26 02:58:54 2006 +0000 @@ -0,0 +1,138 @@ +/** + * The QQ2003C protocol plugin + * + * for gaim + * + * Copyright (C) 2004 Puzzlebird + * + * 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 + */ + +// START OF FILE +/*****************************************************************************/ +#ifndef _WIN32 +#include +#else +#include "win32dep.h" +#endif + +#include + +#include "packet_parse.h" + +/*****************************************************************************/ +// read one byte from buf, +// return the number of bytes read if succeeds, otherwise return -1 +gint read_packet_b(guint8 * buf, guint8 ** cursor, gint buflen, guint8 * b) +{ + if (*cursor <= buf + buflen - sizeof(*b)) { + *b = **(guint8 **) cursor; + *cursor += sizeof(*b); + return sizeof(*b); + } else + return -1; +} // read_packet_b + +/*****************************************************************************/ +// read two bytes as "guint16" from buf, +// return the number of bytes read if succeeds, otherwise return -1 +gint read_packet_w(guint8 * buf, guint8 ** cursor, gint buflen, guint16 * w) +{ + if (*cursor <= buf + buflen - sizeof(*w)) { + *w = ntohs(**(guint16 **) cursor); + *cursor += sizeof(*w); + return sizeof(*w); + } else + return -1; +} // read_packet_w + +/*****************************************************************************/ +// read four bytes as "guint32" from buf, +// return the number of bytes read if succeeds, otherwise return -1 +gint read_packet_dw(guint8 * buf, guint8 ** cursor, gint buflen, guint32 * dw) +{ + if (*cursor <= buf + buflen - sizeof(*dw)) { + *dw = ntohl(**(guint32 **) cursor); + *cursor += sizeof(*dw); + return sizeof(*dw); + } else + return -1; +} // read_packet_dw + +/*****************************************************************************/ +// read datalen bytes from buf, +// return the number of bytes read if succeeds, otherwise return -1 +gint read_packet_data(guint8 * buf, guint8 ** cursor, gint buflen, guint8 * data, gint datalen) { + if (*cursor <= buf + buflen - datalen) { + g_memmove(data, *cursor, datalen); + *cursor += datalen; + return datalen; + } else + return -1; +} // read_packet_data + +/*****************************************************************************/ +// pack one byte into buf +// return the number of bytes packed, otherwise return -1 +gint create_packet_b(guint8 * buf, guint8 ** cursor, guint8 b) +{ + if (*cursor <= buf + MAX_PACKET_SIZE - sizeof(guint8)) { + **(guint8 **) cursor = b; + *cursor += sizeof(guint8); + return sizeof(guint8); + } else + return -1; +} // create_packet_b + +/*****************************************************************************/ +// pack two bytes as "guint16" into buf +// return the number of bytes packed, otherwise return -1 +gint create_packet_w(guint8 * buf, guint8 ** cursor, guint16 w) +{ + if (*cursor <= buf + MAX_PACKET_SIZE - sizeof(guint16)) { + **(guint16 **) cursor = htons(w); + *cursor += sizeof(guint16); + return sizeof(guint16); + } else + return -1; +} // create_packet_w + +/*****************************************************************************/ +// pack four bytes as "guint32" into buf +// return the number of bytes packed, otherwise return -1 +gint create_packet_dw(guint8 * buf, guint8 ** cursor, guint32 dw) +{ + if (*cursor <= buf + MAX_PACKET_SIZE - sizeof(guint32)) { + **(guint32 **) cursor = htonl(dw); + *cursor += sizeof(guint32); + return sizeof(guint32); + } else + return -1; +} // create_packet_dw + +/*****************************************************************************/ +// pack datalen bytes into buf +// return the number of bytes packed, otherwise return -1 +gint create_packet_data(guint8 * buf, guint8 ** cursor, guint8 * data, gint datalen) { + if (*cursor <= buf + MAX_PACKET_SIZE - datalen) { + g_memmove(*cursor, data, datalen); + *cursor += datalen; + return datalen; + } else + return -1; +} // create_packet_data + +/*****************************************************************************/ +// END OF FILE diff -r 5642f4658b59 -r 983fd420e86b src/protocols/qq/packet_parse.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/protocols/qq/packet_parse.h Mon Jun 26 02:58:54 2006 +0000 @@ -0,0 +1,50 @@ +/** + * The QQ2003C protocol plugin + * + * for gaim + * + * Copyright (C) 2004 Puzzlebird + * + * 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 + */ + +// START OF FILE +/*****************************************************************************/ +#ifndef _QQ_PACKET_PARSE_H_ +#define _QQ_PACKED_PARSE_H_ + +#include + +/* According to "UNIX Network Programming", all TCP/IP implementations + * must support a minimum IP datagram size of 576 bytes, regardless of the MTU. + * Assuming a 20 byte IP header and 8 byte UDP header, this leaves 548 bytes + * as a safe maximum size for UDP messages. + * + * TCP allows packet 64K + */ +#define MAX_PACKET_SIZE 65535 + +gint read_packet_b(guint8 * buf, guint8 ** cursor, gint buflen, guint8 * b); +gint read_packet_w(guint8 * buf, guint8 ** cursor, gint buflen, guint16 * w); +gint read_packet_dw(guint8 * buf, guint8 ** cursor, gint buflen, guint32 * dw); +gint read_packet_data(guint8 * buf, guint8 ** cursor, gint buflen, guint8 * data, gint datalen); +gint create_packet_b(guint8 * buf, guint8 ** cursor, guint8 b); +gint create_packet_w(guint8 * buf, guint8 ** cursor, guint16 w); +gint create_packet_dw(guint8 * buf, guint8 ** cursor, guint32 dw); +gint create_packet_data(guint8 * buf, guint8 ** cursor, guint8 * data, gint datalen); + +#endif +/*****************************************************************************/ +// END OF FILE diff -r 5642f4658b59 -r 983fd420e86b src/protocols/qq/qq.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/protocols/qq/qq.c Mon Jun 26 02:58:54 2006 +0000 @@ -0,0 +1,1195 @@ +/** + * @file qq.c The QQ2003C protocol plugin + * + * gaim + * + * Copyright (C) 2004 Puzzlebird + * + * 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 "internal.h" + +#ifdef _WIN32 +#define random rand +#endif + +#include "debug.h" +#include "notify.h" +#include "prefs.h" +#include "request.h" +#include "accountopt.h" +#include "prpl.h" +#include "gtkroomlist.h" +#include "gtklog.h" +#include "server.h" +#include "util.h" /* GaimMenuAction, gaim_menu_action_new, gaim2beta2, gfhuang*/ + +#include "utils.h" +#include "buddy_info.h" +#include "buddy_opt.h" +#include "buddy_status.h" +#include "char_conv.h" +#include "group_find.h" /* qq_group_find_member_by_channel_and_nickname */ +#include "group_im.h" /* qq_send_packet_group_im */ +#include "group_info.h" /* qq_send_cmd_group_get_group_info */ +#include "group_join.h" /* qq_group_join */ +#include "group_opt.h" /* qq_group_manage_members */ +#include "group.h" /* chat_info, etc */ +#include "header_info.h" /* qq_get_cmd_desc */ +#include "im.h" +#include "infodlg.h" +#include "keep_alive.h" +#include "ip_location.h" /* qq_ip_get_location */ +#include "login_logout.h" +#include "qq_proxy.h" /* qq_connect, qq_disconnect */ +#include "send_core.h" +#include "qq.h" +#include "send_file.h" +#include "version.h" + +#define OPENQ_AUTHOR "Puzzlebird" +#define OPENQ_WEBSITE "http://openq.sourceforge.net" +#define QQ_TCP_QUERY_PORT "8000" +#define QQ_UDP_PORT "8000" + +const gchar *udp_server_list[] = { + "sz.tencent.com", // 61.144.238.145 + "sz2.tencent.com", // 61.144.238.146 + "sz3.tencent.com", // 202.104.129.251 + "sz4.tencent.com", // 202.104.129.254 + "sz5.tencent.com", // 61.141.194.203 + "sz6.tencent.com", // 202.104.129.252 + "sz7.tencent.com", // 202.104.129.253 + "202.96.170.64", + "64.144.238.155", + "202.104.129.254" +}; +const gint udp_server_amount = (sizeof(udp_server_list) / sizeof(udp_server_list[0])); + + +const gchar *tcp_server_list[] = { + "tcpconn.tencent.com", // 218.17.209.23 + "tcpconn2.tencent.com", // 218.18.95.153 + "tcpconn3.tencent.com", // 218.17.209.23 + "tcpconn4.tencent.com", // 218.18.95.153 +}; +const gint tcp_server_amount = (sizeof(tcp_server_list) / sizeof(tcp_server_list[0])); + +/*********** Prototypes ***********/ + +static void _qq_login(GaimAccount * account); +static void _qq_close(GaimConnection * gc); +static const gchar *_qq_list_icon(GaimAccount * a, GaimBuddy * b); +static gchar *_qq_status_text(GaimBuddy *b); +static void _qq_tooltip_text(GaimBuddy *b, GString *tooltip, gboolean full); +static void _qq_list_emblems(GaimBuddy * b, const char **se, const char **sw, const char **nw, const char **ne); +static GList *_qq_away_states(GaimAccount *ga); +static void _qq_set_away(GaimAccount *account, GaimStatus *status); +static gint _qq_send_im(GaimConnection * gc, const gchar * who, const gchar * message, GaimMessageFlags flags); +static int _qq_chat_send(GaimConnection *gc, int channel, const char *message, GaimMessageFlags flags); +static void _qq_get_info(GaimConnection * gc, const gchar * who); +static void _qq_menu_get_my_info(GaimPluginAction * action); +//static void _qq_menu_block_buddy(GaimBlistNode * node); +static void _qq_menu_show_login_info(GaimPluginAction * action); +static void _qq_menu_show_about(GaimPluginAction * action); +static void _qq_menu_any_cmd_send_cb(GaimConnection * gc, GaimRequestFields * fields); +static void _qq_menu_any_cmd(GaimPluginAction * action); +static void _qq_menu_locate_ip_cb(GaimConnection * gc, GaimRequestFields * fields); +static void _qq_menu_locate_ip(GaimPluginAction *action); +static void _qq_menu_search_or_add_permanent_group(GaimPluginAction * action); +static void _qq_menu_create_permanent_group(GaimPluginAction * action); +static void _qq_menu_unsubscribe_group(GaimBlistNode * node); +static void _qq_menu_manage_group(GaimBlistNode * node); +static void _qq_menu_show_system_message(GaimPluginAction *action); +//static void _qq_menu_send_file(GaimBlistNode * node, gpointer ignored); +static GList *_qq_actions(GaimPlugin * plugin, gpointer context); +static GList *_qq_chat_menu(GaimBlistNode *node); +static GList *_qq_buddy_menu(GaimBlistNode * node); +static void _qq_keep_alive(GaimConnection * gc); +static void _qq_get_chat_buddy_info(GaimConnection * gc, gint channel, const gchar * who); +static gchar *_qq_get_chat_buddy_real_name(GaimConnection * gc, gint channel, const gchar * who); +//static GaimPluginPrefFrame *get_plugin_pref_frame(GaimPlugin * plugin); +static void init_plugin(GaimPlugin * plugin); + +static void _qq_login(GaimAccount * account) +{ + const gchar *qq_server, *qq_port; + qq_data *qd; + GaimConnection *gc; + GaimPresence *presence; //gfhuang + gboolean login_hidden, use_tcp; + + g_return_if_fail(account != NULL); + + gc = gaim_account_get_connection(account); + g_return_if_fail(gc != NULL); + + gc->flags |= GAIM_CONNECTION_HTML | GAIM_CONNECTION_NO_BGCOLOR | GAIM_CONNECTION_AUTO_RESP; + + qd = g_new0(qq_data, 1); + gc->proto_data = qd; + + qq_server = gaim_account_get_string(account, "server", NULL); + qq_port = gaim_account_get_string(account, "port", NULL); + use_tcp = gaim_account_get_bool(account, "use_tcp", FALSE); +// login_hidden = gaim_account_get_bool(account, "hidden", FALSE); gfhuang + presence = gaim_account_get_presence(account); + login_hidden = gaim_presence_is_status_primitive_active(presence, GAIM_STATUS_INVISIBLE); + + qd->use_tcp = use_tcp; + + if (login_hidden) { + qd->login_mode = QQ_LOGIN_MODE_HIDDEN; + gaim_debug(GAIM_DEBUG_INFO, "QQ", "Login in hidden mode\n"); + } + else { + qd->login_mode = QQ_LOGIN_MODE_NORMAL; + gaim_debug(GAIM_DEBUG_INFO, "QQ", "Login in normal mode\n"); + } + + if (qq_server == NULL || strlen(qq_server) == 0) + qq_server = use_tcp ? + tcp_server_list[random() % tcp_server_amount] : udp_server_list[random() % udp_server_amount]; + + if (qq_port == NULL || strtol(qq_port, NULL, 10) == 0) + qq_port = use_tcp ? QQ_TCP_QUERY_PORT : QQ_UDP_PORT; + + gaim_connection_update_progress(gc, _("Connecting"), 0, QQ_CONNECT_STEPS); + + if (qq_connect(account, qq_server, strtol(qq_port, NULL, 10), use_tcp, FALSE) < 0) + gaim_connection_error(gc, _("Unable to connect.")); +} + +/* directly goes for qq_disconnect */ +static void _qq_close(GaimConnection * gc) +{ + g_return_if_fail(gc != NULL); + qq_disconnect(gc); +} + +/* returns the icon name for a buddy or protocol */ +static const gchar *_qq_list_icon(GaimAccount * a, GaimBuddy * b) +{ + gchar *filename; + qq_buddy *q_bud; + gchar icon_suffix; + + /* do not use g_return_val_if_fail, as it is not assertion */ + if (b == NULL || b->proto_data == NULL) + return "qq"; + + q_bud = (qq_buddy *) b->proto_data; + icon_suffix = get_suffix_from_status(q_bud->status); + filename = get_icon_name(q_bud->icon / 3 + 1, icon_suffix); + + return filename; +} + + +/* a short status text beside buddy icon*/ +static gchar *_qq_status_text(GaimBuddy *b) +{ + qq_buddy *q_bud; +// gboolean show_info; + GString *status; + gchar *ret; + +// show_info = gaim_prefs_get_bool("/plugins/prpl/qq/show_status_by_icon"); +// if (!show_info) +// return NULL; + + q_bud = (qq_buddy *) b->proto_data; + if (q_bud == NULL) + return NULL; + + status = g_string_new(""); + + //by gfhuang + switch(q_bud->status) { + case QQ_BUDDY_OFFLINE: + g_string_append(status, "My Offline"); + break; + case QQ_BUDDY_ONLINE_NORMAL: + return NULL; + break; + case QQ_BUDDY_ONLINE_OFFLINE: + g_string_append(status, "Online Offline"); + break; + case QQ_BUDDY_ONLINE_AWAY: + g_string_append(status, "Away"); + break; + case QQ_BUDDY_ONLINE_INVISIBLE: + g_string_append(status, "Invisible"); + break; + default: + g_string_printf(status, "Unknown-%d", q_bud->status); + } + /* + switch (q_bud->gender) { + case QQ_BUDDY_GENDER_GG: + g_string_append(status, " GG"); + break; + case QQ_BUDDY_GENDER_MM: + g_string_append(status, " MM"); + break; + case QQ_BUDDY_GENDER_UNKNOWN: + g_string_append(status, "^_*"); + break; + default: + g_string_append(status, "^_^"); + } + + g_string_append_printf(status, " Age: %d", q_bud->age); + g_string_append_printf(status, " Client: %04x", q_bud->client_version); + */ +// having_video = q_bud->comm_flag & QQ_COMM_FLAG_VIDEO; +// if (having_video) +// g_string_append(status, " (video)"); + + ret = status->str; + g_string_free(status, FALSE); + + return ret; +} + + +/* a floating text when mouse is on the icon, show connection status here */ +static void _qq_tooltip_text(GaimBuddy *b, GString *tooltip, gboolean full) +{ + qq_buddy *q_bud; + gchar *country, *country_utf8, *city, *city_utf8; + guint32 ip_value; + gchar *ip_str; + + g_return_if_fail(b != NULL); + + q_bud = (qq_buddy *) b->proto_data; + g_return_if_fail(q_bud != NULL); + +// if (is_online(q_bud->status)) //disable by gfhuang + { + ip_value = ntohl(*(guint32 *) (q_bud->ip)); +// tooltip = g_string_new(""); beta2, gfhuang + if (qq_ip_get_location(ip_value, &country, &city)) { + country_utf8 = qq_to_utf8(country, QQ_CHARSET_DEFAULT); + city_utf8 = qq_to_utf8(city, QQ_CHARSET_DEFAULT); + g_string_append_printf(tooltip, "\n%s, %s", country_utf8, city_utf8); + g_free(country); + g_free(city); + g_free(country_utf8); + g_free(city_utf8); + } + //memory leak fixed by gfhuang + ip_str = gen_ip_str(q_bud->ip); + g_string_append_printf(tooltip, "\n%s Address: %s:%d", (q_bud->comm_flag & QQ_COMM_FLAG_TCP_MODE) + ? "TCP" : "UDP", ip_str, q_bud->port); + g_free(ip_str); + //added by gfhuang + g_string_append_printf(tooltip, "\nAge: %d", q_bud->age); + switch (q_bud->gender) { + case QQ_BUDDY_GENDER_GG: + g_string_append(tooltip, "\nGender: GG"); + break; + case QQ_BUDDY_GENDER_MM: + g_string_append(tooltip, "\nGender: MM"); + break; + case QQ_BUDDY_GENDER_UNKNOWN: + g_string_append(tooltip, "\nGender: UNKNOWN"); + break; + default: + g_string_append_printf(tooltip, "\nGender: ERROR(%d)", q_bud->gender); + } /* switch gender */ + g_string_append_printf(tooltip, "\nFlag: %01x", q_bud->flag1); + g_string_append_printf(tooltip, "\nCommFlag: %01x", q_bud->comm_flag); + g_string_append_printf(tooltip, "\nClient: %04x", q_bud->client_version); + } +} + +/* we can show tiny icons on the four corners of buddy icon, */ +static void _qq_list_emblems(GaimBuddy * b, const char **se, const char **sw, const char **nw, const char **ne) { //add const by gfhuang + // each char ** are refering to filename in pixmaps/gaim/status/default/*png + + qq_buddy *q_bud = b->proto_data; +// GaimPresence *presence; + const char *emblems[4] = { NULL, NULL, NULL, NULL }; + int i = 0; + +/* presence = gaim_buddy_get_presence(b); + + if (!gaim_presence_is_online(presence)) + emblems[i++] = "offline"; + else if (gaim_presence_is_status_active(presence, "busy") || + gaim_presence_is_status_active(presence, "phone")) + emblems[i++] = "occupied"; + else if (!gaim_presence_is_available(presence)) + emblems[i++] = "away"; +*/ + + if (q_bud == NULL) + { + emblems[0] = "offline"; + } + else + { + if (q_bud->comm_flag & QQ_COMM_FLAG_QQ_MEMBER) + emblems[i++] = "qq_member"; + if (q_bud->comm_flag & QQ_COMM_FLAG_BIND_MOBILE) + emblems[i++] = "wireless"; + if (q_bud->comm_flag & QQ_COMM_FLAG_VIDEO) + emblems[i++] = "video"; + + +// if (!(user->list_op & (1 << MSN_LIST_RL))) +// emblems[i++] = "nr"; + } + + *se = emblems[0]; + *sw = emblems[1]; + *nw = emblems[2]; + *ne = emblems[3]; + + return; +} + +/* QQ away status (used to initiate QQ away packet) */ +//Rewritten by gfhuang +static GList *_qq_away_states(GaimAccount *ga) +{ + GaimStatusType *status; + GList *types = NULL; + + status = gaim_status_type_new_full(GAIM_STATUS_AVAILABLE, + "available", _("QQ: Available"), FALSE, TRUE, FALSE); + types = g_list_append(types, status); + + status = gaim_status_type_new_full(GAIM_STATUS_AWAY, + "away", _("QQ: Away"), FALSE, TRUE, FALSE); + types = g_list_append(types, status); + + status = gaim_status_type_new_full(GAIM_STATUS_INVISIBLE, /* HIDDEN, change to gaim2beta2, gfhuang */ + "invisible", _("QQ: Invisible"), FALSE, TRUE, FALSE); + types = g_list_append(types, status); + + status = gaim_status_type_new_full(GAIM_STATUS_OFFLINE, + "offline", _("QQ: Offline"), FALSE, TRUE, FALSE); + types = g_list_append(types, status); + + return types; +} +/* +GList *_qq_away_states(GaimConnection * gc) +{ + GList *m; + + g_return_val_if_fail(gc != NULL, NULL); + + m = NULL; + m = g_list_append(m, _("QQ: Available")); + m = g_list_append(m, _("QQ: Away")); + m = g_list_append(m, _("QQ: Invisible")); + m = g_list_append(m, GAIM_AWAY_CUSTOM); + return m; +} +*/ + + + +/* initiate QQ away with proper change_status packet */ +//void _qq_set_away(GaimConnection * gc, const char *state, const char *msg) +static void _qq_set_away(GaimAccount *account, GaimStatus *status) +{ + // by gfhuang + GaimConnection *gc = gaim_account_get_connection(account); + const char *state = gaim_status_get_id(status); + + qq_data *qd; + + + g_return_if_fail(gc != NULL && gc->proto_data != NULL); + + qd = (qq_data *) gc->proto_data; + + // by gfhuang + if(0 == strcmp(state, "available")) + qd->status = QQ_SELF_STATUS_AVAILABLE; + else if (0 == strcmp(state, "away")) + qd->status = QQ_SELF_STATUS_AWAY; + else if (0 == strcmp(state, "invisible")) + qd->status = QQ_SELF_STATUS_INVISIBLE; + else + qd->status = QQ_SELF_STATUS_AVAILABLE; + +/* if (gc->away) { //disable by gfhuang, 1.2006 + g_free(gc->away); + gc->away = NULL; + } + + if (msg) { + qd->status = QQ_SELF_STATUS_CUSTOM; + gc->away = g_strdup(msg); + } else if (state) { + gc->away = g_strdup(""); + if (g_ascii_strcasecmp(state, _("QQ: Available")) == 0) + qd->status = QQ_SELF_STATUS_AVAILABLE; + else if (g_ascii_strcasecmp(state, _("QQ: Away")) == 0) + qd->status = QQ_SELF_STATUS_AWAY; + else if (g_ascii_strcasecmp(state, _("QQ: Invisible")) == 0) + qd->status = QQ_SELF_STATUS_INVISIBLE; + else if (g_ascii_strcasecmp(state, GAIM_AWAY_CUSTOM) == 0) { + if (gc->is_idle) + qd->status = QQ_SELF_STATUS_IDLE; + else + qd->status = QQ_SELF_STATUS_AVAILABLE; + } + } else if (gc->is_idle) + qd->status = QQ_SELF_STATUS_IDLE; + else + qd->status = QQ_SELF_STATUS_AVAILABLE; +*/ + qq_send_packet_change_status(gc); +} + + +// IMPORTANT: GaimConvImFlags -> GaimMessageFlags //gfhuang +/* send an instance msg to a buddy */ +static gint _qq_send_im(GaimConnection * gc, const gchar * who, const gchar * message, GaimMessageFlags flags) +{ + gint type, to_uid; + gchar *msg, *msg_with_qq_smiley; + qq_data *qd; + + g_return_val_if_fail(gc != NULL && gc->proto_data != NULL && who != NULL, -1); + + qd = (qq_data *) gc->proto_data; + + g_return_val_if_fail(strlen(message) <= QQ_MSG_IM_MAX, -E2BIG); + + type = (flags == GAIM_MESSAGE_AUTO_RESP ? QQ_IM_AUTO_REPLY : QQ_IM_TEXT); + to_uid = gaim_name_to_uid(who); + + /* if msg is to myself, bypass the network */ + if (to_uid == qd->uid) + serv_got_im(gc, who, message, flags, time(NULL)); + else { + msg = utf8_to_qq(message, QQ_CHARSET_DEFAULT); + msg_with_qq_smiley = gaim_smiley_to_qq(msg); + qq_send_packet_im(gc, to_uid, msg_with_qq_smiley, type); + g_free(msg); + g_free(msg_with_qq_smiley); + } + + return 1; +} + +/* send a chat msg to a QQ Qun */ +static int _qq_chat_send(GaimConnection *gc, int channel, const char *message, GaimMessageFlags flags/*gfhuang*/) +{ + gchar *msg, *msg_with_qq_smiley; + qq_group *group; + + g_return_val_if_fail(gc != NULL && message != NULL, -1); + g_return_val_if_fail(strlen(message) <= QQ_MSG_IM_MAX, -E2BIG); + + group = qq_group_find_by_channel(gc, channel); + g_return_val_if_fail(group != NULL, -1); + + msg = utf8_to_qq(message, QQ_CHARSET_DEFAULT); + msg_with_qq_smiley = gaim_smiley_to_qq(msg); + qq_send_packet_group_im(gc, group, msg_with_qq_smiley); + g_free(msg); + g_free(msg_with_qq_smiley); + + return 1; +} + +/* send packet to get who's detailed information */ +static void _qq_get_info(GaimConnection * gc, const gchar * who) +{ + guint32 uid; + qq_data *qd; + + g_return_if_fail(gc != NULL && gc->proto_data != NULL); + qd = gc->proto_data; + uid = gaim_name_to_uid(who); + + if (uid <= 0) { + gaim_debug(GAIM_DEBUG_ERROR, "QQ", "Not valid QQid: %s\n", who); + gaim_notify_error(gc, NULL, _("Invalid name, please input in qq-xxxxxxxx format"), NULL); + return; + } + + qq_send_packet_get_info(gc, uid, TRUE); /* need to show up info window */ +} + +/* get my own information */ +static void _qq_menu_get_my_info(GaimPluginAction * action) +{ + GaimConnection *gc = (GaimConnection *) action->context; + qq_data *qd; + + g_return_if_fail(gc != NULL && gc->proto_data != NULL); + + qd = (qq_data *) gc->proto_data; + _qq_get_info(gc, uid_to_gaim_name(qd->uid)); +} + +/* remove a buddy from my list and remove myself from his list */ +/* TODO: re-enable this +static void _qq_menu_block_buddy(GaimBlistNode * node) +{ + guint32 uid; + gc_and_uid *g; + GaimBuddy *buddy; + GaimConnection *gc; +// const gchar *who = param_who; gfhuang + const gchar *who; + + g_return_if_fail(GAIM_BLIST_NODE_IS_BUDDY(node)); + + buddy = (GaimBuddy *) node; + gc = gaim_account_get_connection(buddy->account); + who = buddy->name; + g_return_if_fail(gc != NULL && who != NULL); + + uid = gaim_name_to_uid(who); + g_return_if_fail(uid > 0); + + g = g_new0(gc_and_uid, 1); + g->gc = gc; + g->uid = uid; + + gaim_request_action(gc, _("Block Buddy"), + _("Are you sure to block this buddy?"), NULL, + 1, g, 2, + _("Cancel"), + G_CALLBACK(qq_do_nothing_with_gc_and_uid), + _("Block"), G_CALLBACK(qq_block_buddy_with_gc_and_uid)); +} +*/ + +/* show a brief summary of what we get from login packet */ +static void _qq_menu_show_login_info(GaimPluginAction * action) +{ + GaimConnection *gc = (GaimConnection *) action->context; + qq_data *qd; + GString *info; + + g_return_if_fail(gc != NULL && gc->proto_data != NULL); + + qd = (qq_data *) gc->proto_data; + info = g_string_new("\n"); + + g_string_append_printf(info, _("Current Online: %d
\n"), qd->all_online); + g_string_append_printf(info, _("Last Refresh: %s
\n"), ctime(&qd->last_get_online)); + + g_string_append(info, "
\n"); + + g_string_append_printf(info, _("Connection Mode: %s
\n"), qd->use_tcp ? "TCP" : "UDP"); + g_string_append_printf(info, _("Server IP: %s: %d
\n"), qd->server_ip, qd->server_port); + g_string_append_printf(info, _("My Public IP: %s
\n"), qd->my_ip); + + g_string_append(info, "
\n"); + g_string_append(info, "Information below may not be accurate
\n"); + + g_string_append_printf(info, _("Login Time: %s
\n"), ctime(&qd->login_time)); + g_string_append_printf(info, _("Last Login IP: %s
\n"), qd->last_login_ip); + g_string_append_printf(info, _("Last Login Time: %s\n"), ctime(&qd->last_login_time)); + + g_string_append(info, ""); + + gaim_notify_formatted(gc, NULL, _("Login Information"), NULL, info->str, NULL, NULL); + + g_string_free(info, TRUE); +} + +/* show about page about QQ plugin */ +static void _qq_menu_show_about(GaimPluginAction * action) +{ + GaimConnection *gc = (GaimConnection *) action->context; + qq_data *qd; + GString *info; + gchar *head; + + g_return_if_fail(gc != NULL && gc->proto_data != NULL); + + qd = (qq_data *) gc->proto_data; + info = g_string_new("\n"); + + g_string_append_printf(info, _("Author : %s
\n"), OPENQ_AUTHOR); + g_string_append(info, "Copyright (c) 2004. All rights reserved.

\n"); + + g_string_append(info, _("

Code Contributors
\n")); + g_string_append(info, "gfhuang : patches for gaim 2.0.0beta2
\n"); + g_string_append(info, "henryouly : file transfer, udp sock5 proxy and qq_show
\n"); + g_string_append(info, "arfankai : fixed bugs in char_conv.c
\n"); + g_string_append(info, "rakescar : provided filter for HTML tag
\n"); + g_string_append(info, "yyw : improved performance on PPC linux
\n"); + g_string_append(info, "lvxiang : provided ip to location original code

\n"); + + g_string_append(info, _("

Acknowledgement
\n")); + g_string_append(info, "Shufeng Tan : http://sf.net/projects/perl-oicq
\n"); + g_string_append(info, "Jeff Ye : http://www.sinomac.com
\n"); + g_string_append(info, "Hu Zheng : http://forlinux.yeah.net

\n"); + + g_string_append(info, "

And, my parents...\n"); + + g_string_append(info, ""); + + head = g_strdup_printf("About QQ Plugin Ver %s", VERSION); + gaim_notify_formatted(gc, NULL, head, NULL, info->str, NULL, NULL); + + g_free(head); + g_string_free(info, TRUE); +} + +/* callback of sending any command to QQ server */ +static void _qq_menu_any_cmd_send_cb(GaimConnection * gc, GaimRequestFields * fields) +{ + GList *groups, *flds; + GaimRequestField *field; + const gchar *id, *value; + gchar *cmd_str, *data_str, **segments; + guint16 cmd; + guint8 *data; + gint i, data_len; + + cmd_str = NULL; + data_str = NULL; + cmd = 0x00; + data = NULL; + data_len = 0; + + for (groups = gaim_request_fields_get_groups(fields); groups; groups = groups->next) { + for (flds = gaim_request_field_group_get_fields(groups->data); flds; flds = flds->next) { + field = flds->data; + id = gaim_request_field_get_id(field); + value = gaim_request_field_string_get_value(field); + + if (!g_ascii_strcasecmp(id, "cmd")) + cmd_str = g_strdup(value); + else if (!g_ascii_strcasecmp(id, "data")) + data_str = g_strdup(value); + } + } + + if (cmd_str != NULL) + cmd = (guint16) strtol(cmd_str, NULL, 16); + + if (data_str != NULL) { + if (NULL == (segments = split_data(data_str, strlen(data_str), ",", 0))) { + g_free(cmd_str); + g_free(data_str); + return; + } + for (data_len = 0; segments[data_len] != NULL; data_len++) {; + } + data = g_newa(guint8, data_len); + for (i = 0; i < data_len; i++) + data[i] = (guint8) strtol(segments[i], NULL, 16); + g_strfreev(segments); + } + + if (cmd && data_len > 0) { + gaim_debug(GAIM_DEBUG_INFO, "QQ", + "Send Any cmd: %s, data dump\n%s", qq_get_cmd_desc(cmd), hex_dump_to_str(data, data_len)); + qq_send_cmd(gc, cmd, TRUE, 0, TRUE, data, data_len); + } + + g_free(cmd_str); + g_free(data_str); +} + +/* send any command with data to QQ server, for testing and debuggin only */ +static void _qq_menu_any_cmd(GaimPluginAction * action) +{ + GaimConnection *gc = (GaimConnection *) action->context; + qq_data *qd; + const char *tips; + GaimRequestField *field; + GaimRequestFields *fields; + GaimRequestFieldGroup *group; + + g_return_if_fail(gc != NULL && gc->proto_data != NULL); + qd = (qq_data *) gc->proto_data; + + tips = _("Separate the value with \",\"\nAllow \"0x\" before each value"); + fields = gaim_request_fields_new(); + group = gaim_request_field_group_new(NULL); + gaim_request_fields_add_group(fields, group); + + /* sample: 0x22 */ + field = gaim_request_field_string_new("cmd", _("CMD Code"), NULL, FALSE); + gaim_request_field_group_add_field(group, field); + /* sample: 0x00,0x15,0xAB */ + /* or: 00,15,AB */ + /* the delimit is ",", allow 0x before the value */ + field = gaim_request_field_string_new("data", _("Raw Data"), NULL, FALSE); + gaim_request_field_group_add_field(group, field); + + gaim_request_fields(gc, _("QQ Any Command"), + _("Send Arbitrary Command"), tips, fields, + _("Send"), G_CALLBACK(_qq_menu_any_cmd_send_cb), _("Cancel"), NULL, gc); +} + +/* added by gfhuang */ +static void _qq_menu_locate_ip_cb(GaimConnection * gc, GaimRequestFields * fields) +{ + GList *groups, *flds; + GaimRequestField *field; + const gchar *id, *value; + gchar *ip_str = NULL, *ip_dupstr = NULL; + guint8 *ip; + gchar *country, *country_utf8, *city, *city_utf8; + guint32 ip_value; + + for (groups = gaim_request_fields_get_groups(fields); groups && !ip_str; groups = groups->next) { + for (flds = gaim_request_field_group_get_fields(groups->data); flds && !ip_str; flds = flds->next) { + field = flds->data; + id = gaim_request_field_get_id(field); + value = gaim_request_field_string_get_value(field); + + if (!g_ascii_strcasecmp(id, "ip")) { + ip_str = g_strdup(value); + break; + } + } + } + + if(ip_str) { + ip = str_ip_gen(ip_str); + ip_dupstr = gen_ip_str(ip); + + ip_value = ntohl(*(guint32 *)ip); + if (qq_ip_get_location(ip_value, &country, &city)) { + country_utf8 = qq_to_utf8(country, QQ_CHARSET_DEFAULT); + city_utf8 = qq_to_utf8(city, QQ_CHARSET_DEFAULT); + gaim_notify_info(gc, ip_dupstr, country_utf8, city_utf8); + g_free(country); + g_free(city); + g_free(country_utf8); + g_free(city_utf8); + } + else + gaim_notify_info(gc, ip_dupstr, "IP not found", NULL); + g_free(ip); + g_free(ip_dupstr); + g_free(ip_str); + } +} + +/* added by gfhuang */ +static void _qq_menu_locate_ip(GaimPluginAction *action) +{ + GaimConnection *gc = (GaimConnection *) action->context; + GaimRequestField *field; + GaimRequestFields *fields; + GaimRequestFieldGroup *group; + + g_return_if_fail(gc != NULL); + + fields = gaim_request_fields_new(); + group = gaim_request_field_group_new(NULL); + gaim_request_fields_add_group(fields, group); + + field = gaim_request_field_string_new("ip", _("IP Address"), NULL, FALSE); + gaim_request_field_group_add_field(group, field); + + gaim_request_fields(gc, _("Locate an IP"), + _("Locate an IP address"), NULL, fields, + _("Check"), G_CALLBACK(_qq_menu_locate_ip_cb), _("Cancel"), NULL, gc); +} + +static void _qq_menu_search_or_add_permanent_group(GaimPluginAction * action) +{ + gaim_gtk_roomlist_dialog_show(); +} + +static void _qq_menu_create_permanent_group(GaimPluginAction * action) +{ + GaimConnection *gc = (GaimConnection *) action->context; + g_return_if_fail(gc != NULL); + gaim_request_input(gc, _("Create QQ Qun"), + _("Input Qun name here"), + _("Only QQ member can create permanent Qun"), + "OpenQ", FALSE, FALSE, NULL, + _("Create"), G_CALLBACK(qq_group_create_with_name), _("Cancel"), NULL, gc); +} + +static void _qq_menu_unsubscribe_group(GaimBlistNode * node) // by gfhuang, gpointer param_components) +{ +// GaimBuddy *buddy; by gfhuang + GaimChat *chat = (GaimChat *)node; + GaimConnection *gc = gaim_account_get_connection(chat->account); + GHashTable *components = chat -> components; +// GHashTable *components = (GHashTable *) param_components; + +// g_return_if_fail(GAIM_BLIST_NODE_IS_BUDDY(node)); bug! found by gfhuang + g_return_if_fail(GAIM_BLIST_NODE_IS_CHAT(node)); + +// buddy = (GaimBuddy *) node; +// gc = gaim_account_get_connection(buddy->account); + + g_return_if_fail(gc != NULL && components != NULL); + qq_group_exit(gc, components); +} + +static void _qq_menu_manage_group(GaimBlistNode * node) // by gfhuang, gpointer param_components) +{ +// GaimBuddy *buddy; by gfhuang + GaimChat *chat = (GaimChat *)node; + GaimConnection *gc = gaim_account_get_connection(chat->account); + GHashTable *components = chat -> components; +// GHashTable *components = (GHashTable *) param_components; + +// g_return_if_fail(GAIM_BLIST_NODE_IS_BUDDY(node)); bug! found by gfhuang + g_return_if_fail(GAIM_BLIST_NODE_IS_CHAT(node)); + +// buddy = (GaimBuddy *) node; +// gc = gaim_account_get_connection(buddy->account); + + g_return_if_fail(gc != NULL && components != NULL); + qq_group_manage_group(gc, components); +} + +static void _qq_menu_show_system_message(GaimPluginAction *action) +{ + GaimConnection *gc = (GaimConnection *) action->context; + g_return_if_fail ( gc != NULL ); + gaim_gtk_log_show(GAIM_LOG_IM, "systemim", gaim_connection_get_account(gc)); +} + +/* TODO: re-enable this +static void _qq_menu_send_file(GaimBlistNode * node, gpointer ignored) +{ + GaimBuddy *buddy; + GaimConnection *gc; + qq_buddy *q_bud; + + g_return_if_fail (GAIM_BLIST_NODE_IS_BUDDY (node)); + buddy = (GaimBuddy *) node; + q_bud = (qq_buddy *) buddy->proto_data; +// if (is_online (q_bud->status)) { + gc = gaim_account_get_connection (buddy->account); + g_return_if_fail (gc != NULL && gc->proto_data != NULL); + qq_send_file(gc, buddy->name, NULL); +// } +} +*/ + +/* protocol related menus */ +static GList *_qq_actions(GaimPlugin * plugin, gpointer context) +{ + GList *m; + GaimPluginAction *act; + + m = NULL; + act = gaim_plugin_action_new(_("Modify My Information"), _qq_menu_get_my_info); + m = g_list_append(m, act); + + act = gaim_plugin_action_new(_("Show Login Information"), _qq_menu_show_login_info); + m = g_list_append(m, act); + + act = gaim_plugin_action_new(_("Show System Message"), _qq_menu_show_system_message); + m = g_list_append(m, act); + + act = gaim_plugin_action_new(_("Any QQ Command"), _qq_menu_any_cmd); + m = g_list_append(m, act); + + act = gaim_plugin_action_new(_("Qun: Search a permanent Qun"), _qq_menu_search_or_add_permanent_group); + m = g_list_append(m, act); + + act = gaim_plugin_action_new(_("Qun: Create a permanent Qun"), _qq_menu_create_permanent_group); + m = g_list_append(m, act); + + act = gaim_plugin_action_new(_("Locate an IP"), _qq_menu_locate_ip); + m = g_list_append(m, act); + + act = gaim_plugin_action_new(_("About QQ Plugin"), _qq_menu_show_about); + m = g_list_append(m, act); + + return m; +} + +/* chat-related (QQ Qun) menu shown up with right-click */ +static GList *_qq_chat_menu(GaimBlistNode *node) //gfhuang +{ + GList *m; + GaimMenuAction *act; + + m = NULL; + act = gaim_menu_action_new(_("Exit this QQ Qun"), GAIM_CALLBACK(_qq_menu_unsubscribe_group), NULL, NULL); //add NULL by gfhuang + m = g_list_append(m, act); + + act = gaim_menu_action_new(_("Show Details"), GAIM_CALLBACK(_qq_menu_manage_group), NULL, NULL); //add NULL by gfhuang + m = g_list_append(m, act); + + return m; +} +/* buddy-related menu shown up with right-click */ +static GList *_qq_buddy_menu(GaimBlistNode * node) +{ + GList *m; + /*GaimBlistNodeAction->GaimMenuAction, gaim2beta2, gfhuang*/ +// GaimMenuAction *act; + + if(GAIM_BLIST_NODE_IS_CHAT(node)) //by gfhuang + return _qq_chat_menu(node); + + m = NULL; + /*gaim_blist_node_action_new -> gaim_menu_action_new, gaim2beta2, gfhuang */ + +/* TODO : not working, temp commented out by gfhuang + + act = gaim_menu_action_new(_("Block this buddy"), GAIM_CALLBACK(_qq_menu_block_buddy), NULL, NULL); //add NULL by gfhuang + m = g_list_append(m, act); +// if (q_bud && is_online(q_bud->status)) { + act = gaim_menu_action_new(_("Send File"), GAIM_CALLBACK(_qq_menu_send_file), NULL, NULL); //add NULL by gfhuang + m = g_list_append(m, act); +// } +*/ + + return m; +} + + +static void _qq_keep_alive(GaimConnection * gc) +{ + qq_group *group; + qq_data *qd; + GList *list; + + g_return_if_fail(gc != NULL); + if (NULL == (qd = (qq_data *) gc->proto_data)) + return; + + list = qd->groups; + while (list != NULL) { + group = (qq_group *) list->data; + if (group->my_status == QQ_GROUP_MEMBER_STATUS_IS_MEMBER || + group->my_status == QQ_GROUP_MEMBER_STATUS_IS_ADMIN) +// no need to get info time and time again, online members enough, gfhuang +// qq_send_cmd_group_get_group_info(gc, group); + qq_send_cmd_group_get_online_member(gc, group); + + list = list->next; + } + + qq_send_packet_keep_alive(gc); + +} + +/* convert chat nickname to qq-uid to get this buddy info */ +/* who is the nickname of buddy in QQ chat-room (Qun) */ +static void _qq_get_chat_buddy_info(GaimConnection * gc, gint channel, const gchar * who) +{ + gchar *gaim_name; + g_return_if_fail(gc != NULL && gc->proto_data != NULL && who != NULL); + + gaim_name = qq_group_find_member_by_channel_and_nickname(gc, channel, who); + if (gaim_name != NULL) + _qq_get_info(gc, gaim_name); + +} + +/* convert chat nickname to qq-uid to invite individual IM to buddy */ +/* who is the nickname of buddy in QQ chat-room (Qun) */ +static gchar *_qq_get_chat_buddy_real_name(GaimConnection * gc, gint channel, const gchar * who) +{ + g_return_val_if_fail(gc != NULL && gc->proto_data != NULL && who != NULL, NULL); + return qq_group_find_member_by_channel_and_nickname(gc, channel, who); + +} + +void qq_function_not_implemented(GaimConnection * gc) +{ + gaim_notify_warning(gc, NULL, _("This function has not be implemented yet"), _("Please wait for new version")); +} + +/* +static GaimPluginPrefFrame *get_plugin_pref_frame(GaimPlugin * plugin) +{ + GaimPluginPrefFrame *frame; + GaimPluginPref *ppref; + + frame = gaim_plugin_pref_frame_new(); + + ppref = gaim_plugin_pref_new_with_label(_("Convert IP to location")); + gaim_plugin_pref_frame_add(frame, ppref); + + ppref = gaim_plugin_pref_new_with_name_and_label("/plugins/prpl/qq/ipfile", _("IP file")); + gaim_plugin_pref_frame_add(frame, ppref); + + ppref = gaim_plugin_pref_new_with_label(_("Display Options")); + gaim_plugin_pref_frame_add(frame, ppref); + + ppref = gaim_plugin_pref_new_with_name_and_label + ("/plugins/prpl/qq/show_status_by_icon", _("Show gender/age information beside buddy icons")); + gaim_plugin_pref_frame_add(frame, ppref); + + ppref = gaim_plugin_pref_new_with_name_and_label + ("/plugins/prpl/qq/show_fake_video", _("Fake an video for GAIM QQ (re-login to activate)")); + gaim_plugin_pref_frame_add(frame, ppref); + + ppref = gaim_plugin_pref_new_with_label(_("System Options")); + gaim_plugin_pref_frame_add(frame, ppref); + + ppref = gaim_plugin_pref_new_with_name_and_label + ("/plugins/prpl/qq/prompt_for_missing_packet", _("Prompt user for actions if there are missing packets")); + gaim_plugin_pref_frame_add(frame, ppref); + + ppref = gaim_plugin_pref_new_with_name_and_label + ("/plugins/prpl/qq/prompt_group_msg_on_recv", _("Pop up Qun chat window when receive Qun message")); + gaim_plugin_pref_frame_add(frame, ppref); + + ppref = gaim_plugin_pref_new_with_name_and_label("/plugins/prpl/qq/datadir", _("OpenQ installed directory")); + gaim_plugin_pref_frame_add(frame, ppref); + + return frame; +} +*/ + +GaimPlugin *my_protocol = NULL; +static GaimPluginProtocolInfo prpl_info = { + OPT_PROTO_CHAT_TOPIC | OPT_PROTO_USE_POINTSIZE, + NULL, /* user_splits */ + NULL, /* protocol_options */ + NO_BUDDY_ICONS, /* icon_spec */ + _qq_list_icon, /* list_icon */ + _qq_list_emblems, /* list_emblems */ + _qq_status_text, /* status_text */ + _qq_tooltip_text, /* tooltip_text */ + _qq_away_states, /* away_states */ + _qq_buddy_menu, /* blist_node_menu */ + qq_chat_info, /* chat_info */ + NULL, /* chat_info_defaults */ + _qq_login, /* login */ + _qq_close, /* close */ + _qq_send_im, /* send_im */ + NULL, /* set_info */ + NULL, /* send_typing */ + _qq_get_info, /* get_info */ + _qq_set_away, /* set_away */ + NULL, /* set_idle */ + NULL, /* change_passwd */ + qq_add_buddy, /* add_buddy */ + NULL, /* add_buddies */ + qq_remove_buddy, /* remove_buddy */ + NULL, /* remove_buddies */ + NULL, /* add_permit */ + NULL, /* add_deny */ + NULL, /* rem_permit */ + NULL, /* rem_deny */ + NULL, /* set_permit_deny */ + qq_group_join, /* join_chat */ + NULL, /* reject chat invite */ + NULL, /* get_chat_name */ + NULL, /* chat_invite */ + NULL, /* chat_leave */ + NULL, /* chat_whisper */ + _qq_chat_send, /* chat_send */ + _qq_keep_alive, /* keepalive */ + NULL, /* register_user */ + _qq_get_chat_buddy_info, /* get_cb_info */ + NULL, /* get_cb_away */ + NULL, /* alias_buddy */ + NULL, /* group_buddy */ + NULL, /* rename_group */ + NULL, /* buddy_free */ + NULL, /* convo_closed */ + NULL, /* normalize */ + NULL, /* set_buddy_icon */ + NULL, /* remove_group */ + _qq_get_chat_buddy_real_name, /* get_cb_real_name */ + NULL, /* set_chat_topic */ + NULL, /* find_blist_chat */ + qq_roomlist_get_list, /* roomlist_get_list */ + qq_roomlist_cancel, /* roomlist_cancel */ + NULL, /* roomlist_expand_category */ + qq_can_receive_file, /* can_receive_file */ + qq_send_file, /* send_file */ + NULL, /* new xfer, by gfhuang */ + NULL, /* offline_message, gaim2beta2, gfhuang */ + NULL, /* GaimWhiteboardPrplOps, gaim2beta2, gfhuang */ +}; + +/* +static GaimPluginUiInfo prefs_info = { + get_plugin_pref_frame +}; +*/ + +static GaimPluginInfo info = { + GAIM_PLUGIN_MAGIC, + GAIM_MAJOR_VERSION, + GAIM_MINOR_VERSION, + GAIM_PLUGIN_PROTOCOL, /**< type */ + NULL, /**< ui_requirement */ + 0, /**< flags */ + NULL, /**< dependencies */ + GAIM_PRIORITY_DEFAULT, /**< priority */ + + "prpl-qq", /**< id */ + "QQ", /**< name */ + VERSION, /**< version */ + /** summary */ + N_("QQ Protocol Plugin"), + /** description */ + N_("QQ Protocol Plugin"), + OPENQ_AUTHOR, /**< author */ + OPENQ_WEBSITE, /**< homepage */ + + NULL, /**< load */ + NULL, /**< unload */ + NULL, /**< destroy */ + + NULL, /**< ui_info */ + &prpl_info, /**< extra_info */ + NULL, /**< prefs_info */ + _qq_actions +}; + + +static void init_plugin(GaimPlugin * plugin) +{ + GaimAccountOption *option; + + bindtextdomain(PACKAGE, LOCALEDIR); + bind_textdomain_codeset(PACKAGE, "UTF-8"); + + option = gaim_account_option_bool_new(_("Login in TCP"), "use_tcp", FALSE); + prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option); + + option = gaim_account_option_bool_new(_("Login Hidden"), "hidden", FALSE); + prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option); + + option = gaim_account_option_string_new(_("QQ Server"), "server", NULL); + prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option); + + option = gaim_account_option_string_new(_("QQ Port"), "port", NULL); + prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option); + + my_protocol = plugin; + + gaim_prefs_add_none("/plugins/prpl/qq"); + gaim_prefs_add_string("/plugins/prpl/qq/ipfile", ""); + gaim_prefs_add_bool("/plugins/prpl/qq/show_status_by_icon", TRUE); + gaim_prefs_add_bool("/plugins/prpl/qq/show_fake_video", FALSE); + gaim_prefs_add_string("/plugins/prpl/qq/datadir", DATADIR); + gaim_prefs_add_bool("/plugins/prpl/qq/prompt_for_missing_packet", TRUE); + gaim_prefs_add_bool("/plugins/prpl/qq/prompt_group_msg_on_recv", TRUE); +} + +GAIM_INIT_PLUGIN(qq, init_plugin, info); diff -r 5642f4658b59 -r 983fd420e86b src/protocols/qq/qq.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/protocols/qq/qq.h Mon Jun 26 02:58:54 2006 +0000 @@ -0,0 +1,111 @@ +/** + * The QQ2003C protocol plugin + * + * for gaim + * + * Copyright (C) 2004 Puzzlebird + * + * 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 + */ + +// START OF FILE +/*****************************************************************************/ +#ifndef _QQ_QQ_H_ +#define _QQ_QQ_H_ + +#include +#include "proxy.h" // GaimProxyType +#include "internal.h" // socket +#include "roomlist.h" // GaimRoomlist +#include "ft.h" // GaimXfer + +#define QQ_KEY_LENGTH 16 +#define QQ_DEBUG 1 // whether we are doing DEBUG + +typedef struct _qq_data qq_data; +typedef struct _qq_buddy qq_buddy; + +struct _qq_buddy { + guint32 uid; + guint8 icon; // index: 01 - 85 + guint8 age; + guint8 gender; + gchar *nickname; + guint8 ip[4]; + guint16 port; + guint8 status; + guint8 flag1; + guint8 comm_flag; // details in qq_buddy_list.c + guint16 client_version; // added by gfhuang + time_t signon; + time_t idle; + time_t last_refresh; + + gint8 role; // role in group, used only in group->members list, gfhuang +}; + +struct _qq_data { + gint fd; // socket file handler + guint32 uid; // QQ number + guint8 *inikey; // initial key to encrypt login packet + guint8 *pwkey; // password in md5 (or md5' md5) + guint8 *session_key; // later use this as key in this session + + guint16 send_seq; // send sequence number + guint8 login_mode; // online of invisible + guint8 status; // + gboolean logged_in; // used by qq-add_buddy + gboolean use_tcp; // network in tcp or udp + + GaimProxyType proxy_type; // proxy type + GaimXfer *xfer; // file transfer handler + struct sockaddr_in dest_sin; + + // from real connction + gchar *server_ip; + guint16 server_port; + // get from login reply packet + time_t login_time; + time_t last_login_time; + gchar *last_login_ip; + // get from keep_alive packet + gchar *my_ip; // my ip address detected by server + guint16 my_port; // my port detected by server + guint8 my_icon; // my icon index + guint32 all_online; // the number of online QQ users + time_t last_get_online; // last time send get_friends_online packet + + guint8 window[1 << 13]; // check up for duplicated packet + gint sendqueue_timeout; + + GaimRoomlist *roomlist; + gint channel; // the id for opened chat conversation + + GList *groups; + GList *group_packets; + GList *buddies; + GList *contact_info_window; + GList *qun_info_window; + GList *sendqueue; + GList *info_query; + GList *add_buddy_request; + GQueue *before_login_packets; +}; + +void qq_function_not_implemented(GaimConnection * gc); + +#endif +/*****************************************************************************/ +// END OF FILE diff -r 5642f4658b59 -r 983fd420e86b src/protocols/qq/qq_proxy.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/protocols/qq/qq_proxy.c Mon Jun 26 02:58:54 2006 +0000 @@ -0,0 +1,436 @@ +/** + * The QQ2003C protocol plugin + * + * for gaim + * + * Copyright (C) 2004 Puzzlebird + * Henry Ou + * + * 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 + */ + +// START OF FILE +/*****************************************************************************/ +#include "debug.h" // gaim_debug +#include "internal.h" // strlen, _("get_text")#include "md5.h" // md5 functions +//#include "md5.h" +#include "cipher.h" //gfhuang + +#ifdef _WIN32 +#define random rand +#define srandom srand +#endif + +#include "utils.h" // qq_debug +#include "packet_parse.h" // MAX_PACKET_SIZE +#include "buddy_info.h" // qq_info_query_free +#include "buddy_opt.h" // qq_add_buddy_request_free +#include "group_admindlg.h" // qq_qun_info_window_free +#include "group_free.h" // qq_group_packets_free +#include "infodlg.h" // qq_contact_info_window_free +#include "login_logout.h" // qq_send_packet_login +#include "qq_proxy.h" // +#include "recv_core.h" // qq_pending, qq_b4_packets_free +#include "send_core.h" // qq_send_cmd +#include "sendqueue.h" // qq_sendqueue_timeout_callback +#include "udp_proxy_s5.h" // qq_proxy_sock5 + +/*****************************************************************************/ + +/* These functions are used only in development phased + * +static void _qq_show_socket(gchar *desc, gint fd) { + struct sockaddr_in sin; + gint len = sizeof(sin); + getsockname(fd, (struct sockaddr *)&sin, &len); + gaim_debug(GAIM_DEBUG_INFO, desc, "%s:%d\n", + inet_ntoa(sin.sin_addr), ntohs(sin.sin_port)); +} +*/ + +static void _qq_show_packet(gchar * desc, gchar * buf, gint len) +{ + char buf1[4096], buf2[10]; + int i; + buf1[0] = 0; + for (i = 0; i < len; i++) { + sprintf(buf2, " %02x(%d)", buf[i] & 0xff, buf[i] & 0xff); + strcat(buf1, buf2); + } + strcat(buf1, "\n"); + gaim_debug(GAIM_DEBUG_INFO, desc, buf1); +} + +/*****************************************************************************/ +// QQ 2003iii uses double MD5 for the pwkey to get the session key +static guint8 *_gen_pwkey(const gchar * pwd) +{ +// md5_state_t ctx; //gfhuang + GaimCipher *cipher; + GaimCipherContext *context; + + gchar pwkey_tmp[QQ_KEY_LENGTH]; +/* + md5_init(&ctx); + md5_append(&ctx, pwd, strlen(pwd)); + md5_finish(&ctx, pwkey_tmp); + + md5_init(&ctx); + md5_append(&ctx, pwkey_tmp, QQ_KEY_LENGTH); + md5_finish(&ctx, pwkey_tmp); +*/ //gfhuang + + cipher = gaim_ciphers_find_cipher("md5"); + context = gaim_cipher_context_new(cipher, NULL); + gaim_cipher_context_append(context, pwd, strlen(pwd)); + gaim_cipher_context_digest(context, sizeof(pwkey_tmp), pwkey_tmp, NULL); + gaim_cipher_context_destroy(context); + context = gaim_cipher_context_new(cipher, NULL); + gaim_cipher_context_append(context, pwkey_tmp, QQ_KEY_LENGTH); + gaim_cipher_context_digest(context, sizeof(pwkey_tmp), pwkey_tmp, NULL); + gaim_cipher_context_destroy(context); + + + return g_memdup(pwkey_tmp, QQ_KEY_LENGTH); +} // _gen_pwkey + + +/*****************************************************************************/ +static gint _qq_fill_host(struct sockaddr_in * addr, const gchar * host, guint16 port) +{ + if (!inet_aton(host, &(addr->sin_addr))) { + struct hostent *hp; + if (!(hp = gethostbyname(host))) { + return -1; + } + memset(addr, 0, sizeof(struct sockaddr_in)); + memcpy(&(addr->sin_addr.s_addr), hp->h_addr, hp->h_length); + addr->sin_family = hp->h_addrtype; + } else + addr->sin_family = AF_INET; + + addr->sin_port = htons(port); + return 0; +} // _qq_fill_host + +/*****************************************************************************/ +// the callback function after socket is built +// we setup the qq protocol related configuration here +static void _qq_got_login(gpointer data, gint source, GaimInputCondition cond) +{ + qq_data *qd; + GaimConnection *gc; + gchar *buf; + const gchar *passwd; + + gc = (GaimConnection *) data; + g_return_if_fail(gc != NULL && gc->proto_data != NULL); + + if (g_list_find(gaim_connections_get_all(), gc) == NULL) { + close(source); + return; + } + + if (source < 0) { // socket returns -1 + gaim_connection_error(gc, _("Unable to connect.")); + return; + } + + qd = (qq_data *) gc->proto_data; + + // QQ use random seq, to minimize duplicated packets + srandom(time(NULL)); + qd->send_seq = random() & 0x0000ffff; + qd->fd = source; + qd->logged_in = FALSE; + qd->channel = 1; + qd->uid = strtol(gaim_account_get_username(gaim_connection_get_account(gc)), NULL, 10); + qd->before_login_packets = g_queue_new(); + + // now generate md5 processed passwd + passwd = gaim_account_get_password(gaim_connection_get_account(gc)); + qd->pwkey = _gen_pwkey(passwd); + + qd->sendqueue_timeout = gaim_timeout_add(QQ_SENDQUEUE_TIMEOUT, qq_sendqueue_timeout_callback, gc); + gc->inpa = gaim_input_add(qd->fd, GAIM_INPUT_READ, qq_input_pending, gc); + + // Update the login progress status display + buf = g_strdup_printf("Login as %d", qd->uid); + gaim_connection_update_progress(gc, buf, 1, QQ_CONNECT_STEPS); + g_free(buf); + +// qq_send_packet_login(gc); // finally ready to fire + qq_send_packet_request_login_token(gc); +} // _qq_got_login + +/*****************************************************************************/ +// clean up qq_data structure and all its components +// always used before a redirectly connection +static void _qq_common_clean(GaimConnection * gc) +{ + qq_data *qd; + + g_return_if_fail(gc != NULL && gc->proto_data != NULL); + qd = (qq_data *) gc->proto_data; + + // finish all I/O + if (qd->fd >= 0 && qd->logged_in) + qq_send_packet_logout(gc); + close(qd->fd); + + if (qd->sendqueue_timeout > 0) { + gaim_timeout_remove(qd->sendqueue_timeout); + qd->sendqueue_timeout = 0; + } // qd->sendqueue_timeout + + if (gc->inpa > 0) { + gaim_input_remove(gc->inpa); + gc->inpa = 0; + } // gc->inpa + + qq_b4_packets_free(qd); + qq_sendqueue_free(qd); + qq_group_packets_free(qd); + qq_group_free_all(qd); + qq_add_buddy_request_free(qd); + qq_info_query_free(qd); + qq_contact_info_window_free(qd); + qq_qun_info_window_free(qd); + qq_buddies_list_free(gc->account /* by gfhuang */, qd); + +} // _qq_common_clean + +/*****************************************************************************/ +static gint _qq_proxy_none(struct PHB *phb, struct sockaddr *addr, socklen_t addrlen) +{ + gint fd = -1; + + gaim_debug(GAIM_DEBUG_INFO, "QQ", "Using UDP without proxy\n"); + fd = socket(PF_INET, SOCK_DGRAM, 0); + + if (fd < 0) { + gaim_debug(GAIM_DEBUG_ERROR, "QQ Redirect", "Unable to create socket: %s\n", strerror(errno)); + return -1; + } // if fd + + // we use non-blocking mode to speed up connection + fcntl(fd, F_SETFL, O_NONBLOCK); + + /* From Unix-socket-FAQ: http://www.faqs.org/faqs/unix-faq/socket/ + * + * If a UDP socket is unconnected, which is the normal state after a + * bind() call, then send() or write() are not allowed, since no + * destination is available; only sendto() can be used to send data. + * + * Calling connect() on the socket simply records the specified address + * and port number as being the desired communications partner. That + * means that send() or write() are now allowed; they use the destination + * address and port given on the connect call as the destination of packets. + */ + if (connect(fd, addr, addrlen) < 0) { + /* [EINPROGRESS] + * The socket is marked as non-blocking and the connection cannot be + * completed immediately. It is possible to select for completion by + * selecting the socket for writing. + * [EINTR] + * A signal interrupted the call. + * The connection is established asynchronously. + */ + if ((errno == EINPROGRESS) || (errno == EINTR)) + gaim_debug(GAIM_DEBUG_WARNING, "QQ", "Connect in asynchronous mode.\n"); + else { + gaim_debug(GAIM_DEBUG_ERROR, "QQ", "Faiil connection: %d\n", strerror(errno)); + close(fd); + return -1; + } // if errno + } else { // connect returns 0 + gaim_debug(GAIM_DEBUG_INFO, "QQ", "Connected.\n"); + fcntl(fd, F_SETFL, 0); + phb->func(phb->data, fd, GAIM_INPUT_READ); + } // if connect + + return fd; +} // _qq_proxy_none + +/*****************************************************************************/ +// returns the socket handler, or -1 if there is any error +static gint _qq_udp_proxy_connect(GaimAccount * account, + const gchar * server, + guint16 port, void callback(gpointer, gint, GaimInputCondition), GaimConnection * gc) +{ + struct sockaddr_in sin; + struct PHB *phb; + GaimProxyInfo *info; + qq_data *qd; + + g_return_val_if_fail(gc != NULL && gc->proto_data != NULL, -1); + qd = (qq_data *) gc->proto_data; + + info = gaim_account_get_proxy_info(account); + + phb = g_new0(struct PHB, 1); + phb->host = g_strdup(server); + phb->port = port; + phb->account = account; + phb->gpi = info; + phb->func = callback; + phb->data = gc; + + if (_qq_fill_host(&sin, server, port) < 0) { + gaim_debug(GAIM_DEBUG_ERROR, "QQ", + "gethostbyname(\"%s\", %d) failed: %s\n", server, port, hstrerror(h_errno)); + return -1; + } // if _qq_fill_host + + if (info == NULL) { + qd->proxy_type = GAIM_PROXY_NONE; + return _qq_proxy_none(phb, (struct sockaddr *) &sin, sizeof(sin)); + } // if info + + qd->proxy_type = info->type; + gaim_debug(GAIM_DEBUG_INFO, "QQ", "Choosing proxy type %d\n", info->type); + + switch (info->type) { + case GAIM_PROXY_NONE: + return _qq_proxy_none(phb, (struct sockaddr *) &sin, sizeof(sin)); + case GAIM_PROXY_SOCKS5: + // as the destination is always QQ server during the session, + // we can set dest_sin here, instead of _qq_s5_canread_again + _qq_fill_host(&qd->dest_sin, phb->host, phb->port); + _qq_fill_host(&sin, phb->gpi->host, phb->gpi->port); + return qq_proxy_socks5(phb, (struct sockaddr *) &sin, sizeof(sin)); + default: + return _qq_proxy_none(phb, (struct sockaddr *) &sin, sizeof(sin)); + } // switch + + return -1; +} + +/*****************************************************************************/ +// QQ connection via UDP/TCP. +// I use GAIM proxy function to provide TCP proxy support, +// and qq_udp_proxy.c to add UDP proxy support (thanks henry) +// return the socket handle, -1 means fail +static gint _proxy_connect_full + (GaimAccount * account, const gchar * host, guint16 port, GaimInputFunction func, gpointer data, gboolean use_tcp) { + + GaimConnection *gc; + qq_data *qd; + + gc = gaim_account_get_connection(account); + qd = (qq_data *) gc->proto_data; + qd->server_ip = g_strdup(host); + qd->server_port = port; + + return use_tcp ? gaim_proxy_connect(account, host, port, func, data) : // TCP mode + _qq_udp_proxy_connect(account, host, port, func, data); // UDP mode + +} // _gaim_proxy_connect_full + +/*****************************************************************************/ +// establish a generic QQ connection +// TCP/UDP, and direct/redirected +// return the socket handler, or -1 if there is any error +gint qq_connect(GaimAccount * account, const gchar * host, guint16 port, gboolean use_tcp, gboolean is_redirect) { + + GaimConnection *gc; + + g_return_val_if_fail(host != NULL, -1); + g_return_val_if_fail(port > 0, -1); + + gc = gaim_account_get_connection(account); + g_return_val_if_fail(gc != NULL && gc->proto_data != NULL, -1); + + if (is_redirect) + _qq_common_clean(gc); + + return _proxy_connect_full(account, host, port, _qq_got_login, gc, use_tcp); +} // qq_connect + +/*****************************************************************************/ +// clean up the given QQ connection and free all resources +void qq_disconnect(GaimConnection * gc) +{ + qq_data *qd; + + g_return_if_fail(gc != NULL); + + _qq_common_clean(gc); + + qd = gc->proto_data; + g_free(qd->inikey); + g_free(qd->pwkey); + g_free(qd->session_key); + g_free(qd->my_ip); + g_free(qd); + + gc->proto_data = NULL; +} // qq_disconnect + +/*****************************************************************************/ +// send packet with proxy support +gint qq_proxy_write(qq_data * qd, guint8 * data, gint len) +{ + guint8 *buf; + gint ret; + + g_return_val_if_fail(qd != NULL && qd->fd >= 0 && data != NULL && len > 0, -1); + + // TCP sock5 may be processed twice + // so we need to check qd->use_tcp as well + if ((!qd->use_tcp) && qd->proxy_type == GAIM_PROXY_SOCKS5) { // UDP sock5 + buf = g_newa(guint8, len + 10); + buf[0] = 0x00; + buf[1] = 0x00; //reserved + buf[2] = 0x00; //frag + buf[3] = 0x01; //type + g_memmove(buf + 4, &(qd->dest_sin.sin_addr.s_addr), 4); + g_memmove(buf + 8, &(qd->dest_sin.sin_port), 2); + g_memmove(buf + 10, data, len); + ret = send(qd->fd, buf, len + 10, 0); + } else + ret = send(qd->fd, data, len, 0); + + return ret; +} + +/*****************************************************************************/ +// read packet input with proxy support +gint qq_proxy_read(qq_data * qd, guint8 * data, gint len) +{ + guint8 *buf; + gint bytes; + buf = g_newa(guint8, MAX_PACKET_SIZE + 10); + + g_return_val_if_fail(qd != NULL && data != NULL && len > 0, -1); + g_return_val_if_fail(qd->fd > 0, -1); + + bytes = read(qd->fd, buf, len + 10); + if (bytes < 0) + return -1; + + if ((!qd->use_tcp) && qd->proxy_type == GAIM_PROXY_SOCKS5) { // UDP sock5 + if (bytes < 10) + return -1; + bytes -= 10; + g_memmove(data, buf + 10, bytes); //cut off the header + } else + g_memmove(data, buf, bytes); + + return bytes; +} // qq_proxy_read + +/*****************************************************************************/ +// END of FILE diff -r 5642f4658b59 -r 983fd420e86b src/protocols/qq/qq_proxy.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/protocols/qq/qq_proxy.h Mon Jun 26 02:58:54 2006 +0000 @@ -0,0 +1,57 @@ +/** + * The QQ2003C protocol plugin + * + * for gaim + * + * Copyright (C) 2004 Puzzlebird + * Henry Ou + * + * 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 + */ + +// START OF FILE +/*****************************************************************************/ + +#ifndef _QQ_PROXY_H +#define _QQ_PROXY_H + +#include +#include "proxy.h" // GaimAccount, GaimConnection +#include "qq.h" // qq_data + +#define QQ_CONNECT_STEPS 2 // steps in cnnection + +struct PHB { + GaimInputFunction func; + gpointer data; + gchar *host; + gint port; + gint inpa; + GaimProxyInfo *gpi; + GaimAccount *account; + gint udpsock; + gpointer sockbuf; +}; + +gint qq_proxy_read(qq_data * qd, guint8 * data, gint len); +gint qq_proxy_write(qq_data * qd, guint8 * data, gint len); + +gint qq_connect(GaimAccount * account, const gchar * host, guint16 port, gboolean use_tcp, gboolean is_redirect); + +void qq_disconnect(GaimConnection * gc); + +#endif //_QQ_PROXY_H +/*****************************************************************************/ +// END OF FILE diff -r 5642f4658b59 -r 983fd420e86b src/protocols/qq/recv_core.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/protocols/qq/recv_core.c Mon Jun 26 02:58:54 2006 +0000 @@ -0,0 +1,321 @@ +/** +* The QQ2003C protocol plugin + * + * for gaim + * + * Copyright (C) 2004 Puzzlebird + * + * 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 + */ + +// START OF FILE +/*****************************************************************************/ +#include "debug.h" // gaim_debug +#include "internal.h" // _("get_text") + +#include "utils.h" // hex_dump_to_str +#include "packet_parse.h" // MAX_PACKET_SIZE +#include "buddy_info.h" // qq_process_modify_info_reply +#include "buddy_list.h" // qq_process_get_buddies_list_reply +#include "buddy_opt.h" // qq_process_add_buddy_reply +#include "buddy_status.h" // qq_process_friend_change_status +#include "char_conv.h" // qq_to_utf8 +#include "crypt.h" // qq_crypt +#include "group_network.h" // qq_process_group_cmd_reply +#include "header_info.h" // cmd alias +#include "keep_alive.h" // qq_process_keep_alive_reply +#include "im.h" // qq_process_send_im_reply +#include "login_logout.h" // qq_process_login_reply +#include "qq_proxy.h" // qq_proxy_read +#include "recv_core.h" +#include "sendqueue.h" // qq_sendqueue_remove +#include "sys_msg.h" // qq_process_msg_sys + +typedef struct _packet_before_login packet_before_login; +typedef struct _qq_recv_msg_header qq_recv_msg_header; + +struct _packet_before_login { + guint8 *buf; + gint len; +}; + +struct _qq_recv_msg_header { + guint8 header_tag; + guint16 source_tag; + guint16 cmd; + guint16 seq; // can be ack_seq or send_seq, depends on cmd +}; + +/*****************************************************************************/ +// check whether one sequence number is duplicated or not +// return TRUE if it is duplicated, otherwise FALSE +static gboolean _qq_check_packet_set_window(guint16 seq, GaimConnection * gc) +{ + qq_data *qd; + gchar *byte, mask; + + g_return_val_if_fail(gc != NULL && gc->proto_data != NULL, FALSE); + qd = (qq_data *) gc->proto_data; + byte = &(qd->window[seq / 8]); + mask = (1 << (seq % 8)); + + if ((*byte) & mask) + return TRUE; // check mask + (*byte) |= mask; + return FALSE; // set mask +} // _qq_check_packet_set_window + +/*****************************************************************************/ +// default process, decrypt and dump +static void _qq_process_packet_default(guint8 * buf, gint buf_len, guint16 cmd, guint16 seq, GaimConnection * gc) { + + qq_data *qd; + guint8 *data; + gchar *msg_utf8; + gint len; + + g_return_if_fail(gc != NULL && gc->proto_data != NULL); + g_return_if_fail(buf != NULL && buf_len != 0); + + qd = (qq_data *) gc->proto_data; + len = buf_len; + data = g_newa(guint8, len); + msg_utf8 = NULL; + + if (qq_crypt(DECRYPT, buf, buf_len, qd->session_key, data, &len)) { + gaim_debug(GAIM_DEBUG_WARNING, "QQ", + ">>> [%d] %s, %d bytes -> [default] decrypt and dump\n%s", + seq, qq_get_cmd_desc(cmd), buf_len, hex_dump_to_str(data, len)); + try_dump_as_gbk(data, len); + } else + gaim_debug(GAIM_DEBUG_ERROR, "QQ", "Fail decrypt packet with default process\n"); + +} // _qq_process_packet_default + +/*****************************************************************************/ +// process the incoming packet from qq_pending +static void _qq_packet_process(guint8 * buf, gint buf_len, GaimConnection * gc) +{ + qq_data *qd; + gint len, bytes_expected, bytes_read; + guint16 buf_len_read; // two bytes in the begining of TCP packet + guint8 *cursor; + qq_recv_msg_header header; + packet_before_login *b4_packet; + + g_return_if_fail(gc != NULL && gc->proto_data != NULL); + g_return_if_fail(buf != NULL && buf_len > 0); + + qd = (qq_data *) gc->proto_data; + bytes_expected = qd->use_tcp ? QQ_TCP_HEADER_LENGTH : QQ_UDP_HEADER_LENGTH; + + if (buf_len < bytes_expected) { + gaim_debug(GAIM_DEBUG_ERROR, + "QQ", "Received packet is too short, dump and drop\n%s", hex_dump_to_str(buf, buf_len)); + return; + } + // initialize + cursor = buf; + bytes_read = 0; + + // QQ TCP packet returns first 2 bytes the length of this packet + if (qd->use_tcp) { + bytes_read += read_packet_w(buf, &cursor, buf_len, &buf_len_read); + if (buf_len_read != buf_len) { // wrong + gaim_debug + (GAIM_DEBUG_ERROR, + "QQ", + "TCP read %d bytes, header says %d bytes, use header anyway\n", buf_len, buf_len_read); + buf_len = buf_len_read; // we believe header is more accurate + } // if buf_len_read + } // if use_tcp + + // now goes the normal QQ packet as UDP packet + bytes_read += read_packet_b(buf, &cursor, buf_len, &header.header_tag); + bytes_read += read_packet_w(buf, &cursor, buf_len, &header.source_tag); + bytes_read += read_packet_w(buf, &cursor, buf_len, &header.cmd); + bytes_read += read_packet_w(buf, &cursor, buf_len, &header.seq); + + if (bytes_read != bytes_expected) { // read error + gaim_debug(GAIM_DEBUG_ERROR, "QQ", + "Fail reading packet header, expect %d bytes, read %d bytes\n", bytes_expected, bytes_read); + return; + } // if bytes_read + + if ((buf[buf_len - 1] != QQ_PACKET_TAIL) || (header.header_tag != QQ_PACKET_TAG)) { + gaim_debug(GAIM_DEBUG_ERROR, + "QQ", "Unknown QQ proctocol, dump and drop\n%s", hex_dump_to_str(buf, buf_len)); + return; + } // if header_tag + + if (QQ_DEBUG) + gaim_debug(GAIM_DEBUG_INFO, "QQ", + "==> [%05d] %s, from (%s)\n", + header.seq, qq_get_cmd_desc(header.cmd), qq_get_source_str(header.source_tag)); + + if (header.cmd != QQ_CMD_LOGIN && header.cmd != QQ_CMD_REQUEST_LOGIN_TOKEN /* gfhuang */) { + if (!qd->logged_in) { // packets before login + b4_packet = g_new0(packet_before_login, 1); + // must duplicate, buffer will be freed after exiting this function + b4_packet->buf = g_memdup(buf, buf_len); + b4_packet->len = buf_len; + if (qd->before_login_packets == NULL) + qd->before_login_packets = g_queue_new(); + g_queue_push_head(qd->before_login_packets, b4_packet); + return; // do not process it now + } else if (!g_queue_is_empty(qd->before_login_packets)) { + // logged_in, but we have packets before login + b4_packet = (packet_before_login *) + g_queue_pop_head(qd->before_login_packets); + _qq_packet_process(b4_packet->buf, b4_packet->len, gc); + // in fact this is a recursive call, + // all packets before login will be processed before goes on + g_free(b4_packet->buf); // the buf is duplicated, need to be freed + g_free(b4_packet); + } // if logged_in + } //if header.cmd != QQ_CMD_LOGIN + + // this is the length of all the encrypted data (also remove tail tag + len = buf_len - (bytes_read) - 1; + + // whether it is an ack + switch (header.cmd) { + case QQ_CMD_RECV_IM: + case QQ_CMD_RECV_MSG_SYS: + case QQ_CMD_RECV_MSG_FRIEND_CHANGE_STATUS: + // server intiated packet, we need to send ack and check duplicaion + // this must be put after processing b4_packet + // as these packets will be passed in twice + if (_qq_check_packet_set_window(header.seq, gc)) { + gaim_debug(GAIM_DEBUG_WARNING, + "QQ", "dup [%05d] %s, discard...\n", header.seq, qq_get_cmd_desc(header.cmd)); + return; + } + break; + default:{ // ack packet, we need to update sendqueue + // we do not check duplication for server ack + qq_sendqueue_remove(qd, header.seq); + if (QQ_DEBUG) + gaim_debug(GAIM_DEBUG_INFO, "QQ", + "ack [%05d] %s, remove from sendqueue\n", + header.seq, qq_get_cmd_desc(header.cmd)); + } // default + } // switch header.cmd + + // now process the packet + switch (header.cmd) { + case QQ_CMD_KEEP_ALIVE: + qq_process_keep_alive_reply(cursor, len, gc); + break; + case QQ_CMD_UPDATE_INFO: + qq_process_modify_info_reply(cursor, len, gc); + break; + case QQ_CMD_ADD_FRIEND_WO_AUTH: + qq_process_add_buddy_reply(cursor, len, header.seq, gc); + break; + case QQ_CMD_DEL_FRIEND: + qq_process_remove_buddy_reply(cursor, len, gc); + break; + case QQ_CMD_REMOVE_SELF: + qq_process_remove_self_reply(cursor, len, gc); + break; + case QQ_CMD_BUDDY_AUTH: + qq_process_add_buddy_auth_reply(cursor, len, gc); + break; + case QQ_CMD_GET_USER_INFO: + qq_process_get_info_reply(cursor, len, gc); + break; + case QQ_CMD_CHANGE_ONLINE_STATUS: + qq_process_change_status_reply(cursor, len, gc); + break; + case QQ_CMD_SEND_IM: + qq_process_send_im_reply(cursor, len, gc); + break; + case QQ_CMD_RECV_IM: + qq_process_recv_im(cursor, len, header.seq, gc); + break; + case QQ_CMD_LOGIN: + qq_process_login_reply(cursor, len, gc); + break; + case QQ_CMD_GET_FRIENDS_LIST: + qq_process_get_buddies_list_reply(cursor, len, gc); + break; + case QQ_CMD_GET_FRIENDS_ONLINE: + qq_process_get_buddies_online_reply(cursor, len, gc); + break; + case QQ_CMD_GROUP_CMD: + qq_process_group_cmd_reply(cursor, len, header.seq, gc); + break; + case QQ_CMD_GET_ALL_LIST_WITH_GROUP: //added by gfhuang + qq_process_get_all_list_with_group_reply(cursor, len, gc); + break; + case QQ_CMD_REQUEST_LOGIN_TOKEN: //added by gfhuang + qq_process_request_login_token_reply(cursor, len, gc); + break; + case QQ_CMD_RECV_MSG_SYS: + qq_process_msg_sys(cursor, len, header.seq, gc); + break; + case QQ_CMD_RECV_MSG_FRIEND_CHANGE_STATUS: + qq_process_friend_change_status(cursor, len, gc); + break; + default: + _qq_process_packet_default(cursor, len, header.cmd, header.seq, gc); + break; + } // switch header.cmd +} // _qq_packet_process + +/*****************************************************************************/ +// clean up the packets before login +void qq_b4_packets_free(qq_data * qd) +{ + packet_before_login *b4_packet; + g_return_if_fail(qd != NULL); + // now clean up my own data structures + if (qd->before_login_packets != NULL) { + while (NULL != (b4_packet = g_queue_pop_tail(qd->before_login_packets))) { + g_free(b4_packet->buf); + g_free(b4_packet); + } + g_queue_free(qd->before_login_packets); + } // if +} // qq_b4_packets_free + +/*****************************************************************************/ +void qq_input_pending(gpointer data, gint source, GaimInputCondition cond) +{ + GaimConnection *gc; + qq_data *qd;; + guint8 *buf; + gint len; + + gc = (GaimConnection *) data; + g_return_if_fail(gc != NULL && gc->proto_data != NULL && cond == GAIM_INPUT_READ); + + qd = (qq_data *) gc->proto_data; + // according to glib manual memory allocated by g_newa could be + // automatically freed when the current stack frame is cleaned up + buf = g_newa(guint8, MAX_PACKET_SIZE); + + // here we have UDP proxy suppport + len = qq_proxy_read(qd, buf, MAX_PACKET_SIZE); + if (len <= 0) { + gaim_connection_error(gc, _("Unable to read from socket")); + return; + } else + _qq_packet_process(buf, len, gc); +} // qq_input_pending + +/*****************************************************************************/ +// END OF FILE diff -r 5642f4658b59 -r 983fd420e86b src/protocols/qq/recv_core.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/protocols/qq/recv_core.h Mon Jun 26 02:58:54 2006 +0000 @@ -0,0 +1,38 @@ +/** +* The QQ2003C protocol plugin + * + * for gaim + * + * Copyright (C) 2004 Puzzlebird + * + * 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 + */ + +// START OF FILE +/*****************************************************************************/ +#ifndef _QQ_RECV_CORE_H_ +#define _QQ_RECV_CORE_H_ + +#include +#include "connection.h" // GaimConnection +#include "qq.h" // qq_data + +void qq_b4_packets_free(qq_data * qd); + +void qq_input_pending(gpointer data, gint source, GaimInputCondition cond); + +#endif +/*****************************************************************************/ +// END OF FILE diff -r 5642f4658b59 -r 983fd420e86b src/protocols/qq/send_core.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/protocols/qq/send_core.c Mon Jun 26 02:58:54 2006 +0000 @@ -0,0 +1,176 @@ +/** +* The QQ2003C protocol plugin + * + * for gaim + * + * Copyright (C) 2004 Puzzlebird + * + * 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 + */ + +// START OF FILE +/*****************************************************************************/ +#include "debug.h" // gaim_debug +#include "internal.h" // send, socket + +#include "packet_parse.h" // MAX_PACKET_SIZE +#include "crypt.h" // qq_crypt +#include "header_info.h" // cmd alias +#include "qq_proxy.h" // qq_proxy_write +#include "send_core.h" +#include "sendqueue.h" // qq_sendpacket +#include "qq.h" // qq_data, QQ_DEBUG + +/*****************************************************************************/ +// create qq packet header with given sequence +// return the number of bytes in header if succeeds +// return -1 if there is any error +static gint _create_packet_head_seq(guint8 * buf, guint8 ** cursor, + GaimConnection * gc, guint16 cmd, gboolean is_auto_seq, guint16 * seq) +{ + qq_data *qd; + gint bytes_expected, bytes_written; + + g_return_val_if_fail(gc != NULL && + gc->proto_data != NULL && buf != NULL && cursor != NULL && *cursor != NULL, -1); + + qd = (qq_data *) gc->proto_data; + if (is_auto_seq) + *seq = ++(qd->send_seq); + + *cursor = buf; + bytes_written = 0; + bytes_expected = (qd->use_tcp) ? QQ_TCP_HEADER_LENGTH : QQ_UDP_HEADER_LENGTH; + + // QQ TCP packet has two bytes in the begining defines packet length + // so I leave room here for size + if (qd->use_tcp) + bytes_written += create_packet_w(buf, cursor, 0x0000); + + // now comes the normal QQ packet as UDP + bytes_written += create_packet_b(buf, cursor, QQ_PACKET_TAG); + bytes_written += create_packet_w(buf, cursor, QQ_CLIENT); + bytes_written += create_packet_w(buf, cursor, cmd); + bytes_written += create_packet_w(buf, cursor, *seq); + + if (bytes_written != bytes_expected) { + gaim_debug(GAIM_DEBUG_ERROR, "QQ", + "Fail create qq header, expect %d bytes, written %d bytes\n", bytes_expected, bytes_written); + bytes_written = -1; + } + return bytes_written; +} // _create_packet_head_seq + +/*****************************************************************************/ +// for those need ack and resend no ack feed back from server +// return number of bytes written to the socket, +// return -1 if there is any error +static gint _qq_send_packet(GaimConnection * gc, guint8 * buf, gint len, guint16 cmd) +{ + qq_data *qd; + qq_sendpacket *p; + gint bytes_sent; + guint8 *cursor; + + g_return_val_if_fail(gc != NULL && gc->proto_data != NULL, -1); + + qd = (qq_data *) gc->proto_data; + + if (qd->use_tcp) { + if (len > MAX_PACKET_SIZE) { + gaim_debug(GAIM_DEBUG_ERROR, "QQ", + "xxx [%05d] %s, %d bytes is too large, do not send\n", + qq_get_cmd_desc(cmd), qd->send_seq, len); + return -1; + } else { // I update the len for TCP packet + cursor = buf; + create_packet_w(buf, &cursor, len); + } // if len + } // if use_tcp + + bytes_sent = qq_proxy_write(qd, buf, len); + + if (bytes_sent >= 0) { //put to queue, for matching server ACK usage + p = g_new0(qq_sendpacket, 1); + p->fd = qd->fd; + p->cmd = cmd; + p->send_seq = qd->send_seq; + p->resend_times = 0; + p->sendtime = time(NULL); + p->buf = g_memdup(buf, len); // don't use g_strdup, may have 0x00 + p->len = len; + qd->sendqueue = g_list_append(qd->sendqueue, p); + } // if bytes_sent + + return bytes_sent; + +} // _qq_send_packet + +/*****************************************************************************/ +// send the packet generated with the given cmd and data +// return the number of bytes sent to socket if succeeds +// return -1 if there is any error +gint qq_send_cmd(GaimConnection * gc, guint16 cmd, + gboolean is_auto_seq, guint16 seq, gboolean need_ack, guint8 * data, gint len) +{ + qq_data *qd; + guint8 *buf, *cursor, *encrypted_data; + guint16 seq_ret; + gint encrypted_len, bytes_written, bytes_expected, bytes_sent; + + g_return_val_if_fail(gc != NULL && gc->proto_data != NULL, -1); + + qd = (qq_data *) gc->proto_data; + g_return_val_if_fail(qd->session_key != NULL, -1); + + // use g_newa, so that the allocated memory will be freed + // when the current stack frame is cleaned up + buf = g_newa(guint8, MAX_PACKET_SIZE); + encrypted_len = len + 16; // at most 16 bytes more + encrypted_data = g_newa(guint8, encrypted_len); + cursor = buf; + bytes_written = 0; + + qq_crypt(ENCRYPT, data, len, qd->session_key, encrypted_data, &encrypted_len); + + seq_ret = seq; + if (_create_packet_head_seq(buf, &cursor, gc, cmd, is_auto_seq, &seq_ret) >= 0) { + bytes_expected = 4 + encrypted_len + 1; + bytes_written += create_packet_dw(buf, &cursor, (guint32) qd->uid); + bytes_written += create_packet_data(buf, &cursor, encrypted_data, encrypted_len); + bytes_written += create_packet_b(buf, &cursor, QQ_PACKET_TAIL); + if (bytes_written == bytes_expected) { // packet OK + // if it does not need ACK, we send ACK manually several times + if (need_ack) // my request, send it + bytes_sent = _qq_send_packet(gc, buf, cursor - buf, cmd); + else // server's request, send ACK + bytes_sent = qq_proxy_write(qd, buf, cursor - buf); + + if (QQ_DEBUG) + gaim_debug(GAIM_DEBUG_INFO, "QQ", + "<== [%05d] %s, %d bytes\n", seq_ret, qq_get_cmd_desc(cmd), bytes_sent); + return bytes_sent; + } else { // bad packet + gaim_debug(GAIM_DEBUG_ERROR, "QQ", + "Fail creating packet, expect %d bytes, written %d bytes\n", + bytes_expected, bytes_written); + return -1; + } // if bytes_written + } // if create_packet_head_seq + return -1; +} // qq_send_cmd + +/*****************************************************************************/ +// END OF FILE diff -r 5642f4658b59 -r 983fd420e86b src/protocols/qq/send_core.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/protocols/qq/send_core.h Mon Jun 26 02:58:54 2006 +0000 @@ -0,0 +1,40 @@ +/** +* The QQ2003C protocol plugin + * + * for gaim + * + * Copyright (C) 2004 Puzzlebird + * + * 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 + */ + +// START OF FILE +/*****************************************************************************/ +#ifndef _QQ_SEND_CORE_H_ +#define _QQ_SEND_CORE_H_ + +#include +#include "connection.h" // GaimConnection + + +#define QQ_CLIENT 0x0E1B + +gint +qq_send_cmd(GaimConnection * gc, + guint16 cmd, gboolean is_auto_seq, guint16 seq, gboolean need_ack, guint8 * data, gint len); + +#endif +/*****************************************************************************/ +// END OF FILE diff -r 5642f4658b59 -r 983fd420e86b src/protocols/qq/send_file.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/protocols/qq/send_file.c Mon Jun 26 02:58:54 2006 +0000 @@ -0,0 +1,978 @@ +/** + * The QQ2003C protocol plugin + * + * for gaim + * + * Author: Henry Ou + * + * Copyright (C) 2004 Puzzlebird + * + * 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 , beta2, gfhuang + +#include "send_file.h" +#include "debug.h" +#include "notify.h" +#include "network.h" //gaim_network_get_my_ip + +#include "im.h" //qq_create_packet_im_header +#include "packet_parse.h" +#include "crypt.h" +#include "header_info.h" +#include "send_core.h" +#include "utils.h" // gaim_name_to_uid +#include "file_trans.h" // qq_send_file_ctl +#include "qq.h" +#include "buddy_status.h" // by gfhuang +#include "keep_alive.h" //by gfhuang + +enum +{ + QQ_FILE_TRANS_REQ = 0x0035, + QQ_FILE_TRANS_ACC_UDP = 0x0037, + QQ_FILE_TRANS_ACC_TCP = 0x0003, + QQ_FILE_TRANS_DENY_UDP = 0x0039, + QQ_FILE_TRANS_DENY_TCP = 0x0005, + QQ_FILE_TRANS_NOTIFY = 0x003b, + QQ_FILE_TRANS_NOTIFY_ACK = 0x003c, + QQ_FILE_TRANS_CANCEL = 0x0049, + QQ_FILE_TRANS_PASV = 0x003f +}; + +static int _qq_in_same_lan(ft_info *info) +{ + if (info->remote_internet_ip == info->local_internet_ip) return 1; + gaim_debug(GAIM_DEBUG_INFO, "QQ", "Not in the same LAN, remote internet ip[%x], local internet ip[%x]\n", info->remote_internet_ip + , info->local_internet_ip); + return 0; +} + +static int _qq_xfer_init_udp_channel(ft_info *info) +{ + struct sockaddr_in sin; + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + if (!_qq_in_same_lan(info)) { + sin.sin_port = htons(info->remote_major_port); + sin.sin_addr.s_addr = htonl(info->remote_internet_ip); + } else { + sin.sin_port = htons(info->remote_minor_port); + sin.sin_addr.s_addr = htonl(info->remote_real_ip); + } + return 0; +// return connect(info->sender_fd, (struct sockaddr *) &sin, sizeof(sin)); +} + +/* these 2 functions send and recv buffer from/to UDP channel */ +static ssize_t _qq_xfer_udp_recv(char *buf, size_t len, GaimXfer *xfer) +{ + struct sockaddr_in sin; + int sinlen; + ft_info *info; + int r; + + info = (ft_info *) xfer->data; + sinlen = sizeof(sin); + r = recvfrom(info->recv_fd, buf, len, 0, (struct sockaddr *) &sin, &sinlen); + gaim_debug(GAIM_DEBUG_INFO, "QQ", "==> recv %d bytes from File UDP Channel, remote ip[%s], remote port[%d]\n", + r, inet_ntoa(sin.sin_addr), ntohs(sin.sin_port)); + return r; +} + +/* +static ssize_t _qq_xfer_udp_send(const char *buf, size_t len, GaimXfer *xfer) +{ + ft_info *info; + + info = (ft_info *) xfer->data; + return send(info->sender_fd, buf, len, 0); +} +*/ +static ssize_t _qq_xfer_udp_send(const char *buf, size_t len, GaimXfer *xfer) +{ + struct sockaddr_in sin; + ft_info *info; + + info = (ft_info *) xfer->data; + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + if (!_qq_in_same_lan(info)) { + sin.sin_port = htons(info->remote_major_port); + sin.sin_addr.s_addr = htonl(info->remote_internet_ip); + } else if (info->use_major) { + sin.sin_port = htons(info->remote_major_port); + sin.sin_addr.s_addr = htonl(info->remote_real_ip); + } else { + sin.sin_port = htons(info->remote_minor_port); + sin.sin_addr.s_addr = htonl(info->remote_real_ip); + } + gaim_debug(GAIM_DEBUG_INFO, "QQ", "sending to channel: %d.%d.%d.%d:%d\n", + sin.sin_addr.s_addr & 0xff, + (sin.sin_addr.s_addr >> 8) & 0xff, + (sin.sin_addr.s_addr >> 16) & 0xff, + sin.sin_addr.s_addr >> 24, + ntohs(sin.sin_port) + ); + return sendto(info->sender_fd, buf, len, 0, (struct sockaddr *) &sin, sizeof(sin)); +} + +/* user-defined functions for gaim_xfer_read and gaim_xfer_write */ + +/* +static ssize_t _qq_xfer_read(char **buf, GaimXfer *xfer) +{ + *buf = g_newa(char, QQ_FILE_FRAGMENT_MAXLEN + 100); + return _qq_xfer_udp_recv(*buf, QQ_FILE_FRAGMENT_MAXLEN + 100, xfer); +} +*/ + +static gssize _qq_xfer_write(const guchar *buf, size_t len, GaimXfer *xfer) //gfhuang +{ + return _qq_xfer_udp_send(buf, len, xfer); +} + +static void +_qq_xfer_recv_packet(gpointer data, gint source, GaimInputCondition condition) +{ + GaimXfer *xfer = (GaimXfer *) data; + GaimAccount *account = gaim_xfer_get_account(xfer); + GaimConnection *gc = gaim_account_get_connection(account); + guint8 *buf; + gint size; + /* FIXME: It seems that the transfer never use a packet + * larger than 1500 bytes, so if it happened to be a + * larger packet, either error occured or protocol should + * be modified + */ + ft_info *info; + info = xfer->data; + g_return_if_fail (source == info->recv_fd); + buf = g_newa(guint8, 1500); + size = _qq_xfer_udp_recv(buf, 1500, xfer); + qq_process_recv_file(gc, buf, size); +} +/*****************************************************************************/ +// start file transfer process +static void +_qq_xfer_send_start (GaimXfer * xfer) +{ + GaimAccount *account; + GaimConnection *gc; + ft_info *info; + + account = gaim_xfer_get_account(xfer); + gc = gaim_account_get_connection(account); + info = (ft_info *) xfer->data; +} + +static void +_qq_xfer_send_ack (GaimXfer *xfer, const char *buffer, size_t len) +{ + + GaimAccount *account; + GaimConnection *gc; + + account = gaim_xfer_get_account(xfer); + gc = gaim_account_get_connection(account); + qq_process_recv_file(gc, (guint8 *) buffer, len); +} + +static void +_qq_xfer_recv_start(GaimXfer *xfer) +{ +} + +static void +_qq_xfer_end(GaimXfer *xfer) +{ + ft_info *info; + g_return_if_fail(xfer != NULL && xfer->data != NULL); + info = (ft_info *) xfer->data; + + qq_xfer_close_file(xfer); + if (info->dest_fp != NULL) { + fclose(info->dest_fp); + gaim_debug(GAIM_DEBUG_INFO, "QQ", "file closed\n"); + } + if (info->major_fd != 0) { + close(info->major_fd); + gaim_debug(GAIM_DEBUG_INFO, "QQ", "major port closed\n"); + } + if (info->minor_fd != 0) { + close(info->minor_fd); + gaim_debug(GAIM_DEBUG_INFO, "QQ", "minor port closed\n"); + } +// if (info->buffer != NULL) { +// munmap(info->buffer, gaim_xfer_get_size(xfer)); +// gaim_debug(GAIM_DEBUG_INFO, "QQ", "file mapping buffer is freed.\n"); +// } + g_free(info); +} + +void qq_show_conn_info(ft_info *info) +{ + gchar *internet_ip_str, *real_ip_str; + guint32 ip; + + ip = htonl(info->remote_real_ip); + real_ip_str = gen_ip_str((guint8 *) &ip); + ip = htonl(info->remote_internet_ip); + internet_ip_str = gen_ip_str((guint8 *) &ip); + gaim_debug(GAIM_DEBUG_INFO, "QQ", "remote internet ip[%s:%d], major port[%d], real ip[%s], minor port[%d]\n", + internet_ip_str, info->remote_internet_port, + info->remote_major_port, real_ip_str, info->remote_minor_port + ); + g_free(real_ip_str); + g_free(internet_ip_str); +} + +void qq_get_conn_info(guint8 *data, guint8 **cursor, gint data_len, ft_info *info) +{ + read_packet_data(data, cursor, data_len, info->file_session_key, 16); + *cursor += 30; + read_packet_b(data, cursor, data_len, &info->conn_method); + read_packet_dw(data, cursor, data_len, &info->remote_internet_ip); + read_packet_w(data, cursor, data_len, &info->remote_internet_port); + read_packet_w(data, cursor, data_len, &info->remote_major_port); + read_packet_dw(data, cursor, data_len, &info->remote_real_ip); + read_packet_w(data, cursor, data_len, &info->remote_minor_port); + qq_show_conn_info(info); +} + +gint qq_fill_conn_info(guint8 *raw_data, guint8 ** cursor, ft_info *info) +{ + gint bytes; + bytes = 0; + // 064: connection method, UDP 0x00, TCP 0x03 + bytes += create_packet_b (raw_data, cursor, info->conn_method); + // 065-068: outer ip address of sender (proxy address) + bytes += create_packet_dw (raw_data, cursor, info->local_internet_ip); + // 069-070: sender port + bytes += create_packet_w (raw_data, cursor, info->local_internet_port); + // 071-072: the first listening port(TCP doesn't have this part) + bytes += create_packet_w (raw_data, cursor, info->local_major_port); + // 073-076: real ip + bytes += create_packet_dw (raw_data, cursor, info->local_real_ip); + // 077-078: the second listening port + bytes += create_packet_w (raw_data, cursor, info->local_minor_port); + return bytes; +} + + +extern gchar *_gen_session_md5(gint uid, gchar *session_key); + +/*****************************************************************************/ +// fill in the common information of file transfer +static gint _qq_create_packet_file_header +(guint8 *raw_data, guint8 ** cursor, guint32 to_uid, guint16 message_type, qq_data *qd, gboolean seq_ack) +{ + gint bytes; + time_t now; + gchar *md5; + guint16 seq; + ft_info *info; + + bytes = 0; + now = time(NULL); + md5 = _gen_session_md5(qd->uid, qd->session_key); + if (!seq_ack) seq = qd->send_seq; + else { + info = (ft_info *) qd->xfer->data; + seq = info->send_seq; + } + + // 000-003: receiver uid + bytes += create_packet_dw (raw_data, cursor, qd->uid); + // 004-007: sender uid + bytes += create_packet_dw (raw_data, cursor, to_uid); + // 008-009: sender client version + bytes += create_packet_w (raw_data, cursor, QQ_CLIENT); + // 010-013: receiver uid + bytes += create_packet_dw (raw_data, cursor, qd->uid); + // 014-017: sender uid + bytes += create_packet_dw (raw_data, cursor, to_uid); + // 018-033: md5 of (uid+session_key) + bytes += create_packet_data (raw_data, cursor, md5, 16); + // 034-035: message type + bytes += create_packet_w (raw_data, cursor, message_type); + // 036-037: sequence number + bytes += create_packet_w (raw_data, cursor, seq); + // 038-041: send time + bytes += create_packet_dw (raw_data, cursor, (guint32) now); + // 042-042: always 0x00 + bytes += create_packet_b (raw_data, cursor, 0x00); + // 043-043: sender icon + bytes += create_packet_b (raw_data, cursor, qd->my_icon); + // 044-046: always 0x00 + bytes += create_packet_w (raw_data, cursor, 0x0000); + bytes += create_packet_b (raw_data, cursor, 0x00); + // 047-047: we use font attr + bytes += create_packet_b (raw_data, cursor, 0x01); + // 048-051: always 0x00 + bytes += create_packet_dw (raw_data, cursor, 0x00000000); + + // 052-062: always 0x00 + bytes += create_packet_dw (raw_data, cursor, 0x00000000); + bytes += create_packet_dw (raw_data, cursor, 0x00000000); + bytes += create_packet_w (raw_data, cursor, 0x0000); + bytes += create_packet_b (raw_data, cursor, 0x00); + // 063: transfer_type, 0x65: FILE 0x6b: FACE + bytes += create_packet_b (raw_data, cursor, QQ_FILE_TRANSFER_FILE); /* FIXME by gfhuang */ + + g_free (md5); + return bytes; +} //_qq_create_packet_file_header + + +#if 0 +in_addr_t get_real_ip() +{ + char hostname[40]; + struct hostent *host; + + gethostname(hostname, sizeof(hostname)); + host = gethostbyname(hostname); + return *(host->h_addr); +} + + +#include +#include + +#define MAXINTERFACES 16 +in_addr_t get_real_ip() +{ + int fd, intrface, i; + struct ifconf ifc; + struct ifreq buf[MAXINTERFACES]; + in_addr_t ret; + + if ((fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) return 0; + ifc.ifc_len = sizeof(buf); + ifc.ifc_buf = (caddr_t) buf; + if (ioctl(fd, SIOCGIFCONF, (char *) &ifc) < 0) return 0; + intrface = ifc.ifc_len / sizeof(struct ifreq); + for (i = 0; i < intrface; i++) { + //buf[intrface].ifr_name + if (ioctl(fd, SIOCGIFADDR, (char *) &buf[i]) >= 0) + { + ret = (((struct sockaddr_in *)(&buf[i].ifr_addr))->sin_addr).s_addr; + if (ret == ntohl(0x7f000001)) continue; + return ret; + } + } + return 0; +} +#endif + +static void +_qq_xfer_init_socket(GaimXfer *xfer) +{ + int sockfd, listen_port = 0, i, sin_len; + struct sockaddr_in sin; + ft_info *info; + + g_return_if_fail(xfer != NULL); + g_return_if_fail(xfer->data != NULL); + info = (ft_info *) xfer->data; + +// info->local_real_ip = ntohl(get_real_ip()); +//debug +// info->local_real_ip = 0x7f000001; + info->local_real_ip = ntohl(inet_addr(gaim_network_get_my_ip(-1))); + gaim_debug(GAIM_DEBUG_INFO, "QQ", "local real ip is %x", info->local_real_ip); + + for (i = 0; i < 2; i++) { + sockfd = socket(PF_INET, SOCK_DGRAM, 0); + g_return_if_fail(sockfd >= 0); + + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_port = 0; + sin.sin_addr.s_addr = INADDR_ANY; + sin_len = sizeof(sin); + bind(sockfd, (struct sockaddr *) &sin, sin_len); + getsockname(sockfd, (struct sockaddr *) &sin, &sin_len); + listen_port = ntohs(sin.sin_port); + + switch (i) { + case 0: + info->local_major_port = listen_port; + info->major_fd = sockfd; + gaim_debug(GAIM_DEBUG_INFO, "QQ", "UDP Major Channel created on port[%d]\n", + info->local_major_port); + break; + case 1: + info->local_minor_port = listen_port; + info->minor_fd = sockfd; + gaim_debug(GAIM_DEBUG_INFO, "QQ", "UDP Minor Channel created on port[%d]\n", + info->local_minor_port); + break; + } + } + + if (_qq_in_same_lan(info)) { + info->sender_fd = info->recv_fd = info->minor_fd; + } else { + info->sender_fd = info->recv_fd = info->major_fd; + } + +// xfer->watcher = gaim_input_add(info->recv_fd, GAIM_INPUT_READ, _qq_xfer_recv_packet, xfer); +} + +/*****************************************************************************/ +// create the QQ_FILE_TRANS_REQ packet with file infomations +static void +_qq_send_packet_file_request (GaimConnection * gc, guint32 to_uid, + gchar * filename, gint filesize) +{ + qq_data *qd; + guint8 *cursor, *raw_data; + gchar *filelen_str; + gint filename_len, filelen_strlen, packet_len, bytes; + ft_info *info; + + qd = (qq_data *) gc->proto_data; + + info = g_new0(ft_info, 1); + info->to_uid = to_uid; + info->send_seq = qd->send_seq; + info->local_internet_ip = ntohl(inet_addr(qd->my_ip)); + info->local_internet_port = qd->my_port; + info->local_real_ip = 0x00000000; + info->conn_method = 0x00; + qd->xfer->data = info; + + filename_len = strlen (filename); + filelen_str = g_strdup_printf ("%d ×Ö½Ú", filesize); + filelen_strlen = strlen (filelen_str); + + packet_len = 82 + filename_len + filelen_strlen; + raw_data = g_newa (guint8, packet_len); + cursor = raw_data; + + bytes = _qq_create_packet_file_header(raw_data, &cursor, to_uid, QQ_FILE_TRANS_REQ, qd, FALSE); + bytes += qq_fill_conn_info(raw_data, &cursor, info); + // 079: 0x20 + bytes += create_packet_b (raw_data, &cursor, 0x20); + // 080: 0x1f + bytes += create_packet_b (raw_data, &cursor, 0x1f); + // undetermined len: filename + bytes += create_packet_data (raw_data, &cursor, filename, + filename_len); + // 0x1f + bytes += create_packet_b (raw_data, &cursor, 0x1f); + // file length + bytes += create_packet_data (raw_data, &cursor, filelen_str, + filelen_strlen); + + if (packet_len == bytes) + qq_send_cmd (gc, QQ_CMD_SEND_IM, TRUE, 0, TRUE, raw_data, + cursor - raw_data); + else + gaim_debug (GAIM_DEBUG_INFO, "qq_send_packet_file_request", + "%d bytes expected but got %d bytes\n", + packet_len, bytes); + + g_free (filelen_str); +} //_qq_send_packet_file_request + +/*****************************************************************************/ +// tell the buddy we want to accept the file +static void +_qq_send_packet_file_accept(GaimConnection *gc, guint32 to_uid) +{ + qq_data *qd; + guint8 *cursor, *raw_data; + gint packet_len, bytes; + ft_info *info; + + g_return_if_fail (gc != NULL && gc->proto_data != NULL); + qd = (qq_data *) gc->proto_data; + info = (ft_info *) qd->xfer->data; + + gaim_debug(GAIM_DEBUG_INFO, "QQ", "I've accepted the file transfer request from %d\n", to_uid); + _qq_xfer_init_socket(qd->xfer); + guint16 minor_port; + guint32 real_ip; + + packet_len = 79; + raw_data = g_newa (guint8, packet_len); + cursor = raw_data; + + minor_port = info->local_minor_port; + real_ip = info->local_real_ip; + info->local_minor_port = 0; + info->local_real_ip = 0; + + bytes = _qq_create_packet_file_header(raw_data, &cursor, to_uid, QQ_FILE_TRANS_ACC_UDP, qd, TRUE); + bytes += qq_fill_conn_info(raw_data, &cursor, info); + + info->local_minor_port = minor_port; + info->local_real_ip = real_ip; + + if (packet_len == bytes) + qq_send_cmd (gc, QQ_CMD_SEND_IM, TRUE, 0, TRUE, raw_data, + cursor - raw_data); + else + gaim_debug (GAIM_DEBUG_INFO, "qq_send_packet_file_accept", + "%d bytes expected but got %d bytes\n", + packet_len, bytes); +} //_qq_send_packet_packet_accept + +static void _qq_send_packet_file_notifyip(GaimConnection *gc, guint32 to_uid) +{ + GaimXfer *xfer; + ft_info *info; + qq_data *qd; + guint8 *cursor, *raw_data; + gint packet_len, bytes; + + qd = (qq_data *) gc->proto_data; + xfer = qd->xfer; + info = xfer->data; + + packet_len = 79; + raw_data = g_newa (guint8, packet_len); + cursor = raw_data; + + gaim_debug(GAIM_DEBUG_INFO, "QQ", "<== sending qq file notify ip packet\n"); + bytes = _qq_create_packet_file_header(raw_data, &cursor, to_uid, QQ_FILE_TRANS_NOTIFY, qd, TRUE); + bytes += qq_fill_conn_info(raw_data, &cursor, info); + if (packet_len == bytes) + qq_send_cmd (gc, QQ_CMD_SEND_IM, TRUE, 0, TRUE, raw_data, + cursor - raw_data); + else + gaim_debug (GAIM_DEBUG_INFO, "qq_send_packet_file_notify", + "%d bytes expected but got %d bytes\n", + packet_len, bytes); + + if (xfer->watcher) gaim_input_remove(xfer->watcher); + xfer->watcher = gaim_input_add(info->recv_fd, GAIM_INPUT_READ, _qq_xfer_recv_packet, xfer); + gaim_input_add(info->major_fd, GAIM_INPUT_READ, _qq_xfer_recv_packet, xfer); +// gaim_input_add(info->minor_fd, GAIM_INPUT_READ, _qq_xfer_recv_packet, xfer); +} + +/*****************************************************************************/ +// tell the buddy we don't want the file +static void +_qq_send_packet_file_reject (GaimConnection *gc, guint32 to_uid) +{ + qq_data *qd; + guint8 *cursor, *raw_data; + gint packet_len, bytes; + + gaim_debug(GAIM_DEBUG_INFO, "_qq_send_packet_file_reject", "start"); + g_return_if_fail(gc != NULL && gc->proto_data != NULL); + qd = (qq_data *) gc->proto_data; + + packet_len = 64; + raw_data = g_newa (guint8, packet_len); + cursor = raw_data; + bytes = 0; + + bytes = _qq_create_packet_file_header(raw_data, &cursor, to_uid, QQ_FILE_TRANS_DENY_UDP, qd, TRUE); + + if (packet_len == bytes) + qq_send_cmd (gc, QQ_CMD_SEND_IM, TRUE, 0, TRUE, raw_data, + cursor - raw_data); + else + gaim_debug (GAIM_DEBUG_INFO, "qq_send_packet_file", + "%d bytes expected but got %d bytes\n", + packet_len, bytes); + +// gaim_debug (GAIM_DEBUG_INFO, "qq_send_packet_file_reject", "end\n"); +} // qq_send_packet_file_reject + +/*****************************************************************************/ +// tell the buddy to cancel transfer +static void +_qq_send_packet_file_cancel (GaimConnection *gc, guint32 to_uid) +{ + qq_data *qd; + guint8 *cursor, *raw_data; + gint packet_len, bytes; + + gaim_debug(GAIM_DEBUG_INFO, "_qq_send_packet_file_cancel", "start\n"); + g_return_if_fail(gc != NULL && gc->proto_data != NULL); + qd = (qq_data *) gc->proto_data; + + packet_len = 64; + raw_data = g_newa (guint8, packet_len); + cursor = raw_data; + bytes = 0; + + gaim_debug(GAIM_DEBUG_INFO, "_qq_send_packet_file_cancel", "before create header\n"); + bytes = _qq_create_packet_file_header(raw_data, &cursor, to_uid, QQ_FILE_TRANS_CANCEL, qd, TRUE); + gaim_debug(GAIM_DEBUG_INFO, "_qq_send_packet_file_cancel", "end create header\n"); + + if (packet_len == bytes) { + gaim_debug(GAIM_DEBUG_INFO, "_qq_send_packet_file_cancel", "before send cmd\n"); + qq_send_cmd (gc, QQ_CMD_SEND_IM, TRUE, 0, TRUE, raw_data, + cursor - raw_data); + } + else + gaim_debug (GAIM_DEBUG_INFO, "qq_send_packet_file", + "%d bytes expected but got %d bytes\n", + packet_len, bytes); + + gaim_debug (GAIM_DEBUG_INFO, "qq_send_packet_file_cancel", "end\n"); +} // qq_send_packet_file_cancel + +/*****************************************************************************/ +// request to send a file +static void +_qq_xfer_init (GaimXfer * xfer) +{ + GaimConnection *gc; + GaimAccount *account; + guint32 to_uid; + gchar *filename, *filename_without_path; + + g_return_if_fail (xfer != NULL); + account = gaim_xfer_get_account(xfer); + gc = gaim_account_get_connection(account); + g_return_if_fail (gc != NULL && gc->proto_data != NULL); + + to_uid = gaim_name_to_uid (xfer->who); + g_return_if_fail (to_uid != 0); + + filename = (gchar *) gaim_xfer_get_local_filename (xfer); + g_return_if_fail (filename != NULL); + + filename_without_path = strrchr (filename, '/') + 1; + + _qq_send_packet_file_request (gc, to_uid, filename_without_path, + gaim_xfer_get_size(xfer)); +} // qq_xfer_init + +/*****************************************************************************/ +// cancel the transfer of receiving files +static void +_qq_xfer_cancel(GaimXfer *xfer) +{ + GaimConnection *gc; + GaimAccount *account; + guint16 *seq; + + g_return_if_fail (xfer != NULL); + seq = (guint16 *) xfer->data; + account = gaim_xfer_get_account(xfer); + gc = gaim_account_get_connection(account); + + switch (gaim_xfer_get_status(xfer)) { + case GAIM_XFER_STATUS_CANCEL_LOCAL: + _qq_send_packet_file_cancel(gc, gaim_name_to_uid(xfer->who)); + break; + case GAIM_XFER_STATUS_CANCEL_REMOTE: + _qq_send_packet_file_cancel(gc, gaim_name_to_uid(xfer->who)); + break; + case GAIM_XFER_STATUS_NOT_STARTED: + break; + case GAIM_XFER_STATUS_UNKNOWN: + _qq_send_packet_file_reject(gc, gaim_name_to_uid(xfer->who)); + break; + case GAIM_XFER_STATUS_DONE: + break; + case GAIM_XFER_STATUS_ACCEPTED: + break; + case GAIM_XFER_STATUS_STARTED: + break; + } +} + +/*****************************************************************************/ +// init the transfer of receiving files +static void +_qq_xfer_recv_init(GaimXfer *xfer) +{ + GaimConnection *gc; + GaimAccount *account; + ft_info *info; + + g_return_if_fail (xfer != NULL && xfer->data != NULL); + info = (ft_info *) xfer->data; + account = gaim_xfer_get_account(xfer); + gc = gaim_account_get_connection(account); + + _qq_send_packet_file_accept(gc, gaim_name_to_uid(xfer->who)); +} + +/*****************************************************************************/ +// process reject im for file transfer request +void qq_process_recv_file_reject + (guint8 * data, guint8 ** cursor, gint data_len, guint32 sender_uid, + GaimConnection * gc) +{ + gchar *msg, *filename; + qq_data *qd; + + g_return_if_fail (gc != NULL && data != NULL && data_len != 0); + qd = (qq_data *) gc->proto_data; + g_return_if_fail (qd != NULL && qd->xfer != NULL); + + if (*cursor >= (data + data_len - 1)) { + gaim_debug (GAIM_DEBUG_WARNING, "QQ", + "Received file reject message is empty\n"); + return; + } + filename = strrchr(gaim_xfer_get_local_filename(qd->xfer), '/') + 1; + msg = g_strdup_printf + (_ + ("Your request to send file[%s] has been rejected by buddy[%d]"), + filename, sender_uid); + + gaim_notify_warning (gc, _("File Send"), msg, NULL); + gaim_xfer_request_denied(qd->xfer); + qd->xfer = NULL; + + g_free (msg); +} // qq_process_recv_file_reject + +/*****************************************************************************/ +// process cancel im for file transfer request +void qq_process_recv_file_cancel + (guint8 * data, guint8 ** cursor, gint data_len, guint32 sender_uid, + GaimConnection * gc) +{ + gchar *msg, *filename; + qq_data *qd; + + g_return_if_fail (gc != NULL && data != NULL && data_len != 0); + qd = (qq_data *) gc->proto_data; + g_return_if_fail (qd != NULL && qd->xfer != NULL + && gaim_xfer_get_filename(qd->xfer) != NULL); + + if (*cursor >= (data + data_len - 1)) { + gaim_debug (GAIM_DEBUG_WARNING, "QQ", + "Received file reject message is empty\n"); + return; + } + filename = strrchr(gaim_xfer_get_local_filename(qd->xfer), '/') + 1; + msg = g_strdup_printf + (_("The sending process of file[%s] has been cancaled by buddy[%d]"), + filename, sender_uid); + + gaim_notify_warning (gc, _("File Send"), msg, NULL); + gaim_xfer_cancel_remote(qd->xfer); + qd->xfer = NULL; + + g_free (msg); +} // qq_process_recv_file_cancel + +/*****************************************************************************/ +// process accept im for file transfer request +void qq_process_recv_file_accept + (guint8 * data, guint8 ** cursor, gint data_len, guint32 sender_uid, + GaimConnection * gc) +{ + qq_data *qd; + ft_info *info; + GaimXfer *xfer; + + g_return_if_fail (gc != NULL && data != NULL && data_len != 0); + qd = (qq_data *) gc->proto_data; + xfer = qd->xfer; + + if (*cursor >= (data + data_len - 1)) { + gaim_debug (GAIM_DEBUG_WARNING, "QQ", + "Received file reject message is empty\n"); + return; + } + + info = (ft_info *) qd->xfer->data; + + *cursor = data + 18 + 12; + qq_get_conn_info(data, cursor, data_len, info); + _qq_xfer_init_socket(qd->xfer); + + _qq_xfer_init_udp_channel(info); + _qq_send_packet_file_notifyip(gc, sender_uid); +} // qq_process_recv_file_accept + +/*****************************************************************************/ +// process request from buddy's im for file transfer request +void qq_process_recv_file_request + (guint8 * data, guint8 ** cursor, gint data_len, guint32 sender_uid, + GaimConnection * gc) +{ + qq_data *qd; + GaimXfer *xfer; + gchar *sender_name; + ft_info *info; + GaimBuddy *b; //by gfhuang + qq_buddy *q_bud; + + g_return_if_fail (gc != NULL && data != NULL && data_len != 0); + qd = (qq_data *) gc->proto_data; + + if (*cursor >= (data + data_len - 1)) { + gaim_debug (GAIM_DEBUG_WARNING, "QQ", + "Received file reject message is empty\n"); + return; + } + + info = g_new0(ft_info, 1); + info->local_internet_ip = ntohl(inet_addr(qd->my_ip)); + info->local_internet_port = qd->my_port; + info->local_real_ip = 0x00000000; + info->to_uid = sender_uid; + read_packet_w(data, cursor, data_len, &(info->send_seq)); + + *cursor = data + 18 + 12; + qq_get_conn_info(data, cursor, data_len, info); + + gchar **fileinfo; + fileinfo = g_strsplit(data + 81 + 12, "\x1f", 2); + g_return_if_fail (fileinfo != NULL && fileinfo[0] != NULL && fileinfo[1] != NULL); + + sender_name = uid_to_gaim_name(sender_uid); + + //FACE from IP detector, ignored by gfhuang + if(g_ascii_strcasecmp(fileinfo[0], "FACE") == 0) { + gaim_debug(GAIM_DEBUG_WARNING, "QQ", + "Received a FACE ip detect from qq-%d, so he/she must be online :)\n", sender_uid); + + b = gaim_find_buddy(gc->account, sender_name); + q_bud = (b == NULL) ? NULL : (qq_buddy *) b->proto_data; + if (q_bud) { + if(0 != info->remote_real_ip) { //by gfhuang + g_memmove(q_bud->ip, &info->remote_real_ip, 4); + q_bud->port = info->remote_minor_port; + } + else if (0 != info->remote_internet_ip) { + g_memmove(q_bud->ip, &info->remote_internet_ip, 4); + q_bud->port = info->remote_major_port; + } + + if(!is_online(q_bud->status)) { + q_bud->status = QQ_BUDDY_ONLINE_INVISIBLE; + qq_update_buddy_contact(gc, q_bud); + } + else + gaim_debug(GAIM_DEBUG_INFO, "QQ", "buddy %d is already online\n", sender_uid); + + } + else + gaim_debug(GAIM_DEBUG_WARNING, "QQ", "buddy %d is not in my friendlist\n", sender_uid); + + g_free(sender_name); + g_strfreev(fileinfo); + return; + } + + xfer = gaim_xfer_new(gaim_connection_get_account(gc), + GAIM_XFER_RECEIVE, + sender_name); + gaim_xfer_set_filename(xfer, fileinfo[0]); + gaim_xfer_set_size(xfer, atoi(fileinfo[1])); + + gaim_xfer_set_init_fnc(xfer, _qq_xfer_recv_init); + gaim_xfer_set_request_denied_fnc(xfer, _qq_xfer_cancel); + gaim_xfer_set_cancel_recv_fnc(xfer, _qq_xfer_cancel); + gaim_xfer_set_end_fnc(xfer, _qq_xfer_end); + gaim_xfer_set_write_fnc(xfer, _qq_xfer_write); + + xfer->data = info; + qd->xfer = xfer; + + gaim_xfer_request(xfer); + + g_free(sender_name); + g_strfreev(fileinfo); + +// gaim_debug (GAIM_DEBUG_INFO, "qq_process_recv_file_request", "end\n"); +} // qq_process_recv_file_request + +static void +_qq_xfer_send_notify_ip_ack(gpointer data, gint source, GaimInputCondition cond) +{ + GaimXfer *xfer = (GaimXfer *) data; + GaimAccount *account = gaim_xfer_get_account(xfer); + GaimConnection *gc = gaim_account_get_connection(account); + ft_info *info = (ft_info *) xfer->data; + + gaim_input_remove(xfer->watcher); + xfer->watcher = gaim_input_add(info->recv_fd, GAIM_INPUT_READ, _qq_xfer_recv_packet, xfer); + qq_send_file_ctl_packet(gc, QQ_FILE_CMD_NOTIFY_IP_ACK, info->to_uid, 0); +// info->use_major = TRUE; +// qq_send_file_ctl_packet(gc, QQ_FILE_CMD_NOTIFY_IP_ACK, info->to_uid, 0); +// info->use_major = FALSE; +} + +void qq_process_recv_file_notify + (guint8 * data, guint8 ** cursor, gint data_len, guint32 sender_uid, + GaimConnection * gc) +{ + qq_data *qd; + ft_info *info; + GaimXfer *xfer; + + g_return_if_fail (gc != NULL && data != NULL && data_len != 0); + qd = (qq_data *) gc->proto_data; + + if (*cursor >= (data + data_len - 1)) { + gaim_debug (GAIM_DEBUG_WARNING, "QQ", + "Received file notify message is empty\n"); + return; + } + + xfer = qd->xfer; + info = (ft_info *) qd->xfer->data; + /* FIXME */ + read_packet_w(data, cursor, data_len, &(info->send_seq)); + + *cursor = data + 18 + 12; + qq_get_conn_info(data, cursor, data_len, info); + + _qq_xfer_init_udp_channel(info); + + xfer->watcher = gaim_input_add(info->sender_fd, GAIM_INPUT_WRITE, _qq_xfer_send_notify_ip_ack, xfer); +} + +//temp placeholder until a working function can be implemented +gboolean qq_can_receive_file(GaimConnection *gc, const char *who) +{ + return TRUE; +} + +void qq_send_file(GaimConnection *gc, const char *who, const char *file) +{ + qq_data *qd; + GaimXfer *xfer; + + g_return_if_fail (gc != NULL && gc->proto_data != NULL); + qd = (qq_data *) gc->proto_data; + + xfer = gaim_xfer_new (gc->account, GAIM_XFER_SEND, + who); + gaim_xfer_set_init_fnc (xfer, _qq_xfer_init); + gaim_xfer_set_cancel_send_fnc (xfer, _qq_xfer_cancel); + gaim_xfer_set_write_fnc(xfer, _qq_xfer_write); + + qd->xfer = xfer; + gaim_xfer_request (xfer); +} + +static void qq_send_packet_request_key(GaimConnection *gc, guint8 key) +{ + qq_send_cmd(gc, QQ_CMD_REQUEST_KEY, TRUE, 0, TRUE, &key, 1); +} + +static void qq_process_recv_request_key(GaimConnection *gc) +{ +} diff -r 5642f4658b59 -r 983fd420e86b src/protocols/qq/send_file.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/protocols/qq/send_file.h Mon Jun 26 02:58:54 2006 +0000 @@ -0,0 +1,66 @@ +#ifndef _QQ_QQ_SEND_FILE_H_ +#define _QQ_QQ_SEND_FILE_H_ + +#include "ft.h" + +typedef struct _ft_info { + guint32 to_uid; + guint16 send_seq; + guint8 file_session_key[16]; + guint8 conn_method; + guint32 remote_internet_ip; + guint16 remote_internet_port; + guint16 remote_major_port; + guint32 remote_real_ip; + guint16 remote_minor_port; + guint32 local_internet_ip; + guint16 local_internet_port; + guint16 local_major_port; + guint32 local_real_ip; + guint16 local_minor_port; + /* we use these to control the packets sent or received */ + guint32 fragment_num; + guint32 fragment_len; + /* The max index of sending/receiving fragment + * for sender, it is the lower bolder of a slide window for sending + * for receiver, it seems that packets having a fragment index lower + * than max_fragment_index have been received already + */ + guint32 max_fragment_index; + guint32 window; + + /* It seems that using xfer's function is not enough for our + * transfer module. So I will use our own structure instead + * of xfer provided + */ + int major_fd; + int minor_fd; + int sender_fd; + int recv_fd; + union { + FILE *dest_fp; + guint8 *buffer; + }; + gboolean use_major; +} ft_info; + +void qq_process_recv_file_accept + (guint8 * data, guint8 ** cursor, gint data_len, guint32 sender_uid, + GaimConnection * gc); +void qq_process_recv_file_reject + (guint8 * data, guint8 ** cursor, gint data_len, guint32 sender_uid, + GaimConnection * gc); +void qq_process_recv_file_cancel + (guint8 * data, guint8 ** cursor, gint data_len, guint32 sender_uid, + GaimConnection * gc); +void qq_process_recv_file_request + (guint8 * data, guint8 ** cursor, gint data_len, guint32 sender_uid, + GaimConnection * gc); +void qq_process_recv_file_notify + (guint8 * data, guint8 ** cursor, gint data_len, guint32 sender_uid, + GaimConnection * gc); +gboolean qq_can_receive_file(GaimConnection *gc, const char *who); +void qq_send_file(GaimConnection *gc, const char *who, const char *file); +void qq_get_conn_info(guint8 *data, guint8 **cursor, gint data_len, ft_info *info); +gint qq_fill_conn_info(guint8 *data, guint8 **cursor, ft_info *info); +#endif diff -r 5642f4658b59 -r 983fd420e86b src/protocols/qq/sendqueue.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/protocols/qq/sendqueue.c Mon Jun 26 02:58:54 2006 +0000 @@ -0,0 +1,230 @@ +/** + * The QQ2003C protocol plugin + * + * for gaim + * + * Copyright (C) 2004 Puzzlebird + * + * 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 + */ + +// START OF FILE +/*****************************************************************************/ +#include "connection.h" // GaimConnection +#include "debug.h" // gaim_debug +#include "internal.h" // _("get_text") +#include "notify.h" // gaim_notify +#include "prefs.h" // gaim_prefs_get_bool +#include "request.h" // gaim_request_action + +#include "header_info.h" // cmd alias +#include "qq_proxy.h" // qq_proxy_write +#include "sendqueue.h" + +#define QQ_RESEND_MAX 5 // max resend per packet + +typedef struct _gc_and_packet gc_and_packet; + +struct _gc_and_packet { + GaimConnection *gc; + qq_sendpacket *packet; +}; + +/*****************************************************************************/ +// Remove a packet with send_seq from sendqueue +void qq_sendqueue_remove(qq_data * qd, guint16 send_seq) +{ + GList *list; + qq_sendpacket *p; + + g_return_if_fail(qd != NULL); + + list = qd->sendqueue; + while (list != NULL) { + p = (qq_sendpacket *) (list->data); + if (p->send_seq == send_seq) { + qd->sendqueue = g_list_remove(qd->sendqueue, p); + g_free(p->buf); + g_free(p); + break; + } + list = list->next; + } // while +} // qq_sendqueue_remove + +/*****************************************************************************/ +// clean up sendqueue and free all contents +void qq_sendqueue_free(qq_data * qd) +{ + qq_sendpacket *p; + gint i; + + i = 0; + while (qd->sendqueue != NULL) { + p = (qq_sendpacket *) (qd->sendqueue->data); + qd->sendqueue = g_list_remove(qd->sendqueue, p); + g_free(p->buf); + g_free(p); + i++; + } + gaim_debug(GAIM_DEBUG_INFO, "QQ", "%d packets in sendqueue are freed!\n", i); +} // qq_sendqueue_free + +/*****************************************************************************/ +// packet lost, agree to send again, (and will NOT prompt again) +// it is removed only when ack-ed by server +static void _qq_send_again(gc_and_packet * gp) +{ + GaimConnection *gc; + qq_data *qd; + qq_sendpacket *packet; + GList *list; + + g_return_if_fail(gp != NULL && gp->gc != NULL && gp->packet != NULL); + g_return_if_fail(gp->gc->proto_data != NULL); + + gc = gp->gc; + packet = gp->packet; + qd = (qq_data *) gc->proto_data; + + list = g_list_find(qd->sendqueue, packet); + if (list != NULL) { + packet->resend_times = 0; + packet->sendtime = time(NULL); + qq_proxy_write(qd, packet->buf, packet->len); + } + g_free(gp); +} // _qq_send_again + +/*****************************************************************************/ +// packet lost, do not send again +static void _qq_send_cancel(gc_and_packet * gp) +{ + GaimConnection *gc; + qq_data *qd; + qq_sendpacket *packet; + GList *list; + + g_return_if_fail(gp != NULL && gp->gc != NULL && gp->packet != NULL); + g_return_if_fail(gp->gc->proto_data != NULL); + + gc = gp->gc; + packet = gp->packet; + qd = (qq_data *) gc->proto_data; + + list = g_list_find(qd->sendqueue, packet); + if (list != NULL) + qq_sendqueue_remove(qd, packet->send_seq); + + g_free(gp); +} // _qq_send_cancel + +/*****************************************************************************/ +gboolean qq_sendqueue_timeout_callback(gpointer data) +{ + GaimConnection *gc; + qq_data *qd; + GList *list; + qq_sendpacket *p; + gc_and_packet *gp; + time_t now; + gint wait_time; + gboolean need_action; + + gc = (GaimConnection *) data; + qd = (qq_data *) gc->proto_data; + now = time(NULL); + list = qd->sendqueue; + + // empty queue, return TRUE so that timeout continues functioning + if (qd->sendqueue == NULL) + return TRUE; + + while (list != NULL) { // remove all packet whose resend_times == -1 + p = (qq_sendpacket *) list->data; + if (p->resend_times == -1) { // to remove + qd->sendqueue = g_list_remove(qd->sendqueue, p); + g_free(p->buf); + g_free(p); + list = qd->sendqueue; + } else + list = list->next; + } // while list + + list = qd->sendqueue; + while (list != NULL) { + p = (qq_sendpacket *) list->data; + if (p->resend_times >= QQ_RESEND_MAX) { + if (p->resend_times == QQ_RESEND_MAX) { // reach max + switch (p->cmd) { + case QQ_CMD_KEEP_ALIVE: + if (qd->logged_in) { + gaim_debug(GAIM_DEBUG_ERROR, "QQ", "Connection lost!\n"); + gaim_connection_error(gc, _("Connection lost!")); + qd->logged_in = FALSE; + } // if logged_in + p->resend_times = -1; + break; + case QQ_CMD_LOGIN: + if (!qd->logged_in) // cancel logging progress + gaim_connection_error(gc, _("Login failed, no reply!")); + p->resend_times = -1; + break; + case QQ_CMD_UPDATE_INFO: + gaim_notify_error(gc, NULL, + _("Connection timeout!"), _("User info is not updated")); + p->resend_times = -1; + break; + default:{ + need_action = + gaim_prefs_get_bool("/plugins/prpl/qq/prompt_for_missing_packet"); + if (!need_action) + p->resend_times = -1; // it will be removed next time + else { // prompt for action + gp = g_new0(gc_and_packet, 1); + gp->gc = gc; + gp->packet = p; + gaim_request_action + (gc, NULL, + _ + ("Send packet"), + _ + ("Packets lost, send again?"), + 0, gp, 2, + _("Send"), + G_CALLBACK + (_qq_send_again), + _("Cancel"), G_CALLBACK(_qq_send_cancel)); + p->resend_times++; // will send once more, but only once + } // if !need_action + } // default + } // switch + } // resend_times == QQ_RESEND_MAX + } else { // resend_times < QQ_RESEND_MAX, so sent it again + wait_time = (gint) (QQ_SENDQUEUE_TIMEOUT / 1000); + if (difftime(now, p->sendtime) > (wait_time * (p->resend_times + 1))) { + qq_proxy_write(qd, p->buf, p->len); + p->resend_times++; + gaim_debug(GAIM_DEBUG_INFO, + "QQ", "<<< [%05d] send again for %d times!\n", p->send_seq, p->resend_times); + } // if difftime + } // if resend_times >= QQ_RESEND_MAX + list = list->next; + } // whiile list + return TRUE; // if we return FALSE, the timeout callback stops functioning +} // qq_sendqueue_timeout_callback + +/*****************************************************************************/ +// END OF FILE diff -r 5642f4658b59 -r 983fd420e86b src/protocols/qq/sendqueue.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/protocols/qq/sendqueue.h Mon Jun 26 02:58:54 2006 +0000 @@ -0,0 +1,52 @@ +/** + * The QQ2003C protocol plugin + * + * for gaim + * + * Copyright (C) 2004 Puzzlebird + * + * 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 + */ + +// START OF FILE +/*****************************************************************************/ +#ifndef _QQ_SEND_QUEUE_H_ +#define _QQ_SEND_QUEUE_H_ + +#include +#include "qq.h" // qq_data + +#define QQ_SENDQUEUE_TIMEOUT 5000 // in 1/1000 sec + +typedef struct _qq_sendpacket qq_sendpacket; + +struct _qq_sendpacket { + gint fd; + gint len; + gchar *buf; + guint16 cmd; + guint16 send_seq; + gint resend_times; + time_t sendtime; +}; + +void qq_sendqueue_free(qq_data * qd); + +void qq_sendqueue_remove(qq_data * qd, guint16 send_seq); +gboolean qq_sendqueue_timeout_callback(gpointer data); + +#endif +/*****************************************************************************/ +// END OF FILE diff -r 5642f4658b59 -r 983fd420e86b src/protocols/qq/show.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/protocols/qq/show.c Mon Jun 26 02:58:54 2006 +0000 @@ -0,0 +1,222 @@ +/** +* The QQ2003C protocol plugin + * + * for gaim + * + * Copyright (C) 2004 Puzzlebird + * + * 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 + * + * This code is based on qq_show.c, kindly contributed by herryouly@linuxsir + */ + +// START OF FILE +/*****************************************************************************/ +#include // mkdir +#include + +#include "internal.h" // _("get_text") +#include "debug.h" // gaim_debug +#include "notify.h" // gaim_notify_error +#include "util.h" // gaim_url_fetch + +#include "show.h" + +#define QQ_SHOW_SERVER "http://qqshow-user.tencent.com" +#define QQ_SHOW_IMAGE "10/00/00.gif" + +#define QQ_SHOW_CACHE_DIR "qqshow" +#define QQ_SHOW_DEFAULT_IMAGE "qqshow_default.gif" + +#define QQ_SHOW_DEST_X 0 +#define QQ_SHOW_DEST_Y 0 +#define QQ_SHOW_DEST_WIDTH 120 +#define QQ_SHOW_DEST_HEIGHT 180 +#define QQ_SHOW_OFFSET_X -10 +#define QQ_SHOW_OFFSET_Y -35 +#define QQ_SHOW_SCALE_X 0.68 +#define QQ_SHOW_SCALE_Y 0.68 + +enum { + QQ_SHOW_READ, + QQ_SHOW_WRITE, +}; + +/*****************************************************************************/ +// return the local cached QQ show file name for uid +static gchar *_qq_show_get_cache_name(guint32 uid, gint io) +{ + gchar *path, *file, *file_fullname; + + g_return_val_if_fail(uid > 0, NULL); + + path = g_build_filename(gaim_user_dir(), QQ_SHOW_CACHE_DIR, NULL); + if (!g_file_test(path, G_FILE_TEST_EXISTS)) + g_mkdir(path, 0700); + + file = g_strdup_printf("%d.gif", uid); + file_fullname = g_build_filename(path, file, NULL); + + if (io == QQ_SHOW_READ) { + if (!g_file_test(file_fullname, G_FILE_TEST_EXISTS)) { + gaim_debug(GAIM_DEBUG_WARNING, "QQ", "No cached QQ show image for buddy %d\n", uid); + g_free(file_fullname); + file_fullname = + g_build_filename(gaim_prefs_get_string + ("/plugins/prpl/qq/datadir"), + "pixmaps", "gaim", "status", "default", QQ_SHOW_DEFAULT_IMAGE, NULL); + } // if g_file_test + } // if io + + g_free(path); + g_free(file); + return file_fullname; + +} // _qq_show_get_cache_name + +/*****************************************************************************/ +// scale the image to suit my window +static GdkPixbuf *_qq_show_scale_image(GdkPixbuf * pixbuf_src) +{ + GdkPixbuf *pixbuf_dst; + + pixbuf_dst = gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8, + QQ_SHOW_DEST_WIDTH * QQ_SHOW_SCALE_X, QQ_SHOW_DEST_HEIGHT * QQ_SHOW_SCALE_Y); + + gdk_pixbuf_scale(pixbuf_src, pixbuf_dst, + QQ_SHOW_DEST_X * QQ_SHOW_SCALE_X, + QQ_SHOW_DEST_Y * QQ_SHOW_SCALE_Y, + QQ_SHOW_DEST_WIDTH * QQ_SHOW_SCALE_X, + QQ_SHOW_DEST_HEIGHT * QQ_SHOW_SCALE_Y, + QQ_SHOW_OFFSET_X * QQ_SHOW_SCALE_X, + QQ_SHOW_OFFSET_Y * QQ_SHOW_SCALE_Y, QQ_SHOW_SCALE_X, QQ_SHOW_SCALE_Y, GDK_INTERP_BILINEAR); + + g_object_unref(G_OBJECT(pixbuf_src)); + return pixbuf_dst; +} // qq_show_scale_image + +/*****************************************************************************/ +// keep a local copy of QQ show image to speed up loading +static void _qq_show_cache_image(const gchar * url_ret, size_t len, guint32 uid) +{ + + GError *err; + GIOChannel *cache; + gchar *file; + + g_return_if_fail(uid > 0); + + err = NULL; + file = _qq_show_get_cache_name(uid, QQ_SHOW_WRITE); + cache = g_io_channel_new_file(file, "w", &err); + + if (err != NULL) { // file error + gaim_debug(GAIM_DEBUG_WARNING, "QQ", "Error with QQ show file: %s\n", err->message); + g_error_free(err); + return; + } else { // OK, go ahead + g_io_channel_set_encoding(cache, NULL, NULL); // binary mode + g_io_channel_write_chars(cache, url_ret, len, NULL, &err); + if (err != NULL) { + gaim_debug(GAIM_DEBUG_WARNING, "QQ", "Fail cache QQ show for %d: %s\n", uid, err->message); + g_error_free(err); + } else + gaim_debug(GAIM_DEBUG_INFO, "QQ", "Cache QQ show for %d, done\n", uid); + g_io_channel_shutdown(cache, TRUE, NULL); + } // if err + +} // _qq_show_cache_image + +/*****************************************************************************/ +// display the image for downloaded data +static void qq_show_render_image(void *data, const gchar * url_ret, size_t len) +{ + guint32 uid; + GdkPixbufLoader *pixbuf_loader; + GdkPixbuf *pixbuf_src, *pixbuf_dst; + GtkWidget *qq_show; + + g_return_if_fail(data != NULL && url_ret != NULL && len > 0); + + qq_show = (GtkWidget *) data; + + pixbuf_loader = gdk_pixbuf_loader_new(); + gdk_pixbuf_loader_write(pixbuf_loader, url_ret, len, NULL); + gdk_pixbuf_loader_close(pixbuf_loader, NULL); // finish loading + + pixbuf_src = gdk_pixbuf_loader_get_pixbuf(pixbuf_loader); + + if (pixbuf_src != NULL) { + uid = (guint32) g_object_get_data(G_OBJECT(qq_show), "user_data"); + _qq_show_cache_image(url_ret, len, uid); + pixbuf_dst = _qq_show_scale_image(pixbuf_src); + gtk_image_set_from_pixbuf(GTK_IMAGE(qq_show), pixbuf_dst); + } else { + gaim_notify_error(NULL, NULL, _("Fail getting QQ show image"), NULL); + } // if pixbuf_src + +} // qq_show_render_image + +/*****************************************************************************/ +// show the default image (either local cache or default image) +GtkWidget *qq_show_default(contact_info * info) +{ + guint32 uid; + GdkPixbuf *pixbuf_src; + GError *err; + gchar *file; + + g_return_val_if_fail(info != NULL, NULL); + + uid = strtol(info->uid, NULL, 10); + g_return_val_if_fail(uid != 0, NULL); + + file = _qq_show_get_cache_name(uid, QQ_SHOW_READ); + gaim_debug(GAIM_DEBUG_INFO, "QQ", "Load QQ show image: %s\n", file); + + err = NULL; + pixbuf_src = gdk_pixbuf_new_from_file(file, &err); + + if (err != NULL) { + gaim_debug(GAIM_DEBUG_ERROR, "QQ", "Fail loaing QQ show: %s\n", err->message); + g_free(file); + return NULL; + } // if err + + g_free(file); + return gtk_image_new_from_pixbuf(_qq_show_scale_image(pixbuf_src)); +} // qq_show_default + +/*****************************************************************************/ +// refresh the image +void qq_show_get_image(GtkWidget * event_box, GdkEventButton * event, gpointer data) +{ + gchar *url; + gint uid; + GtkWidget *qq_show; + + qq_show = (GtkWidget *) data; + g_return_if_fail(qq_show != NULL); + + uid = (gint) g_object_get_data(G_OBJECT(qq_show), "user_data"); + g_return_if_fail(uid != 0); + + url = g_strdup_printf("%s/%d/%s", QQ_SHOW_SERVER, uid, QQ_SHOW_IMAGE); + gaim_url_fetch(url, FALSE, NULL, TRUE, qq_show_render_image, qq_show); + + g_free(url); +} // qq_show_get_image + +/*****************************************************************************/ diff -r 5642f4658b59 -r 983fd420e86b src/protocols/qq/show.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/protocols/qq/show.h Mon Jun 26 02:58:54 2006 +0000 @@ -0,0 +1,38 @@ +/** +* The QQ2003C protocol plugin + * + * for gaim + * + * Copyright (C) 2004 Puzzlebird + * + * 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 + */ + +// START OF FILE +/*****************************************************************************/ +#ifndef _QQ_SHOW_H_ +#define _QQ_SHOW_H_ + +#include +#include + +#include "buddy_info.h" // contact_info + +GtkWidget *qq_show_default(contact_info * info); + +void qq_show_get_image(GtkWidget * event_box, GdkEventButton * event, gpointer data); + +#endif +/*****************************************************************************/ diff -r 5642f4658b59 -r 983fd420e86b src/protocols/qq/sys_msg.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/protocols/qq/sys_msg.c Mon Jun 26 02:58:54 2006 +0000 @@ -0,0 +1,323 @@ +/** +* The QQ2003C protocol plugin + * + * for gaim + * + * Copyright (C) 2004 Puzzlebird + * + * 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 + */ + +// START OF FILE +/*****************************************************************************/ +#include "debug.h" // gaim_debug +#include "internal.h" // _("get_text") +#include "notify.h" // gaim_noitfy_xx +#include "request.h" // gaim_request_action + +#include "utils.h" // hex_dump_to_str +#include "packet_parse.h" // create_packet_ +#include "buddy_info.h" // qq_send_packet_get_info +#include "buddy_list.h" // qq_send_packet_get_buddies_online +#include "buddy_opt.h" // gc_and_uid +#include "char_conv.h" // qq_to_utf8 +#include "crypt.h" // qq_crypt +#include "header_info.h" // cmd alias +#include "send_core.h" // qq_send_cmd +#include "sys_msg.h" +#include "qq.h" // qq_data + +enum { + QQ_MSG_SYS_BEING_ADDED = 0x01, + QQ_MSG_SYS_ADD_CONTACT_REQUEST = 0x02, + QQ_MSG_SYS_ADD_CONTACT_APPROVED = 0x03, + QQ_MSG_SYS_ADD_CONTACT_REJECTED = 0x04, + QQ_MSG_SYS_NEW_VERSION = 0x09, +}; + +/* Henry: private function for reading/writing of system log */ +static void _qq_sys_msg_log_write(GaimConnection *gc, gchar *msg, gchar *from) +{ + GaimLog *log; + GaimAccount *account; + + account = gaim_connection_get_account(gc); + g_return_if_fail(gc != NULL && gc->proto_data != NULL); + + log = gaim_log_new(GAIM_LOG_IM, + "systemim", + account, + NULL, //gfhuang + time(NULL), + NULL + ); + gaim_log_write(log, GAIM_MESSAGE_SYSTEM, from, + time(NULL), msg); + gaim_log_free(log); +} + +/*****************************************************************************/ +// suggested by rakescar@linuxsir, can still approve after search +static void _qq_search_before_auth_with_gc_and_uid(gc_and_uid * g) +{ + GaimConnection *gc; + guint32 uid; + + g_return_if_fail(g != NULL); + + gc = g->gc; + uid = g->uid; + g_return_if_fail(gc != 0 && uid != 0); + + qq_send_packet_get_info(gc, uid, TRUE); // we wanna see window + gaim_request_action + (gc, NULL, _("Do you wanna approve the request?"), "", 2, g, 2, + _("Reject"), + G_CALLBACK(qq_reject_add_request_with_gc_and_uid), + _("Approve"), G_CALLBACK(qq_approve_add_request_with_gc_and_uid)); + +} // _qq_search_before_auth_with_gc_and_uid + +/*****************************************************************************/ +static void _qq_search_before_add_with_gc_and_uid(gc_and_uid * g) +{ + GaimConnection *gc; + guint32 uid; + + g_return_if_fail(g != NULL); + + gc = g->gc; + uid = g->uid; + g_return_if_fail(gc != 0 && uid != 0); + + qq_send_packet_get_info(gc, uid, TRUE); // we wanna see window + gaim_request_action + (gc, NULL, _("Do you wanna add this buddy?"), "", 2, g, 2, + _("Cancel"), NULL, _("Add"), G_CALLBACK(qq_add_buddy_with_gc_and_uid)); + +} // _qq_search_before_add_with_gc_and_uid + +/*****************************************************************************/ +// Send ACK if the sys message needs an ACK +static void _qq_send_packet_ack_msg_sys(GaimConnection * gc, guint8 code, guint32 from, guint16 seq) { + guint8 bar, *ack, *cursor; + gchar *str; + gint ack_len, bytes; + + str = g_strdup_printf("%d", from); + bar = 0x1e; + ack_len = 1 + 1 + strlen(str) + 1 + 2; + ack = g_newa(guint8, ack_len); + cursor = ack; + bytes = 0; + + bytes += create_packet_b(ack, &cursor, code); + bytes += create_packet_b(ack, &cursor, bar); + bytes += create_packet_data(ack, &cursor, str, strlen(str)); + bytes += create_packet_b(ack, &cursor, bar); + bytes += create_packet_w(ack, &cursor, seq); + + g_free(str); + + if (bytes == ack_len) // creation OK + qq_send_cmd(gc, QQ_CMD_ACK_SYS_MSG, TRUE, 0, FALSE, ack, ack_len); + else + gaim_debug(GAIM_DEBUG_ERROR, "QQ", + "Fail creating sys msg ACK, expect %d bytes, build %d bytes\n", ack_len, bytes); + +} // _qq_send_packet_ack_msg_sys + +/*****************************************************************************/ +// when you are added by a person, QQ server will send sys message +static void _qq_process_msg_sys_being_added(GaimConnection * gc, gchar * from, gchar * to, gchar * msg_utf8) { + gchar *message; + GaimBuddy *b; + guint32 uid; + gc_and_uid *g; + gchar *name; //for memory leak bug, by gfhuang + + g_return_if_fail(gc != NULL && from != NULL && to != NULL); + + uid = strtol(from, NULL, 10); + name = uid_to_gaim_name(uid); //by gfhuang + b = gaim_find_buddy(gc->account, name); + g_free(name); + if (b == NULL) { // the person is not in my list + g = g_new0(gc_and_uid, 1); + g->gc = gc; + g->uid = uid; // only need to get value + message = g_strdup_printf(_("You have been added by %s"), from); + _qq_sys_msg_log_write(gc, message, from); + gaim_request_action(gc, NULL, message, + _("Would like to add him?"), 2, g, 3, + _("Cancel"), NULL, _("Add"), + G_CALLBACK + (qq_add_buddy_with_gc_and_uid), + _("Search"), G_CALLBACK(_qq_search_before_add_with_gc_and_uid)); + } else { + message = g_strdup_printf(_("%s has added you [%s]"), from, to); + _qq_sys_msg_log_write(gc, message, from); + gaim_notify_info(gc, NULL, message, NULL); + } + + g_free(message); +} // qq_process_msg_sys_being_added + +/*****************************************************************************/ +// you are rejected by the person +static void _qq_process_msg_sys_add_contact_rejected(GaimConnection * gc, gchar * from, gchar * to, gchar * msg_utf8) { + gchar *message, *reason; + + g_return_if_fail(gc != NULL && from != NULL && to != NULL); + + message = g_strdup_printf(_("User %s rejected your request"), from); + reason = g_strdup_printf(_("Reason: %s"), msg_utf8); + _qq_sys_msg_log_write(gc, message, from); + + gaim_notify_info(gc, NULL, message, reason); + g_free(message); + g_free(reason); +} // qq_process_msg_sys_add_contact_rejected + +/*****************************************************************************/ +// the buddy approves your request of adding him/her as your friend +static void _qq_process_msg_sys_add_contact_approved(GaimConnection * gc, gchar * from, gchar * to, gchar * msg_utf8) { + gchar *message; + qq_data *qd; + + g_return_if_fail(gc != NULL && from != NULL && to != NULL); + + qd = (qq_data *) gc->proto_data; + qq_add_buddy_by_recv_packet(gc, strtol(from, NULL, 10), TRUE, TRUE); + + message = g_strdup_printf(_("Use %s has approved your request"), from); + _qq_sys_msg_log_write(gc, message, from); + gaim_notify_info(gc, NULL, message, NULL); + + g_free(message); +} // qq_process_msg_sys_add_contact_approved + +/*****************************************************************************/ +// someone wants to add you to his buddy list +static void _qq_process_msg_sys_add_contact_request(GaimConnection * gc, gchar * from, gchar * to, gchar * msg_utf8) { + gchar *message, *reason; + guint32 uid; + gc_and_uid *g, *g2; + GaimBuddy *b; + gchar *name; // by gfhuang + + g_return_if_fail(gc != NULL && from != NULL && to != NULL); + + uid = strtol(from, NULL, 10); + g = g_new0(gc_and_uid, 1); + g->gc = gc; + g->uid = uid; + + message = g_strdup_printf(_("%s wanna add you [%s] as friends"), from, to); + reason = g_strdup_printf(_("Message: %s"), msg_utf8); + _qq_sys_msg_log_write(gc, message, from); + + gaim_request_action + (gc, NULL, message, reason, 2, g, 3, + _("Reject"), + G_CALLBACK(qq_reject_add_request_with_gc_and_uid), + _("Approve"), + G_CALLBACK(qq_approve_add_request_with_gc_and_uid), + _("Search"), G_CALLBACK(_qq_search_before_auth_with_gc_and_uid)); + + g_free(message); + g_free(reason); + + name = uid_to_gaim_name(uid); //by gfhuang + b = gaim_find_buddy(gc->account, name); + g_free(name); + if (b == NULL) { // the person is not in my list + g2 = g_new0(gc_and_uid, 1); + g2->gc = gc; + g2->uid = strtol(from, NULL, 10); + message = g_strdup_printf(_("%s is not in your buddy list"), from); + gaim_request_action(gc, NULL, message, + _("Would you like to add him?"), 2, g2, + 3, _("Cancel"), NULL, _("Add"), + G_CALLBACK + (qq_add_buddy_with_gc_and_uid), + _("Search"), G_CALLBACK(_qq_search_before_add_with_gc_and_uid)); + g_free(message); + } // if b== NULL + +} // qq_process_msg_sys_add_contact_request + +/*****************************************************************************/ +void qq_process_msg_sys(guint8 * buf, gint buf_len, guint16 seq, GaimConnection * gc) { + qq_data *qd; + gint len; + guint8 *data; + gchar **segments, *code, *from, *to, *msg, *msg_utf8; + + g_return_if_fail(gc != NULL && gc->proto_data != NULL); + g_return_if_fail(buf != NULL && buf_len != 0); + + qd = (qq_data *) gc->proto_data; + len = buf_len; + data = g_newa(gchar, len); + + if (qq_crypt(DECRYPT, buf, buf_len, qd->session_key, data, &len)) { + if (NULL == (segments = split_data(data, len, "\x1f", 4))) + return; + code = segments[0]; + from = segments[1]; + to = segments[2]; + msg = segments[3]; + + _qq_send_packet_ack_msg_sys(gc, code[0], strtol(from, NULL, 10), seq); + + if (strtol(to, NULL, 10) != qd->uid) { // not to me + gaim_debug(GAIM_DEBUG_ERROR, "QQ", "Recv sys msg to [%s], not me!, discard\n", to); + g_strfreev(segments); + return; + } + + msg_utf8 = qq_to_utf8(msg, QQ_CHARSET_DEFAULT); + switch (strtol(code, NULL, 10)) { + case QQ_MSG_SYS_BEING_ADDED: + _qq_process_msg_sys_being_added(gc, from, to, msg_utf8); + break; + case QQ_MSG_SYS_ADD_CONTACT_REQUEST: + _qq_process_msg_sys_add_contact_request(gc, from, to, msg_utf8); + break; + case QQ_MSG_SYS_ADD_CONTACT_APPROVED: + _qq_process_msg_sys_add_contact_approved(gc, from, to, msg_utf8); + break; + case QQ_MSG_SYS_ADD_CONTACT_REJECTED: + _qq_process_msg_sys_add_contact_rejected(gc, from, to, msg_utf8); + break; + case QQ_MSG_SYS_NEW_VERSION: + gaim_debug(GAIM_DEBUG_WARNING, "QQ", + "QQ server says there is newer version than %s\n", qq_get_source_str(QQ_CLIENT)); + break; + default: + gaim_debug(GAIM_DEBUG_WARNING, "QQ", "Recv unknown sys msg code: %s\n", code); + gaim_debug(GAIM_DEBUG_WARNING, "QQ", "the msg is : %s\n", msg_utf8); + } // switch code + g_free(msg_utf8); + g_strfreev(segments); + + } else + gaim_debug(GAIM_DEBUG_ERROR, "QQ", "Error decrypt recv msg sys\n"); + +} // qq_process_msg_sys + +/*****************************************************************************/ +// END OF FILE diff -r 5642f4658b59 -r 983fd420e86b src/protocols/qq/sys_msg.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/protocols/qq/sys_msg.h Mon Jun 26 02:58:54 2006 +0000 @@ -0,0 +1,35 @@ +/** +* The QQ2003C protocol plugin + * + * for gaim + * + * Copyright (C) 2004 Puzzlebird + * + * 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 + */ + +// START OF FILE +/*****************************************************************************/ +#ifndef _QQ_SYS_MSG_H_ +#define _QQ_SYS_MSG_H_ + +#include +#include "connection.h" // GaimConnection + +void qq_process_msg_sys(guint8 * buf, gint buf_len, guint16 seq, GaimConnection * gc); + +#endif +/*****************************************************************************/ +// END OF FILE diff -r 5642f4658b59 -r 983fd420e86b src/protocols/qq/udp_proxy_s5.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/protocols/qq/udp_proxy_s5.c Mon Jun 26 02:58:54 2006 +0000 @@ -0,0 +1,375 @@ +/** + * The QQ2003C protocol plugin + * + * for gaim + * + * Copyright (C) 2004 Puzzlebird + * Henry Ou + * + * 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 + */ + +// START OF FILE +/*****************************************************************************/ +#include "debug.h" // gaim_debug + +#include "udp_proxy_s5.h" + +extern gint // defined in qq_proxy.c + _qq_fill_host(struct sockaddr_in *addr, const gchar * host, guint16 port); + +/*****************************************************************************/ +static void _qq_s5_canread_again(gpointer data, gint source, GaimInputCondition cond) +{ + unsigned char buf[512]; + struct PHB *phb = data; + struct sockaddr_in sin; + int len; + + gaim_input_remove(phb->inpa); + gaim_debug(GAIM_DEBUG_INFO, "socks5 proxy", "Able to read again.\n"); + + len = read(source, buf, 10); + if (len < 10) { + gaim_debug(GAIM_DEBUG_WARNING, "socks5 proxy", "or not...\n"); + close(source); + + if (phb->account == NULL || gaim_account_get_connection(phb->account) != NULL) { + + phb->func(phb->data, source, GAIM_INPUT_READ); + } + + g_free(phb->host); + g_free(phb); + return; + } + if ((buf[0] != 0x05) || (buf[1] != 0x00)) { + if ((buf[0] == 0x05) && (buf[1] < 0x09)) + gaim_debug(GAIM_DEBUG_ERROR, "socks5 proxy", "socks5 error: %x\n", buf[1]); + else + gaim_debug(GAIM_DEBUG_ERROR, "socks5 proxy", "Bad data.\n"); + close(source); + + if (phb->account == NULL || gaim_account_get_connection(phb->account) != NULL) { + + phb->func(phb->data, -1, GAIM_INPUT_READ); + } + + g_free(phb->host); + g_free(phb); + return; + } + + sin.sin_family = AF_INET; + memcpy(&sin.sin_addr.s_addr, buf + 4, 4); + memcpy(&sin.sin_port, buf + 8, 2); + + if (connect(phb->udpsock, (struct sockaddr *) &sin, sizeof(struct sockaddr_in)) < 0) { + gaim_debug(GAIM_DEBUG_INFO, "s5_canread_again", "connect failed: %s\n", strerror(errno)); + close(phb->udpsock); + close(source); + g_free(phb->host); + g_free(phb); + return; + } + + int error = ETIMEDOUT; + gaim_debug(GAIM_DEBUG_INFO, "QQ", "Connect didn't block\n"); + len = sizeof(error); + if (getsockopt(phb->udpsock, SOL_SOCKET, SO_ERROR, &error, &len) < 0) { + gaim_debug(GAIM_DEBUG_ERROR, "QQ", "getsockopt failed.\n"); + close(phb->udpsock); + return; + } + fcntl(phb->udpsock, F_SETFL, 0); + + if (phb->account == NULL || gaim_account_get_connection(phb->account) != NULL) { + phb->func(phb->data, phb->udpsock, GAIM_INPUT_READ); + } + + g_free(phb->host); + g_free(phb); +} + +/*****************************************************************************/ +static void _qq_s5_sendconnect(gpointer data, gint source) +{ + unsigned char buf[512]; + struct PHB *phb = data; + struct sockaddr_in sin, ctlsin; + int port, ctllen; + + gaim_debug(GAIM_DEBUG_INFO, "s5_sendconnect", "remote host is %s:%d\n", phb->host, phb->port); + + buf[0] = 0x05; + buf[1] = 0x03; /* udp relay */ + buf[2] = 0x00; /* reserved */ + buf[3] = 0x01; /* address type -- ipv4 */ + memset(buf + 4, 0, 0x04); + + ctllen = sizeof(ctlsin); + if (getsockname(source, (struct sockaddr *) &ctlsin, &ctllen) < 0) { + gaim_debug(GAIM_DEBUG_INFO, "QQ", "getsockname: %s\n", strerror(errno)); + close(source); + g_free(phb->host); + g_free(phb); + return; + } + + phb->udpsock = socket(PF_INET, SOCK_DGRAM, 0); + gaim_debug(GAIM_DEBUG_INFO, "s5_sendconnect", "UDP socket=%d\n", phb->udpsock); + if (phb->udpsock < 0) { + close(source); + g_free(phb->host); + g_free(phb); + return; + } + + fcntl(phb->udpsock, F_SETFL, O_NONBLOCK); + + port = ntohs(ctlsin.sin_port) + 1; + while (1) { + _qq_fill_host(&sin, "0.0.0.0", port); + if (bind(phb->udpsock, (struct sockaddr *) &sin, sizeof(sin)) < 0) { + port++; + if (port > 65500) { + close(source); + g_free(phb->host); + g_free(phb); + return; + } + } else + break; + } + + memset(buf + 4, 0, 0x04); + memcpy(buf + 8, &(sin.sin_port), 0x02); + + if (write(source, buf, 10) < 10) { + close(source); + gaim_debug(GAIM_DEBUG_INFO, "s5_sendconnect", "packet too small\n"); + + if (phb->account == NULL || gaim_account_get_connection(phb->account) != NULL) { + phb->func(phb->data, -1, GAIM_INPUT_READ); + } + + g_free(phb->host); + g_free(phb); + return; + } + + phb->inpa = gaim_input_add(source, GAIM_INPUT_READ, _qq_s5_canread_again, phb); +} + +/*****************************************************************************/ +static void _qq_s5_readauth(gpointer data, gint source, GaimInputCondition cond) +{ + unsigned char buf[512]; + struct PHB *phb = data; + + gaim_input_remove(phb->inpa); + gaim_debug(GAIM_DEBUG_INFO, "socks5 proxy", "Got auth response.\n"); + + if (read(source, buf, 2) < 2) { + close(source); + + if (phb->account == NULL || gaim_account_get_connection(phb->account) != NULL) { + + phb->func(phb->data, -1, GAIM_INPUT_READ); + } + + g_free(phb->host); + g_free(phb); + return; + } + + if ((buf[0] != 0x01) || (buf[1] != 0x00)) { + close(source); + + if (phb->account == NULL || gaim_account_get_connection(phb->account) != NULL) { + + phb->func(phb->data, -1, GAIM_INPUT_READ); + } + + g_free(phb->host); + g_free(phb); + return; + } + + _qq_s5_sendconnect(phb, source); +} + +/*****************************************************************************/ +static void _qq_s5_canread(gpointer data, gint source, GaimInputCondition cond) +{ + unsigned char buf[512]; + struct PHB *phb = data; + + gaim_input_remove(phb->inpa); + gaim_debug(GAIM_DEBUG_INFO, "socks5 proxy", "Able to read.\n"); + + int ret = read(source, buf, 2); + if (ret < 2) { + gaim_debug(GAIM_DEBUG_INFO, "s5_canread", "packet smaller than 2 octet\n"); + close(source); + + if (phb->account == NULL || gaim_account_get_connection(phb->account) != NULL) { + + phb->func(phb->data, source, GAIM_INPUT_READ); + } + + g_free(phb->host); + g_free(phb); + return; + } + + if ((buf[0] != 0x05) || (buf[1] == 0xff)) { + gaim_debug(GAIM_DEBUG_INFO, "s5_canread", "unsupport\n"); + close(source); + + if (phb->account == NULL || gaim_account_get_connection(phb->account) != NULL) { + + phb->func(phb->data, -1, GAIM_INPUT_READ); + } + + g_free(phb->host); + g_free(phb); + return; + } + + if (buf[1] == 0x02) { + unsigned int i, j; + + i = strlen(gaim_proxy_info_get_username(phb->gpi)); + j = strlen(gaim_proxy_info_get_password(phb->gpi)); + + buf[0] = 0x01; /* version 1 */ + buf[1] = i; + memcpy(buf + 2, gaim_proxy_info_get_username(phb->gpi), i); + buf[2 + i] = j; + memcpy(buf + 2 + i + 1, gaim_proxy_info_get_password(phb->gpi), j); + + if (write(source, buf, 3 + i + j) < 3 + i + j) { + close(source); + + if (phb->account == NULL || gaim_account_get_connection(phb->account) != NULL) { + + phb->func(phb->data, -1, GAIM_INPUT_READ); + } + + g_free(phb->host); + g_free(phb); + return; + } + + phb->inpa = gaim_input_add(source, GAIM_INPUT_READ, _qq_s5_readauth, phb); + } else { + gaim_debug(GAIM_DEBUG_INFO, "s5_canread", "calling s5_sendconnect\n"); + _qq_s5_sendconnect(phb, source); + } +} + +/*****************************************************************************/ +void _qq_s5_canwrite(gpointer data, gint source, GaimInputCondition cond) +{ + unsigned char buf[512]; + int i; + struct PHB *phb = data; + unsigned int len; + int error = ETIMEDOUT; + + gaim_debug(GAIM_DEBUG_INFO, "socks5 proxy", "Connected.\n"); + + if (phb->inpa > 0) + gaim_input_remove(phb->inpa); + + len = sizeof(error); + if (getsockopt(source, SOL_SOCKET, SO_ERROR, &error, &len) < 0) { + gaim_debug(GAIM_DEBUG_INFO, "getsockopt", "%s\n", strerror(errno)); + close(source); + if (phb->account == NULL || gaim_account_get_connection(phb->account) != NULL) { + + phb->func(phb->data, -1, GAIM_INPUT_READ); + } + + g_free(phb->host); + g_free(phb); + return; + } + fcntl(source, F_SETFL, 0); + + i = 0; + buf[0] = 0x05; /* SOCKS version 5 */ + + if (gaim_proxy_info_get_username(phb->gpi) != NULL) { + buf[1] = 0x02; /* two methods */ + buf[2] = 0x00; /* no authentication */ + buf[3] = 0x02; /* username/password authentication */ + i = 4; + } else { + buf[1] = 0x01; + buf[2] = 0x00; + i = 3; + } + + if (write(source, buf, i) < i) { + gaim_debug(GAIM_DEBUG_INFO, "write", "%s\n", strerror(errno)); + gaim_debug(GAIM_DEBUG_ERROR, "socks5 proxy", "Unable to write\n"); + close(source); + + if (phb->account == NULL || gaim_account_get_connection(phb->account) != NULL) { + + phb->func(phb->data, -1, GAIM_INPUT_READ); + } + + g_free(phb->host); + g_free(phb); + return; + } + + phb->inpa = gaim_input_add(source, GAIM_INPUT_READ, _qq_s5_canread, phb); +} + +/*****************************************************************************/ +gint qq_proxy_socks5(struct PHB * phb, struct sockaddr * addr, socklen_t addrlen) +{ + + gint fd; + gaim_debug(GAIM_DEBUG_INFO, "QQ", + "Connecting to %s:%d via %s:%d using SOCKS5\n", + phb->host, phb->port, gaim_proxy_info_get_host(phb->gpi), gaim_proxy_info_get_port(phb->gpi)); + + if ((fd = socket(addr->sa_family, SOCK_STREAM, 0)) < 0) + return -1; + + gaim_debug(GAIM_DEBUG_INFO, "QQ", "proxy_sock5 return fd=%d\n", fd); + + fcntl(fd, F_SETFL, O_NONBLOCK); + if (connect(fd, addr, addrlen) < 0) { + if ((errno == EINPROGRESS) || (errno == EINTR)) { + gaim_debug(GAIM_DEBUG_WARNING, "QQ", "Connect in asynchronous mode.\n"); + phb->inpa = gaim_input_add(fd, GAIM_INPUT_WRITE, _qq_s5_canwrite, phb); + } else { + close(fd); + return -1; + } // if error + } else { + gaim_debug(GAIM_DEBUG_MISC, "QQ", "Connect in blocking mode.\n"); + fcntl(fd, F_SETFL, 0); + _qq_s5_canwrite(phb, fd, GAIM_INPUT_WRITE); + } // if connect + + return fd; +} // qq_proxy_connect diff -r 5642f4658b59 -r 983fd420e86b src/protocols/qq/udp_proxy_s5.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/protocols/qq/udp_proxy_s5.h Mon Jun 26 02:58:54 2006 +0000 @@ -0,0 +1,37 @@ +/** + * The QQ2003C protocol plugin + * + * for gaim + * + * Copyright (C) 2004 Puzzlebird + * Henry Ou + * + * 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 + */ + +// START OF FILE +/*****************************************************************************/ +#ifndef _QQ_UDP_PROXY_S5_H_ +#define _QQ_UDP_PROXY_S5_H_ + +#include "internal.h" // for socket stuff + +#include "qq_proxy.h" + +gint qq_proxy_socks5(struct PHB *phb, struct sockaddr *addr, socklen_t addrlen); + +#endif +/*****************************************************************************/ +// END OF FILE diff -r 5642f4658b59 -r 983fd420e86b src/protocols/qq/utils.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/protocols/qq/utils.c Mon Jun 26 02:58:54 2006 +0000 @@ -0,0 +1,270 @@ +/** + * The QQ2003C protocol plugin + * + * for gaim + * + * Copyright (C) 2004 Puzzlebird + * + * 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 + */ + +// START OF FILE +/*****************************************************************************/ +#include "stdlib.h" // strtol +#include "limits.h" +#include "string.h" // strlen + +#ifdef _WIN32 +#include "win32dep.h" +#endif + +#include "debug.h" // gaim_debug +#include "utils.h" +#include "char_conv.h" // qq_to_utf8 +#include "prefs.h" // gaim_prefs_get_string + +#define QQ_NAME_FORMAT "qq-%d" + +#ifndef g_str_has_prefix +gint g_str_has_prefix(const gchar *str, const gchar *prefix) +{ + gint len = strlen(prefix); + return !strncmp(str, prefix, len); +} +#endif + +/*****************************************************************************/ +gchar *get_name_by_index_str(gchar ** array, const gchar * index_str, gint amount) { + gint index; + + index = atoi(index_str); + if (index < 0 || index >= amount) + index = 0; + + return array[index]; +} // get_name_by_index_str + +/*****************************************************************************/ +gchar *get_index_str_by_name(gchar ** array, const gchar * name, gint amount) { + gint index; + + for (index = 0; index <= amount; index++) + if (g_ascii_strcasecmp(array[index], name) == 0) + break; + + if (index >= amount) + index = 0; // meaning no match + return g_strdup_printf("%d", index); +} // get_index_str_by_name + +/*****************************************************************************/ +gint qq_string_to_dec_value(const gchar * str) +{ + g_return_val_if_fail(str != NULL, 0); + return strtol(str, NULL, 10); +} // _qq_string_to_dec_value + +/*****************************************************************************/ +// split the given data(len) with delimit, +// check the number of field matches the expected_fields (<=0 means all) +// return gchar* array (needs to be freed by g_strfreev later), or NULL +gchar **split_data(guint8 * data, gint len, const gchar * delimit, gint expected_fields) { + + guint8 *input; + gchar **segments; + gint i, j; + + g_return_val_if_fail(data != NULL && len != 0 && delimit != 0, NULL); + + // as the last field would be string, but data is not ended with 0x00 + // we have to duplicate the data and append a 0x00 at the end + input = g_newa(guint8, len + 1); + g_memmove(input, data, len); + input[len] = 0x00; + + segments = g_strsplit(input, delimit, 0); + if (expected_fields <= 0) + return segments; + + for (i = 0; segments[i] != NULL; i++) {; + } + if (i < expected_fields) { // not enough fields + gaim_debug(GAIM_DEBUG_ERROR, "QQ", + "Invalid data, expect %d fields, found only %d, discard\n", expected_fields, i); + g_strfreev(segments); + return NULL; + } else if (i > expected_fields) { // more fields, OK + gaim_debug(GAIM_DEBUG_WARNING, "QQ", + "Dangerous data, expect %d fields, found %d, return all\n", expected_fields, i); + // free up those not used + for (j = expected_fields; j < i; j++) { + gaim_debug(GAIM_DEBUG_WARNING, "QQ", "field[%d] is %s\n", j, segments[j]); + g_free(segments[j]); // bug found by gfhuang ! i -> j + } + + segments[expected_fields] = NULL; + } // if i + + return segments; +} // split_data + +/*****************************************************************************/ +// given a four-byte ip data, convert it into a human readable ip string +// the return needs to be freed +gchar *gen_ip_str(guint8 * ip) +{ + return g_strdup_printf("%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]); +} // gen_ip_str + +// by gfhuang +guint8 *str_ip_gen(gchar *str) { + guint8 *ip = g_new(guint8, 4); + int a, b, c, d; + sscanf(str, "%d.%d.%d.%d", &a, &b, &c, &d); + ip[0] = a; + ip[1] = b; + ip[2] = c; + ip[3] = d; + return ip; +} + +/*****************************************************************************/ +// return the QQ icon file name +// the return needs to be freed +gchar *get_icon_name(gint set, gint suffix) +{ + return g_strdup_printf("qq_%d-%d", set, suffix); +} // get_icon_name + +/*****************************************************************************/ +// convert a QQ UID to a unique name of GAIM +// the return needs to be freed +gchar *uid_to_gaim_name(guint32 uid) +{ + return g_strdup_printf(QQ_NAME_FORMAT, uid); +} // uid_to_gaim_name + +/*****************************************************************************/ +// convert GAIM name to original QQ UID +guint32 gaim_name_to_uid(const gchar * name) +{ + gchar *p; + + g_return_val_if_fail(g_str_has_prefix(name, QQ_NAME_PREFIX), 0); + + p = g_strrstr(name, QQ_NAME_PREFIX); + // atoi is not thread-safe and also not async-cancel safe + // atoi is deprecated by strtol() and should not be used in new code + return (p == NULL) ? 0 : strtol(p + strlen(QQ_NAME_PREFIX), NULL, 10); +} // gaim_name_to_uid + +/*****************************************************************************/ +// convert QQ icon index into its pixbuf +GdkPixbuf *get_face_gdkpixbuf(guint8 index) +{ + gint set, suffix; + gchar *image_name, *file_name; + GdkPixbuf *pixbuf; + const gchar *datadir; + + set = (index / 3) + 1; + suffix = (index % 3) + 1; + + image_name = g_strdup_printf("%s.png", get_icon_name(set, suffix)); + // we need to configure DATADIR in Makefile.am + // st = -DDATADIR=\"$(datadir)\" + datadir = gaim_prefs_get_string("/plugins/prpl/qq/datadir"); + if (datadir == NULL || strlen(datadir) == 0) + file_name = g_build_filename(datadir, "pixmaps", "gaim", "status", "default", image_name, NULL); + else + file_name = g_build_filename(DATADIR, "pixmaps", "gaim", "status", "default", image_name, NULL); + + pixbuf = gdk_pixbuf_new_from_file(file_name, NULL); + + g_free(image_name); + g_free(file_name); + + return pixbuf; +} // get_face_gdkpixbuf + +/*****************************************************************************/ +// try to dump the data as GBK +void try_dump_as_gbk(guint8 * data, gint len) +{ + gint i; + guint8 *incoming; + gchar *msg_utf8; + + incoming = g_newa(guint8, len + 1); + g_memmove(incoming, data, len); + incoming[len] = 0x00; + // GBK code: + // Single-byte ASCII: 0x21-0x7E + // GBK first byte range: 0x81-0xFE + // GBK second byte range: 0x40-0x7E and 0x80-0xFE + for (i = 0; i < len; i++) + if (incoming[i] >= 0x81) + break; + + msg_utf8 = i < len ? qq_to_utf8(&incoming[i], QQ_CHARSET_DEFAULT) : NULL; + + if (msg_utf8 != NULL) { + gaim_debug(GAIM_DEBUG_WARNING, "QQ", "Try extract GB msg: %s\n", msg_utf8); + g_free(msg_utf8); + } // msg_utf8 != NULL +} // try_dump_gbk + +/*****************************************************************************/ +// dump a chunk of raw data into hex string +// the return should be freed later +gchar *hex_dump_to_str(const guint8 * buffer, gint bytes) +{ + GString *str; + gchar *ret; + gint i, j, ch; + + str = g_string_new(""); + for (i = 0; i < bytes; i += 16) { + // length label + g_string_append_printf(str, "%04d: ", i); + + // dump hex value + for (j = 0; j < 16; j++) + if ((i + j) < bytes) + g_string_append_printf(str, " %02X", buffer[i + j]); + else + g_string_append(str, " "); + g_string_append(str, " "); + + // dump ascii value + for (j = 0; j < 16 && (i + j) < bytes; j++) { + ch = buffer[i + j] & 127; + if (ch < ' ' || ch == 127) + g_string_append_c(str, '.'); + else + g_string_append_c(str, ch); + } // for j + g_string_append_c(str, '\n'); + } // for i + + ret = str->str; + // GString can be freed without freeing it character data + g_string_free(str, FALSE); + + return ret; +} // hex_dump_to_str + +/*****************************************************************************/ +// ENF OF FILE diff -r 5642f4658b59 -r 983fd420e86b src/protocols/qq/utils.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/protocols/qq/utils.h Mon Jun 26 02:58:54 2006 +0000 @@ -0,0 +1,54 @@ +/** + * The QQ2003C protocol plugin + * + * for gaim + * + * Copyright (C) 2004 Puzzlebird + * + * 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 + */ + +// START OF FILE +/*****************************************************************************/ +#ifndef _QQ_MY_UTILS_H_ +#define _QQ_MY_UTILS_H_ + +#include +#include + +#define QQ_NAME_PREFIX "qq-" + +gchar *get_name_by_index_str(gchar ** array, const gchar * index_str, gint amount); +gchar *get_index_str_by_name(gchar ** array, const gchar * name, gint amount); +gint qq_string_to_dec_value(const gchar * str); + +gchar **split_data(guint8 * data, gint len, const gchar * delimit, gint expected_fields); +gchar *gen_ip_str(guint8 * ip); +guint8 *str_ip_gen(gchar *str); //by gfhuang +gchar *uid_to_gaim_name(guint32 uid); + +guint32 gaim_name_to_uid(const gchar * name); + +gchar *get_icon_name(gint set, gint suffix); + +GdkPixbuf *get_face_gdkpixbuf(guint8 index); + +void try_dump_as_gbk(guint8 * data, gint len); + +gchar *hex_dump_to_str(const guint8 * buf, gint buf_len); + +#endif +/*****************************************************************************/ +// END OF FILE