first
This commit is contained in:
		
						commit
						04f66b8504
					
				
					 6 changed files with 1532 additions and 0 deletions
				
			
		
							
								
								
									
										3
									
								
								.eslintrc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								.eslintrc
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,3 @@ | ||||||
|  | { | ||||||
|  |   "extends": ["prettier", "eslint:recommended"] | ||||||
|  | } | ||||||
							
								
								
									
										4
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,4 @@ | ||||||
|  | index.js | ||||||
|  | node_modules/ | ||||||
|  | output/ | ||||||
|  | temp/ | ||||||
							
								
								
									
										152
									
								
								index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										152
									
								
								index.ts
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,152 @@ | ||||||
|  | import renderLottie from "puppeteer-lottie"; | ||||||
|  | import * as fs from "fs/promises"; | ||||||
|  | import { spawn } from "node:child_process"; | ||||||
|  | 
 | ||||||
|  | const API_URL = "https://discord.com/api/v9/sticker-packs"; | ||||||
|  | const STICKER_SIZE = 160; | ||||||
|  | 
 | ||||||
|  | const LOTTIE_BASE_URL = "https://discord.com/stickers/{id}.json"; | ||||||
|  | const PNG_BASE_URL = "https://media.discordapp.net/stickers/{id}.png"; | ||||||
|  | 
 | ||||||
