@ryuu-reinzz/haruka-lib 1.2.21 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/main/index.js +4 -1
  2. package/main/socket.js +1 -2
  3. package/main/sticker-engine/java-script/Sticker.d.ts +130 -0
  4. package/main/sticker-engine/java-script/Sticker.js +187 -0
  5. package/main/sticker-engine/java-script/Types.d.ts +43 -0
  6. package/main/sticker-engine/java-script/Types.js +1 -0
  7. package/main/sticker-engine/java-script/Utils.d.ts +9 -0
  8. package/main/sticker-engine/java-script/Utils.js +11 -0
  9. package/main/sticker-engine/java-script/extractMetadata.d.ts +6 -0
  10. package/main/sticker-engine/java-script/extractMetadata.js +11 -0
  11. package/main/sticker-engine/java-script/index.d.ts +8 -0
  12. package/main/sticker-engine/java-script/index.js +8 -0
  13. package/main/sticker-engine/java-script/internal/Metadata/Exif.d.ts +9 -0
  14. package/main/sticker-engine/java-script/internal/Metadata/Exif.js +34 -0
  15. package/main/sticker-engine/java-script/internal/Metadata/RawMetadata.d.ts +8 -0
  16. package/main/sticker-engine/java-script/internal/Metadata/RawMetadata.js +9 -0
  17. package/main/sticker-engine/java-script/internal/Metadata/StickerMetadata.d.ts +18 -0
  18. package/main/sticker-engine/java-script/internal/Metadata/StickerMetadata.js +49 -0
  19. package/main/sticker-engine/java-script/internal/Metadata/StickerTypes.d.ts +7 -0
  20. package/main/sticker-engine/java-script/internal/Metadata/StickerTypes.js +8 -0
  21. package/main/sticker-engine/java-script/internal/convert.d.ts +3 -0
  22. package/main/sticker-engine/java-script/internal/convert.js +69 -0
  23. package/main/sticker-engine/java-script/internal/crop.d.ts +2 -0
  24. package/main/sticker-engine/java-script/internal/crop.js +30 -0
  25. package/main/sticker-engine/java-script/internal/imagesToWebp.d.ts +2 -0
  26. package/main/sticker-engine/java-script/internal/imagesToWebp.js +17 -0
  27. package/main/sticker-engine/java-script/internal/videoToGif.d.ts +3 -0
  28. package/main/sticker-engine/java-script/internal/videoToGif.js +16 -0
  29. package/main/sticker-engine/type-script/Sticker.ts +206 -0
  30. package/main/sticker-engine/type-script/Types.ts +189 -0
  31. package/main/sticker-engine/type-script/Utils.ts +12 -0
  32. package/main/sticker-engine/type-script/extractMetadata.ts +13 -0
  33. package/main/sticker-engine/type-script/index.ts +9 -0
  34. package/main/sticker-engine/type-script/internal/Metadata/Exif.ts +39 -0
  35. package/main/sticker-engine/type-script/internal/Metadata/RawMetadata.ts +15 -0
  36. package/main/sticker-engine/type-script/internal/Metadata/StickerMetadata.ts +60 -0
  37. package/main/sticker-engine/type-script/internal/Metadata/StickerTypes.ts +7 -0
  38. package/main/sticker-engine/type-script/internal/convert.ts +87 -0
  39. package/main/sticker-engine/type-script/internal/crop.ts +32 -0
  40. package/main/sticker-engine/type-script/internal/imagesToWebp.ts +19 -0
  41. package/main/sticker-engine/type-script/internal/node-webpmux.d.ts +8 -0
  42. package/main/sticker-engine/type-script/internal/videoToGif.ts +18 -0
  43. package/package.json +12 -3
  44. package/tsconfig.json +13 -0
