@azuro-org/images-generator 0.0.1
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.
- package/.nvmrc +1 -0
- package/.tool-versions +1 -0
- package/README.md +126 -0
- package/package.json +44 -0
- package/rollup.config.js +107 -0
- package/src/fonts/fivo-sans-modern/bold.woff +0 -0
- package/src/fonts/fivo-sans-modern/bold.woff2 +0 -0
- package/src/index.ts +1 -0
- package/src/templates/_template/index.css +13 -0
- package/src/templates/_template/index.html +13 -0
- package/src/templates/_template/index.ts +22 -0
- package/src/templates/bet-nft/example.png +0 -0
- package/src/templates/bet-nft/images/logo.png +0 -0
- package/src/templates/bet-nft/images/separator.png +0 -0
- package/src/templates/bet-nft/images/shadow.png +0 -0
- package/src/templates/bet-nft/images/team1.png +0 -0
- package/src/templates/bet-nft/images/team2.png +0 -0
- package/src/templates/bet-nft/index.html +325 -0
- package/src/templates/bet-nft/index.ts +69 -0
- package/src/templates/bet-og/example.jpeg +0 -0
- package/src/templates/bet-og/images/bg.jpg +0 -0
- package/src/templates/bet-og/images/logo.png +0 -0
- package/src/templates/bet-og/index.html +176 -0
- package/src/templates/bet-og/index.ts +57 -0
- package/src/utils/generateImage.ts +89 -0
- package/src/utils/index.ts +37 -0
- package/src/utils/types.ts +8 -0
- package/tsconfig.json +20 -0
package/.nvmrc
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
16.15.1
|
package/.tool-versions
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
nodejs 16.15.1
|
package/README.md
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
> This readme is for Developers only.
|
|
2
|
+
|
|
3
|
+
## Current packages
|
|
4
|
+
|
|
5
|
+
[@azuro-protocol/nft-image-generator](https://www.npmjs.com/package/@azuro-protocol/nft-image-generator)<br />
|
|
6
|
+
[@azuro-protocol/bet-og-image-generator](https://www.npmjs.com/package/@azuro-protocol/bet-og-image-generator)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
## Usage
|
|
10
|
+
|
|
11
|
+
### Types Declaration
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
import { generateImage } from '@azuro-org/image-generator';
|
|
15
|
+
import template, { type Props } from '@azuro-org/image-generator/templates/bet-nft';
|
|
16
|
+
|
|
17
|
+
const props: Props = {
|
|
18
|
+
type: 'match',
|
|
19
|
+
sport: 'soccer',
|
|
20
|
+
league: 'Leinster Senior League Senior Division',
|
|
21
|
+
team1: {
|
|
22
|
+
img: 'https://content.bookieratings.net/images/fq/tx/fqtxnf_20181001112329_100x100.png',
|
|
23
|
+
name: 'Nizhny Novgorod'
|
|
24
|
+
},
|
|
25
|
+
team2: {
|
|
26
|
+
img: 'https://content.bookieratings.net/images/fq/tx/fqtxnf_20181001112329_100x100.png',
|
|
27
|
+
name: 'Lokomotiv Moscow'
|
|
28
|
+
},
|
|
29
|
+
date: '21.03.2022 8:00 UTC',
|
|
30
|
+
betAmount: '100 USDC',
|
|
31
|
+
outcome: 'Total Under(2.5)',
|
|
32
|
+
betOdds: '2.88',
|
|
33
|
+
currentOdds: '1.88'
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// get image buffer
|
|
37
|
+
const buffer = generateImage({
|
|
38
|
+
template,
|
|
39
|
+
props,
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
// create image file
|
|
43
|
+
generateImage({
|
|
44
|
+
template,
|
|
45
|
+
props,
|
|
46
|
+
output: './dist',
|
|
47
|
+
})
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Options
|
|
51
|
+
|
|
52
|
+
```
|
|
53
|
+
type PuppeteerOptions = Parameters<typeof puppeteer.launch>[0]
|
|
54
|
+
|
|
55
|
+
type PuppeteerInitialOptions = {
|
|
56
|
+
headless: boolean
|
|
57
|
+
devtools: boolean
|
|
58
|
+
args: string[]
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
generateImage({
|
|
62
|
+
output?: string // output filepath
|
|
63
|
+
filename?: string // default "image"
|
|
64
|
+
props: any
|
|
65
|
+
modifyPuppeteerOptions?(options: PuppeteerInitialOptions): PuppeteerOptions
|
|
66
|
+
})
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
# Contributing
|
|
71
|
+
|
|
72
|
+
## Add new template
|
|
73
|
+
|
|
74
|
+
1. Copy `templates/_template` to `templates/{your_template_name}`.
|
|
75
|
+
3. Use `index.html` for HTML. Write CSS in `index.html` file.
|
|
76
|
+
4. Create `templates/{your_template_name}/images` folder for images if required.
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
## Setup generator
|
|
80
|
+
|
|
81
|
+
Edit `{your_template_name}/index.ts` file:
|
|
82
|
+
|
|
83
|
+
```typescript
|
|
84
|
+
import { type Template, getFile, downloadImage, createGenerator } from '../../utils'
|
|
85
|
+
|
|
86
|
+
export type Props = {
|
|
87
|
+
team1ImageSrc: string
|
|
88
|
+
team2ImageSrc: string
|
|
89
|
+
date: string
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const template = {
|
|
93
|
+
width: 800,
|
|
94
|
+
height: 400,
|
|
95
|
+
type: 'jpeg',
|
|
96
|
+
html: async (props: Props) => {
|
|
97
|
+
const { team1ImageSrc, team2ImageSrc, date } = props
|
|
98
|
+
|
|
99
|
+
let html = getFile('./index.html')
|
|
100
|
+
let css = getFile('./index.css')
|
|
101
|
+
|
|
102
|
+
const team1Img = await downloadImage(team1ImageSrc)
|
|
103
|
+
const team2Img = await downloadImage(team2ImageSrc)
|
|
104
|
+
|
|
105
|
+
return html
|
|
106
|
+
.replace('.style{}', css)
|
|
107
|
+
.replace('{image1}', team1Img)
|
|
108
|
+
.replace('{image2}', team2Img)
|
|
109
|
+
.replace('{date}', date)
|
|
110
|
+
},
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export default template
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
## `createGenerator` options
|
|
118
|
+
|
|
119
|
+
`type: 'png' | 'jpeg'`<br /><br />
|
|
120
|
+
`headless: Boolean` - use true to see compiled html in browser<br /><br />
|
|
121
|
+
`scaleFactor: 1 | 2` - use 2 if you need to generate x2 sized image
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
## Publish
|
|
125
|
+
|
|
126
|
+
Publish npm package with `npm run publish`. For access to `@azuro-org` scope ask Pavel Ivanov or Stas Onatskiy.
|
package/package.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@azuro-org/images-generator",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"license": "ISC",
|
|
5
|
+
"engines": {
|
|
6
|
+
"node": "=16.15.1",
|
|
7
|
+
"npm": "=8.11.0"
|
|
8
|
+
},
|
|
9
|
+
"typings": "./index.d.ts",
|
|
10
|
+
"scripts": {
|
|
11
|
+
"dev": "rollup -c ./rollup.config.js -w",
|
|
12
|
+
"build": "rimraf ./dist && rimraf ./lib && rollup -c ./rollup.config.js --compact",
|
|
13
|
+
"build-and-test": "npm run build && npm run test",
|
|
14
|
+
"prepublish": "npm run build"
|
|
15
|
+
},
|
|
16
|
+
"dependencies": {
|
|
17
|
+
"axios": "^0.26.1",
|
|
18
|
+
"builtin-modules": "^3.2.0",
|
|
19
|
+
"dayjs": "^1.11.7",
|
|
20
|
+
"puppeteer": "^19.3.0"
|
|
21
|
+
},
|
|
22
|
+
"devDependencies": {
|
|
23
|
+
"@babel/core": "^7.17.0",
|
|
24
|
+
"@babel/plugin-proposal-object-rest-spread": "^7.16.7",
|
|
25
|
+
"@babel/plugin-transform-destructuring": "^7.16.7",
|
|
26
|
+
"@babel/plugin-transform-runtime": "^7.17.0",
|
|
27
|
+
"@babel/preset-env": "^7.16.11",
|
|
28
|
+
"@babel/preset-react": "^7.16.7",
|
|
29
|
+
"@babel/preset-typescript": "^7.16.7",
|
|
30
|
+
"@rollup/plugin-babel": "^6.0.3",
|
|
31
|
+
"@rollup/plugin-commonjs": "^23.0.3",
|
|
32
|
+
"@rollup/plugin-json": "^4.1.0",
|
|
33
|
+
"@rollup/plugin-node-resolve": "^15.0.1",
|
|
34
|
+
"@types/node": "^17.0.21",
|
|
35
|
+
"glob": "^8.1.0",
|
|
36
|
+
"rimraf": "^3.0.2",
|
|
37
|
+
"rollup": "^2.67.0",
|
|
38
|
+
"rollup-plugin-babel": "^4.4.0",
|
|
39
|
+
"rollup-plugin-copy": "^3.4.0",
|
|
40
|
+
"rollup-plugin-typescript2": "^0.34.1",
|
|
41
|
+
"tslib": "^2.4.1",
|
|
42
|
+
"typescript": "^4.6.2"
|
|
43
|
+
}
|
|
44
|
+
}
|
package/rollup.config.js
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import glob from 'glob'
|
|
2
|
+
import path from 'node:path'
|
|
3
|
+
import { nodeResolve } from '@rollup/plugin-node-resolve'
|
|
4
|
+
import builtins from 'builtin-modules/static'
|
|
5
|
+
import commonjs from '@rollup/plugin-commonjs'
|
|
6
|
+
import typescript from 'rollup-plugin-typescript2'
|
|
7
|
+
import babel from 'rollup-plugin-babel'
|
|
8
|
+
import json from '@rollup/plugin-json'
|
|
9
|
+
import copy from 'rollup-plugin-copy'
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
const templateFolders = (
|
|
13
|
+
glob.sync('src/templates/*')
|
|
14
|
+
.filter((folder) => !folder.includes('_template'))
|
|
15
|
+
.map((folder) => path.relative('src', folder))
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
const TARGETS_TO_COPY = [
|
|
19
|
+
'index.html',
|
|
20
|
+
'images',
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
const main = {
|
|
24
|
+
input: './src/index.ts',
|
|
25
|
+
output: [
|
|
26
|
+
{
|
|
27
|
+
file: './lib/index.js',
|
|
28
|
+
format: 'cjs',
|
|
29
|
+
exports: 'named',
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
file: './dist/index.es.js',
|
|
33
|
+
format: 'es',
|
|
34
|
+
exports: 'named',
|
|
35
|
+
},
|
|
36
|
+
],
|
|
37
|
+
external: [
|
|
38
|
+
...builtins,
|
|
39
|
+
'puppeteer',
|
|
40
|
+
],
|
|
41
|
+
plugins: [
|
|
42
|
+
nodeResolve(),
|
|
43
|
+
commonjs(),
|
|
44
|
+
json(),
|
|
45
|
+
babel({
|
|
46
|
+
exclude: 'node_modules/**',
|
|
47
|
+
}),
|
|
48
|
+
typescript({
|
|
49
|
+
tsconfigOverride: {
|
|
50
|
+
include: [
|
|
51
|
+
'src/index.ts',
|
|
52
|
+
],
|
|
53
|
+
},
|
|
54
|
+
clean: true,
|
|
55
|
+
}),
|
|
56
|
+
copy({
|
|
57
|
+
targets: templateFolders.map((folder) => (
|
|
58
|
+
TARGETS_TO_COPY.map((fileName) => (
|
|
59
|
+
[ 'dist', 'lib' ].map((dest) => ({
|
|
60
|
+
src: path.join('src', folder, fileName),
|
|
61
|
+
dest: path.join(dest, folder),
|
|
62
|
+
})).flat()
|
|
63
|
+
)).flat()
|
|
64
|
+
)).flat(),
|
|
65
|
+
})
|
|
66
|
+
],
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const templates = {
|
|
70
|
+
input: Object.fromEntries(
|
|
71
|
+
templateFolders.map((file) => [
|
|
72
|
+
path.join(file, 'index'),
|
|
73
|
+
path.resolve(`src/${file}/index.ts`),
|
|
74
|
+
])
|
|
75
|
+
),
|
|
76
|
+
output: [
|
|
77
|
+
{
|
|
78
|
+
dir: 'lib',
|
|
79
|
+
format: 'cjs',
|
|
80
|
+
exports: 'named',
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
dir: 'dist',
|
|
84
|
+
format: 'es',
|
|
85
|
+
exports: 'named',
|
|
86
|
+
},
|
|
87
|
+
],
|
|
88
|
+
external: [
|
|
89
|
+
...builtins,
|
|
90
|
+
],
|
|
91
|
+
plugins: [
|
|
92
|
+
nodeResolve(),
|
|
93
|
+
commonjs(),
|
|
94
|
+
json(),
|
|
95
|
+
babel({
|
|
96
|
+
exclude: 'node_modules/**',
|
|
97
|
+
}),
|
|
98
|
+
typescript({
|
|
99
|
+
clean: true,
|
|
100
|
+
}),
|
|
101
|
+
],
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export default [
|
|
105
|
+
main,
|
|
106
|
+
templates,
|
|
107
|
+
]
|
|
Binary file
|
|
File without changes
|
package/src/index.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as generateImage } from './utils/generateImage'
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<title>Document</title>
|
|
5
|
+
<meta charset="UTF-8">
|
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
7
|
+
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet" />
|
|
8
|
+
<style>.style{}</style>
|
|
9
|
+
</head>
|
|
10
|
+
<body>
|
|
11
|
+
<!-- Write your HTML here -->
|
|
12
|
+
</body>
|
|
13
|
+
</html>
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { type Template, getFile, getBase64Image, downloadImage } from '../../utils'
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
export type Props = {
|
|
5
|
+
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const template: Template = {
|
|
9
|
+
width: 1000,
|
|
10
|
+
height: 1000,
|
|
11
|
+
type: 'png',
|
|
12
|
+
html: (props: Props) => {
|
|
13
|
+
const { } = props
|
|
14
|
+
|
|
15
|
+
let html = getFile('./index.html')
|
|
16
|
+
let css = getFile('./index.css')
|
|
17
|
+
|
|
18
|
+
return html.replace('.style{}', css)
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export default template
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
7
|
+
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet" />
|
|
8
|
+
<style>
|
|
9
|
+
html {
|
|
10
|
+
font-family: 'Inter', sans-serif;
|
|
11
|
+
font-weight: 500;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
html, body {
|
|
15
|
+
margin: 0;
|
|
16
|
+
width: fit-content;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
* {
|
|
20
|
+
box-sizing: border-box;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.flex {
|
|
24
|
+
display: flex;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.flex-wrap {
|
|
28
|
+
flex-wrap: wrap;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
.flex-1 {
|
|
32
|
+
flex: 1 1 0;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.flex-auto {
|
|
36
|
+
flex: 1 1 auto;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.flex-none {
|
|
40
|
+
flex: none;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
.items-center {
|
|
45
|
+
align-items: center;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.items-start {
|
|
49
|
+
align-items: flex-start;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.items-end {
|
|
53
|
+
align-items: flex-end;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.items-baseline {
|
|
57
|
+
align-items: baseline;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.items-stretch {
|
|
61
|
+
align-items: stretch;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
.self-auto {
|
|
65
|
+
align-self: auto;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.self-start {
|
|
69
|
+
align-self: flex-start;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.self-end {
|
|
73
|
+
align-self: flex-end;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
.self-center {
|
|
77
|
+
align-self: center;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
.self-stretch {
|
|
81
|
+
align-self: stretch;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.justify-around {
|
|
85
|
+
justify-content: space-around;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
.justify-between {
|
|
89
|
+
justify-content: space-between;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
.justify-center {
|
|
93
|
+
justify-content: center;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
.justify-start {
|
|
97
|
+
justify-content: flex-start;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
.justify-end {
|
|
101
|
+
justify-content: flex-end;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
.flex-row {
|
|
105
|
+
flex-direction: row;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
.flex-col {
|
|
109
|
+
flex-direction: column;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
.flex-col-reverse {
|
|
113
|
+
flex-direction: column-reverse;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
.text-upper {
|
|
117
|
+
text-transform: uppercase;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
.text-center {
|
|
121
|
+
text-align: center;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
.text-600 {
|
|
125
|
+
font-weight: 600;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
.text-400 {
|
|
129
|
+
font-weight: 400;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
.card {
|
|
133
|
+
width: 510px;
|
|
134
|
+
position: relative;
|
|
135
|
+
padding: 24px 16px 16px;
|
|
136
|
+
border: 1px solid rgba(0, 0, 0, 0.1);
|
|
137
|
+
border-radius: 16px;
|
|
138
|
+
background: #FFFFFF;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
.logo {
|
|
142
|
+
position: relative;
|
|
143
|
+
z-index: 1;
|
|
144
|
+
margin-bottom: 40px;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
.shadow {
|
|
148
|
+
position: absolute;
|
|
149
|
+
left: 0;
|
|
150
|
+
width: 100%;
|
|
151
|
+
top: 0;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
.sport {
|
|
155
|
+
margin-bottom: 8px;
|
|
156
|
+
font-size: 16px;
|
|
157
|
+
line-height: 24px;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
.league {
|
|
161
|
+
font-size: 18px;
|
|
162
|
+
line-height: 27px
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
.sport::before, .sport::after {
|
|
166
|
+
content: '';
|
|
167
|
+
display: block;
|
|
168
|
+
border-radius: 50%;
|
|
169
|
+
width: 4px;
|
|
170
|
+
height: 4px;
|
|
171
|
+
margin: 0 8px;
|
|
172
|
+
background: #000000;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
.teams {
|
|
176
|
+
margin: 24px 0 16px;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
.team {
|
|
180
|
+
width: 100%;
|
|
181
|
+
background: #FAFAFA;
|
|
182
|
+
border-radius: 12px;
|
|
183
|
+
padding: 16px;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
.team img {
|
|
187
|
+
display: block;
|
|
188
|
+
width: 72px;
|
|
189
|
+
height: 72px;
|
|
190
|
+
margin: 0 auto;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
.date {
|
|
194
|
+
opacity: 0.6;
|
|
195
|
+
font-size: 16px;
|
|
196
|
+
line-height: 24px;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
.game {
|
|
200
|
+
font-size: 24px;
|
|
201
|
+
line-height: 36px;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
.table {
|
|
205
|
+
border: 1px solid transparent;
|
|
206
|
+
border-radius: 12px;
|
|
207
|
+
overflow: hidden;
|
|
208
|
+
margin-top: 16px;
|
|
209
|
+
background: linear-gradient(180deg, rgba(250, 250, 250, 0) 0%, #FAFAFA 100%);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
.table__head {
|
|
213
|
+
font-size: 14px;
|
|
214
|
+
line-height: 21px;
|
|
215
|
+
padding: 8px;
|
|
216
|
+
text-transform: capitalize;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
.table__content {
|
|
220
|
+
padding: 24px
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
.table__item {
|
|
224
|
+
font-size: 20px;
|
|
225
|
+
line-height: 30px;
|
|
226
|
+
margin-top: 4px;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
.table__item:first-child {
|
|
230
|
+
margin-top: 0;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
.table_match {
|
|
234
|
+
border-color: #007FFF;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
.table_match .table__head {
|
|
238
|
+
color: #007FFF;
|
|
239
|
+
background: #E0EEFE;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
.table_claim {
|
|
243
|
+
border-color: #FFA000;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
.table_claim .table__head {
|
|
247
|
+
color: #FFA000;
|
|
248
|
+
background: #FFF8E1;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
.table_claimed {
|
|
252
|
+
border-color: #4CAF50;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
.table_claimed .table__head {
|
|
256
|
+
color: #4CAF50;
|
|
257
|
+
background: #E8F5E9;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
.table_lose {
|
|
261
|
+
border-color: #A3A3A3;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
.table_lose .table__head {
|
|
265
|
+
color: #A3A3A3;
|
|
266
|
+
background: rgba(163, 163, 163, 0.12);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
.table_canceled {
|
|
270
|
+
border-color: #FF1717;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
.table_canceled .table__head {
|
|
274
|
+
color: #FF1717;
|
|
275
|
+
background: rgba(255, 23, 23, 0.12);
|
|
276
|
+
}
|
|
277
|
+
</style>
|
|
278
|
+
</head>
|
|
279
|
+
<body>
|
|
280
|
+
<div class="card" id="card">
|
|
281
|
+
<img src="{shadow}" alt="" class="shadow">
|
|
282
|
+
<div class="logo text-center">
|
|
283
|
+
<img src="{logo}" alt="">
|
|
284
|
+
</div>
|
|
285
|
+
<div id="sport" class="sport flex items-center justify-center text-upper text-600">{sport}</div>
|
|
286
|
+
<div id="league" class="league text-400 text-center">{league}</div>
|
|
287
|
+
<div class="teams flex">
|
|
288
|
+
<div class="team text-center">
|
|
289
|
+
<img id="team1-img" src="{image1}" alt="">
|
|
290
|
+
</div>
|
|
291
|
+
<img src="{separator}" alt="">
|
|
292
|
+
<div class="team text-center">
|
|
293
|
+
<img id="team2-img" src="{image2}" alt="">
|
|
294
|
+
</div>
|
|
295
|
+
</div>
|
|
296
|
+
<div id="date" class="date text-center text-400">
|
|
297
|
+
{date}
|
|
298
|
+
</div>
|
|
299
|
+
<div id="teams" class="game text-center text-600">
|
|
300
|
+
{game}
|
|
301
|
+
</div>
|
|
302
|
+
<div id="table" class="table table_{tableType}">
|
|
303
|
+
<div class="table__head text-center">{tableHead}</div>
|
|
304
|
+
<div class="table__content">
|
|
305
|
+
<div class="table__item flex items-center justify-between">
|
|
306
|
+
<div class="table__item__name text-400">Bet Amount:</div>
|
|
307
|
+
<div class="table__item__value text-600">{betAmount}</div>
|
|
308
|
+
</div>
|
|
309
|
+
<div class="table__item flex items-center justify-between">
|
|
310
|
+
<div class="table__item__name text-400">Outcome:</div>
|
|
311
|
+
<div class="table__item__value text-600">{outcome}</div>
|
|
312
|
+
</div>
|
|
313
|
+
<div class="table__item flex items-center justify-between">
|
|
314
|
+
<div class="table__item__name text-400">Bet odds:</div>
|
|
315
|
+
<div class="table__item__value text-600">{betOdds}</div>
|
|
316
|
+
</div>
|
|
317
|
+
<div class="table__item flex items-center justify-between">
|
|
318
|
+
<div class="table__item__name text-400">Current odds:</div>
|
|
319
|
+
<div class="table__item__value text-600">{currentOdds}</div>
|
|
320
|
+
</div>
|
|
321
|
+
</div>
|
|
322
|
+
</div>
|
|
323
|
+
</div>
|
|
324
|
+
</body>
|
|
325
|
+
</html>
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { type Template, getFile, getBase64Image, downloadImage } from '../../utils'
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
const matchType = {
|
|
5
|
+
'match': 'Waiting for match',
|
|
6
|
+
'claim': 'Waiting for claim',
|
|
7
|
+
'claimed': 'Claimed',
|
|
8
|
+
'lose': 'Lose',
|
|
9
|
+
'canceled': 'Canceled match'
|
|
10
|
+
} as const
|
|
11
|
+
|
|
12
|
+
type MatchType = keyof typeof matchType
|
|
13
|
+
|
|
14
|
+
type Team = {
|
|
15
|
+
img: string
|
|
16
|
+
name: string
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export type Props = {
|
|
20
|
+
type: MatchType
|
|
21
|
+
sport: string
|
|
22
|
+
league: string
|
|
23
|
+
team1: Team
|
|
24
|
+
team2: Team
|
|
25
|
+
date: string
|
|
26
|
+
betAmount: string
|
|
27
|
+
outcome: string
|
|
28
|
+
betOdds: string
|
|
29
|
+
currentOdds: string
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const template: Template = {
|
|
33
|
+
width: 510,
|
|
34
|
+
height: 510,
|
|
35
|
+
type: 'png',
|
|
36
|
+
html: async (props: Props) => {
|
|
37
|
+
const { type, sport, league, team1, team2, date, betAmount, outcome, betOdds, currentOdds } = props
|
|
38
|
+
|
|
39
|
+
let html = getFile('./index.html')
|
|
40
|
+
let css = getFile('./index.css')
|
|
41
|
+
|
|
42
|
+
const shadow = getBase64Image('./images/shadow.png')
|
|
43
|
+
const logo = getBase64Image('./images/logo.png')
|
|
44
|
+
const separator = getBase64Image('./images/separator.png')
|
|
45
|
+
|
|
46
|
+
const team1Img = await downloadImage(team1.img)
|
|
47
|
+
const team2Img = await downloadImage(team2.img)
|
|
48
|
+
|
|
49
|
+
return html
|
|
50
|
+
.replace('.style{}', css)
|
|
51
|
+
.replace('{sport}', sport)
|
|
52
|
+
.replace('{league}', league)
|
|
53
|
+
.replace('{image1}', team1Img)
|
|
54
|
+
.replace('{image2}', team2Img)
|
|
55
|
+
.replace('{date}', date)
|
|
56
|
+
.replace('{game}', `${team1.name} - ${team2.name}`)
|
|
57
|
+
.replace('{tableType}', type)
|
|
58
|
+
.replace('{tableHead}', matchType[type])
|
|
59
|
+
.replace('{betAmount}', betAmount)
|
|
60
|
+
.replace('{outcome}', outcome)
|
|
61
|
+
.replace('{betOdds}', betOdds)
|
|
62
|
+
.replace('{currentOdds}', currentOdds)
|
|
63
|
+
.replace('{separator}', separator)
|
|
64
|
+
.replace('{shadow}', shadow)
|
|
65
|
+
.replace('{logo}', logo)
|
|
66
|
+
},
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export default template
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet" />
|
|
7
|
+
<style>
|
|
8
|
+
@font-face {
|
|
9
|
+
font-weight: 700;
|
|
10
|
+
font-family: 'Fivo Sans Modern';
|
|
11
|
+
font-style: normal;
|
|
12
|
+
src: url('/fonts/fivo-sans-modern/bold.woff2') format('woff2'),
|
|
13
|
+
url('/fonts/fivo-sans-modern/bold.woff') format('woff');
|
|
14
|
+
font-display: swap;
|
|
15
|
+
unicode-range: U+000-5FF; /* Latin glyphs */
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
html {
|
|
19
|
+
font-family: 'Inter', sans-serif;
|
|
20
|
+
font-weight: 500;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
html, body {
|
|
24
|
+
width: 600px;
|
|
25
|
+
height: 315px;
|
|
26
|
+
margin: 0;
|
|
27
|
+
padding: 0;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
* {
|
|
31
|
+
box-sizing: border-box;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.opengraph {
|
|
35
|
+
width: 600px;
|
|
36
|
+
height: 315px;
|
|
37
|
+
padding: 30px 16px 0 23px;
|
|
38
|
+
background-size: cover;
|
|
39
|
+
overflow: hidden;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.logo {
|
|
43
|
+
display: block;
|
|
44
|
+
height: 24px;
|
|
45
|
+
margin-bottom: 36px;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.content {
|
|
49
|
+
display: flex;
|
|
50
|
+
align-items: center;
|
|
51
|
+
justify-content: space-between;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.title {
|
|
55
|
+
padding-right: 40px;
|
|
56
|
+
color: #fff;
|
|
57
|
+
font-size: 30px;
|
|
58
|
+
line-height: 32px;
|
|
59
|
+
font-weight: 700;
|
|
60
|
+
font-family: 'Fivo Sans Modern', sans-serif;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.card {
|
|
64
|
+
flex: none;
|
|
65
|
+
width: 280px;
|
|
66
|
+
padding: 12px 4px 8px;
|
|
67
|
+
font-weight: 500;
|
|
68
|
+
background: #1F1F24;
|
|
69
|
+
border: 1px solid #333338;
|
|
70
|
+
box-shadow: 0 4px 22px rgba(0, 0, 0, 0.2);
|
|
71
|
+
border-radius: 9px;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
.cardTitle {
|
|
75
|
+
padding: 0 10px;
|
|
76
|
+
margin-bottom: 11px;
|
|
77
|
+
color: #8A8A98;
|
|
78
|
+
text-align: center;
|
|
79
|
+
font-size: 13px;
|
|
80
|
+
line-height: 16px;
|
|
81
|
+
overflow: hidden;
|
|
82
|
+
text-overflow: ellipsis;
|
|
83
|
+
white-space: nowrap;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
.teams {
|
|
87
|
+
display: flex;
|
|
88
|
+
align-items: center;
|
|
89
|
+
justify-content: center;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
.teamImage {
|
|
93
|
+
display: flex;
|
|
94
|
+
align-items: center;
|
|
95
|
+
justify-content: center;
|
|
96
|
+
width: 68px;
|
|
97
|
+
height: 68px;
|
|
98
|
+
background: #27272B;
|
|
99
|
+
border-radius: 50%;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
.teamImage img {
|
|
103
|
+
display: block;
|
|
104
|
+
width: 40px;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
.dates {
|
|
108
|
+
padding: 0 24px;
|
|
109
|
+
text-align: center;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
.date {
|
|
113
|
+
color: #8A8A98;
|
|
114
|
+
font-size: 13px;
|
|
115
|
+
line-height: 16px;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
.time {
|
|
119
|
+
margin-top: 2px;
|
|
120
|
+
color: #fff;
|
|
121
|
+
font-size: 13px;
|
|
122
|
+
line-height: 16px;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
.teamNames {
|
|
126
|
+
display: grid;
|
|
127
|
+
grid-template-columns: 1fr 1fr;
|
|
128
|
+
grid-gap: 44px;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
.teamName {
|
|
132
|
+
display: flex;
|
|
133
|
+
justify-content: center;
|
|
134
|
+
align-items: center;
|
|
135
|
+
min-height: 32px;
|
|
136
|
+
margin-top: 4px;
|
|
137
|
+
padding: 0 6px;
|
|
138
|
+
color: #fff;
|
|
139
|
+
text-align: center;
|
|
140
|
+
font-size: 13px;
|
|
141
|
+
line-height: 16px;
|
|
142
|
+
}
|
|
143
|
+
</style>
|
|
144
|
+
</head>
|
|
145
|
+
<body>
|
|
146
|
+
<div class="opengraph" style="background-image: url({bgImage});">
|
|
147
|
+
<img class="logo" src="{logoImage}" alt="" />
|
|
148
|
+
<div class="content">
|
|
149
|
+
<div class="title">{title}</div>
|
|
150
|
+
<div class="card">
|
|
151
|
+
<div class="cardTitle">{country} · {league}</div>
|
|
152
|
+
<div class="teams">
|
|
153
|
+
<div class="teamImage">
|
|
154
|
+
<img src="{team1Image}" alt="" />
|
|
155
|
+
</div>
|
|
156
|
+
<div class="dates">
|
|
157
|
+
<div class="date">{date}</div>
|
|
158
|
+
<div class="time">{time}</div>
|
|
159
|
+
</div>
|
|
160
|
+
<div class="teamImage">
|
|
161
|
+
<img src="{team2Image}" alt="" />
|
|
162
|
+
</div>
|
|
163
|
+
</div>
|
|
164
|
+
<div class="teamNames">
|
|
165
|
+
<div class="teamName">
|
|
166
|
+
<span>{team1Name}</span>
|
|
167
|
+
</div>
|
|
168
|
+
<div class="teamName">
|
|
169
|
+
<span>{team2Name}</span>
|
|
170
|
+
</div>
|
|
171
|
+
</div>
|
|
172
|
+
</div>
|
|
173
|
+
</div>
|
|
174
|
+
</div>
|
|
175
|
+
</body>
|
|
176
|
+
</html>
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import dayjs from 'dayjs'
|
|
2
|
+
|
|
3
|
+
import { type Template, getFile, getBase64Image } from '../../utils'
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
export type Props = {
|
|
7
|
+
title: string
|
|
8
|
+
game: {
|
|
9
|
+
country: string
|
|
10
|
+
league: string
|
|
11
|
+
participants: {
|
|
12
|
+
name: string
|
|
13
|
+
image: string
|
|
14
|
+
}[]
|
|
15
|
+
startsAt: number
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const template: Template = {
|
|
20
|
+
width: 600,
|
|
21
|
+
height: 315,
|
|
22
|
+
type: 'jpeg',
|
|
23
|
+
scaleFactor: 2,
|
|
24
|
+
html: async (props: Props) => {
|
|
25
|
+
const { title, game } = props
|
|
26
|
+
const { country, league, participants, startsAt } = game
|
|
27
|
+
|
|
28
|
+
const bgImage = getBase64Image('./images/bg.jpg')
|
|
29
|
+
const logoImage = getBase64Image('./images/logo.png')
|
|
30
|
+
|
|
31
|
+
const team1Image = participants[0].image
|
|
32
|
+
const team1Name = participants[0].name
|
|
33
|
+
const team2Image = participants[1].image
|
|
34
|
+
const team2Name = participants[1].name
|
|
35
|
+
|
|
36
|
+
const dateTime = dayjs(startsAt)
|
|
37
|
+
const date = dateTime.format('DD MMM')
|
|
38
|
+
const time = dateTime.format('HH:mm')
|
|
39
|
+
|
|
40
|
+
let html = getFile('./index.html')
|
|
41
|
+
|
|
42
|
+
return html
|
|
43
|
+
.replace('{bgImage}', bgImage)
|
|
44
|
+
.replace('{logoImage}', logoImage)
|
|
45
|
+
.replace('{title}', title)
|
|
46
|
+
.replace('{country}', country)
|
|
47
|
+
.replace('{league}', league)
|
|
48
|
+
.replace('{team1Image}', team1Image)
|
|
49
|
+
.replace('{team1Name}', team1Name)
|
|
50
|
+
.replace('{team2Image}', team2Image)
|
|
51
|
+
.replace('{team2Name}', team2Name)
|
|
52
|
+
.replace('{date}', date)
|
|
53
|
+
.replace('{time}', time)
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export default template
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import puppeteer from 'puppeteer'
|
|
2
|
+
|
|
3
|
+
import { type Template } from './types'
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
type PuppeteerOptions = Parameters<typeof puppeteer.launch>[0]
|
|
7
|
+
|
|
8
|
+
type PuppeteerInitialOptions = {
|
|
9
|
+
headless: boolean
|
|
10
|
+
devtools: boolean
|
|
11
|
+
args: string[]
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
type GenerateImageResult<T> = T extends { output: string } ? void : (string | Buffer)
|
|
15
|
+
|
|
16
|
+
type GenerateImageProps = {
|
|
17
|
+
template: Template
|
|
18
|
+
output?: string // output filepath
|
|
19
|
+
filename?: string
|
|
20
|
+
props: any
|
|
21
|
+
modifyPuppeteerOptions?(options: PuppeteerInitialOptions): PuppeteerOptions
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export default async function generateImage<T extends GenerateImageProps>(props: T): Promise<GenerateImageResult<T> | undefined> {
|
|
25
|
+
const {
|
|
26
|
+
output,
|
|
27
|
+
filename = 'image',
|
|
28
|
+
template,
|
|
29
|
+
props: htmlProps,
|
|
30
|
+
modifyPuppeteerOptions,
|
|
31
|
+
} = props
|
|
32
|
+
|
|
33
|
+
const {
|
|
34
|
+
headless = true,
|
|
35
|
+
width,
|
|
36
|
+
height,
|
|
37
|
+
type,
|
|
38
|
+
scaleFactor = 1,
|
|
39
|
+
html: getHtml,
|
|
40
|
+
} = template
|
|
41
|
+
|
|
42
|
+
const html = await getHtml(htmlProps)
|
|
43
|
+
|
|
44
|
+
let launchOptions: PuppeteerOptions = {
|
|
45
|
+
headless,
|
|
46
|
+
devtools: false,
|
|
47
|
+
args: [
|
|
48
|
+
'--no-sandbox',
|
|
49
|
+
'--disable-gpu',
|
|
50
|
+
'--disable-accelerated-video-decode',
|
|
51
|
+
// '--allow-file-access-from-files',
|
|
52
|
+
],
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (typeof modifyPuppeteerOptions === 'function') {
|
|
56
|
+
launchOptions = modifyPuppeteerOptions(launchOptions as PuppeteerInitialOptions)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const browser = await puppeteer.launch(launchOptions)
|
|
60
|
+
|
|
61
|
+
const page = await browser.newPage()
|
|
62
|
+
|
|
63
|
+
await page.setViewport({ width, height, deviceScaleFactor: scaleFactor })
|
|
64
|
+
await page.setContent(html)
|
|
65
|
+
|
|
66
|
+
const content = await page.$('body')
|
|
67
|
+
|
|
68
|
+
// dont' change this condition!
|
|
69
|
+
if (headless === false) {
|
|
70
|
+
await new Promise(() => {})
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (output) {
|
|
74
|
+
const filePath = `${output.replace(/\/$/, '')}/${filename.replace(/\..+$/, '')}.${type}`
|
|
75
|
+
|
|
76
|
+
await content!.screenshot({ path: filePath })
|
|
77
|
+
await page.close()
|
|
78
|
+
await browser.close()
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
const imageBuffer = await content!.screenshot({ omitBackground: true, type })
|
|
82
|
+
|
|
83
|
+
await page.close()
|
|
84
|
+
await browser.close()
|
|
85
|
+
|
|
86
|
+
// @ts-ignore
|
|
87
|
+
return imageBuffer
|
|
88
|
+
}
|
|
89
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import fs from 'fs'
|
|
2
|
+
import path from 'path'
|
|
3
|
+
import axios from 'axios'
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
export const getPath = (filePath: string) => {
|
|
7
|
+
return path.join(__dirname, filePath)
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const getFile = (filePath: string) => {
|
|
11
|
+
return fs.readFileSync(getPath(filePath), 'utf8')
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const getBase64Image = (filePath: string) => {
|
|
15
|
+
return `data:image/png;base64,${fs.readFileSync(getPath(filePath)).toString('base64')}`
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export const downloadImage = async (url: string) => {
|
|
19
|
+
// empty pixel
|
|
20
|
+
let base64 = 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg=='
|
|
21
|
+
|
|
22
|
+
try {
|
|
23
|
+
const response = await axios.get(url, { responseType: 'arraybuffer' })
|
|
24
|
+
const buffer = Buffer.from(response.data, 'utf-8')
|
|
25
|
+
|
|
26
|
+
base64 = buffer.toString('base64')
|
|
27
|
+
}
|
|
28
|
+
catch (err) {
|
|
29
|
+
console.error(err)
|
|
30
|
+
// empty pixel
|
|
31
|
+
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return `data:image/png;base64,${base64}`
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export { type Template } from './types'
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"rootDir": "src",
|
|
4
|
+
"target": "es5",
|
|
5
|
+
"module": "esnext",
|
|
6
|
+
"moduleResolution": "node",
|
|
7
|
+
"lib": [ "es2017", "es7", "es6", "dom" ],
|
|
8
|
+
"strict": true,
|
|
9
|
+
"declaration": true,
|
|
10
|
+
"esModuleInterop": true
|
|
11
|
+
},
|
|
12
|
+
"include": [
|
|
13
|
+
"src"
|
|
14
|
+
],
|
|
15
|
+
"exclude": [
|
|
16
|
+
"node_modules",
|
|
17
|
+
"dist",
|
|
18
|
+
"lib"
|
|
19
|
+
]
|
|
20
|
+
}
|