diff --git a/debian/changelog b/debian/changelog
index ab300ff..da7645d 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,9 @@
+fcitx-cloudpinyin (0.3.7+git20191103.1.a42ae67-1) UNRELEASED; urgency=low
+
+  * New upstream snapshot.
+
+ -- Debian Janitor <janitor@jelmer.uk>  Fri, 15 Apr 2022 01:25:14 -0000
+
 fcitx-cloudpinyin (0.3.7-1) unstable; urgency=medium
 
   * Team upload.
diff --git a/src/cloudpinyin.c b/src/cloudpinyin.c
index 44bd787..42bc7da 100644
--- a/src/cloudpinyin.c
+++ b/src/cloudpinyin.c
@@ -690,7 +690,7 @@ void _CloudPinyinAddCandidateWord(FcitxCloudPinyin* cloudpinyin, const char* pin
 }
 
 #define LOADING_TIME_QUICK_THRESHOLD 300
-#define DUP_PLACE_HOLDER "\xe2\x98\x81"
+#define DUP_PLACE_HOLDER "\xe2\x98\xba"
 
 void CloudPinyinFillCandidateWord(FcitxCloudPinyin* cloudpinyin,
                                   const char* pinyin)
diff --git a/src/parse.c b/src/parse.c
index 58895ac..ae619fe 100644
--- a/src/parse.c
+++ b/src/parse.c
@@ -8,12 +8,135 @@
 #include <ctype.h>
 #include "cloudpinyin.h"
 