@@ -0,0 +1,69 @@
1
+ import sharp, { fit } from 'sharp';
2
+ import videoToGif from './videoToGif';
3
+ import { writeFile } from 'fs-extra';
4
+ import { tmpdir } from 'os';
5
+ import crop from './crop';
6
+ import { StickerTypes } from './Metadata/StickerTypes';
7
+ import { defaultBg } from '../Utils';
8
+ const convert = async (data, mime, { quality = 100, background = defaultBg, type = StickerTypes.DEFAULT }) => {
9
+ const isVideo = mime.startsWith('video');
10
+ let image = isVideo ? await videoToGif(data) : data;
11
+ const isAnimated = isVideo || mime.includes('gif') || mime.includes('webp');
12
+ if (isAnimated && ['crop', 'circle', 'rouded'].includes(type)) {
13
+ const filename = `${tmpdir()}/${Math.random().toString(36)}.webp`;
14
+ await writeFile(filename, image);
15
+ [image, type] = [
16
+ await crop(filename),
17
+ type === StickerTypes.CIRCLE
18
+ ? StickerTypes.CIRCLE
19
+ : type === StickerTypes.ROUNDED
20
+ ? StickerTypes.ROUNDED
21
+ : StickerTypes.DEFAULT
22
+ ];
23
+ }
24
+ const img = sharp(image, { animated: isAnimated }).toFormat('webp');
25
+ switch (type) {
26
+ case StickerTypes.CROPPED:
27
+ img.resize(512, 512, {
28
+ fit: fit.cover
29
+ });
30
+ break;
31
+ case StickerTypes.FULL:
32
+ img.resize(512, 512, {
33
+ fit: fit.contain,
34
+ background
35
+ });
36
+ break;
37
+ case StickerTypes.CIRCLE:
38
+ img.resize(512, 512, {
39
+ fit: fit.cover
40
+ }).composite([
41
+ {
42
+ input: Buffer.from(`<svg width="512" height="512"><circle cx="256" cy="256" r="256" fill="${background}"/></svg>`),
43
+ blend: 'dest-in',
44
+ gravity: 'northeast',
45
+ tile: true
46
+ }
47
+ ]);
48
+ break;
49
+ case StickerTypes.ROUNDED:
50
+ img.resize(512, 512, {
51
+ fit: fit.cover
52
+ }).composite([
53
+ {
54
+ input: Buffer.from(`<svg width="512" height="512"><rect rx="50" ry="50" width="512" height="512" fill="${background}"/></svg>`),
55
+ blend: 'dest-in',
56
+ gravity: 'northeast',
57
+ tile: true
58
+ }
59
+ ]);
60
+ break;
61
+ }
62
+ return await img
63
+ .webp({
64
+ quality,
65
+ lossless: false
66
+ })
67
+ .toBuffer();
68
+ };
69
+ export default convert;
@@ -0,0 +1,2 @@
1
+ declare const crop: (filename: string) => Promise<Buffer>;
2
+ export default crop;
@@ -0,0 +1,30 @@
1
+ import Ffmpeg from 'fluent-ffmpeg';
2
+ import { readFile } from 'fs-extra';
3
+ import { tmpdir } from 'os';
4
+ const crop = async (filename) => {
5
+ const file = await new Promise((resolve) => {
6
+ const name = `${tmpdir()}/${Math.random().toString(36)}.webp`;
7
+ Ffmpeg(filename)
8
+ // eslint-disable-next-line no-useless-escape
9
+ .outputOptions([
10
+ '-vcodec',
11
+ 'libwebp',
12
+ '-vf',
13
+ // eslint-disable-next-line no-useless-escape
14
+ `crop=w='min(min(iw\,ih)\,500)':h='min(min(iw\,ih)\,500)',scale=500:500,setsar=1,fps=15`,
15
+ '-loop',
16
+ '0',
17
+ '-preset',
18
+ 'default',
19
+ '-an',
20
+ '-vsync',
21
+ '0',
22
+ '-s',
23
+ '512:512'
24
+ ])
25
+ .save(name)
26
+ .on('end', () => resolve(name));
27
+ });
28
+ return await readFile(file);
29
+ };
30
+ export default crop;
@@ -0,0 +1,2 @@
1
+ declare const imagesToWebp: (filename: string) => Promise<Buffer>;
2
+ export default imagesToWebp;
@@ -0,0 +1,17 @@
1
+ import Ffmpeg from 'fluent-ffmpeg';
2
+ import { readFile } from 'fs-extra';
3
+ import { tmpdir } from 'os';
4
+ const imagesToWebp = async (filename) => {
5
+ const file = await new Promise((resolve) => {
6
+ const name = `${tmpdir()}/${Math.random().toString(36)}.webp`;
7
+ Ffmpeg(filename)
8
+ .outputOption('-lavfi split[v],palettegen,[v]paletteuse')
9
+ .outputOption('-vcodec libwebp')
10
+ .outputFPS(10)
11
+ .loop(0)
12
+ .save(name)
13
+ .on('end', () => resolve(name));
14
+ });
15
+ return await readFile(file);
16
+ };
17
+ export default imagesToWebp;
@@ -0,0 +1,3 @@
1
+ /** https://stackoverflow.com/questions/52156713/fluent-ffmpeg-h264-to-gif-throwing-error-1 */
2
+ declare const videoToGif: (data: Buffer) => Promise<Buffer>;
3
+ export default videoToGif;
@@ -0,0 +1,16 @@
1
+ import ffmpeg from 'fluent-ffmpeg';
2
+ import { writeFile, readFile, unlink } from 'fs-extra';
3
+ import { tmpdir } from 'os';
4
+ /** https://stackoverflow.com/questions/52156713/fluent-ffmpeg-h264-to-gif-throwing-error-1 */
5
+ const videoToGif = async (data) => {
6
+ const filename = `${tmpdir()}/${Math.random().toString(36)}`;
7
+ const [video, gif] = ['video', 'gif'].map((ext) => `${filename}.${ext}`);
8
+ await writeFile(video, data);
9
+ await new Promise((resolve) => {
10
+ ffmpeg(video).save(gif).on('end', resolve);
11
+ });
12
+ const buffer = await readFile(gif);
13
+ [video, gif].forEach((file) => unlink(file));
14
+ return buffer;
15
+ };
16
+ export default videoToGif;
@@ -0,0 +1,206 @@
1
+ import { existsSync, readFile, writeFile } from 'fs-extra'
2
+ import { IStickerConfig, IStickerOptions } from './Types'
3
+ import axios from 'axios'
4
+ import Utils, { defaultBg } from './Utils'
5
+ import { fromBuffer } from 'file-type'
6
+ import convert from './internal/convert'
7
+ import Exif from './internal/Metadata/Exif'
8
+ import { StickerTypes } from './internal/Metadata/StickerTypes'
9
+ import { Categories, extractMetadata } from '.'
10
+ import { Color } from 'sharp'
11
+
12
+ /**
13
+ * Sticker class
14
+ */
15
+ export class Sticker {
16
+ /**
17
+ * Sticker Constructor
18
+ * @param {string|Buffer} [data] - File path, url or Buffer of the image/video to be converted
19
+ * @param {IStickerOptions} [options] - Sticker options
20
+ */
21
+ constructor(private data: string | Buffer, public metadata: Partial<IStickerOptions> = {}) {
22
+ this.metadata.author = this.metadata.author ?? ''
23
+ this.metadata.pack = this.metadata.pack ?? ''
24
+ this.metadata.id = this.metadata.id ?? Utils.generateStickerID()
25
+ this.metadata.quality = this.metadata.quality ?? 100
26
+ this.metadata.type = Object.values(StickerTypes).includes(this.metadata.type as StickerTypes)
27
+ ? this.metadata.type
28
+ : StickerTypes.DEFAULT
29
+ this.metadata.background = this.metadata.background ?? defaultBg
30
+ }
31
+
32
+ private _parse = async (): Promise<Buffer> =>
33
+ Buffer.isBuffer(this.data)
34
+ ? this.data
35
+ : this.data.trim().startsWith('<svg')
36
+ ? Buffer.from(this.data)
37
+ : (async () =>
38
+ existsSync(this.data)
39
+ ? readFile(this.data)
40
+ : axios.get(this.data as string, { responseType: 'arraybuffer' }).then(({ data }) => data))()
41
+
42
+ private _getMimeType = async (data: Buffer): Promise<string> => {
43
+ const type = await fromBuffer(data)
44
+ if (!type) {
45
+ if (typeof this.data === 'string') return 'image/svg+xml'
46
+ throw new Error('Invalid file type')
47
+ }
48
+ return type.mime
49
+ }
50
+
51
+ /**
52
+ * Builds the sticker
53
+ * @returns {Promise<Buffer>} A promise that resolves to the sticker buffer
54
+ * @example
55
+ * const sticker = new Sticker('./image.png')
56
+ * const buffer = sticker.build()
57
+ */
58
+ public build = async (): Promise<Buffer> => {
59
+ const data = await this._parse()
60
+ const mime = await this._getMimeType(data)
61
+ return new Exif(this.metadata as IStickerConfig).add(await convert(data, mime, this.metadata))
62
+ }
63
+
64
+ /**
65
+ * Alias for `.build()`
66
+ * @param {string} [type] - How you want your sticker to look like
67
+ * @returns {Promise<Buffer>} A promise that resolves to the sticker buffer
68
+ * @example
69
+ * const sticker = new Sticker('./image.png')
70
+ * const buffer = sticker.build()
71
+ */
72
+ public toBuffer = this.build
73
+
74
+ public get defaultFilename(): string {
75
+ return `./${this.metadata.pack}-${this.metadata.author}.webp`
76
+ }
77
+
78
+ /**
79
+ * Saves the sticker to a file
80
+ * @param [filename] - Filename to save the sticker to
81
+ * @returns filename
82
+ * @example
83
+ * const sticker = new Sticker('./image.png')
84
+ * sticker.toFile('./image.webp')
85
+ */
86
+ public toFile = async (filename = this.defaultFilename): Promise<string> => {
87
+ await writeFile(filename, await this.build())
88
+ return filename
89
+ }
90
+
91
+ /**
92
+ * Set the sticker pack title
93
+ * @param pack - Sticker Pack Title
94
+ * @returns {this}
95
+ * @example
96
+ * const sticker = new Sticker('./image.png')
97
+ * sticker.setPack('My Sticker Pack')
98
+ * sticker.build()
99
+ */
100
+ public setPack = (pack: string): this => {
101
+ this.metadata.pack = pack
102
+ return this
103
+ }
104
+
105
+ /**
106
+ * Set the sticker pack author
107
+ * @param author - Sticker Pack Author
108
+ * @returns
109
+ */
110
+ public setAuthor = (author: string): this => {
111
+ this.metadata.author = author
112
+ return this
113
+ }
114
+
115
+ /**
116
+ * Set the sticker pack ID
117
+ * @param id - Sticker Pack ID
118
+ * @returns {this}
119
+ * @example
120
+ * const sticker = new Sticker('./image.png')
121
+ * sticker.setID('my-sticker-pack')
122
+ * sticker.build()
123
+ */
124
+ public setID = (id: string): this => {
125
+ this.metadata.id = id
126
+ return this
127
+ }
128
+
129
+ /**
130
+ * Set the sticker category
131
+ * @param categories - Sticker Category
132
+ * @returns {this}
133
+ * @example
134
+ * const sticker = new Sticker('./image.png')
135
+ * sticker.setCategories(['🌹'])
136
+ */
137
+ public setCategories = (categories: Categories[]): this => {
138
+ this.metadata.categories = categories
139
+ return this
140
+ }
141
+
142
+ /**
143
+ * Set the sticker type
144
+ * @param {StickerTypes|string}[type] - Sticker Type
145
+ * @returns {this}
146
+ */
147
+ public setType = (type: StickerTypes | string): this => {
148
+ this.metadata.type = type
149
+ return this
150
+ }
151
+
152
+ /**
153
+ * Set the sticker quality
154
+ * @param {number}[quality] - Sticker Quality
155
+ * @returns {this}
156
+ */
157
+ public setQuality = (quality: number): this => {
158
+ this.metadata.quality = quality
159
+ return this
160
+ }
161
+
162
+ /**
163
+ * Set the background color for `full` images
164
+ * @param {Color}[background] - Background color
165
+ * @returns {this}
166
+ */
167
+ public setBackground = (background: Color): this => {
168
+ this.metadata.background = background
169
+ return this
170
+ }
171
+
172
+ /**
173
+ * @deprecated
174
+ * Use the `Sticker.build()` method instead
175
+ */
176
+ public get = this.build
177
+
178
+ /**
179
+ * Get BaileysMD-compatible message object
180
+ * @returns {{ sticker: Buffer }}
181
+ * @example
182
+ * import { create } from '@adiwajshing/baileys-md'
183
+ * const conn = create()
184
+ * ...
185
+ * const sticker = new Sticker('./image.png', { pack: 'My Sticker Pack', author: 'Me' })
186
+ * const message = await sticker.toMessage()
187
+ * conn.sendMessage(jid, message)
188
+ */
189
+ public toMessage = async (): Promise<{ sticker: Buffer }> => ({ sticker: await this.build() })
190
+
191
+ /**
192
+ * Extracts metadata from a WebP image.
193
+ * @param {Buffer}image - The image buffer to extract metadata from
194
+ */
195
+ public static extractMetadata = extractMetadata
196
+ }
197
+
198
+ /**
199
+ *
200
+ * @param {string|Buffer} data - File path, url or Buffer of the image/video to be converted
201
+ * @param {IStickerOptions} [options] - Sticker options
202
+ * @returns {Promise<Buffer>} A promise that resolves to the sticker buffer
203
+ */
204
+ export const createSticker = async (...args: ConstructorParameters<typeof Sticker>): Promise<Buffer> => {
205
+ return new Sticker(...args).build()
206
+ }
@@ -0,0 +1,189 @@
1
+ import sharp, { Color } from 'sharp'
2
+ import { StickerTypes } from './internal/Metadata/StickerTypes'
3
+
4
+ /** Sticker metadata config */
5
+ export interface IStickerConfig {
6
+ /** Sticker Pack title*/
7
+ pack?: string
8
+ /** Sticker Pack Author*/
9
+ author?: string
10
+ /** Sticker Pack ID*/
11
+ id?: string
12
+ /** Sticker Category*/
13
+ categories?: Categories[]
14
+ }
15
+
16
+ export interface IStickerOptions extends IStickerConfig {
17
+ /** How you want your sticker to look like
18
+ * Can be crop or full. Defaults to 'default' (no changes)
19
+ */
20
+ type?: StickerTypes | string
21
+
22
+ /**
23
+ * Quality of the output webp image. Must be an integer from 0 to 100 (defaults to 100
24
+ */
25
+ quality?: sharp.WebpOptions['quality']
26
+ /**
27
+ * Background Color of the sticker (only for type full)
28
+ */
29
+ background?: Color
30
+ }
31
+
32
+ export interface IRawMetadata {
33
+ emojis: string[]
34
+ 'sticker-pack-id': string
35
+ 'sticker-pack-name': string
36
+ 'sticker-pack-publisher': string
37
+ }
38
+
39
+ export type Metadata = IStickerConfig | IStickerOptions
40
+
41
+ type Love =
42
+ | '❤'
43
+ | '😍'
44
+ | '😘'
45
+ | '💕'
46
+ | '😻'
47
+ | '💑'
48
+ | '👩‍❤‍👩'
49
+ | '👨‍❤‍👨'
50
+ | '💏'
51
+ | '👩‍❤‍💋‍👩'
52
+ | '👨‍❤‍💋‍👨'
53
+ | '🧡'
54
+ | '💛'
55
+ | '💚'
56
+ | '💙'
57
+ | '💜'
58
+ | '🖤'
59
+ | '💔'
60
+ | '❣'
61
+ | '💞'
62
+ | '💓'
63
+ | '💗'
64
+ | '💖'
65
+ | '💘'
66
+ | '💝'
67
+ | '💟'
68
+ | '♥'
69
+ | '💌'
70
+ | '💋'
71
+ | '👩‍❤️‍💋‍👩'
72
+ | '👨‍❤️‍💋‍👨'
73
+ | '👩‍❤️‍👨'
74
+ | '👩‍❤️‍👩'
75
+ | '👨‍❤️‍👨'
76
+ | '👩‍❤️‍💋‍👨'
77
+ | '👬'
78
+ | '👭'
79
+ | '👫'
80
+ | '🥰'
81
+ | '😚'
82
+ | '😙'
83
+ | '👄'
84
+ | '🌹'
85
+ | '😽'
86
+ | '❣️'
87
+ | '❤️'
88
+ type Happy =
89
+ | '😀'
90
+ | '😃'
91
+ | '😄'
92
+ | '😁'
93
+ | '😆'
94
+ | '😅'
95
+ | '😂'
96
+ | '🤣'
97
+ | '🙂'
98
+ | '😛'
99
+ | '😝'
100
+ | '😜'
101
+ | '🤪'
102
+ | '🤗'
103
+ | '😺'
104
+ | '😸'
105
+ | '😹'
106
+ | '☺'
107
+ | '😌'
108
+ | '😉'
109
+ | '🤗'
110
+ | '😊'
111
+ type Sad =
112
+ | '☹'
113
+ | '😣'
114
+ | '😖'
115
+ | '😫'
116
+ | '😩'
117
+ | '😢'
118
+ | '😭'
119
+ | '😞'
120
+ | '😔'
121
+ | '😟'
122
+ | '😕'
123
+ | '😤'
124
+ | '😠'
125
+ | '😥'
126
+ | '😰'
127
+ | '😨'
128
+ | '😿'
129
+ | '😾'
130
+ | '😓'
131
+ | '🙍‍♂'
132
+ | '🙍‍♀'
133
+ | '💔'
134
+ | '🙁'
135
+ | '🥺'
136
+ | '🤕'
137
+ | '☔️'
138
+ | '⛈'
139
+ | '🌩'
140
+ | '🌧'
141
+ type Angry =
142
+ | '😯'
143
+ | '😦'
144
+ | '😧'
145
+ | '😮'
146
+ | '😲'
147
+ | '🙀'
148
+ | '😱'
149
+ | '🤯'
150
+ | '😳'
151
+ | '❗'
152
+ | '❕'
153
+ | '🤬'
154
+ | '😡'
155
+ | '😠'
156
+ | '🙄'
157
+ | '👿'
158
+ | '😾'
159
+ | '😤'
160
+ | '💢'
161
+ | '👺'
162
+ | '🗯️'
163
+ | '😒'
164
+ | '🥵'
165
+ type Greet = '👋'
166
+ type Celebrate =
167
+ | '🎊'
168
+ | '🎉'
169
+ | '🎁'
170
+ | '🎈'
171
+ | '👯‍♂️'
172
+ | '👯'
173
+ | '👯‍♀️'
174
+ | '💃'
175
+ | '🕺'
176
+ | '🔥'
177
+ | '⭐️'
178
+ | '✨'
179
+ | '💫'
180
+ | '🎇'
181
+ | '🎆'
182
+ | '🍻'
183
+ | '🥂'
184
+ | '🍾'
185
+ | '🎂'
186
+ | '🍰'
187
+
188
+ /** Sticker Category. Learn More: https://github.com/WhatsApp/stickers/wiki/Tag-your-stickers-with-Emojis*/
189
+ export type Categories = Love | Happy | Sad | Angry | Greet | Celebrate
@@ -0,0 +1,12 @@
1
+ import { randomBytes } from 'crypto'
2
+
3
+ export default abstract class Utils {
4
+ static generateStickerID = (): string => randomBytes(32).toString('hex')
5
+ }
6
+
7
+ export const defaultBg = {
8
+ r: 0,
9
+ g: 0,
10
+ b: 0,
11
+ alpha: 0
12
+ }
@@ -0,0 +1,13 @@
1
+ import { Image } from 'node-webpmux'
2
+ import { IRawMetadata } from '.'
3
+
4
+ /**
5
+ * Extracts metadata from a WebP image.
6
+ * @param {Buffer}image - The image buffer to extract metadata from
7
+ */
8
+ export const extractMetadata = async (image: Buffer): Promise<Partial<IRawMetadata>> => {
9
+ const img = new Image()
10
+ await img.load(image)
11
+ const exif = img.exif?.toString('utf-8') ?? '{}'
12
+ return JSON.parse(exif.substring(exif.indexOf('{'), exif.lastIndexOf('}') + 1) ?? '{}') as IRawMetadata
13
+ }
@@ -0,0 +1,9 @@
1
+ import { Sticker } from './Sticker'
2
+
3
+ export * from './Sticker'
4
+ export * from './extractMetadata'
5
+ export * from './Types'
6
+ export { default as StickerMetadata } from './internal/Metadata/StickerMetadata'
7
+ export { default as Exif } from './internal/Metadata/Exif'
8
+ export * from './internal/Metadata/StickerTypes'
9
+ export default Sticker
@@ -0,0 +1,39 @@
1
+ import { Image } from 'node-webpmux'
2
+ import { TextEncoder } from 'util'
3
+ import { Metadata } from '../../Types'
4
+ import RawMetadata from './RawMetadata'
5
+
6
+ export default class Exif {
7
+ private data: RawMetadata
8
+ private exif: Buffer | null = null
9
+ constructor(options: Metadata) {
10
+ this.data = new RawMetadata(options)
11
+ }
12
+
13
+ build = (): Buffer => {
14
+ const data = JSON.stringify(this.data)
15
+ const exif = Buffer.concat([
16
+ Buffer.from([
17
+ 0x49, 0x49, 0x2a, 0x00, 0x08, 0x00, 0x00, 0x00, 0x01, 0x00, 0x41, 0x57, 0x07, 0x00, 0x00, 0x00, 0x00,
18
+ 0x00, 0x16, 0x00, 0x00, 0x00
19
+ ]),
20
+ Buffer.from(data, 'utf-8')
21
+ ])
22
+ exif.writeUIntLE(new TextEncoder().encode(data).length, 14, 4)
23
+ return exif
24
+ }
25
+
26
+ add = async (image: string | Buffer | Image): Promise<Buffer> => {
27
+ const exif = this.exif || this.build()
28
+ image =
29
+ image instanceof Image
30
+ ? image
31
+ : await (async () => {
32
+ const img = new Image()
33
+ await img.load(image)
34
+ return img
35
+ })()
36
+ image.exif = exif
37
+ return await image.save(null)
38
+ }
39
+ }
@@ -0,0 +1,15 @@
1
+ import { IRawMetadata, Metadata } from '../../Types'
2
+ import Utils from '../../Utils'
3
+
4
+ export default class RawMetadata implements IRawMetadata {
5
+ emojis: string[]
6
+ 'sticker-pack-id': string
7
+ 'sticker-pack-name': string
8
+ 'sticker-pack-publisher': string
9
+ constructor(options: Metadata) {
10
+ this['sticker-pack-id'] = options.id || Utils.generateStickerID()
11
+ this['sticker-pack-name'] = options.pack || ''
12
+ this['sticker-pack-publisher'] = options.author || ''
13
+ this.emojis = options.categories || []
14
+ }
15
+ }