|  | interface StickerPacks { | ||||||
|  |   sticker_packs: StickerPack[]; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | interface StickerPack { | ||||||
|  |   id: string; | ||||||
|  |   stickers: Sticker[]; | ||||||
|  |   name: string; | ||||||
|  |   sku_id: string; | ||||||
|  |   cover_sticker_id: string; | ||||||
|  |   description: string; | ||||||
|  |   banner_asset_id?: string; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | interface Sticker { | ||||||
|  |   id: string; | ||||||
|  |   name: string; | ||||||
|  |   tags: string; | ||||||
|  |   type: number; | ||||||
|  |   format_type: StickerFormat; | ||||||
|  |   description: string; | ||||||
|  |   asset: string; | ||||||
|  |   pack_id: string; | ||||||
|  |   sort_value: number; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | enum StickerFormat { | ||||||
|  |   STATIC = 1, | ||||||
|  |   APNG = 2, | ||||||
|  |   LOTTIE = 3, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | async function findFilesWithPrefix(prefix: string, path: string): Promise<string[]> { | ||||||
|  |   const files = await fs.readdir(path); | ||||||
|  |   const filteredFiles = files.filter((file) => file.startsWith(prefix)); | ||||||
|  |   return filteredFiles; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | const stickerPacks: StickerPacks = await (await fetch(API_URL)).json(); | ||||||
|  | 
 | ||||||
|  | await fs.mkdir("output"); | ||||||
|  | await fs.mkdir("temp"); | ||||||
|  | 
 | ||||||
|  | for (const pack of stickerPacks.sticker_packs) { | ||||||
|  |   for (const sticker of pack.stickers) { | ||||||
|  |     switch (sticker.format_type) { | ||||||
|  |       case StickerFormat.STATIC: | ||||||
|  |         { | ||||||
|  |           const stickerFile = await ( | ||||||
|  |             await fetch(PNG_BASE_URL.replace("{id}", sticker.id)) | ||||||
|  |           ).blob(); | ||||||
|  |           await fs.writeFile( | ||||||
|  |             `output/${sticker.id}.png`, | ||||||
|  |             Buffer.from(await stickerFile.arrayBuffer()) | ||||||
|  |           ); | ||||||
|  |         } | ||||||
|  |         break; | ||||||
|  | 
 | ||||||
|  |       case StickerFormat.APNG: | ||||||
|  |         { | ||||||
|  |           const stickerFile = await ( | ||||||
|  |             await fetch(PNG_BASE_URL.replace("{id}", sticker.id)) | ||||||
|  |           ).blob(); | ||||||
|  |           await fs.writeFile( | ||||||
|  |             `temp/${sticker.id}.apng`, | ||||||
|  |             Buffer.from(await stickerFile.arrayBuffer()) | ||||||
|  |           ); | ||||||
|  | 
 | ||||||
|  |           const ffmpeg = spawn("ffmpeg", [ | ||||||
|  |             "-v", | ||||||
|  |             "error", | ||||||
|  |             "-i", | ||||||
|  |             `temp/${sticker.id}.apng`, | ||||||
|  |             `temp/${sticker.id}-%8d.png`, | ||||||
|  |           ]); | ||||||
|  |           ffmpeg.stdout.on("data", (data) => { | ||||||
|  |             console.log(`stdout: ${data}`); | ||||||
|  |           }); | ||||||
|  |           ffmpeg.stderr.on("data", (data) => { | ||||||
|  |             console.error(`stderr: ${data}`); | ||||||
|  |           }); | ||||||
|  |           await new Promise((resolve) => ffmpeg.on("exit", resolve)); | ||||||
|  | 
 | ||||||
|  |           let framerate = ""; | ||||||
|  | 
 | ||||||
|  |           const ffprobe = spawn("ffprobe", [ | ||||||
|  |             "-v", | ||||||
|  |             "error", | ||||||
|  |             "-select_streams", | ||||||
|  |             "v", | ||||||
|  |             "-of", | ||||||
|  |             "default=noprint_wrappers=1:nokey=1", | ||||||
|  |             "-show_entries", | ||||||
|  |             "stream=r_frame_rate", | ||||||
|  |             `temp/${sticker.id}.apng` | ||||||
|  |           ]); | ||||||
|  |           ffprobe.stdout.on("data", (data) => { | ||||||
|  |             console.log(`stdout: ${data}`); | ||||||
|  |             framerate += data; | ||||||
|  |           }); | ||||||
|  |           ffprobe.stderr.on("data", (data) => { | ||||||
|  |             console.error(`stderr: ${data}`); | ||||||
|  |           }); | ||||||
|  |           await new Promise((resolve) => ffprobe.on("exit", resolve)); | ||||||
|  | 
 | ||||||
|  |           framerate = eval(framerate); | ||||||
|  | 
 | ||||||
|  |           const gifski = spawn("gifski", [ | ||||||
|  |             "-o", | ||||||
|  |             `output/${sticker.id}.gif`, | ||||||
|  |             "--fps", | ||||||
|  |             framerate, | ||||||
|  |             ...(await findFilesWithPrefix(`${sticker.id}-`, "temp")).map(e => "temp/" + e) | ||||||
|  |           ]); | ||||||
|  |           gifski.stdout.on("data", (data) => { | ||||||
|  |             console.log(`stdout: ${data}`); | ||||||
|  |           }); | ||||||
|  |           gifski.stderr.on("data", (data) => { | ||||||
|  |             console.error(`stderr: ${data}`); | ||||||
|  |           }); | ||||||
|  |           await new Promise((resolve) => gifski.on("exit", resolve)); | ||||||
|  |         } | ||||||
|  |         break; | ||||||
|  | 
 | ||||||
|  |       case StickerFormat.LOTTIE: | ||||||
|  |         { | ||||||
|  |           const stickerFile = await ( | ||||||
|  |             await fetch(LOTTIE_BASE_URL.replace("{id}", sticker.id)) | ||||||
|  |           ).json(); | ||||||
|  |           await renderLottie({ | ||||||
|  |             animationData: stickerFile, | ||||||
|  |             output: `output/${sticker.id}.gif`, | ||||||
|  |             width: STICKER_SIZE, | ||||||
|  |             height: STICKER_SIZE, | ||||||
|  |           }); | ||||||
|  |         } | ||||||
|  |         break; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | await fs.rm("temp", { recursive: true }); | ||||||
							
								
								
									
										23
									
								
								package.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								package.json
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,23 @@ | ||||||
|  | { | ||||||
|  |   "name": "discord-sticker-conv", | ||||||
|  |   "version": "1.0.0", | ||||||
|  |   "main": "index.js", | ||||||
|  |   "license": "CC0", | ||||||
|  |   "private": true, | ||||||
|  |   "type": "module", | ||||||
|  |   "devDependencies": { | ||||||
|  |     "@types/puppeteer-lottie": "^1.1.2", | ||||||
|  |     "eslint": "^8.35.0", | ||||||
|  |     "eslint-config-prettier": "^8.7.0", | ||||||
|  |     "prettier": "^2.8.4", | ||||||
|  |     "typescript": "^4.9.5" | ||||||
|  |   }, | ||||||
|  |   "scripts": { | ||||||
|  |     "format": "prettier --write .", | ||||||
|  |     "lint": "prettier --check . && eslint .", | ||||||
|  |     "start": "tsc && node index.js" | ||||||
|  |   }, | ||||||
|  |   "dependencies": { | ||||||
|  |     "puppeteer-lottie": "^1.1.2" | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										103
									
								
								tsconfig.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										103
									
								
								tsconfig.json
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,103 @@ | ||||||
|  | { | ||||||
|  |   "compilerOptions": { | ||||||
|  |     /* Visit https://aka.ms/tsconfig to read more about this file */ | ||||||
|  | 
 | ||||||
|  |     /* Projects */ | ||||||
|  |     // "incremental": true,                              /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ | ||||||
|  |     // "composite": true,                                /* Enable constraints that allow a TypeScript project to be used with project references. */ | ||||||
|  |     // "tsBuildInfoFile": "./.tsbuildinfo",              /* Specify the path to .tsbuildinfo incremental compilation file. */ | ||||||
|  |     // "disableSourceOfProjectReferenceRedirect": true,  /* Disable preferring source files instead of declaration files when referencing composite projects. */ | ||||||
|  |     // "disableSolutionSearching": true,                 /* Opt a project out of multi-project reference checking when editing. */ | ||||||
|  |     // "disableReferencedProjectLoad": true,             /* Reduce the number of projects loaded automatically by TypeScript. */ | ||||||
|  | 
 | ||||||
|  |     /* Language and Environment */ | ||||||
|  |     "target": "ES2017" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */, | ||||||
|  |     // "lib": [],                                        /* Specify a set of bundled library declaration files that describe the target runtime environment. */ | ||||||
|  |     // "jsx": "preserve",                                /* Specify what JSX code is generated. */ | ||||||
|  |     // "experimentalDecorators": true,                   /* Enable experimental support for TC39 stage 2 draft decorators. */ | ||||||
|  |     // "emitDecoratorMetadata": true,                    /* Emit design-type metadata for decorated declarations in source files. */ | ||||||
|  |     // "jsxFactory": "",                                 /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ | ||||||
|  |     // "jsxFragmentFactory": "",                         /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ | ||||||
|  |     // "jsxImportSource": "",                            /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ | ||||||
|  |     // "reactNamespace": "",                             /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ | ||||||
|  |     // "noLib": true,                                    /* Disable including any library files, including the default lib.d.ts. */ | ||||||
|  |     // "useDefineForClassFields": true,                  /* Emit ECMAScript-standard-compliant class fields. */ | ||||||
|  |     // "moduleDetection": "auto",                        /* Control what method is used to detect module-format JS files. */ | ||||||
|  | 
 | ||||||
|  |     /* Modules */ | ||||||
|  |     "module": "ES2022" /* Specify what module code is generated. */, | ||||||
|  |     // "rootDir": "./",                                  /* Specify the root folder within your source files. */ | ||||||
|  |     "moduleResolution": "node" /* Specify how TypeScript looks up a file from a given module specifier. */, | ||||||
|  |     // "baseUrl": "./",                                  /* Specify the base directory to resolve non-relative module names. */ | ||||||
|  |     // "paths": {},                                      /* Specify a set of entries that re-map imports to additional lookup locations. */ | ||||||
|  |     // "rootDirs": [],                                   /* Allow multiple folders to be treated as one when resolving modules. */ | ||||||
|  |     // "typeRoots": [],                                  /* Specify multiple folders that act like './node_modules/@types'. */ | ||||||
|  |     // "types": [],                                      /* Specify type package names to be included without being referenced in a source file. */ | ||||||
|  |     // "allowUmdGlobalAccess": true,                     /* Allow accessing UMD globals from modules. */ | ||||||
|  |     // "moduleSuffixes": [],                             /* List of file name suffixes to search when resolving a module. */ | ||||||
|  |     // "resolveJsonModule": true,                        /* Enable importing .json files. */ | ||||||
|  |     // "noResolve": true,                                /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */ | ||||||
|  | 
 | ||||||
|  |     /* JavaScript Support */ | ||||||
|  |     // "allowJs": true,                                  /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ | ||||||
|  |     // "checkJs": true,                                  /* Enable error reporting in type-checked JavaScript files. */ | ||||||
|  |     // "maxNodeModuleJsDepth": 1,                        /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ | ||||||
|  | 
 | ||||||
|  |     /* Emit */ | ||||||
|  |     // "declaration": true,                              /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ | ||||||
|  |     // "declarationMap": true,                           /* Create sourcemaps for d.ts files. */ | ||||||
|  |     // "emitDeclarationOnly": true,                      /* Only output d.ts files and not JavaScript files. */ | ||||||
|  |     // "sourceMap": true,                                /* Create source map files for emitted JavaScript files. */ | ||||||
|  |     // "outFile": "./",                                  /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ | ||||||
|  |     // "outDir": "./",                                   /* Specify an output folder for all emitted files. */ | ||||||
|  |     // "removeComments": true,                           /* Disable emitting comments. */ | ||||||
|  |     // "noEmit": true,                                   /* Disable emitting files from a compilation. */ | ||||||
|  |     // "importHelpers": true,                            /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ | ||||||
|  |     // "importsNotUsedAsValues": "remove",               /* Specify emit/checking behavior for imports that are only used for types. */ | ||||||
|  |     // "downlevelIteration": true,                       /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ | ||||||
|  |     // "sourceRoot": "",                                 /* Specify the root path for debuggers to find the reference source code. */ | ||||||
|  |     // "mapRoot": "",                                    /* Specify the location where debugger should locate map files instead of generated locations. */ | ||||||
|  |     // "inlineSourceMap": true,                          /* Include sourcemap files inside the emitted JavaScript. */ | ||||||
|  |     // "inlineSources": true,                            /* Include source code in the sourcemaps inside the emitted JavaScript. */ | ||||||
|  |     // "emitBOM": true,                                  /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ | ||||||
|  |     // "newLine": "crlf",                                /* Set the newline character for emitting files. */ | ||||||
|  |     // "stripInternal": true,                            /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ | ||||||
|  |     // "noEmitHelpers": true,                            /* Disable generating custom helper functions like '__extends' in compiled output. */ | ||||||
|  |     // "noEmitOnError": true,                            /* Disable emitting files if any type checking errors are reported. */ | ||||||
|  |     // "preserveConstEnums": true,                       /* Disable erasing 'const enum' declarations in generated code. */ | ||||||
|  |     // "declarationDir": "./",                           /* Specify the output directory for generated declaration files. */ | ||||||
|  |     // "preserveValueImports": true,                     /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ | ||||||
|  | 
 | ||||||
|  |     /* Interop Constraints */ | ||||||
|  |     // "isolatedModules": true,                          /* Ensure that each file can be safely transpiled without relying on other imports. */ | ||||||
|  |     // "allowSyntheticDefaultImports": true,             /* Allow 'import x from y' when a module doesn't have a default export. */ | ||||||
|  |     "esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */, | ||||||
|  |     // "preserveSymlinks": true,                         /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ | ||||||
|  |     "forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */, | ||||||
|  | 
 | ||||||
|  |     /* Type Checking */ | ||||||
|  |     "strict": true /* Enable all strict type-checking options. */, | ||||||
|  |     // "noImplicitAny": true,                            /* Enable error reporting for expressions and declarations with an implied 'any' type. */ | ||||||
|  |     // "strictNullChecks": true,                         /* When type checking, take into account 'null' and 'undefined'. */ | ||||||
|  |     // "strictFunctionTypes": true,                      /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ | ||||||
|  |     // "strictBindCallApply": true,                      /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ | ||||||
|  |     // "strictPropertyInitialization": true,             /* Check for class properties that are declared but not set in the constructor. */ | ||||||
|  |     // "noImplicitThis": true,                           /* Enable error reporting when 'this' is given the type 'any'. */ | ||||||
|  |     // "useUnknownInCatchVariables": true,               /* Default catch clause variables as 'unknown' instead of 'any'. */ | ||||||
|  |     // "alwaysStrict": true,                             /* Ensure 'use strict' is always emitted. */ | ||||||
|  |     // "noUnusedLocals": true,                           /* Enable error reporting when local variables aren't read. */ | ||||||
|  |     // "noUnusedParameters": true,                       /* Raise an error when a function parameter isn't read. */ | ||||||
|  |     // "exactOptionalPropertyTypes": true,               /* Interpret optional property types as written, rather than adding 'undefined'. */ | ||||||
|  |     // "noImplicitReturns": true,                        /* Enable error reporting for codepaths that do not explicitly return in a function. */ | ||||||
|  |     // "noFallthroughCasesInSwitch": true,               /* Enable error reporting for fallthrough cases in switch statements. */ | ||||||
|  |     // "noUncheckedIndexedAccess": true,                 /* Add 'undefined' to a type when accessed using an index. */ | ||||||
|  |     // "noImplicitOverride": true,                       /* Ensure overriding members in derived classes are marked with an override modifier. */ | ||||||
|  |     // "noPropertyAccessFromIndexSignature": true,       /* Enforces using indexed accessors for keys declared using an indexed type. */ | ||||||
|  |     // "allowUnusedLabels": true,                        /* Disable error reporting for unused labels. */ | ||||||
|  |     // "allowUnreachableCode": true,                     /* Disable error reporting for unreachable code. */ | ||||||
|  | 
 | ||||||
|  |     /* Completeness */ | ||||||
|  |     // "skipDefaultLibCheck": true,                      /* Skip type checking .d.ts files that are included with TypeScript. */ | ||||||
|  |     "skipLibCheck": true /* Skip type checking all .d.ts files. */ | ||||||
|  |   } | ||||||
|  | } | ||||||
		Loading…
	
		Reference in a new issue