-char* GoogleParsePinyin(FcitxCloudPinyin* cloudpinyin, CurlQueue* queue)
+static inline boolean ishex(char ch)
+{
+    if ((ch >= '0' && ch <= '9') || (ch >='a' && ch <='f') || (ch >='A' && ch <='F'))
+        return true;
+    return false;
+}
+
+static inline unsigned char tohex(char ch)
+{
+    if (ch >= '0' && ch <= '9')
+        return ch - '0';
+    if (ch >='a' && ch <='f')
+        return ch - 'a' + 10;
+    if (ch >='A' && ch <='F')
+        return ch - 'A' + 10;
+    return 0;
+}
+
+void SogouParseKey(FcitxCloudPinyin* cloudpinyin, CurlQueue* queue)
+{
+    char* str = fcitx_utils_trim(queue->str);
+    const char* ime_patch_key = "ime_patch_key = \"";
+    size_t len = strlen(str);
+    if (len == SOGOU_KEY_LENGTH + strlen(ime_patch_key) + 1
+        && strncmp(str, ime_patch_key, strlen(ime_patch_key)) == 0
+        && str[len - 1] == '\"') {
+        sscanf(str,"ime_patch_key = \"%s\"", cloudpinyin->key);
+        cloudpinyin->initialized = true;
+        cloudpinyin->key[SOGOU_KEY_LENGTH] = '\0';
+    }
+
+    free(str);
+}
+
+char* MapSogouStringToHalf(const char* string)
+{
+    const char* s = string;
+    const char* sn;
+    size_t len = strlen(string);
+    char* half = fcitx_utils_malloc0(sizeof(char) * (len + 1));
+    char* halfp = half;
+    int upperCount = 0;
+
+    while (*s) {
+        unsigned int chr = 0;
+
+        sn = fcitx_utf8_get_char(s, &chr);
+
+        /* from A to Z */
+        if ((chr >= 0xff21 && chr <= 0xff3a) || (chr >= 0xff41 && chr <= 0xff5a)) {
+            *halfp = (char) (chr & 0xff) + 0x20;
+            if (isupper(*halfp))
+                upperCount ++;
+            halfp ++;
+        }
+        else {
+            while(s < sn) {
+                *halfp = *s;
+                if (isupper(*halfp))
+                    upperCount ++;
+                s++;
+                halfp++;
+            }
+        }
+
+        s = sn;
+    }
+    if (*half && isupper(*half) && upperCount == 1) {
+        *half = tolower(*half);
+    }
+    return half;
+}
+
+char* SogouParsePinyin(FcitxCloudPinyin* cloudpinyin, CurlQueue* queue)
 {
     char *start = NULL, *end = NULL;
-    if (!queue->str) {
-        return NULL;
+    if ((start = strchr(queue->str, '"')) != NULL && (end = strstr(queue->str, "%EF%BC%9A")) != NULL)
+    {
+        start ++;
+        if (start < end)
+        {
+            size_t length = end - start;
+            int conv_length;
+            char *unescapedstring = curl_easy_unescape(queue->curl, start, length, &conv_length);
+            char *realstring = MapSogouStringToHalf(unescapedstring);
+            curl_free(unescapedstring);
+            return realstring;
+        }
+    }
+    return NULL;
+}
+
+void QQParseKey(FcitxCloudPinyin* cloudpinyin, CurlQueue* queue)
+{
+    char* str = fcitx_utils_trim(queue->str);
+    const char* ime_patch_key = "{\"key\":\"";
+    if (strncmp(str, ime_patch_key, strlen(ime_patch_key)) == 0)
+    {
+        if (sscanf(str,"{\"key\":\"%32s\",\"ret\":\"suc\"}", cloudpinyin->key) > 0)
+        {
+            cloudpinyin->initialized = true;
+            cloudpinyin->key[QQ_KEY_LENGTH] = '\0';
+        }
     }
+
+    free(str);
+}
+
+char* QQParsePinyin(FcitxCloudPinyin* cloudpinyin, CurlQueue* queue)
+{
+    char *start = NULL, *end = NULL;
+    if ((start = strstr(queue->str, "\"rs\":[\"")) != NULL)
+    {
+        start += strlen( "\"rs\":[\"");
+        if ((end = strstr(start, "\"")) != NULL)
+        {
+            size_t length = end - start;
+            char *realstring = fcitx_utils_malloc0(sizeof(char) * (length + 1));
+            strncpy(realstring, start, length);
+            realstring[length] = '\0';
+            return realstring;
+        }
+    }
+    return NULL;
+}
+
+char* GoogleParsePinyin(FcitxCloudPinyin* cloudpinyin, CurlQueue* queue)
+{
+    char *start = NULL, *end = NULL;
     if ((start = strstr(queue->str, "\",[\"")) != NULL)
     {
         start += strlen( "\",[\"");
@@ -23,12 +146,7 @@ char* GoogleParsePinyin(FcitxCloudPinyin* cloudpinyin, CurlQueue* queue)
             char *realstring = fcitx_utils_malloc0(sizeof(char) * (length + 1));
             strncpy(realstring, start, length);
             realstring[length] = '\0';
-            if (fcitx_utf8_check_string(realstring)) {
-                return realstring;
-            } else {
-                free(realstring);
-                return NULL;
-            }
+            return realstring;
         }
     }
     return NULL;
@@ -37,21 +155,56 @@ char* GoogleParsePinyin(FcitxCloudPinyin* cloudpinyin, CurlQueue* queue)
 char* BaiduParsePinyin(FcitxCloudPinyin* cloudpinyin, CurlQueue* queue)
 {
     char *start = NULL, *end = NULL;
-    if (!queue->str) {
+    static iconv_t conv = 0;
+    if (conv == 0)
+        conv = iconv_open("utf-8", "utf-16be");
+
+    if (conv == (iconv_t)(-1))
         return NULL;
-    }
-    if ((start = strstr(queue->str, "[[\"")) != NULL)
+    if ((start = strstr(queue->str, "[[[\"")) != NULL)
     {
-        start += strlen( "[[\"");
+        start += strlen( "[[[\"");
         if ((end = strstr(start, "\",")) != NULL)
         {
             size_t length = end - start;
-            char *realstring = fcitx_utils_malloc0(sizeof(char) * (length + 1));
-            strncpy(realstring, start, length);
-            realstring[length] = '\0';
-            if (fcitx_utf8_check_string(realstring)) {
+            if (length % 6 != 0 || length == 0)
+                return NULL;
+
+            size_t i = 0, j = 0;
+            char* buf = fcitx_utils_malloc0((length / 6 + 1) * 2);
+            while (i < length)
+            {
+                if (start[i] == '\\' && start[i+1] == 'u')
+                {
+                    if (ishex(start[i+2]) && ishex(start[i+3]) && ishex(start[i+4]) && ishex(start[i+5]))
+                    {
+                        buf[j++] = (tohex(start[i+2]) << 4) | tohex(start[i+3]);
+                        buf[j++] = (tohex(start[i+4]) << 4) | tohex(start[i+5]);
+                    }
+                    else
+                        break;
+                }
+
+                i += 6;
+            }
+
+            if (i != length)
+            {
+                free(buf);
+                return NULL;
+            }
+            buf[j++] = 0;
+            buf[j++] = 0;
+            size_t len = UTF8_MAX_LENGTH * (length / 6) * sizeof(char);
+            char* realstring = fcitx_utils_malloc0(UTF8_MAX_LENGTH * (length / 6) * sizeof(char));
+            IconvStr p = buf; char *pp = realstring;
+            iconv(conv, &p, &j, &pp, &len);
+
+            free(buf);
+            if (fcitx_utf8_check_string(realstring))
                 return realstring;
-            } else {
+            else
+            {
                 free(realstring);
                 return NULL;
             }
diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt
index e69de29..688285e 100644
--- a/test/CMakeLists.txt
+++ b/test/CMakeLists.txt
@@ -0,0 +1,27 @@
+include_directories(
+    ${PROJECT_SOURCE_DIR}/src
+    ${PROJECT_BINARY_DIR}
+    ${CMAKE_CURRENT_SOURCE_DIR}
+    ${CURL_INCLUDE_DIRS}
+    ${FCITX4_FCITX_INCLUDE_DIRS}
+    ${FCITX4_FCITX_CONFIG_INCLUDE_DIRS}
+    ${FCITX4_FCITX_UTILS_INCLUDE_DIRS}
+    ${LIBINTL_INCLUDE_DIR}
+    ${PTHREAD_INCLUDE_DIR}
+)
+
+link_directories(
+    ${CURL_LIBRARY_DIRS}
+    ${FCITX4_FCITX_UTILS_LIBRARY_DIRS}
+)
+
+add_executable(testhalf testhalf.c ../src/parse.c)
+
+target_link_libraries( testhalf
+                       ${PTHREAD_LIBRARIES}
+                       ${FCITX4_FCITX_UTILS_LIBRARIES}
+                       ${CURL_LIBRARIES}
+                       ${LIBINTL_LIBRARIES}
+                       )
+
+add_test(testhalf testhalf)
\ No newline at end of file
diff --git a/test/testhalf.c b/test/testhalf.c
new file mode 100644
index 0000000..b614aba
--- /dev/null
+++ b/test/testhalf.c
@@ -0,0 +1,19 @@
+#include "parse.h"
+
+#include <assert.h>
+
+int main(int argc, char* argv[])
+{
+    char* result = MapSogouStringToHalf("ABCD");
+    printf("%s\n", result);
+    assert(strcmp(result, "ABCD") == 0);
+
+    free(result);
+
+    result = MapSogouStringToHalf("我a测b你CD的");
+    printf("%s\n", result);
+    assert(strcmp(result, "我a测b你CD的") == 0);
+
+    free(result);
+    return 0;
+}