mediawiki-file-url 0.1.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.
- package/.gitattributes +2 -0
- package/LICENSE +21 -0
- package/README.md +54 -0
- package/index.js +261 -0
- package/package.json +23 -0
package/.gitattributes
ADDED
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Benjamin
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# mediawiki-file-url
|
|
2
|
+
Convert MediaWiki filenames into its hashed upload URLs on wikis with `$wgHashedUploadDirectory` enabled.
|
|
3
|
+
|
|
4
|
+
## Install
|
|
5
|
+
```
|
|
6
|
+
npm i mediawiki-file-url
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
## Usage
|
|
10
|
+
```js
|
|
11
|
+
import {
|
|
12
|
+
mwFileUrl,
|
|
13
|
+
mwWikiFileUrl,
|
|
14
|
+
mwSetBaseUrl,
|
|
15
|
+
mwWithBaseUrl,
|
|
16
|
+
} from "mediawiki-file-url";
|
|
17
|
+
|
|
18
|
+
// From a plain filename (default base URL is set to the TDS Wiki images root)
|
|
19
|
+
const url1 = mwFileUrl("WarlockLevel1.png");
|
|
20
|
+
|
|
21
|
+
// Set a module-wide default base URL once (used when you omit baseUrl)
|
|
22
|
+
mwSetBaseUrl("https://wiki.archlinux.org/images");
|
|
23
|
+
const url2 = mwFileUrl("Tango-edit-clear.svg");
|
|
24
|
+
const url3 = mwWikiFileUrl("File:Tango-edit-clear.svg");
|
|
25
|
+
|
|
26
|
+
// Or create bound converters for one base URL (recommended if you need multiple wikis)
|
|
27
|
+
const lol = mwWithBaseUrl("https://wiki.leagueoflegends.com/en-us/images");
|
|
28
|
+
const url4 = lol.mwFileUrl("Champions_Mesh_concept_03.jpg");
|
|
29
|
+
const url5 = lol.mwWikiFileUrl(" Image: Champions_Mesh_concept_02.jpg ");
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## API
|
|
33
|
+
### `mwFileUrl(filename, [baseUrl])`
|
|
34
|
+
|
|
35
|
+
- `filename` (string) - e.g. `"My Image.png"`
|
|
36
|
+
- `baseUrl` (string, optional) — if omitted, uses the configured default base URL (which is the Tower Defense Simulator Wiki)
|
|
37
|
+
|
|
38
|
+
Returns the hashed upload URL.
|
|
39
|
+
|
|
40
|
+
### `mwWikiFileUrl(wikiSyntax, [baseUrl])`
|
|
41
|
+
- `wikiSyntax` (string) - must start with `File:` or `Image:` (whitespace is trimmed)
|
|
42
|
+
- `baseUrl` (string, optional) — if omitted, uses the configured default base URL
|
|
43
|
+
|
|
44
|
+
Extracts the filename and calls `mwFileUrl`.
|
|
45
|
+
|
|
46
|
+
### `mwSetBaseUrl(baseUrl)`
|
|
47
|
+
Sets the module-wide default base URL used by `mwFileUrl` / `mwWikiFileUrl` when `baseUrl` is omitted.
|
|
48
|
+
|
|
49
|
+
### `mwWithBaseUrl(baseUrl)`
|
|
50
|
+
Returns `{ mwFileUrl, mwWikiFileUrl }` functions bound to a specific `baseUrl`, so you don’t have to pass it each call.
|
|
51
|
+
|
|
52
|
+
## Notes
|
|
53
|
+
- ESM only.
|
|
54
|
+
- If a wiki uses a custom hashed layout, it won't work.
|
package/index.js
ADDED
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Itsy bitsy MD5 implementation since browsers don't support MD5.
|
|
3
|
+
* Based on the public domain implementation by Joseph Myers.
|
|
4
|
+
*/
|
|
5
|
+
function md5(message) {
|
|
6
|
+
function safe_add(x, y) {
|
|
7
|
+
var lsw = (x & 0xffff) + (y & 0xffff);
|
|
8
|
+
var msw = (x >> 16) + (y >> 16) + (lsw >> 16);
|
|
9
|
+
return (msw << 16) | (lsw & 0xffff);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function bit_rol(num, cnt) {
|
|
13
|
+
return (num << cnt) | (num >>> (32 - cnt));
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function md5_cmn(q, a, b, x, s, t) {
|
|
17
|
+
return safe_add(bit_rol(safe_add(safe_add(a, q), safe_add(x, t)), s), b);
|
|
18
|
+
}
|
|
19
|
+
function md5_ff(a, b, c, d, x, s, t) {
|
|
20
|
+
return md5_cmn((b & c) | (~b & d), a, b, x, s, t);
|
|
21
|
+
}
|
|
22
|
+
function md5_gg(a, b, c, d, x, s, t) {
|
|
23
|
+
return md5_cmn((b & d) | (c & ~d), a, b, x, s, t);
|
|
24
|
+
}
|
|
25
|
+
function md5_hh(a, b, c, d, x, s, t) {
|
|
26
|
+
return md5_cmn(b ^ c ^ d, a, b, x, s, t);
|
|
27
|
+
}
|
|
28
|
+
function md5_ii(a, b, c, d, x, s, t) {
|
|
29
|
+
return md5_cmn(c ^ (b | ~d), a, b, x, s, t);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function str2binl(str) {
|
|
33
|
+
var nblk = ((str.length + 8) >> 6) + 1;
|
|
34
|
+
var blks = Array(nblk * 16);
|
|
35
|
+
for (var i = 0; i < nblk * 16; i++) blks[i] = 0;
|
|
36
|
+
for (var i = 0; i < str.length; i++)
|
|
37
|
+
blks[i >> 2] |= str.charCodeAt(i) << ((i % 4) * 8);
|
|
38
|
+
blks[i >> 2] |= 0x80 << ((i % 4) * 8);
|
|
39
|
+
blks[nblk * 16 - 2] = str.length * 8;
|
|
40
|
+
return blks;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function binl2hex(binarray) {
|
|
44
|
+
var hex_tab = "0123456789abcdef";
|
|
45
|
+
var str = "";
|
|
46
|
+
for (var i = 0; i < binarray.length * 4; i++) {
|
|
47
|
+
str +=
|
|
48
|
+
hex_tab.charAt((binarray[i >> 2] >> ((i % 4) * 8 + 4)) & 0xf) +
|
|
49
|
+
hex_tab.charAt((binarray[i >> 2] >> ((i % 4) * 8)) & 0xf);
|
|
50
|
+
}
|
|
51
|
+
return str;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
var x = str2binl(message);
|
|
55
|
+
var a = 1732584193;
|
|
56
|
+
var b = -271733879;
|
|
57
|
+
var c = -1732584194;
|
|
58
|
+
var d = 271733878;
|
|
59
|
+
|
|
60
|
+
for (var i = 0; i < x.length; i += 16) {
|
|
61
|
+
var olda = a;
|
|
62
|
+
var oldb = b;
|
|
63
|
+
var oldc = c;
|
|
64
|
+
var oldd = d;
|
|
65
|
+
|
|
66
|
+
a = md5_ff(a, b, c, d, x[i + 0], 7, -680876936);
|
|
67
|
+
d = md5_ff(d, a, b, c, x[i + 1], 12, -389564586);
|
|
68
|
+
c = md5_ff(c, d, a, b, x[i + 2], 17, 606105819);
|
|
69
|
+
b = md5_ff(b, c, d, a, x[i + 3], 22, -1044525330);
|
|
70
|
+
a = md5_ff(a, b, c, d, x[i + 4], 7, -176418897);
|
|
71
|
+
d = md5_ff(d, a, b, c, x[i + 5], 12, 1200080426);
|
|
72
|
+
c = md5_ff(c, d, a, b, x[i + 6], 17, -1473231341);
|
|
73
|
+
b = md5_ff(b, c, d, a, x[i + 7], 22, -45705983);
|
|
74
|
+
a = md5_ff(a, b, c, d, x[i + 8], 7, 1770035416);
|
|
75
|
+
d = md5_ff(d, a, b, c, x[i + 9], 12, -1958414417);
|
|
76
|
+
c = md5_ff(c, d, a, b, x[i + 10], 17, -42063);
|
|
77
|
+
b = md5_ff(b, c, d, a, x[i + 11], 22, -1990404162);
|
|
78
|
+
a = md5_ff(a, b, c, d, x[i + 12], 7, 1804603682);
|
|
79
|
+
d = md5_ff(d, a, b, c, x[i + 13], 12, -40341101);
|
|
80
|
+
c = md5_ff(c, d, a, b, x[i + 14], 17, -1502002290);
|
|
81
|
+
b = md5_ff(b, c, d, a, x[i + 15], 22, 1236535329);
|
|
82
|
+
|
|
83
|
+
a = md5_gg(a, b, c, d, x[i + 1], 5, -165796510);
|
|
84
|
+
d = md5_gg(d, a, b, c, x[i + 6], 9, -1069501632);
|
|
85
|
+
c = md5_gg(c, d, a, b, x[i + 11], 14, 643717713);
|
|
86
|
+
b = md5_gg(b, c, d, a, x[i + 0], 20, -373897302);
|
|
87
|
+
a = md5_gg(a, b, c, d, x[i + 5], 5, -701558691);
|
|
88
|
+
d = md5_gg(d, a, b, c, x[i + 10], 9, 38016083);
|
|
89
|
+
c = md5_gg(c, d, a, b, x[i + 15], 14, -660478335);
|
|
90
|
+
b = md5_gg(b, c, d, a, x[i + 4], 20, -405537848);
|
|
91
|
+
a = md5_gg(a, b, c, d, x[i + 9], 5, 568446438);
|
|
92
|
+
d = md5_gg(d, a, b, c, x[i + 14], 9, -1019803690);
|
|
93
|
+
c = md5_gg(c, d, a, b, x[i + 3], 14, -187363961);
|
|
94
|
+
b = md5_gg(b, c, d, a, x[i + 8], 20, 1163531501);
|
|
95
|
+
a = md5_gg(a, b, c, d, x[i + 13], 5, -1444681467);
|
|
96
|
+
d = md5_gg(d, a, b, c, x[i + 2], 9, -51403784);
|
|
97
|
+
c = md5_gg(c, d, a, b, x[i + 7], 14, 1735328473);
|
|
98
|
+
b = md5_gg(b, c, d, a, x[i + 12], 20, -1926607734);
|
|
99
|
+
|
|
100
|
+
a = md5_hh(a, b, c, d, x[i + 5], 4, -378558);
|
|
101
|
+
d = md5_hh(d, a, b, c, x[i + 8], 11, -2022574463);
|
|
102
|
+
c = md5_hh(c, d, a, b, x[i + 11], 16, 1839030562);
|
|
103
|
+
b = md5_hh(b, c, d, a, x[i + 14], 23, -35309556);
|
|
104
|
+
a = md5_hh(a, b, c, d, x[i + 1], 4, -1530992060);
|
|
105
|
+
d = md5_hh(d, a, b, c, x[i + 4], 11, 1272893353);
|
|
106
|
+
c = md5_hh(c, d, a, b, x[i + 7], 16, -155497632);
|
|
107
|
+
b = md5_hh(b, c, d, a, x[i + 10], 23, -1094730640);
|
|
108
|
+
a = md5_hh(a, b, c, d, x[i + 13], 4, 681279174);
|
|
109
|
+
d = md5_hh(d, a, b, c, x[i + 0], 11, -358537222);
|
|
110
|
+
c = md5_hh(c, d, a, b, x[i + 3], 16, -722521979);
|
|
111
|
+
b = md5_hh(b, c, d, a, x[i + 6], 23, 76029189);
|
|
112
|
+
a = md5_hh(a, b, c, d, x[i + 9], 4, -640364487);
|
|
113
|
+
d = md5_hh(d, a, b, c, x[i + 12], 11, -421815835);
|
|
114
|
+
c = md5_hh(c, d, a, b, x[i + 15], 16, 530742520);
|
|
115
|
+
b = md5_hh(b, c, d, a, x[i + 2], 23, -995338651);
|
|
116
|
+
|
|
117
|
+
a = md5_ii(a, b, c, d, x[i + 0], 6, -198630844);
|
|
118
|
+
d = md5_ii(d, a, b, c, x[i + 7], 10, 1126891415);
|
|
119
|
+
c = md5_ii(c, d, a, b, x[i + 14], 15, -1416354905);
|
|
120
|
+
b = md5_ii(b, c, d, a, x[i + 5], 21, -57434055);
|
|
121
|
+
a = md5_ii(a, b, c, d, x[i + 12], 6, 1700485571);
|
|
122
|
+
d = md5_ii(d, a, b, c, x[i + 3], 10, -1894986606);
|
|
123
|
+
c = md5_ii(c, d, a, b, x[i + 10], 15, -1051523);
|
|
124
|
+
b = md5_ii(b, c, d, a, x[i + 1], 21, -2054922799);
|
|
125
|
+
a = md5_ii(a, b, c, d, x[i + 8], 6, 1873313359);
|
|
126
|
+
d = md5_ii(d, a, b, c, x[i + 15], 10, -30611744);
|
|
127
|
+
c = md5_ii(c, d, a, b, x[i + 6], 15, -1560198380);
|
|
128
|
+
b = md5_ii(b, c, d, a, x[i + 13], 21, 1309151649);
|
|
129
|
+
a = md5_ii(a, b, c, d, x[i + 4], 6, -145523070);
|
|
130
|
+
d = md5_ii(d, a, b, c, x[i + 11], 10, -1120210379);
|
|
131
|
+
c = md5_ii(c, d, a, b, x[i + 2], 15, 718787259);
|
|
132
|
+
b = md5_ii(b, c, d, a, x[i + 9], 21, -343485551);
|
|
133
|
+
|
|
134
|
+
a = safe_add(a, olda);
|
|
135
|
+
b = safe_add(b, oldb);
|
|
136
|
+
c = safe_add(c, oldc);
|
|
137
|
+
d = safe_add(d, oldd);
|
|
138
|
+
}
|
|
139
|
+
return binl2hex([a, b, c, d]);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Default base URL used by mwFileUrl/mwWikiFileUrl when no baseUrl argument is provided.
|
|
144
|
+
* This can be changed at runtime via mwSetBaseUrl().
|
|
145
|
+
*/
|
|
146
|
+
const DEFAULT_BASE_URL =
|
|
147
|
+
"https://static.wikia.nocookie.net/tower-defense-sim/images";
|
|
148
|
+
let defaultBaseUrl = DEFAULT_BASE_URL;
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Set the module-wide default base URL used by mwFileUrl/mwWikiFileUrl
|
|
152
|
+
* when no baseUrl argument is provided.
|
|
153
|
+
*
|
|
154
|
+
* @param {string} baseUrl
|
|
155
|
+
*/
|
|
156
|
+
export function mwSetBaseUrl(baseUrl) {
|
|
157
|
+
if (!baseUrl || typeof baseUrl !== "string") {
|
|
158
|
+
throw new Error("Base URL must be a non-empty string");
|
|
159
|
+
}
|
|
160
|
+
defaultBaseUrl = baseUrl;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Get the current module-wide default base URL.
|
|
165
|
+
* @returns {string}
|
|
166
|
+
*/
|
|
167
|
+
export function getDefaultBaseUrl() {
|
|
168
|
+
return defaultBaseUrl;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Converts a wiki filename to a MediaWiki file URL.
|
|
173
|
+
* Should work for any MediaWiki instance that uses
|
|
174
|
+
* any unmodified $wgHashedUploadDirectory implementation.
|
|
175
|
+
*
|
|
176
|
+
* @param {string} filename - The filename (for example, "WarlockLevel1.png")
|
|
177
|
+
* @param {string} [baseUrl] - The base URL for images (defaults to configured defaultBaseUrl)
|
|
178
|
+
* @returns {string} The full MediaWiki URL
|
|
179
|
+
* @throws {Error} If filename is invalid or hashing fails
|
|
180
|
+
*/
|
|
181
|
+
export function mwFileUrl(filename, baseUrl) {
|
|
182
|
+
if (!filename || typeof filename !== "string") {
|
|
183
|
+
throw new Error("Filename must be a non-empty string");
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const effectiveBaseUrl = baseUrl ?? defaultBaseUrl;
|
|
187
|
+
|
|
188
|
+
try {
|
|
189
|
+
// Normalize spaces to underscores (cuz MediaWiki does that)
|
|
190
|
+
const normalizedFilename = filename.replace(/ /g, "_");
|
|
191
|
+
|
|
192
|
+
// Yada yada give me the md5
|
|
193
|
+
const md5Hash = md5(normalizedFilename);
|
|
194
|
+
const firstChar = md5Hash.charAt(0);
|
|
195
|
+
const firstTwoChars = md5Hash.substring(0, 2);
|
|
196
|
+
const cleanBaseUrl = effectiveBaseUrl.endsWith("/")
|
|
197
|
+
? effectiveBaseUrl.slice(0, -1)
|
|
198
|
+
: effectiveBaseUrl;
|
|
199
|
+
|
|
200
|
+
// baseUrl/a/ab/Filename.png
|
|
201
|
+
const finalUrl = `${cleanBaseUrl}/${firstChar}/${firstTwoChars}/${encodeURIComponent(normalizedFilename)}`;
|
|
202
|
+
|
|
203
|
+
return finalUrl;
|
|
204
|
+
} catch (error) {
|
|
205
|
+
throw new Error(
|
|
206
|
+
`Error converting filename to MediaWiki URL: ${error.message}`,
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Create converters bound to a baseUrl so one does not have to pass it each time.
|
|
213
|
+
*
|
|
214
|
+
* @param {string} baseUrl
|
|
215
|
+
* @returns {{ mwFileUrl: (filename: string) => string, mwWikiFileUrl: (wikiSyntax: string) => string }}
|
|
216
|
+
*/
|
|
217
|
+
export function mwWithBaseUrl(baseUrl) {
|
|
218
|
+
if (!baseUrl || typeof baseUrl !== "string") {
|
|
219
|
+
throw new Error("Base URL must be a non-empty string");
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
return {
|
|
223
|
+
mwFileUrl: (filename) => mwFileUrl(filename, baseUrl),
|
|
224
|
+
mwWikiFileUrl: (wikiSyntax) => mwWikiFileUrl(wikiSyntax, baseUrl),
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Converts a File:/Image: syntax to a MediaWiki URL
|
|
230
|
+
* @param {string} wikiSyntax - The wiki syntax (for example, "File:WarlockLevel1.png")
|
|
231
|
+
* @param {string} [baseUrl] - The base URL for images
|
|
232
|
+
* @returns {string} The full MediaWiki URL
|
|
233
|
+
* @throws {Error} If syntax is invalid or conversion fails
|
|
234
|
+
*/
|
|
235
|
+
export function mwWikiFileUrl(wikiSyntax, baseUrl) {
|
|
236
|
+
if (!wikiSyntax || typeof wikiSyntax !== "string") {
|
|
237
|
+
throw new Error("Wiki syntax must be a non-empty string");
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const trimmed = wikiSyntax.trim();
|
|
241
|
+
|
|
242
|
+
if (trimmed.startsWith("File:")) {
|
|
243
|
+
const filename = trimmed.substring(5).trim();
|
|
244
|
+
return mwFileUrl(filename, baseUrl);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
if (trimmed.startsWith("Image:")) {
|
|
248
|
+
const filename = trimmed.substring(6).trim();
|
|
249
|
+
return mwFileUrl(filename, baseUrl);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
throw new Error('Wiki syntax must start with "File:" or "Image:"');
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
export default {
|
|
256
|
+
mwFileUrl,
|
|
257
|
+
mwWikiFileUrl,
|
|
258
|
+
mwSetBaseUrl,
|
|
259
|
+
getDefaultBaseUrl,
|
|
260
|
+
mwWithBaseUrl,
|
|
261
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "mediawiki-file-url",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Convert MediaWiki filenames into hashed upload URLs",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": "./index.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"test": "node test.js"
|
|
12
|
+
},
|
|
13
|
+
"keywords": [
|
|
14
|
+
"mediawiki",
|
|
15
|
+
"md5"
|
|
16
|
+
],
|
|
17
|
+
"author": "t7ru",
|
|
18
|
+
"license": "MIT",
|
|
19
|
+
"repository": {
|
|
20
|
+
"type": "git",
|
|
21
|
+
"url": "git+https://github.com/t7ru/mediawiki-file-url.git"
|
|
22
|
+
}
|
|
23
|
+
}
|