Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
QuiteAFancyEmerald
GitHub Repository: QuiteAFancyEmerald/Holy-Unblocker
Path: blob/master/src/source-rewrites.mjs
5156 views
1
import { existsSync, readFileSync } from 'node:fs';
2
import {
3
config,
4
serverUrl,
5
flatAltPaths,
6
cookingInserts,
7
vegetables,
8
charRandom,
9
delimiter,
10
textMasks,
11
splashRandom,
12
cacheBustList,
13
versionValue,
14
uvError,
15
sjError,
16
} from './routes.mjs';
17
export { paintSource as default };
18
19
/* Below are lots of function definitions used to obfuscate the website.
20
* This makes the website harder to properly categorize, as its source code
21
* changes with each time it is compiled using npm run build.
22
*
23
* For customizing source code transformation and more, see the config.json file.
24
* For automatically recompiling in production mode, see ecosystem.config.js.
25
*/
26
const regExpEscape = /[-[\]{}()*+?.,\\^$#\s]/g,
27
basicStrEscape = /["'`$\\]/g,
28
charset = /&#173;|&#8203;|&shy;|<wbr>/gi,
29
subtermsByCaps = /[A-Z]?[^A-Z]+|[A-Z]/g,
30
subtermsByVowels = /(?<=[AEIOUYaeiouy])(?!$)/g,
31
termsBySpaces = /\S+/g,
32
containsMask = /&#\d+;|&#x[A-z\d]+;|&[A-z]+;/,
33
getEndPoint = /((?<![^\/])github\/)?[^\/]+$/,
34
getPaths = /[^\/]+(?=\/)/g,
35
getAbsoluteRoot = /^~?\/+|^~$|^(?!\.\/)/,
36
getRoutePath = /(?<={{route}}{{\s*)[^}\s]+(?=\s*}})/,
37
getAttrPath = /(?<=(?:src|href)=(["']?))[\w\.~:\/\?#[\]@!$&()*+,;%=-]+(?=\1)/,
38
getAttrValues = /=(['"])(?:(?!\1)[^])+\1/g,
39
getNodesByLine = /(?<=^\s*)\S.*?(?=\s*$)/gm,
40
routeConditions = {
41
inline: config.disguiseFiles && config.minifyScripts,
42
},
43
applyInsert = (str, insertFunction, numArgs = 0) => {
44
const mode = 'function' === typeof insertFunction,
45
keyword = mode ? insertFunction.name : insertFunction,
46
replaceParams1 = new RegExp(
47
`[^\\S\\n\\r]*{{${keyword}}}\\s*` +
48
'\\s*{{\\s*\\n\\r?((?:(?!}})[^])*\\n\\r?)\\s*}}\\s*?\\n?\\r?'.repeat(
49
numArgs
50
),
51
'g'
52
),
53
replaceParams2 = new RegExp(
54
`{{${keyword}}}` + '{{((?:(?!}})[^])*?)}}'.repeat(numArgs),
55
'g'
56
),
57
replaceFunc = mode
58
? (text, ...captures) => insertFunction(...captures.splice(0, numArgs))
59
: (text) => flatAltPaths[keyword] || text;
60
return numArgs > 0
61
? str
62
.replace(replaceParams1, replaceFunc)
63
.replace(replaceParams2, replaceFunc)
64
: str.replace(replaceParams2, replaceFunc);
65
},
66
applyMassInsert = (str, flatPathObject, shouldIgnore = false) => {
67
const replaceParams = new RegExp(
68
`{{(${Object.keys(flatPathObject).join('|').replace(regExpEscape, '\\$&')})}}`,
69
'g'
70
),
71
replaceFunc = shouldIgnore
72
? (text, capture) => capture
73
: (text, capture) => flatPathObject[capture] || text;
74
return str.replace(replaceParams, replaceFunc);
75
},
76
ifSEO = (text) => (config.usingSEO ? text : ''),
77
ifDisguise = (text) => (config.disguiseFiles ? text : ''),
78
randomListItem = (lis) => () => lis[(Math.random() * lis.length) | 0],
79
getRandomChar = randomListItem(charRandom),
80
/* Text masks, found in src/data.json, are meant to be variations of the
81
* same term. Using a different term as a mask will break the spelling.
82
* HTML entities may also break if their names are used as terms.
83
*/
84
parsedTextMasks = Object.freeze(
85
Object.entries(textMasks).map((entry) => [
86
entry[0],
87
randomListItem(entry[1]),
88
entry[0].match(subtermsByCaps),
89
])
90
),
91
matchTextMasks = new RegExp(
92
Object.keys(textMasks)
93
.sort((term1, term2) => term2.length - term1.length)
94
.join('|')
95
.replace(regExpEscape, '\\$&'),
96
'gi'
97
),
98
maskTerm = (term) => {
99
if (config.usingSEO) return term;
100
const altList = parsedTextMasks.find(
101
(entry) => entry[0].toLowerCase() === term.toLowerCase()
102
);
103
let capitals = altList[2].map((word) => {
104
const letter = term[0];
105
term = term.slice(word.length);
106
return letter;
107
});
108
return altList[1]()
109
.replace(subtermsByCaps, (word) => capitals.shift() + word.slice(1))
110
.replaceAll(delimiter, getRandomChar);
111
},
112
mask = (text) =>
113
config.usingSEO
114
? text
115
: text
116
.replace(matchTextMasks, maskTerm)
117
.replace(termsBySpaces, (term) =>
118
containsMask.test(term)
119
? term
120
: term.replace(subtermsByVowels, getRandomChar)
121
),
122
route = (text, conditionalRoute = false) =>
123
conditionalRoute && routeConditions[conditionalRoute]
124
? text.replace(
125
getEndPoint,
126
(name) => cacheBustList[name] || flatAltPaths['files/' + name] || name
127
)
128
: text
129
.replace(
130
getEndPoint,
131
// cacheBustList is purely for dealing with cached file loading issues.
132
(name, ancestor) =>
133
ancestor
134
? flatAltPaths[name] || name
135
: flatAltPaths['files/' + name] ||
136
cacheBustList[name] ||
137
flatAltPaths[name] ||
138
name
139
)
140
.replace(
141
getPaths,
142
(path) =>
143
flatAltPaths['prefixes/' + path] || flatAltPaths[path] || path
144
)
145
.replace(getAbsoluteRoot, serverUrl.pathname),
146
inlineElement = (htmlStr) => {
147
let relPath = htmlStr.match(getRoutePath) || htmlStr.match(getAttrPath),
148
wrapper = [],
149
fileType;
150
if (relPath)
151
try {
152
relPath = new URL(
153
'../views/dist' +
154
new URL(relPath[0], 'https://www.example.com').pathname,
155
import.meta.url
156
);
157
fileType = relPath.pathname
158
.slice(relPath.pathname.lastIndexOf('.') + 1)
159
.toLowerCase();
160
switch (fileType) {
161
case 'css': {
162
wrapper = ['<style>', '</style>'];
163
break;
164
}
165
case 'js': {
166
const parsedNode = htmlStr
167
.replace(getAttrValues, ' ')
168
.toLowerCase();
169
if (
170
parsedNode.indexOf(' defer ') !== -1 ||
171
parsedNode.indexOf(' defer>') !== -1
172
)
173
wrapper = ['<script defer>', '</script>'];
174
else wrapper = ['<script>', '</script>'];
175
break;
176
}
177
default: {
178
// Do nothing.
179
}
180
}
181
} catch (e) {
182
relPath = '';
183
console.log(e);
184
}
185
return relPath && wrapper.length && existsSync(relPath)
186
? wrapper[0] +
187
readFileSync(relPath, 'utf8').trim() +
188
(wrapper[1] || wrapper[0])
189
: htmlStr;
190
},
191
inline = (htmlStr) =>
192
routeConditions.inline
193
? htmlStr.replace(getNodesByLine, inlineElement)
194
: htmlStr,
195
insertCharset = (str) => str.replace(charset, getRandomChar),
196
getSplash = () => randomListItem(splashRandom)(),
197
getCookingText = () =>
198
`<span style="display:none" data-fact="${randomListItem(vegetables)()}">${randomListItem(cookingInserts)()}</span>`,
199
insertCooking = (str) =>
200
str.replaceAll(
201
'<!-- IMPORTANT-HUCOOKINGINSERT-DONOTDELETE -->',
202
getCookingText
203
),
204
encodingTable = (() => {
205
let yummyOneBytes = '';
206
for (let i = 0; i < 128; i++)
207
if (
208
JSON.stringify(JSON.stringify(String.fromCodePoint(i)).slice(1, -1))
209
.length < 6
210
)
211
yummyOneBytes += String.fromCodePoint(i);
212
return yummyOneBytes;
213
})(),
214
createRandomID = () =>
215
crypto
216
.randomUUID()
217
.split('-')
218
.map((gibberish) => {
219
let randomNumber = parseInt(gibberish, 16),
220
output = '';
221
while (randomNumber >= encodingTable.length) {
222
output +=
223
encodingTable[Math.floor(randomNumber) % encodingTable.length];
224
randomNumber = randomNumber / encodingTable.length;
225
}
226
return output + Math.floor(randomNumber);
227
})
228
.join('')
229
.replaceAll('{', '')
230
.replaceAll('}', ''),
231
// To be used for {{insertions}} that are also encased in string literals.
232
escapeStr = (str) =>
233
str
234
.replace(basicStrEscape, '\\$&')
235
.replaceAll('\r', '\\r')
236
.replaceAll('\n', '\\n'),
237
orderedTransforms = [
238
[getSplash, 0],
239
[route, 2],
240
[route, 1],
241
[ifSEO, 1],
242
[ifDisguise, 1],
243
[mask, 1],
244
[inline, 1],
245
],
246
namedEntries = Object.freeze({
247
__uv$config: escapeStr(
248
config.randomizeIdentifiers ? createRandomID() : '__uv$config'
249
),
250
version: versionValue,
251
cacheVal: crypto.getRandomValues(new Uint32Array(1))[0],
252
defaultSearch: '{{DuckDuckGo}}',
253
}),
254
// List of manual censors for unavoidable cases.
255
manualCensors = Object.freeze({
256
Google: 'Google',
257
Bing: 'Bing',
258
Brave: 'Brave',
259
DuckDuckGo: 'DuckDuckGo',
260
Startpage: 'Startpage',
261
'wisp-transport': 'wst',
262
libcurl: 'unix',
263
epoxy: 'epoch',
264
'hu-lts': 'net-time',
265
}),
266
// Apply most obfuscation changes to an entire file's text content.
267
prePaint = (str) => {
268
let paintedSource = insertCharset(insertCooking(str));
269
paintedSource = applyMassInsert(
270
applyMassInsert(paintedSource, namedEntries),
271
manualCensors,
272
config.usingSEO
273
);
274
for (let i = 0, total = orderedTransforms.length; i < total; i++)
275
paintedSource = applyInsert(paintedSource, ...orderedTransforms[i]);
276
return paintedSource;
277
},
278
// Functionally similar to templates.mjs, but requires more situational formatting.
279
specialTemplates = Object.freeze({
280
'ultraviolet-error': escapeStr(prePaint(uvError)),
281
'scramjet-error': escapeStr(prePaint(sjError)),
282
}),
283
// Apply final changes to a given file's text content.
284
paintSource = (str) => applyMassInsert(prePaint(str), specialTemplates);
285
286