User:Bot doubleredirects/Gadget-DoubleRedirectFixer.js: Difference between revisions
Appearance
No edit summary |
No edit summary |
||
| Line 1: | Line 1: | ||
// | // Credit: HaMiklolah (Improved version - allows execution for any logged-in user) | ||
(() => { | (() => { | ||
const api = new mw.Api(); | const api = new mw.Api(); | ||
/** | /** | ||
* | * Gets the content of the redirect page. | ||
* @param {string} title - | * @param {string} title - The page title. | ||
* @returns {Promise<string|null>} | * @returns {Promise<string|null>} The page content or null if an error occurred. | ||
*/ | */ | ||
const getRedirectContent = async (title) => { | const getRedirectContent = async (title) => { | ||
| Line 20: | Line 20: | ||
return page?.revisions?.[0]?.slots?.main?.content ?? null; | return page?.revisions?.[0]?.slots?.main?.content ?? null; | ||
} catch (error) { | } catch (error) { | ||
console.error(" | console.error("Error getting redirect content for " + title + ":", error); | ||
return null; | return null; | ||
} | } | ||
| Line 26: | Line 26: | ||
/** | /** | ||
* | * Gets the final redirect target. | ||
* @param {string} title - | * @param {string} title - The title of the redirect page. | ||
* @returns {Promise<string|null>} | * @returns {Promise<string|null>} The redirect target or null if not found. | ||
*/ | */ | ||
const getRedirectTarget = async (title) => { | const getRedirectTarget = async (title) => { | ||
| Line 34: | Line 34: | ||
const { query } = await api.get({ | const { query } = await api.get({ | ||
titles: title, | titles: title, | ||
redirects: true, // | redirects: true, // Allows MediaWiki to follow redirects | ||
}); | }); | ||
if (query?.pages) { | if (query?.pages) { | ||
// | // Find the page that is not missing (the final target) | ||
const page = Object.values(query.pages).find((p) => !p.missing); | const page = Object.values(query.pages).find((p) => !p.missing); | ||
return page?.title ?? null; | return page?.title ?? null; | ||
| Line 44: | Line 44: | ||
return null; | return null; | ||
} catch (error) { | } catch (error) { | ||
console.error(" | console.error("Error getting redirect target for " + title + ":", error); | ||
return null; | return null; | ||
} | } | ||
| Line 50: | Line 50: | ||
/** | /** | ||
* | * Extracts the anchor from redirect content (e.g., [[Target_Page#anchor]]). | ||
* @param {string} content - | * @param {string} content - The content of the redirect page. | ||
* @returns {string} | * @returns {string} The anchor with '#' if it exists, otherwise an empty string. | ||
*/ | */ | ||
const extractAnchorFromContent = (content) => { | const extractAnchorFromContent = (content) => { | ||
const match = content.match(/#REDIRECT\s*\[\[[^\]]+?(?:#(.+?))?\]\]/); | // Regex adjusted to look for '#REDIRECT' | ||
const match = content.match(/#REDIRECT\s*\[\[[^\]]+?(?:#(.+?))?\]\]/i); // 'i' for case-insensitive | |||
return match?.[1] ? "#" + match[1] : ""; | return match?.[1] ? "#" + match[1] : ""; | ||
}; | }; | ||
/** | /** | ||
* | * Gets the namespace prefix of a page (e.g., "MediaWiki:", "File:"). | ||
* @param {string} title - | * @param {string} title - The page title. | ||
* @returns {Promise<string>} | * @returns {Promise<string>} The namespace prefix or an empty string. | ||
*/ | */ | ||
const getNamespacePrefix = async (title) => { | const getNamespacePrefix = async (title) => { | ||
| Line 74: | Line 75: | ||
const page = Object.values(query.pages)[0]; | const page = Object.values(query.pages)[0]; | ||
if (page.ns !== undefined) { | if (page.ns !== undefined) { | ||
// | // Standard MediaWiki namespaces. Adjust '4' if Chabadpedia uses a different name for NS 4. | ||
const namespacePrefixes = { | const namespacePrefixes = { | ||
4: " | 0: "", // Main/Article namespace | ||
6: "File:", | 1: "Talk:", | ||
10: "Template:", | 2: "User:", | ||
12: "Help:", | 3: "User talk:", | ||
// . | 4: "Chabadpedia:", // Common for English wikis, could also be "Project:" or "Wikipedia:" | ||
5: "Chabadpedia talk:", | |||
6: "File:", | |||
7: "File talk:", | |||
8: "MediaWiki:", | |||
9: "MediaWiki talk:", | |||
10: "Template:", | |||
11: "Template talk:", | |||
12: "Help:", | |||
13: "Help talk:", | |||
14: "Category:", | |||
15: "Category talk:", | |||
// Add more custom namespaces here if applicable, using their numerical IDs. | |||
}; | }; | ||
return namespacePrefixes[page.ns] ?? ""; | return namespacePrefixes[page.ns] ?? ""; | ||
| Line 87: | Line 100: | ||
return ""; | return ""; | ||
} catch (error) { | } catch (error) { | ||
console.error(" | console.error("Error getting namespace prefix for " + title + ":", error); | ||
return ""; | return ""; | ||
} | } | ||
| Line 93: | Line 106: | ||
/** | /** | ||
* | * Creates/fixes a redirect. | ||
* @param {string} title - | * @param {string} title - The title of the page to become a redirect. | ||
* @param {string} target - | * @param {string} target - The redirect target. | ||
*/ | */ | ||
const createRedirect = async (title, target) => { | const createRedirect = async (title, target) => { | ||
| Line 102: | Line 115: | ||
action: "edit", | action: "edit", | ||
format: "json", | format: "json", | ||
title: title, | title: title, | ||
// Changed from #הפניה to #REDIRECT | |||
text: "#REDIRECT [[" + target + "]]", | text: "#REDIRECT [[" + target + "]]", | ||
summary: " | // Localized summary | ||
summary: "Bot: Fixing double redirect to [[" + target + "]]", | |||
}); | }); | ||
mw.notify(title + ": | // Localized success notification | ||
mw.notify(title + ": Redirect successfully fixed to " + target, { type: 'success', title: 'Success' }); | |||
} catch (error) { | } catch (error) { | ||
console.error(" | console.error("Error creating redirect for " + title + " to " + target + ":", error); | ||
// | // Localized error notifications | ||
if (error.code === 'badtags') { | if (error.code === 'badtags') { | ||
mw.notify(title + ": Error: Permission denied to use edit tags. Please contact a system administrator.", { type: 'error', title: 'Permission Error' }); | |||
} else { | } else { | ||
mw.notify(title + ": Failed to fix redirect. See console for details.", { type: 'error', title: 'Error' }); | |||
} | } | ||
} | } | ||
| Line 120: | Line 135: | ||
/** | /** | ||
* | * Processes a single double redirect: finds the final target and fixes it. | ||
* @param {string} title - | * @param {string} title - The page title of the double redirect. | ||
*/ | */ | ||
const processRedirect = async (title) => { | const processRedirect = async (title) => { | ||
| Line 127: | Line 142: | ||
const content = await getRedirectContent(title); | const content = await getRedirectContent(title); | ||
if (content === null) { | if (content === null) { | ||
mw.notify(title + ": | mw.notify(title + ": Could not retrieve redirect page content.", { type: 'warn' }); | ||
return; | return; | ||
} | } | ||
| Line 135: | Line 150: | ||
if (!finalTarget) { | if (!finalTarget) { | ||
mw.notify(title + ": | mw.notify(title + ": No final redirect target found.", { type: 'warn' }); | ||
return; | return; | ||
} | } | ||
| Line 142: | Line 157: | ||
const fullTarget = namespace + finalTarget + anchor; | const fullTarget = namespace + finalTarget + anchor; | ||
// | // Check to prevent circular redirects or unnecessary fixes | ||
if (title.replace(/_/g, " ") === finalTarget.replace(/_/g, " ") && !anchor) { | if (title.replace(/_/g, " ") === finalTarget.replace(/_/g, " ") && !anchor) { | ||
mw.notify(title + ": | mw.notify(title + ": Redirect is already valid. No fix needed.", { type: 'info' }); | ||
return; | return; | ||
} | } | ||
if (title.replace(/_/g, " ") === fullTarget.replace(/_/g, " ")) { | if (title.replace(/_/g, " ") === fullTarget.replace(/_/g, " ")) { | ||
mw.notify(title + ": | mw.notify(title + ": Circular or valid redirect. No fix needed.", { type: 'info' }); | ||
return; | return; | ||
} | } | ||
console.log(" | console.log("Creating new redirect: " + title + " --> " + fullTarget); | ||
await createRedirect(title, fullTarget); | await createRedirect(title, fullTarget); | ||
} catch (error) { | } catch (error) { | ||
console.error(" | console.error("Error processing double redirect for " + title + ":", error); | ||
mw.notify(title + ": | mw.notify(title + ": An error occurred while processing the redirect. See console for details.", { type: 'error', title: 'Error' }); | ||
} | } | ||
}; | }; | ||
/** | /** | ||
* | * The main function that runs the script. | ||
*/ | */ | ||
const init = async () => { | const init = async () => { | ||
const pageName = mw.config.get("wgPageName"); | const pageName = mw.config.get("wgPageName"); | ||
// | // Ensure the script runs only on the Special:DoubleRedirects page | ||
if (pageName !== "Special:DoubleRedirects") { | if (pageName !== "Special:DoubleRedirects") { | ||
console.log(" | console.log("This script only runs on Special:DoubleRedirects."); | ||
return; | return; | ||
} | } | ||
const numInput = prompt(" | const numInput = prompt("How many double redirects to display and fix? (Recommended to start with a small number, e.g., 10)", "0"); | ||
const num = parseInt(numInput, 10); | const num = parseInt(numInput, 10); | ||
if (isNaN(num) || num <= 0) { | if (isNaN(num) || num <= 0) { | ||
mw.notify(" | mw.notify("Invalid input. Please enter a positive integer.", { type: 'error' }); | ||
return; | return; | ||
} | } | ||
try { | try { | ||
mw.notify(" | mw.notify("Starting check for " + num + " double redirects...", { type: 'info' }); | ||
// | // Get the list of double redirects via API | ||
const { query } = await api.get({ | const { query } = await api.get({ | ||
list: "querypage", | list: "querypage", | ||
| Line 194: | Line 209: | ||
const redirects = query.querypage.results; | const redirects = query.querypage.results; | ||
if (redirects.length === 0) { | if (redirects.length === 0) { | ||
mw.notify(" | mw.notify("No double redirects found to fix.", { type: 'info' }); | ||
return; | return; | ||
} | } | ||
// | // Loop through each redirect and fix it | ||
for (const redirect of redirects) { | for (const redirect of redirects) { | ||
const title = redirect.title.replace(/_/g, " "); | const title = redirect.title.replace(/_/g, " "); | ||
await processRedirect(title); | await processRedirect(title); | ||
} | } | ||
mw.notify(" | mw.notify("Finished processing double redirects.", { type: 'success', title: 'Finished' }); | ||
} catch (error) { | } catch (error) { | ||
console.error(" | console.error("Error fetching double redirects:", error); | ||
mw.notify(" | mw.notify("An error occurred while fetching the list of double redirects.", { type: 'error', title: 'General Error' }); | ||
} | } | ||
}; | }; | ||
// | // Run the script when the page is loaded | ||
$(init); | $(init); | ||
})(); | })(); | ||
Latest revision as of 09:49, 10 July 2025
// Credit: HaMiklolah (Improved version - allows execution for any logged-in user)
(() => {
const api = new mw.Api();
/**
* Gets the content of the redirect page.
* @param {string} title - The page title.
* @returns {Promise<string|null>} The page content or null if an error occurred.
*/
const getRedirectContent = async (title) => {
try {
const response = await api.get({
prop: "revisions",
titles: title,
rvprop: "content",
rvslots: "*",
formatversion: "2",
});
const page = response.query?.pages?.[0];
return page?.revisions?.[0]?.slots?.main?.content ?? null;
} catch (error) {
console.error("Error getting redirect content for " + title + ":", error);
return null;
}
};
/**
* Gets the final redirect target.
* @param {string} title - The title of the redirect page.
* @returns {Promise<string|null>} The redirect target or null if not found.
*/
const getRedirectTarget = async (title) => {
try {
const { query } = await api.get({
titles: title,
redirects: true, // Allows MediaWiki to follow redirects
});
if (query?.pages) {
// Find the page that is not missing (the final target)
const page = Object.values(query.pages).find((p) => !p.missing);
return page?.title ?? null;
}
return null;
} catch (error) {
console.error("Error getting redirect target for " + title + ":", error);
return null;
}
};
/**
* Extracts the anchor from redirect content (e.g., [[Target_Page#anchor]]).
* @param {string} content - The content of the redirect page.
* @returns {string} The anchor with '#' if it exists, otherwise an empty string.
*/
const extractAnchorFromContent = (content) => {
// Regex adjusted to look for '#REDIRECT'
const match = content.match(/#REDIRECT\s*\[\[[^\]]+?(?:#(.+?))?\]\]/i); // 'i' for case-insensitive
return match?.[1] ? "#" + match[1] : "";
};
/**
* Gets the namespace prefix of a page (e.g., "MediaWiki:", "File:").
* @param {string} title - The page title.
* @returns {Promise<string>} The namespace prefix or an empty string.
*/
const getNamespacePrefix = async (title) => {
try {
const { query } = await api.get({
titles: title,
prop: "info",
});
if (query?.pages) {
const page = Object.values(query.pages)[0];
if (page.ns !== undefined) {
// Standard MediaWiki namespaces. Adjust '4' if Chabadpedia uses a different name for NS 4.
const namespacePrefixes = {
0: "", // Main/Article namespace
1: "Talk:",
2: "User:",
3: "User talk:",
4: "Chabadpedia:", // Common for English wikis, could also be "Project:" or "Wikipedia:"
5: "Chabadpedia talk:",
6: "File:",
7: "File talk:",
8: "MediaWiki:",
9: "MediaWiki talk:",
10: "Template:",
11: "Template talk:",
12: "Help:",
13: "Help talk:",
14: "Category:",
15: "Category talk:",
// Add more custom namespaces here if applicable, using their numerical IDs.
};
return namespacePrefixes[page.ns] ?? "";
}
}
return "";
} catch (error) {
console.error("Error getting namespace prefix for " + title + ":", error);
return "";
}
};
/**
* Creates/fixes a redirect.
* @param {string} title - The title of the page to become a redirect.
* @param {string} target - The redirect target.
*/
const createRedirect = async (title, target) => {
try {
await api.postWithEditToken({
action: "edit",
format: "json",
title: title,
// Changed from #הפניה to #REDIRECT
text: "#REDIRECT [[" + target + "]]",
// Localized summary
summary: "Bot: Fixing double redirect to [[" + target + "]]",
});
// Localized success notification
mw.notify(title + ": Redirect successfully fixed to " + target, { type: 'success', title: 'Success' });
} catch (error) {
console.error("Error creating redirect for " + title + " to " + target + ":", error);
// Localized error notifications
if (error.code === 'badtags') {
mw.notify(title + ": Error: Permission denied to use edit tags. Please contact a system administrator.", { type: 'error', title: 'Permission Error' });
} else {
mw.notify(title + ": Failed to fix redirect. See console for details.", { type: 'error', title: 'Error' });
}
}
};
/**
* Processes a single double redirect: finds the final target and fixes it.
* @param {string} title - The page title of the double redirect.
*/
const processRedirect = async (title) => {
try {
const content = await getRedirectContent(title);
if (content === null) {
mw.notify(title + ": Could not retrieve redirect page content.", { type: 'warn' });
return;
}
const anchor = extractAnchorFromContent(content);
const finalTarget = await getRedirectTarget(title);
if (!finalTarget) {
mw.notify(title + ": No final redirect target found.", { type: 'warn' });
return;
}
const namespace = await getNamespacePrefix(finalTarget);
const fullTarget = namespace + finalTarget + anchor;
// Check to prevent circular redirects or unnecessary fixes
if (title.replace(/_/g, " ") === finalTarget.replace(/_/g, " ") && !anchor) {
mw.notify(title + ": Redirect is already valid. No fix needed.", { type: 'info' });
return;
}
if (title.replace(/_/g, " ") === fullTarget.replace(/_/g, " ")) {
mw.notify(title + ": Circular or valid redirect. No fix needed.", { type: 'info' });
return;
}
console.log("Creating new redirect: " + title + " --> " + fullTarget);
await createRedirect(title, fullTarget);
} catch (error) {
console.error("Error processing double redirect for " + title + ":", error);
mw.notify(title + ": An error occurred while processing the redirect. See console for details.", { type: 'error', title: 'Error' });
}
};
/**
* The main function that runs the script.
*/
const init = async () => {
const pageName = mw.config.get("wgPageName");
// Ensure the script runs only on the Special:DoubleRedirects page
if (pageName !== "Special:DoubleRedirects") {
console.log("This script only runs on Special:DoubleRedirects.");
return;
}
const numInput = prompt("How many double redirects to display and fix? (Recommended to start with a small number, e.g., 10)", "0");
const num = parseInt(numInput, 10);
if (isNaN(num) || num <= 0) {
mw.notify("Invalid input. Please enter a positive integer.", { type: 'error' });
return;
}
try {
mw.notify("Starting check for " + num + " double redirects...", { type: 'info' });
// Get the list of double redirects via API
const { query } = await api.get({
list: "querypage",
qppage: "DoubleRedirects",
qplimit: num,
});
const redirects = query.querypage.results;
if (redirects.length === 0) {
mw.notify("No double redirects found to fix.", { type: 'info' });
return;
}
// Loop through each redirect and fix it
for (const redirect of redirects) {
const title = redirect.title.replace(/_/g, " ");
await processRedirect(title);
}
mw.notify("Finished processing double redirects.", { type: 'success', title: 'Finished' });
} catch (error) {
console.error("Error fetching double redirects:", error);
mw.notify("An error occurred while fetching the list of double redirects.", { type: 'error', title: 'General Error' });
}
};
// Run the script when the page is loaded
$(init);
})();