diff --git a/docs/source/command-line-options.rst b/docs/source/command-line-options.rst index 7650daa..45779da 100644 --- a/docs/source/command-line-options.rst +++ b/docs/source/command-line-options.rst @@ -97,9 +97,10 @@ recursive search by them. false positives). ``--id-type`` - Specify identifier(s) type (default: username). -Supported types: gaia_id, vk_id, yandex_public_id, ok_id, wikimapia_uid. -Currently, you must add ``-a`` flag to run a scan on sites with custom -id types, sites will be filtered automatically. +Supported types: gaia_id, steam_id, vk_id, yandex_public_id, ok_id, +wikimapia_uid, uidme_uguid, yelp_userid, orcid, qq_id. +Sites whose type does not match are filtered out automatically. See +:ref:`supported-identifier-types` for details and an example. ``--ignore-ids`` - Do not make search by the specified username or other ids. Useful for repeated scanning with found known irrelevant usernames. diff --git a/docs/source/locale/zh_CN/LC_MESSAGES/command-line-options.po b/docs/source/locale/zh_CN/LC_MESSAGES/command-line-options.po index 9990d4f..0997057 100644 --- a/docs/source/locale/zh_CN/LC_MESSAGES/command-line-options.po +++ b/docs/source/locale/zh_CN/LC_MESSAGES/command-line-options.po @@ -210,16 +210,18 @@ msgid "" "false positives)." msgstr "``--use-disabled-sites`` —— 把已禁用的站点也纳入搜索范围(可能产生大量误报)。" -#: ../../source/command-line-options.rst:99 20ce94c212df49338e550f7ecf71cc28 +#: ../../source/command-line-options.rst:99 msgid "" -"``--id-type`` - Specify identifier(s) type (default: username). Supported" -" types: gaia_id, vk_id, yandex_public_id, ok_id, wikimapia_uid. " -"Currently, you must add ``-a`` flag to run a scan on sites with custom id" -" types, sites will be filtered automatically." +"``--id-type`` - Specify identifier(s) type (default: username). Supported " +"types: gaia_id, steam_id, vk_id, yandex_public_id, ok_id, wikimapia_uid, " +"uidme_uguid, yelp_userid, orcid, qq_id. Sites whose type does not match are " +"filtered out automatically. See :ref:`supported-identifier-types` for " +"details and an example." msgstr "" -"``--id-type`` —— " -"指定标识符类型(默认:username)。支持的类型有:gaia_id、vk_id、yandex_public_id、ok_id、wikimapia_uid。目前必须同时加" -" ``-a``,才能扫描支持自定义 ID 类型的站点 —— 站点会被自动筛选。" +"``--id-type`` —— 指定标识符类型(默认:username)。支持的类型有:gaia_id、" +"steam_id、vk_id、yandex_public_id、ok_id、wikimapia_uid、uidme_uguid、" +"yelp_userid、orcid、qq_id。类型不匹配的站点会被自动筛除。详情及示例见 " +":ref:`supported-identifier-types`。" #: ../../source/command-line-options.rst:104 ef2bfbd405b1471abfed99b2b82974d7 msgid "" diff --git a/docs/source/locale/zh_CN/LC_MESSAGES/supported-identifier-types.po b/docs/source/locale/zh_CN/LC_MESSAGES/supported-identifier-types.po index 421981d..94c06ee 100644 --- a/docs/source/locale/zh_CN/LC_MESSAGES/supported-identifier-types.po +++ b/docs/source/locale/zh_CN/LC_MESSAGES/supported-identifier-types.po @@ -78,3 +78,32 @@ msgstr "**ok_id** —— OK.ru 内部的数字用户标识符。" #: 5eac68e6daa548d4a0559b3c70fc5de8 msgid "**yelp_userid** - Yelp inner user identifier." msgstr "**yelp_userid** —— Yelp 内部的用户标识符。" + +#: ../../source/supported-identifier-types.rst:16 +msgid "" +"**qq_id** - QQ (Tencent) numeric account number, also used as the Qzone " +"profile id." +msgstr "**qq_id** —— QQ(腾讯)的数字账号,同时用作 Qzone 空间的用户 id。" + +#: ../../source/supported-identifier-types.rst:18 +msgid "Example" +msgstr "示例" + +#: ../../source/supported-identifier-types.rst:21 +msgid "" +"Pass the identifier as the target and select its type with ``--id-type``. " +"For example, to look up the QQ number ``10001``:" +msgstr "" +"将标识符作为搜索目标传入,并通过 ``--id-type`` 指定其类型。例如,查询 QQ 号 " +"``10001``:" + +#: ../../source/supported-identifier-types.rst:27 +msgid "" +"Maigret resolves the account via the Qzone portrait API and, when found, " +"extracts the nickname and avatar. Any site whose ``type`` does not match the " +"given ``--id-type`` is skipped, so a plain ``maigret 10001`` (default " +"``username``) will not query QQ." +msgstr "" +"Maigret 通过 Qzone 头像 API 解析该账号;找到后会提取昵称和头像。凡是 ``type`` " +"与所给 ``--id-type`` 不匹配的站点都会被跳过,因此直接运行 ``maigret 10001``" +"(默认为 ``username``)不会查询 QQ。" diff --git a/docs/source/supported-identifier-types.rst b/docs/source/supported-identifier-types.rst index 312a223..fc32a4d 100644 --- a/docs/source/supported-identifier-types.rst +++ b/docs/source/supported-identifier-types.rst @@ -13,3 +13,15 @@ Maigret can search against not only ordinary usernames, but also through certain - **vk_id** - VK.com inner numeric user identifier. - **ok_id** - OK.ru inner numeric user identifier. - **yelp_userid** - Yelp inner user identifier. +- **qq_id** - QQ (Tencent) numeric account number, also used as the Qzone profile id. + +Example +------- + +Pass the identifier as the target and select its type with ``--id-type``. For example, to look up the QQ number ``10001``: + +.. code-block:: console + + $ maigret 10001 --id-type qq_id + +Maigret resolves the account via the Qzone portrait API and, when found, extracts the nickname and avatar. Any site whose ``type`` does not match the given ``--id-type`` is skipped, so a plain ``maigret 10001`` (default ``username``) will not query QQ. diff --git a/maigret/checking.py b/maigret/checking.py index 91a8dc9..8bda036 100644 --- a/maigret/checking.py +++ b/maigret/checking.py @@ -69,6 +69,7 @@ SUPPORTED_IDS = ( "uidme_uguid", "yelp_userid", "orcid", + "qq_id", ) BAD_CHARS = "#" @@ -133,6 +134,7 @@ class CheckerBase: self.timeout = 0 self.method = 'get' self.payload = None + self.encoding = None class SimpleAiohttpChecker(CheckerBase): @@ -150,13 +152,14 @@ class SimpleAiohttpChecker(CheckerBase): # "Could not contact DNS servers" for every site. self.dns_resolver = kwargs.get('dns_resolver', 'async') - def prepare(self, url, headers=None, allow_redirects=True, timeout=0, method='get', payload=None): + def prepare(self, url, headers=None, allow_redirects=True, timeout=0, method='get', payload=None, encoding=None): self.url = url self.headers = headers self.allow_redirects = allow_redirects self.timeout = timeout self.method = method self.payload = payload + self.encoding = encoding return None async def close(self): @@ -190,7 +193,7 @@ class SimpleAiohttpChecker(CheckerBase): async with request_method(**kwargs) as response: status_code = response.status response_content = await response.content.read() - charset = response.charset or "utf-8" + charset = self.encoding or response.charset or "utf-8" decoded_content = response_content.decode(charset, "ignore") error = CheckError("Connection lost") if status_code == 0 else None @@ -275,7 +278,7 @@ class AiodnsDomainResolver(CheckerBase): loop = asyncio.get_event_loop() self.resolver = aiodns.DNSResolver(loop=loop) - def prepare(self, url, headers=None, allow_redirects=True, timeout=0, method='get', payload=None): + def prepare(self, url, headers=None, allow_redirects=True, timeout=0, method='get', payload=None, encoding=None): self.url = url return None @@ -308,13 +311,14 @@ class CurlCffiChecker(CheckerBase): self.browser_emulate = kwargs.get('browser_emulate', 'chrome') self.proxy = kwargs.get('proxy') - def prepare(self, url, headers=None, allow_redirects=True, timeout=0, method='get', payload=None): + def prepare(self, url, headers=None, allow_redirects=True, timeout=0, method='get', payload=None, encoding=None): self.url = url self.headers = headers self.allow_redirects = allow_redirects self.timeout = timeout self.method = method self.payload = payload + self.encoding = encoding return None async def close(self): @@ -350,6 +354,8 @@ class CurlCffiChecker(CheckerBase): response = await session.get(**kwargs) status_code = response.status_code + if self.encoding: + response.encoding = self.encoding decoded_content = response.text self.logger.debug(decoded_content) @@ -416,13 +422,14 @@ class CloudflareWebgateChecker(CheckerBase): host_safe = re.sub(r"[^a-zA-Z0-9.-]", "_", host) return f"{self._session_prefix}-{host_safe}" - def prepare(self, url, headers=None, allow_redirects=True, timeout=0, method='get', payload=None): + def prepare(self, url, headers=None, allow_redirects=True, timeout=0, method='get', payload=None, encoding=None): self.url = url self.headers = headers or {} self.allow_redirects = allow_redirects self.timeout = timeout self.method = method self.payload = payload + self.encoding = encoding return None async def close(self): @@ -599,7 +606,7 @@ class CheckerMock: def __init__(self, *args, **kwargs): pass - def prepare(self, url, headers=None, allow_redirects=True, timeout=0, method='get', payload=None): + def prepare(self, url, headers=None, allow_redirects=True, timeout=0, method='get', payload=None, encoding=None): return None async def check(self) -> Tuple[Optional[str], int, Optional[CheckError]]: @@ -965,6 +972,7 @@ def make_site_result( allow_redirects=allow_redirects, timeout=options['timeout'], payload=payload, + encoding=site.encoding, ) # Store future request object in the results object @@ -1033,6 +1041,7 @@ async def check_site_for_username( timeout=checker.timeout, method=checker.method, payload=getattr(checker, 'payload', None), + encoding=site.encoding, ) response = await checker.check() @@ -1711,6 +1720,7 @@ async def run_url_mutations(checker, site, results_info, options, logger, query_ allow_redirects=True, timeout=checker.timeout, method='get', + encoding=site.encoding, ) options["enrich_requests"] = options.get("enrich_requests", 0) + 1 html_text, status_code, err = await checker.check() diff --git a/maigret/resources/data.json b/maigret/resources/data.json index e3e8627..178b0fc 100644 --- a/maigret/resources/data.json +++ b/maigret/resources/data.json @@ -1,5 +1,27 @@ { "sites": { + "QQ": { + "type": "qq_id", + "encoding": "gbk", + "regexCheck": "^[1-9][0-9]{4,8}$|^[1-3][0-9]{9}$", + "checkType": "message", + "presenseStrs": [ + "portraitCallBack(" + ], + "absenceStrs": [ + "\"\",0]", + "\"error\"" + ], + "urlProbe": "https://users.qzone.qq.com/fcg-bin/cgi_get_portrait.fcg?uins={username}", + "url": "https://user.qzone.qq.com/{username}", + "urlMain": "https://qq.com", + "usernameClaimed": "10001", + "usernameUnclaimed": "3999999999", + "tags": [ + "cn", + "social" + ] + }, "Facebook": { "regexCheck": "^[a-zA-Z0-9_\\.]{3,49}(?