User:Justarandomamerican/globalUserLock.js: Difference between revisions
From WikiOasis Meta
More actions
Me attempting to get a Claude script to work |
No edit summary |
||
| Line 8: | Line 8: | ||
* | * | ||
* Activation: adds a "Global user lock" link to the "More" (p-cactions) | * Activation: adds a "Global user lock" link to the "More" (p-cactions) | ||
* menu on every page | * menu on every page. Clicking it opens an OOUI dialog. | ||
* | * | ||
* Usage: Add to your global.js / common.js: | * Usage: Add to your global.js / common.js: | ||
* | * mw.loader.load('//meta.wikimedia.org/w/index.php?title=User:YourName/globalUserLock.js&action=raw&ctype=text/javascript'); | ||
* | * | ||
* API actions used: | * API actions used: | ||
* | * - query + list=globalallusers (find matching accounts) | ||
* | * - centralauth (lock accounts) | ||
* | * - query + meta=tokens (CSRF) | ||
* | * | ||
* Author: WikiOasis T&S tooling | * Author: WikiOasis T&S tooling | ||
| Line 30: | Line 29: | ||
const TOOL_TITLE = 'Global User Lock'; | const TOOL_TITLE = 'Global User Lock'; | ||
const PORTLET_ID = 'ca-global-user-lock'; | const PORTLET_ID = 'ca-global-user-lock'; | ||
const API_ENDPOINT = mw. | const API_ENDPOINT = mw.util.wikiScript( 'api' ); | ||
// Maximum accounts to process in a single run (safety cap) | // Maximum accounts to process in a single run (safety cap) | ||
| Line 283: | Line 282: | ||
font-family: inherit; | font-family: inherit; | ||
color: var( --gul-text ); | color: var( --gul-text ); | ||
overflow-y: auto; | |||
} | } | ||
.gul-body fieldset { | .gul-body fieldset { | ||
| Line 608: | Line 608: | ||
.html( '' ) | .html( '' ) | ||
.hide(); | .hide(); | ||
} | } | ||
function setBadge( username, type, text ) { | function setBadge( username, type, text ) { | ||
$( ' | $( 'tr[data-user="' + $.escapeSelector( username ) + '"] .gul-status-cell' ) | ||
.html( '<span class="gul-badge gul-badge-' + type + '">' + text + '</span>' ); | .html( '<span class="gul-badge gul-badge-' + type + '">' + text + '</span>' ); | ||
} | } | ||
| Line 627: | Line 622: | ||
// ── API helpers ──────────────────────────────────────────────────────────── | // ── API helpers ──────────────────────────────────────────────────────────── | ||
function | function getCSRFToken() { | ||
return $.ajax( { | return $.ajax( { | ||
url: API_ENDPOINT, | url: API_ENDPOINT, | ||
data: { action: 'query', meta: 'tokens', type: ' | data: { action: 'query', meta: 'tokens', type: 'csrf', format: 'json' } | ||
} ).then( function ( data ) { | } ).then( function ( data ) { | ||
return data.query.tokens. | return data.query.tokens.csrftoken; | ||
} ); | } ); | ||
} | } | ||
| Line 662: | Line 657: | ||
list: 'globalallusers', | list: 'globalallusers', | ||
agulimit: '500', | agulimit: '500', | ||
aguprop: 'lockinfo | aguprop: 'lockinfo', // NOTE: 'registration' is not a valid globalallusers property | ||
format: 'json' | format: 'json' | ||
}; | }; | ||
| Line 706: | Line 701: | ||
* Lock a single global account via the CentralAuth API. | * Lock a single global account via the CentralAuth API. | ||
* Uses action=setglobalaccountstatus per Extension:CentralAuth/API. | * Uses action=setglobalaccountstatus per Extension:CentralAuth/API. | ||
*/ | */ | ||
function lockAccount( username, token, reason, hide, suppress ) { | function lockAccount( username, token, reason, hide, suppress ) { | ||
const payload = { | |||
action: 'setglobalaccountstatus', | |||
user: username, | |||
locked: 'lock', | |||
reason: reason, | |||
token: token, | |||
format: 'json' | |||
}; | |||
// Only include the hidden parameter if we intend to actively hide/suppress. | |||
// Sending an empty string would accidentally UNHIDE previously hidden accounts. | |||
if ( suppress ) { | if ( suppress ) { | ||
hidden = 'suppressed'; | payload.hidden = 'suppressed'; | ||
} else if ( hide ) { | } else if ( hide ) { | ||
hidden = 'lists'; | payload.hidden = 'lists'; | ||
} | } | ||
| Line 725: | Line 723: | ||
url: API_ENDPOINT, | url: API_ENDPOINT, | ||
method: 'POST', | method: 'POST', | ||
data | data: payload | ||
} ); | } ); | ||
} | } | ||
| Line 760: | Line 750: | ||
const $tbody = $( '<tbody>' ); | const $tbody = $( '<tbody>' ); | ||
users.forEach( function ( u ) { | users.forEach( function ( u ) { | ||
const isLocked = | // MW API boolean flags return an empty string ("") if true. Must check for property existence. | ||
const $row = $( '<tr>' ).attr( ' | const isLocked = u.locked !== undefined; | ||
// Use data-user safely instead of building an ID to avoid regex collision bugs | |||
const $row = $( '<tr>' ).attr( 'data-user', u.name ); | |||
$row.append( | $row.append( | ||
| Line 777: | Line 770: | ||
.text( u.name ) | .text( u.name ) | ||
), | ), | ||
$( '<td>' ).css( 'text-align', 'center' ).html( | $( '<td>' ).css( 'text-align', 'center' ).html( | ||
isLocked | isLocked | ||
| Line 795: | Line 787: | ||
$( '<th>' ).css( 'width', '28px' ), | $( '<th>' ).css( 'width', '28px' ), | ||
$( '<th>' ).text( 'Username' ), | $( '<th>' ).text( 'Username' ), | ||
$( '<th>' ).text( 'Currently locked?' ), | $( '<th>' ).text( 'Currently locked?' ), | ||
$( '<th>' ).css( 'width', '110px' ).text( 'Action status' ) | $( '<th>' ).css( 'width', '110px' ).text( 'Action status' ) | ||
| Line 873: | Line 864: | ||
$( '#gul-progress' ).addClass( 'visible' ); | $( '#gul-progress' ).addClass( 'visible' ); | ||
setProgress( 0, selected.length ); | setProgress( 0, selected.length ); | ||
setStatus( 'Fetching token…', 'info' ); | setStatus( 'Fetching CSRF token…', 'info' ); | ||
getCSRFToken() | |||
.then( function ( token ) { | .then( function ( token ) { | ||
setStatus( 'Locking ' + selected.length + ' account(s)…', 'info' ); | setStatus( 'Locking ' + selected.length + ' account(s)…', 'info' ); | ||
| Line 902: | Line 893: | ||
const username = selected[ i ]; | const username = selected[ i ]; | ||
const wasLocked = $( ' | const wasLocked = $( 'tr[data-user="' + $.escapeSelector( username ) + '"] td:nth-child(3)' ) | ||
.text().trim() === 'Locked'; | .text().trim() === 'Locked'; | ||