Image Max URL
23 |Thank you for installing Image Max URL!
24 |Please review the following options, which balance privacy with functionality.
For more information, please review our Privacy Policy.
26 | 27 |
├── .eslintignore
├── .jshintrc
├── reddit-bot
├── .jshintrc
├── .eslintrc.json
├── package.json
├── comment-bot.js
├── blacklist.json
└── service-bot.js
├── lib
├── shim.js
├── fetch_shim.js
├── aes1.patch
├── libs.txt
└── cryptojs_aes.js
├── resources
├── logo.png
├── logo_256.png
├── logo_40.png
├── logo_48.png
├── logo_64.png
├── logo_96.png
├── disabled_40.png
├── disabled_48.png
├── disabled_64.png
├── disabled_96.png
├── imu_opera_banner.png
├── imu_opera_banner.xcf
├── imu_opera_banner_transparent.png
└── imu.svg
├── tools
├── watch_tsc.sh
├── get_old_userscript.sh
├── rules_template.js
├── update_signed_xpi.sh
├── update_site_userscript.sh
├── fetch_libs.sh
├── bigimage_template.js
├── update_sitesnum.js
├── gen_minified.js
├── util.js
├── build_libs.sh
├── gen_po.js
├── update_strings.js
├── patch_libs.js
├── update_from_po.js
├── gen_rules_js.js
└── remcomments.js
├── .gitignore
├── extension
├── updates.xml
├── options.html
├── welcome.html
├── popup.html
├── popup.js
└── welcome.js
├── .github_old
└── workflows
│ └── build.yml
├── package.json
├── manifest.json
├── src
└── module.d.ts
├── userscript.meta.js
├── tsconfig.json
├── docs
└── pt
│ ├── README.pt-BR.md
│ └── CONTRIBUTING.pt-BR.md
├── README.md
├── LICENSE.txt
└── CONTRIBUTING.md
/.eslintignore:
--------------------------------------------------------------------------------
1 | **/userscript.user.js
--------------------------------------------------------------------------------
/.jshintrc:
--------------------------------------------------------------------------------
1 | {
2 | "esversion": 6,
3 | "node": true
4 | }
5 |
--------------------------------------------------------------------------------
/reddit-bot/.jshintrc:
--------------------------------------------------------------------------------
1 | {
2 | "esversion": 6,
3 | "node": true
4 | }
5 |
--------------------------------------------------------------------------------
/lib/shim.js:
--------------------------------------------------------------------------------
1 | if (typeof module !== 'undefined')
2 | module.exports = lib_export;
3 |
--------------------------------------------------------------------------------
/resources/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qsniyg/maxurl/HEAD/resources/logo.png
--------------------------------------------------------------------------------
/resources/logo_256.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qsniyg/maxurl/HEAD/resources/logo_256.png
--------------------------------------------------------------------------------
/resources/logo_40.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qsniyg/maxurl/HEAD/resources/logo_40.png
--------------------------------------------------------------------------------
/resources/logo_48.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qsniyg/maxurl/HEAD/resources/logo_48.png
--------------------------------------------------------------------------------
/resources/logo_64.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qsniyg/maxurl/HEAD/resources/logo_64.png
--------------------------------------------------------------------------------
/resources/logo_96.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qsniyg/maxurl/HEAD/resources/logo_96.png
--------------------------------------------------------------------------------
/resources/disabled_40.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qsniyg/maxurl/HEAD/resources/disabled_40.png
--------------------------------------------------------------------------------
/resources/disabled_48.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qsniyg/maxurl/HEAD/resources/disabled_48.png
--------------------------------------------------------------------------------
/resources/disabled_64.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qsniyg/maxurl/HEAD/resources/disabled_64.png
--------------------------------------------------------------------------------
/resources/disabled_96.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qsniyg/maxurl/HEAD/resources/disabled_96.png
--------------------------------------------------------------------------------
/resources/imu_opera_banner.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qsniyg/maxurl/HEAD/resources/imu_opera_banner.png
--------------------------------------------------------------------------------
/resources/imu_opera_banner.xcf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qsniyg/maxurl/HEAD/resources/imu_opera_banner.xcf
--------------------------------------------------------------------------------
/resources/imu_opera_banner_transparent.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qsniyg/maxurl/HEAD/resources/imu_opera_banner_transparent.png
--------------------------------------------------------------------------------
/reddit-bot/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "parserOptions": {
3 | "ecmaVersion": 6
4 | },
5 | "env": {
6 | "node": true
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/tools/watch_tsc.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | cd "$(dirname "$(readlink -f "$0")")/.."
4 |
5 | # tsc --watch segfaults after a few updates
6 | while true; do
7 | npx tsc --watch
8 | sleep 1 # eases ^C
9 | done
10 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .env*
2 | .env*.json
3 | node_modules
4 | *~
5 | site/
6 | #extension.xpi
7 | extension_source.zip
8 | *.pem
9 | #*.crx
10 | maxurl.zip
11 | *.pub
12 | *.sig
13 | build/tsout.js
14 | standalone_config.json
15 |
--------------------------------------------------------------------------------
/tools/get_old_userscript.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | cd "`dirname "$0"`/.."
4 |
5 | LASTTAG="`git tag | sort -V | tail -n1`"
6 | curl 'https://raw.githubusercontent.com/qsniyg/maxurl/'"$LASTTAG"'/userscript_smaller.user.js' -o olduserscript
7 |
--------------------------------------------------------------------------------
/tools/rules_template.js:
--------------------------------------------------------------------------------
1 | var __IMU_GETBIGIMAGE__ = function(shared_variables) {
2 | // imu:shared_variables
3 |
4 | return {
5 | bigimage: function(src, options) {
6 | // imu:bigimage
7 | },
8 | nonce: __IMU_NONCE__ // imu:nonce = __IMU_NONCE__
9 | };
10 | };
11 |
--------------------------------------------------------------------------------
/extension/updates.xml:
--------------------------------------------------------------------------------
1 |
2 |
Thank you for installing Image Max URL!
24 |Please review the following options, which balance privacy with functionality.
For more information, please review our Privacy Policy.
15 | Enabled
16 |
10 |
11 |
3 |
4 |
5 | ---
6 |
7 | 8 | English | Português (Brasil) 9 |
10 | 11 | --- 12 | 13 | Image Max URL is a program that will try to find larger/original versions of images and videos, usually by replacing URL patterns. 14 | 15 | It currently contains support for \>10,000 hardcoded websites (full list in [sites.txt](https://github.com/qsniyg/maxurl/blob/master/sites.txt)), 16 | but it also supports a number of generic engines (such as Wordpress and MediaWiki), which means it can work for many other websites as well. 17 | 18 | It is currently released as: 19 | 20 | - Userscript: (most browsers) 21 | - Stable: [userscript_smaller.user.js](https://github.com/qsniyg/maxurl/blob/master/userscript_smaller.user.js?raw=true) or [OpenUserJS](https://openuserjs.org/scripts/qsniyg/Image_Max_URL) 22 | - Development: [userscript.user.js](https://github.com/qsniyg/maxurl/blob/master/userscript.user.js?raw=true) (recommended) 23 | - It serves as the base for everything listed below. It also serves as a node module (used by the reddit bot), and can be embedded in a website. 24 | - Browser extension: [Firefox](https://addons.mozilla.org/firefox/addon/image-max-url/) 25 | - Other browsers supporting WebExtensions can sideload the extension through this git repository. 26 | - Since addons have more privileges than userscripts, it has a bit of extra functionality over the userscript. 27 | - Source code is in [manifest.json](https://github.com/qsniyg/maxurl/blob/master/manifest.json) and the [extension](https://github.com/qsniyg/maxurl/tree/master/extension) folder. 28 | - [Website](https://qsniyg.github.io/maxurl/) 29 | - Due to browser security constraints, some URLs (requiring cross-origin requests) can't be supported by the website. 30 | - Source code is in the [gh-pages](https://github.com/qsniyg/maxurl/tree/gh-pages) branch. 31 | - Reddit bot ([/u/MaxImageBot](https://www.reddit.com/user/MaxImageBot/)) 32 | - Source code is in [reddit-bot/comment-bot.js](https://github.com/qsniyg/maxurl/blob/master/reddit-bot/comment-bot.js) and [reddit-bot/dourl.js](https://github.com/qsniyg/maxurl/blob/master/reddit-bot/dourl.js) 33 | 34 | Community: 35 | 36 | - [Discord Server](https://discord.gg/fH9Pf54) 37 | - [Matrix](https://matrix.to/#/#image-max-url:tedomum.net?via=tedomum.net) (`#image-max-url:tedomum.net`) 38 | - [Subreddit](http://reddit.com/r/MaxImage) 39 | 40 | # Sideloading the extension 41 | 42 | The extension is currently unavailable to other browsers\' addon stores (such as Chrome and Microsoft Edge), 43 | but you can sideload this repository if you wish to use the extension version instead of the userscript. 44 | 45 | - Repository: 46 | - Download the repository however you wish (I\'d recommend cloning it through git as it allows easier updating) 47 | - Chromium: 48 | - Go to>>6*(3-v)&63));if(l=t.charAt(64))for(;d.length%4;)d.push(l);return d.join("")},parse:function(d){var l=d.length,s=this._map,t=s.charAt(64);t&&(t=d.indexOf(t),-1!=t&&(l=t));for(var t=[],r=0,w=0;w<
15 | l;w++)if(w%4){var v=s.indexOf(d.charAt(w-1))<<2*(w%4),b=s.indexOf(d.charAt(w))>>>6-2*(w%4);t[r>>>2]|=(v|b)<<24-8*(r%4);r++}return p.create(t,r)},_map:"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="}})();
16 | (function(u){function p(b,n,a,c,e,j,k){b=b+(n&a|~n&c)+e+k;return(b<>>2]&255}};d.BlockCipher=v.extend({cfg:v.cfg.extend({mode:b,padding:q}),reset:function(){v.reset.call(this);var a=this.cfg,b=a.iv,a=a.mode;if(this._xformMode==this._ENC_XFORM_MODE)var c=a.createEncryptor;else c=a.createDecryptor,this._minBufferSize=1;this._mode=c.call(a,
28 | this,b&&b.words)},_doProcessBlock:function(a,b){this._mode.processBlock(a,b)},_doFinalize:function(){var a=this.cfg.padding;if(this._xformMode==this._ENC_XFORM_MODE){a.pad(this._data,this.blockSize);var b=this._process(!0)}else b=this._process(!0),a.unpad(b);return b},blockSize:4});var n=d.CipherParams=l.extend({init:function(a){this.mixIn(a)},toString:function(a){return(a||this.formatter).stringify(this)}}),b=(p.format={}).OpenSSL={stringify:function(a){var b=a.ciphertext;a=a.salt;return(a?s.create([1398893684,
29 | 1701076831]).concat(a).concat(b):b).toString(r)},parse:function(a){a=r.parse(a);var b=a.words;if(1398893684==b[0]&&1701076831==b[1]){var c=s.create(b.slice(2,4));b.splice(0,4);a.sigBytes-=16}return n.create({ciphertext:a,salt:c})}},a=d.SerializableCipher=l.extend({cfg:l.extend({format:b}),encrypt:function(a,b,c,d){d=this.cfg.extend(d);var l=a.createEncryptor(c,d);b=l.finalize(b);l=l.cfg;return n.create({ciphertext:b,key:c,iv:l.iv,algorithm:a,mode:l.mode,padding:l.padding,blockSize:a.blockSize,formatter:d.format})},
30 | decrypt:function(a,b,c,d){d=this.cfg.extend(d);b=this._parse(b,d.format);return a.createDecryptor(c,d).finalize(b.ciphertext)},_parse:function(a,b){return"string"==typeof a?b.parse(a,this):a}}),p=(p.kdf={}).OpenSSL={execute:function(a,b,c,d){d||(d=s.random(8));a=w.create({keySize:b+c}).compute(a,d);c=s.create(a.words.slice(b),4*c);a.sigBytes=4*b;return n.create({key:a,iv:c,salt:d})}},c=d.PasswordBasedCipher=a.extend({cfg:a.cfg.extend({kdf:p}),encrypt:function(b,c,d,l){l=this.cfg.extend(l);d=l.kdf.execute(d,
31 | b.keySize,b.ivSize);l.iv=d.iv;b=a.encrypt.call(this,b,c,d.key,l);b.mixIn(d);return b},decrypt:function(b,c,d,l){l=this.cfg.extend(l);c=this._parse(c,l.format);d=l.kdf.execute(d,b.keySize,b.ivSize,c.salt);l.iv=d.iv;return a.decrypt.call(this,b,c,d.key,l)}})}();
32 | (function(){for(var u=CryptoJS,p=u.lib.BlockCipher,d=u.algo,l=[],s=[],t=[],r=[],w=[],v=[],b=[],x=[],q=[],n=[],a=[],c=0;256>c;c++)a[c]=128>c?c<<1:c<<1^283;for(var e=0,j=0,c=0;256>c;c++){var k=j^j<<1^j<<2^j<<3^j<<4,k=k>>>8^k&255^99;l[e]=k;s[k]=e;var z=a[e],F=a[z],G=a[F],y=257*a[k]^16843008*k;t[e]=y<<24|y>>>8;r[e]=y<<16|y>>>16;w[e]=y<<8|y>>>24;v[e]=y;y=16843009*G^65537*F^257*z^16843008*e;b[k]=y<<24|y>>>8;x[k]=y<<16|y>>>16;q[k]=y<<8|y>>>24;n[k]=y;e?(e=z^a[a[a[G^z]]],j^=a[a[j]]):e=j=1}var H=[0,1,2,4,8,
33 | 16,32,64,128,27,54],d=d.AES=p.extend({_doReset:function(){for(var a=this._key,c=a.words,d=a.sigBytes/4,a=4*((this._nRounds=d+6)+1),e=this._keySchedule=[],j=0;j>>24]<<24|l[k>>>16&255]<<16|l[k>>>8&255]<<8|l[k&255]):(k=k<<8|k>>>24,k=l[k>>>24]<<24|l[k>>>16&255]<<16|l[k>>>8&255]<<8|l[k&255],k^=H[j/d|0]<<24);e[j]=e[j-d]^k}c=this._invKeySchedule=[];for(d=0;dd||4>=j?k:b[l[k>>>24]]^x[l[k>>>16&255]]^q[l[k>>>
34 | 8&255]]^n[l[k&255]]},encryptBlock:function(a,b){this._doCryptBlock(a,b,this._keySchedule,t,r,w,v,l)},decryptBlock:function(a,c){var d=a[c+1];a[c+1]=a[c+3];a[c+3]=d;this._doCryptBlock(a,c,this._invKeySchedule,b,x,q,n,s);d=a[c+1];a[c+1]=a[c+3];a[c+3]=d},_doCryptBlock:function(a,b,c,d,e,j,l,f){for(var m=this._nRounds,g=a[b]^c[0],h=a[b+1]^c[1],k=a[b+2]^c[2],n=a[b+3]^c[3],p=4,r=1;r
/);
160 | if (!img_match) {
161 | // Erro para facilitar a depuração se falhar
162 | console_error(cache_key, "Não foi possível encontrar correspondência de imagem para", resp);
163 |
164 | // Primeiro argumento é o resultado (nulo) e o segundo é o tempo de armazenamento (false significa não armazenar)
165 | return done(null, false);
166 | }
167 |
168 | var title = get_meta(resp.responseText, "og:description");
169 |
170 | // Decodifica entidades HTML no interior das tags.
171 | // Adicionalmente, faz isso para garantir a correta exibição de fontes de imagens.
172 | var src = decode_entities(img_match[1]);
173 |
174 | return done({
175 | url: src,
176 | extra: {
177 | caption: title
178 | }
179 | }, 6 * 60 * 60); // Armazena a imagem por 6 horas (em segundos)
180 | }
181 | });
182 |
183 | // `newsrc` será undefined (se a URL não corresponder ou `options.do_request` não existir) ou {waiting: true}
184 | if (newsrc) return newsrc;
185 | }
186 |
187 | if (domain === "image.mysocialnetwork.com") {
188 | // Exemplo de URL: https://image.mysocialnetwork.com/postimage/123.jpg?width=500
189 |
190 | // Substitui a URL para remover parâmetros de consulta.
191 | newsrc = src.replace(/(\/postimage\/+[0-9]+\.[^/.?#]+)(?:[?#].*)?$/, "$1$2");
192 | // Permite continuar para a próxima parte da regra se a URL não for substituída.
193 | // `bigimage` geralmente é executado mais de uma vez, resultando em um histórico,
194 | // o que permite cair de volta para esta URL (ou a anterior) se a próxima falhar.
195 | if (newsrc !== src)
196 | return newsrc;
197 |
198 | match = src.match(/\/postimage\/+([0-9]+)\./);
199 | if (match) {
200 | return {
201 | url: "https://mysocialnetwork.com/post/" + match[1],
202 |
203 | // Impede que redirecione ou consulte a página erroneamente no popup,
204 | // caso `bigimage` não seja executado novamente por qualquer motivo.
205 | is_pagelink: true
206 | };
207 | }
208 | }
209 | ```
210 |
211 | ---
212 |
213 | Agradecemos qualquer contribuição!
214 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | Translations
4 | ============
5 |
6 | Translations are done through standard .po (gettext) files, located in the [po subdirectory](https://github.com/qsniyg/maxurl/tree/master/po).
7 | You can either translate it manually using a text editor, or use one of the numerous .po translation tools, such as [poedit](https://poedit.net/) or [poeditor](https://poeditor.com/) (online).
8 |
9 | To test a modified translation, run: `node tools/update_from_po.js`. This will update src/userscript.ts with the translations from the po subdirectory.
10 |
11 | Note: when submitting a pull request for a translation, please do not include the modified userscript.ts, as it will increase the risk of merge conflicts.
12 |
13 | To add support for a new language, create a new .po file for the language code from [po/imu.pot](https://github.com/qsniyg/maxurl/blob/master/po/imu.pot),
14 | and make sure to translate `$language_native$` (the native word for your language, such as Français for French, or 한국어 for Korean).
15 |
16 | Website/rule contributions
17 | ==========================
18 |
19 | If you spot any issue with existing rules, or want to suggest a new websites, **the easiest way for the moment is if you file an issue**.
20 |
21 | Pull requests are also accepted (especially if the rule you want to submit is complex), but since everything is currently stored in one file (src/userscript.ts),
22 | it can lead to merge conflicts.
23 |
24 | ------------
25 |
26 | If you decide to make a pull request, here are the general guidelines I follow when adding a new rule. Don't worry too much about getting it
27 | perfect though, I often get it wrong myself :) I can fix it up if you make a mistake.
28 |
29 | - Check if the rule already exists
30 |
31 | - There's a chance the rule might already exist, but without support for the specific website you want to add support for.
32 | Try doing a regex search of the script to see if a similar rule has already been created.
33 |
34 | - New website-specific rules are generally added before the `// -- general rules --` line (there's a large whitespace gap above it to make it clear).
35 |
36 | - General rules are added at the end of the general rules section (after the aforementioned line)
37 | - Sometimes some rules need to be above others for various reasons (e.g. host-specific rules).
38 |
39 | - Use `domain`, `domain_nosub` or `domain_nowww` with a `===` comparison if possible for the `if` check.
40 |
41 | - If a regex test is needed, use `/regex/.test(...)`, and always try to make sure that it's after the initial `===` comparison.
42 | For example, if you want to match `img[0-9]+\.example\.com`, you can use `if (domain_nosub === "example.com" && /^img[0-9]+\./.test(domain))`.
43 | This helps to ensure that performance won't be too terrible :)
44 | - `domain_nowww` matches both example.com and www.example.com. Unless both domains are different (or one is nonexistant), use `domain_nowww`
45 | when referring to either of these domains.
46 | - An exception for this is with amazon buckets (e.g. bucket.s3.amazonaws.com or s3.amazonaws.com/bucket/). Use `amazon_container === "bucket"` instead.
47 | Note that both URL forms are usually (always?) valid, so make sure the rule accounts for both.
48 | For example, be careful when doing something like: `://[^/]+\/+images\/+` as it won't work for the second form.
49 |
50 | - Use the script's wrapper functions over builtin functions:
51 |
52 | - For example, `array_indexof` or `string_indexof` instead of `foo.indexOf()`, `base64_decode` instead of `atob`, `JSON_parse` instead of `JSON.parse`, etc.
53 | This is because some websites (or adblock) override these functions with broken implementations.
54 | IMU will use its own implementation of these functions if the browser's version fails a few sanity checks.
55 | - Search for `var JSON_parse` in the userscript to find a list of them.
56 |
57 | - The script has a lot of helper functions that may be useful (you can find a list of them in `variables_list` in [tools/gen_rules_js.js](https://github.com/qsniyg/maxurl/blob/master/tools/gen_rules_js.js)). They're currently undocumented, but here is a list of commonly used ones:
58 |
59 | - `get_queries`: Returns the queries as an object:
60 | - `get_queries("https://example.com/?a=5&b=10")` -> `{a: 5, b: 10}`
61 |
62 | - `remove_queries`: Removes the specified queries:
63 | - `remove_queries("https://example.com/?a=5&b=10&c=20", ["b", "c"])` -> `"https://example.com/?a=5"`
64 | - `remove_queries("https://example.com/?a=5&b=10&c=20", "b")` -> `"https://example.com/?a=5&c=20"`
65 |
66 | - `keep_queries`: Removes every query except for the specified queries:
67 | - `keep_queries("https://example.com/?a=5&b=10&c=20", ["b", "c"])` -> `"https://example.com/?b=10&c=20"`
68 | - `keep_queries("https://example.com/?a=5&b=10&c=20", "b")` -> `"https://example.com/?b=10"`
69 | - `keep_queries("https://example.com/?a=5&b=10&c=20", ["b", "c"], {overwrite: {"c": 1, "d": 2}})` -> `"https://example.com/?b=10&c=1&d=2"`
70 | - `keep_queries("https://example.com/?a=5&b=10", ["b", "c"], {required: ["c"]})` -> `"https://example.com/?a=5&b=10"`
71 |
72 | - `add_queries`: Adds or overwrites queries:
73 | - `add_queries("https://example.com/?a=5&b=10", {b: 20, c: 30})` -> `"https://example.com/?a=5&b=20&c=30"`
74 |
75 | - `decodeuri_ifneeded`: Runs `decodeURIComponent` only if a url looks encoded:
76 | - `decodeuri_ifneeded("https%3A%2F%2Fexample.com%2F")` -> `"https://example.com/"`
77 | - `decodeuri_ifneeded("https%253A%252F%252Fexample.com%252F")` -> `"https://example.com/"` (it supports decoding more than one time)
78 | - `decodeuri_ifneeded("https://example.com/?a=5%20")` -> `"https://example.com/?a=5%20"` (unchanged because `https://` is not encoded)
79 | - Use this function if you want to return a URL from a query (e.g. `https://example.com/thumb.php?img=https%3A%2F%2Fexample.com%2Ftest.png`)
80 |
81 | - Add test cases
82 |
83 | - The general format is:
84 |
85 | ```ts
86 | // https://img1.example.com/thumbs/image.jpg -- smaller image (-- denotes a comment)
87 | // https://img1.example.com/medium/image.jpg -- a larger image of the one above available on the website that this rule also works for
88 | // https://img1.example.com/images/image.jpg -- largest image returned by this rule from any of the above (/medium/ or /thumbs/)
89 | ```
90 |
91 | - The "format" is quite loose though, don't worry too much about getting it right.
92 | - **Please do not add any NSFW test cases.**
93 |
94 | - Regex style
95 |
96 | - Folder identifiers (`/`) should be referred to as `/+` (unless the web server distinguishes between one or more slashes)
97 | - Account for query strings or hash strings possibly including a /. The way I usually do it is to add `(?:[?#].*)?$` at the end
98 | - Try to keep the rule as tight as possible (within reason). For example:
99 |
100 | ```ts
101 | // https://www.example.com/images/image_500.jpg
102 | // https://www.example.com/images/image.jpg
103 | return src.replace(/(\/images\/+[^/?#]+)_[0-9]+(\.[^/.]+(?:[?#].*)?)$/, "$1$2"); // good
104 | return src.replace(/_[0-9]+(\.[^/.]+(?:[?#].*)?)$/, "$1$2"); // bad
105 | ```
106 |
107 | - While not a strict rule, I don't use `\d` or `\w` as I find that specifying exactly which characters are allowed allows it to be easier
108 | to understand and modify. Your choice though :)
109 |
110 | - You'll probably see that a lot of the rules don't follow many of the guidelines above. More recent rules tend to follow the guidelines better, but older
111 | rules haven't been updated, and are often either too specific or too generic as a result. I try to update them as I see them, but since there are literally thousands
112 | of rules, and each update often breaks something (meaning at least a few edits are required to update a single rule), I haven't been able to update the
113 | majority of the script yet. The wonders of organically written software!
114 |
115 | As mentioned earlier, these are just guidelines, and you don't have to get it to be perfect to submit a rule :)
116 |
117 | To build the userscript:
118 |
119 | - Single build: `npm run build`
120 | - Build and watch for changes: `npm run watch`
121 |
122 | Personally I install build/userscript_extr_cat.js as a userscript under Violentmonkey, with the "Track local file..." setting enabled. This allows the userscript
123 | to be automatically updated within ~5 seconds after saving. Using the built file instead of userscript.user.js also has a few advantages:
124 |
125 | - Due to the size of the userscript, your editor might take a while to save the entire script, which can lead to a race condition where Violentmonkey will
126 | update an incomplete version of the userscript. While it's still possible when using a built version, it's significantly less likely.
127 | - Since the built version is the one that is published on Greasyfork/OUJS, in case there are any issues with it (such as if a shared variable is missing),
128 | this allows one to catch the issues much quicker.
129 |
130 | ### API calls/Pagelink rules
131 |
132 | There are a few considerations for implementing rules that use API calls:
133 |
134 | - Check for `options.do_request` and `options.cb`
135 |
136 | - There are a few parts of the script that call `bigimage` without `do_request`, which will cause API calls to crash if the check isn't present.
137 | - This isn't required if you use `website_query`, it will do this automatically for you.
138 |
139 | - Return `{waiting: true}` at the end of the function if the result will be returned in a callback (`options.cb`).
140 |
141 | - Otherwise it will result in inconsistent behavior (such as multiple popups).
142 | - Think of this like returning a Promise.
143 |
144 | - Use `api_cache` wherever possible in order to reduce duplicate API calls.
145 |
146 | - Unless there's a good reason not to, prefer `api_cache.fetch` over `api_cache.has/get` + `api_cache.set`. This allows for much simpler logic, as well as avoiding races.
147 | - You (likely) don't need to interact with `api_cache` if using `api_query` or `website_query` (more on that later)
148 | - For cache duration, my general (though admittedly rather arbitrary) rule is an hour (`60*60`) for data that has been generated (or is otherwise expected to change within a day or so), and 6 hours (`6*60*60`) for permanent data, unless it's huge (e.g. html pages, scripts, or images).
149 |
150 | - Use `api_query` over `api_cache.fetch` + `options.do_request` if possible.
151 |
152 | - This allows for much simpler code with less indentation. Note that even though `options.do_request` is called implicitly, you must still check for it.
153 |
154 | - Use pagelink rules (`website_query`) over `api_query` or direct `options.do_request` if possible.
155 |
156 | - Pagelink rules are relatively new to the script, but allow for (usually) simpler code, access to the main media embedded in a page from only the link, and for code deduplication without relying on `common_functions`.
157 |
158 | The idea behind pagelink rules is to support a public-facing URL, generally (always?) an HTML page, then return the main media (or an album).
159 |
160 | To document it, I'll give an example with an imaginary social network:
161 |
162 | ```ts
163 | if (domain_nowww === "mysocialnetwork.com") {
164 | // https://mysocialnetwork.com/post/123
165 |
166 | // Note that newsrc is defined at the beginning of bigimage, so no need to `var newsrc = ...`.
167 | newsrc = website_query({
168 | // Regex(es) for the supported URLs.
169 | // The capture group is the ID, which will internally be used for the cache key (mysocialnetwork.com:ID),
170 | // as well as externally to query the page or API call.
171 | // This can also be an array (for multiple different patterns) if needed.
172 | website_regex: /^[a-z]+:\/\/[^/]+\/+post\/+([0-9]+)(?:[?#].*)?$/,
173 |
174 | // ${id} is replaced to the first capture. You can also use ${1}, ${2}, etc. for the first, second, ... capture group.
175 | // This will query the page, then run `process`.
176 | query_for_id: "https://mysocialnetwork.com/post/${id}",
177 |
178 | // Same arguments as for api_query, with "match" added, which is the regex match.
179 | process: function(done, resp, cache_key, match) {
180 | var img_match = resp.responseText.match(/
/);
181 | if (!img_match) {
182 | // An error to make it easier to debug if it fails
183 | console_error(cache_key, "Unable to find image match for", resp);
184 |
185 | // First argument is the result (null) and the second is how long to store it (false means not to store it)
186 | return done(null, false);
187 | }
188 |
189 | var title = get_meta(resp.responseText, "og:description");
190 |
191 | // Remember that the inside of tags may also use html entities (such as ").
192 | // While unlikely for image sources, it also never hurts to add this.
193 | var src = decode_entities(img_match[1]);
194 |
195 | return done({
196 | url: src,
197 | extra: {
198 | caption: title
199 | }
200 | }, 6*60*60);
201 | // Though it's arbitrary, 6*60*60 (6 hours measured in seconds) is used for images that will not expire.
202 | }
203 | });
204 |
205 | // newsrc will either be undefined (if the URL doesn't match, or if options.do_request doesn't exist), or {waiting: true}
206 | if (newsrc) return newsrc;
207 | }
208 |
209 | if (domain === "image.mysocialnetwork.com") {
210 | // https://image.mysocialnetwork.com/postimage/123.jpg?width=500
211 |
212 | newsrc = src.replace(/(\/postimage\/+[0-9]+\.[^/.?#]+)(?:[?#].*)?$/, "$1$2");
213 | // This allows us to fall through to the next portion of the rule if the URL hasn't been replaced.
214 | // Since bigimage is (generally) run more than once, this will result in a history,
215 | // which allows it to fall back to this URL (or the previous) if the next one fails.
216 | if (newsrc !== src)
217 | return newsrc;
218 |
219 | match = src.match(/\/postimage\/+([0-9]+)\./);
220 | if (match) {
221 | return {
222 | url: "https://mysocialnetwork.com/post/" + match[1],
223 |
224 | // In case bigimage isn't run again for whatever reason, this will prevent it from wrongfully
225 | // redirecting to/querying the page in the popup.
226 | is_pagelink: true
227 | };
228 | }
229 | }
230 | ```
231 |
232 | ---
233 |
234 | Thank you very much for whatever contribution you wish to give to this script, it's really appreciated!
235 |
--------------------------------------------------------------------------------