Skip to content

Commit eaf4545

Browse files
committed
Add Github image minimizer
1 parent b8ad35b commit eaf4545

2 files changed

Lines changed: 183 additions & 0 deletions

File tree

image-minimizer.js

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
/*
2+
* Script for minimizing big images (jpg,gif,png) when they are uploaded to GitHub and not edited otherwise.
3+
* Source: https://github.com/TeamNewPipe/NewPipe/blob/dev/.github/workflows/image-minimizer.js
4+
*/
5+
module.exports = async ({github, context}) => {
6+
const IGNORE_KEY = '<!-- IGNORE IMAGE MINIFY -->';
7+
const IGNORE_ALT_NAME_END = 'ignoreImageMinify';
8+
// Targeted maximum height
9+
const IMG_MAX_HEIGHT_PX = 600;
10+
// maximum width of GitHub issues/comments
11+
const IMG_MAX_WIDTH_PX = 800;
12+
// all images that have a lower aspect ratio (-> have a smaller width) than this will be minimized
13+
const MIN_ASPECT_RATIO = IMG_MAX_WIDTH_PX / IMG_MAX_HEIGHT_PX
14+
15+
// Get the body of the image
16+
let initialBody = null;
17+
if (context.eventName == 'issue_comment') {
18+
initialBody = context.payload.comment.body;
19+
} else if (context.eventName == 'issues') {
20+
initialBody = context.payload.issue.body;
21+
} else if (context.eventName == 'pull_request') {
22+
initialBody = context.payload.pull_request.body;
23+
} else {
24+
console.log('Aborting: No body found');
25+
return;
26+
}
27+
console.log(`Found body: \n${initialBody}\n`);
28+
29+
// Check if we should ignore the currently processing element
30+
if (initialBody.includes(IGNORE_KEY)) {
31+
console.log('Ignoring: Body contains IGNORE_KEY');
32+
return;
33+
}
34+
35+
// Regex for finding images (simple variant) ![ALT_TEXT](https://*.githubusercontent.com/<number>/<variousHexStringsAnd->.<fileExtension>)
36+
const REGEX_USER_CONTENT_IMAGE_LOOKUP = /\!\[(.*)\]\((https:\/\/[-a-z0-9]+\.githubusercontent\.com\/\d+\/[-0-9a-f]{32,512}\.(jpg|gif|png))\)/gm;
37+
const REGEX_ASSETS_IMAGE_LOCKUP = /\!\[(.*)\]\((https:\/\/github\.com\/[-\w\d]+\/[-\w\d]+\/assets\/\d+\/[\-0-9a-f]{32,512})\)/gm;
38+
39+
// Check if we found something
40+
let foundSimpleImages = REGEX_USER_CONTENT_IMAGE_LOOKUP.test(initialBody)
41+
|| REGEX_ASSETS_IMAGE_LOCKUP.test(initialBody);
42+
if (!foundSimpleImages) {
43+
console.log('Found no simple images to process');
44+
return;
45+
}
46+
47+
console.log('Found at least one simple image to process');
48+
49+
// Require the probe lib for getting the image dimensions
50+
const probe = require('probe-image-size');
51+
52+
var wasMatchModified = false;
53+
54+
// Try to find and replace the images with minimized ones
55+
let newBody = await replaceAsync(initialBody, REGEX_USER_CONTENT_IMAGE_LOOKUP, minimizeAsync);
56+
newBody = await replaceAsync(newBody, REGEX_ASSETS_IMAGE_LOCKUP, minimizeAsync);
57+
58+
if (!wasMatchModified) {
59+
console.log('Nothing was modified. Skipping update');
60+
return;
61+
}
62+
63+
// Update the corresponding element
64+
if (context.eventName == 'issue_comment') {
65+
console.log('Updating comment with id', context.payload.comment.id);
66+
await github.rest.issues.updateComment({
67+
comment_id: context.payload.comment.id,
68+
owner: context.repo.owner,
69+
repo: context.repo.repo,
70+
body: newBody
71+
})
72+
} else if (context.eventName == 'issues') {
73+
console.log('Updating issue', context.payload.issue.number);
74+
await github.rest.issues.update({
75+
issue_number: context.payload.issue.number,
76+
owner: context.repo.owner,
77+
repo: context.repo.repo,
78+
body: newBody
79+
});
80+
} else if (context.eventName == 'pull_request') {
81+
console.log('Updating pull request', context.payload.pull_request.number);
82+
await github.rest.pulls.update({
83+
pull_number: context.payload.pull_request.number,
84+
owner: context.repo.owner,
85+
repo: context.repo.repo,
86+
body: newBody
87+
});
88+
}
89+
90+
// Async replace function from https://stackoverflow.com/a/48032528
91+
async function replaceAsync(str, regex, asyncFn) {
92+
const promises = [];
93+
str.replace(regex, (match, ...args) => {
94+
const promise = asyncFn(match, ...args);
95+
promises.push(promise);
96+
});
97+
const data = await Promise.all(promises);
98+
return str.replace(regex, () => data.shift());
99+
}
100+
101+
async function minimizeAsync(match, g1, g2) {
102+
console.log(`Found match '${match}'`);
103+
104+
if (g1.endsWith(IGNORE_ALT_NAME_END)) {
105+
console.log(`Ignoring match '${match}': IGNORE_ALT_NAME_END`);
106+
return match;
107+
}
108+
109+
let probeAspectRatio = 0;
110+
let shouldModify = false;
111+
try {
112+
console.log(`Probing ${g2}`);
113+
let probeResult = await probe(g2);
114+
if (probeResult == null) {
115+
throw 'No probeResult';
116+
}
117+
if (probeResult.hUnits != 'px') {
118+
throw `Unexpected probeResult.hUnits (expected px but got ${probeResult.hUnits})`;
119+
}
120+
if (probeResult.height <= 0) {
121+
throw `Unexpected probeResult.height (height is invalid: ${probeResult.height})`;
122+
}
123+
if (probeResult.wUnits != 'px') {
124+
throw `Unexpected probeResult.wUnits (expected px but got ${probeResult.wUnits})`;
125+
}
126+
if (probeResult.width <= 0) {
127+
throw `Unexpected probeResult.width (width is invalid: ${probeResult.width})`;
128+
}
129+
console.log(`Probing resulted in ${probeResult.width}x${probeResult.height}px`);
130+
131+
probeAspectRatio = probeResult.width / probeResult.height;
132+
shouldModify = probeResult.height > IMG_MAX_HEIGHT_PX && probeAspectRatio < MIN_ASPECT_RATIO;
133+
} catch(e) {
134+
console.log('Probing failed:', e);
135+
// Immediately abort
136+
return match;
137+
}
138+
139+
if (shouldModify) {
140+
wasMatchModified = true;
141+
console.log(`Modifying match '${match}'`);
142+
return `<img alt="${g1}" src="${g2}" width=${Math.min(600, Math.floor(IMG_MAX_HEIGHT_PX * probeAspectRatio))} />`;
143+
}
144+
145+
console.log(`Match '${match}' is ok/will not be modified`);
146+
return match;
147+
}
148+
}

image-minimizer.yml

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
name: Image Minimizer
2+
3+
on:
4+
issue_comment:
5+
types: [created, edited]
6+
issues:
7+
types: [opened, edited]
8+
pull_request:
9+
types: [opened, edited]
10+
11+
permissions:
12+
issues: write
13+
pull-requests: write
14+
15+
jobs:
16+
try-minimize:
17+
runs-on: ubuntu-latest
18+
19+
steps:
20+
- uses: actions/checkout@v3
21+
22+
- uses: actions/setup-node@v3
23+
with:
24+
node-version: 16
25+
26+
- name: Install probe-image-size
27+
run: npm i probe-image-size@7.2.3 --ignore-scripts
28+
29+
- name: Minimize simple images
30+
uses: actions/github-script@v6
31+
timeout-minutes: 3
32+
with:
33+
script: |
34+
const script = require('.github/workflows/image-minimizer.js');
35+
await script({github, context});

0 commit comments

Comments
 (0)