User:It's moon/Sandbox/ReHaunt-form.js
Appearance
Note: After publishing, you may have to bypass your browser's cache to see the changes.
- Firefox / Safari: Hold Shift while clicking Reload, or press either Ctrl-F5 or Ctrl-R (⌘-R on a Mac)
- Google Chrome: Press Ctrl-Shift-R (⌘-Shift-R on a Mac)
- Edge: Hold Ctrl while clicking Refresh, or press Ctrl-F5.
/* ReHaunt, multi reverse image search tool
Copyright (C) 2025, 2026 Fumple and It's moon
This file is part of ReHaunt.
ReHaunt is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
ReHaunt is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with ReHaunt. If not, see <https://www.gnu.org/licenses/>. */
$(function () {
// Import dependencies (Temporary until converted to Gadget)
mw.loader.using([
'mediawiki.api',
'oojs-ui-core',
'oojs-ui.styles.icons-alerts',
'oojs-ui-widgets'
]).then(function () {
// Assign variables to URL query parameters - This has some problems at handling file names with & and ? characters, might need REGEX or a different approach.
const urlQuery = Object.fromEntries(
new URLSearchParams(location.search)
);
// Create the search box and button
const inputWidget = new OO.ui.SearchInputWidget({
placeholder: "Search by image URL or file name",
// Use URL query parameters to autofill the form - this currently doesn't accept filenames that contain ? or & symbols
value: (urlQuery.search && urlQuery.page) ? urlQuery.search + '?page=' + urlQuery.page : (urlQuery.search || '')
});
const buttonWidget = new OO.ui.ButtonWidget({
label: "Search"
});
inputWidget.$element.find('input').add(buttonWidget.$element.find('a')).css({ height: "35px" });
const actionFieldLayout = new OO.ui.ActionFieldLayout(inputWidget, buttonWidget);
// Create a container for the output
const $outputWidget = $("<p>");
// Replace the loading icon with the search box, button and output container
$(".user-itsmoon-rehaunt").replaceWith(
$("<p>").append(actionFieldLayout.$element, $outputWidget)
);
// Button and enter key clicks
buttonWidget.on("click", performSearch);
inputWidget.on("enter", performSearch);
// Perform the search
function performSearch() {
const input = inputWidget.getValue().trim();
$outputWidget.empty();
$('.img-test').empty(); // Part of image thumbnail test down below
// Check if user input is empty
if (!input) return;
// Disable the search box and button
inputWidget.setDisabled(true);
buttonWidget.setDisabled(true);
if (/^https?:\/\//.test(input)) { // I was thinking of replacing this check by a GET request... maybe not, but I'd like to support URLs that start with // (no http/https)... we'll see. GOOD NEWS! There *can* be pages in the File: namespace with / in the title, but they can't be files eg. [[File:Schmettau Plan de la ville de Berlin (georeferenced).jpg/overlay.kml]] which means I can support // urls without a get request (tool needs to add http/https).
// Encode input URL
const encodedInput = encodeURIComponent(input);
renderOutput(encodedInput);
} else {
// Separate the filename from input query parameters
const [ inputFileRaw ] = input.split(/[?&](?=[^?&=\s]+=(?!\.))/);
// Remove namespace from filename
const inputFile = inputFileRaw.split(':').pop(); // Needs to be fixed for files that have : in the filename eg. [[File:Wiki Academy 2011 - Wiki Technology: Uses in Education and Business.ogv]]
// Assign variables to input query parameters
const inputQuery = Object.fromEntries(
new URLSearchParams(input.slice(inputFileRaw.length + 1))
);
new mw.Api().get({
action: 'query',
titles: 'File:' + inputFile,
prop: 'imageinfo',
iiprop: 'url',
iiurlwidth: 800,
iipage: inputQuery.page,
redirects: 1
}).then(function({query}) {
Object.values(query.pages).forEach(function({imageinfo}) {
const encodedInput = imageinfo ? encodeURIComponent(imageinfo[0].thumburl) : undefined;
renderOutput(encodedInput);
});
});
}
// Generate output
function renderOutput(encodedInput) {
// Image thumbnail test, to develop this further use [gerrit.wikimedia.org/g/mediawiki/core/+/HEAD/resources/src/mediawiki.special.upload] as a reference. If really needed do research on who wrote the code and ask them further.
$('.img-test').append($('<img>',{src:decodeURIComponent(encodedInput), width: '300px'}));
$outputWidget.append(
encodedInput
? new OO.ui.LabelWidget({
label: new OO.ui.HtmlSnippet(
'<a href="https://lens.google.com/uploadbyurl?url=' + encodedInput + '" target="_blank" rel="noopener noreferrer" class="external text">Google Lens</a>' +
' • <a href="https://www.google.com/searchbyimage?client=app&image_url=' + encodedInput + '" target="_blank" rel="noopener noreferrer" class="external text">Google Classic</a>' +
' • <a href="https://www.bing.com/images/searchbyimage?cbir=sbi&imgurl=' + encodedInput + '" target="_blank" rel="noopener noreferrer" class="external text">Bing</a>' +
' • <a href="https://yandex.com/images/search?rpt=imageview&url=' + encodedInput + '" target="_blank" rel="noopener noreferrer" class="external text">Yandex</a>' +
' • <a href="https://graph.baidu.com/details?isfromtusoupc=1&carousel=0&extUiData[isLogoShow]=1&image=' + encodedInput + '&promotion_name=" target="_blank" rel="noopener noreferrer" class="external text">Baidu</a>' +
' • <a href="https://www.tineye.com/search?url=' + encodedInput + '" target="_blank" rel="noopener noreferrer" class="external text">TinEye</a>'
/* Currently not working:
' • <a href="https://karmadecay.com/search?q=' + encodedInput + '" target="_blank" rel="noopener noreferrer" class="external text">Karma Decay</a>' */
)
}).$element.css({ textWrap: "balance" })
: new OO.ui.MessageWidget({
type: "error",
label: "Please enter a valid URL or file name.",
inline: true
}).$element.attr("id", "rehaunt-error").css({ display: "inline-block", textAlign: "left" })
);
// Reenable the search box and button
inputWidget.setDisabled(false);
buttonWidget.setDisabled(false);
}
}
// Inkipedia only styling, not needed for other wikis, to be moved to TemplateStyles on that wiki.
const style = document.createElement("style");
style.textContent = `
#rehaunt-error .oo-ui-iconElement-icon {filter:none;}
#rehaunt-error .oo-ui-iconElement-icon::before {background:#d33;}
#rehaunt-error .oo-ui-labelElement-label {color:#d33;}
`;
document.head.appendChild(style);
// End of Inkipedia only styling
}); // Import dependencies closing tag (Temporary until converted to Gadget)
});