Skip to content

Instantly share code, notes, and snippets.

@TransparentLC
Last active March 8, 2024 23:07
Show Gist options
  • Star 7 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save TransparentLC/84968e5a8988e6ed9ace68743ab5d342 to your computer and use it in GitHub Desktop.
Save TransparentLC/84968e5a8988e6ed9ace68743ab5d342 to your computer and use it in GitHub Desktop.
下载手机 QQ 的原创表情,使用方式:qsticker.py --emoticonid 203291 --destination /any/dir --zip
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