Last active
November 19, 2024 08:43
-
-
Save TransparentLC/84968e5a8988e6ed9ace68743ab5d342 to your computer and use it in GitHub Desktop.
下载手机 QQ 的原创表情,使用方式:qsticker.py --emoticonid 203291 --destination /any/dir --zip
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import argparse | |
import json | |
import os | |
import re | |
import requests | |
import requests.exceptions | |
import threading | |
import zipfile | |
from requests.models import Response | |
def downloadTask(url: str, dest = None) -> Response: | |
retry = 0 | |
success = False | |
res = None | |
while not success: | |
try: | |
res = requests.get(url, proxies={'http': None, 'https': None}) | |
res.raise_for_status() | |
success = True | |
except requests.exceptions.HTTPError as ex: | |
if ex.response.status_code == 404: | |
raise ex | |
else: | |
print('HTTP error, retrying') | |
retry += 1 | |
if retry > 3: | |
raise ex | |
if dest: | |
with open(dest, 'wb') as f: | |
f.write(res.content) | |
return res | |
def getFrameCount(imgData: bytes) -> bool: | |
# GifFrameExtractor::isAnimatedGif() | |
# https://github.com/Sybio/GifFrameExtractor/blob/13e9880add3ae9cca10a48a0a3897fe3d0069f71/src/GifFrameExtractor/GifFrameExtractor.php#L192 | |
return len(re.findall(b'\x00\x21\xF9\x04.{4}\x00[\x2C\x21]', imgData, re.DOTALL)) | |
parser = argparse.ArgumentParser() | |
parser.add_argument( | |
'-e', | |
'--emoticonid', | |
dest='emoticonID', | |
type=int, | |
required=True, | |
help='The ID of emoticon pack.' | |
) | |
parser.add_argument( | |
'-d', | |
'--destination', | |
dest='destination', | |
type=str, | |
default=os.getcwd(), | |
help='The path to save the files.' | |
) | |
parser.add_argument( | |
'-z', | |
'--zip', | |
dest='zip', | |
action='store_true', | |
help='Pack the emoticons into a ZIP file.' | |
) | |
args = parser.parse_args() | |
print(f'Fetching metadata of sticker #{args.emoticonID}') | |
try: | |
meta = json.loads(downloadTask(f'https://gxh.vip.qq.com/qqshow/admindata/comdata/vipEmoji_item_{args.emoticonID}/xydata.json').text) | |
except requests.exceptions.HTTPError: | |
print('The metadata is not found!\n') | |
exit(0) | |
metaOriginal = json.dumps(meta, indent=2, ensure_ascii=False, sort_keys=True) | |
meta = { | |
'name': meta['data']['baseInfo'][0]['name'], | |
'desc': meta['data']['baseInfo'][0]['desc'], | |
'tag': [i for i in meta['data']['baseInfo'][0]['tag'].split(' ') if i], | |
'icon': f'https://i.gtimg.cn/club/item/parcel/img/parcel/{args.emoticonID % 10}/{args.emoticonID}/200x200.png', | |
'image': [ | |
{ | |
'keyword': i['name'], | |
'url': { | |
'gif': f'https://i.gtimg.cn/club/item/parcel/item/{i["md5"][:2]}/{i["md5"]}/raw200.gif', | |
'png': f'https://i.gtimg.cn/club/item/parcel/item/{i["md5"][:2]}/{i["md5"]}/{300 if args.emoticonID > 100000 else 126}x{300 if args.emoticonID > 100000 else 126}.png', | |
}, | |
} | |
for i in meta['data']['md5Info'] | |
], | |
} | |
print(f'Name: {meta["name"]}') | |
print(f'Description: {meta["desc"]}') | |
print(f'Tag: {" ".join(meta["tag"])}') | |
print('Downloading icon') | |
icon = downloadTask(meta['icon']).content | |
destFolder = os.path.join(args.destination, f'{args.emoticonID}-{meta["name"]}') | |
destImageFolder = os.path.join(destFolder, 'image') | |
if not os.path.isdir(destFolder): | |
os.mkdir(destFolder) | |
if not os.path.isdir(destImageFolder): | |
os.mkdir(destImageFolder) | |
with open(os.path.join(destFolder, 'meta.json'), 'w') as f: | |
f.write(metaOriginal) | |
with open(os.path.join(destFolder, 'icon.png'), 'wb') as f: | |
f.write(icon) | |
firstImageContent = None | |
try: | |
firstImageContent = downloadTask(meta['image'][0]['url']['gif']).content | |
frameCount = getFrameCount(firstImageContent) | |
except requests.exceptions.HTTPError: | |
frameCount = 1 | |
processes = [] | |
print('Creating threads to download the images') | |
if frameCount == 1: | |
print('The emoticon pack is not animated, downloading PNG') | |
taskArgs = [ | |
[image['url']['png'], os.path.join(destImageFolder, f'{image["keyword"]}.png')] | |
for image in meta['image'] | |
] | |
else: | |
with open(os.path.join(destImageFolder, f'{meta["image"][0]["keyword"]}.gif'), 'wb') as f: | |
f.write(firstImageContent) | |
print('The emoticon pack is animated, downloading GIF') | |
taskArgs = [ | |
[image['url']['gif'], os.path.join(destImageFolder, f'{image["keyword"]}.gif')] | |
for image in meta['image'][1:] | |
] | |
for a in taskArgs: | |
process = threading.Thread(target=downloadTask, args=a) | |
process.start() | |
processes.append(process) | |
for process in processes: | |
process.join() | |
if args.zip: | |
print('Packing ZIP...') | |
with zipfile.ZipFile(f'{destFolder}.zip', 'w', zipfile.ZIP_DEFLATED) as f: | |
for dirpath, dirnames, filenames in os.walk(destFolder, False): | |
for fn in filenames: | |
filepath = os.path.join(dirpath, fn) | |
f.write(filepath, os.path.join(dirpath[(len(destFolder)):], fn)) | |
os.remove(filepath) | |
for dn in dirnames: | |
os.removedirs(os.path.join(dirpath, dn)) | |
print('Complete!\n') |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment