Toggle menu
Toggle preferences menu
Toggle personal menu
Not logged in
Your IP address will be publicly visible if you make any edits.

User:Justarandomamerican/globalUserLock.js: Difference between revisions

From WikiOasis Meta
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, matching the pattern used by all-in-one.js.
  * menu on every page. Clicking it opens an OOUI dialog.
* 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');
  * 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)
  * - query + list=globalallusers  (find matching accounts)
  *   - centralauth                  (lock accounts)
  * - centralauth                  (lock accounts)
  *   - query + meta=tokens          (CSRF)
  * - 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.config.get( 'wgScriptPath' ) + '/api.php';
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();
}
// Row IDs must be safe CSS identifiers; replace anything that isn't \w or -
function rowId( username ) {
return 'gul-row-' + username.replace( /[^\w-]/g, '_' );
}
}


function setBadge( username, type, text ) {
function setBadge( username, type, text ) {
$( '#' + rowId( username ) + ' .gul-status-cell' )
$( '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 getSetGlobalAccountStatusToken() {
function getCSRFToken() {
return $.ajax( {
return $.ajax( {
url:  API_ENDPOINT,
url:  API_ENDPOINT,
data: { action: 'query', meta: 'tokens', type: 'setglobalaccountstatus', format: 'json' }
data: { action: 'query', meta: 'tokens', type: 'csrf', format: 'json' }
} ).then( function ( data ) {
} ).then( function ( data ) {
return data.query.tokens.setglobalaccountstatustoken;
return data.query.tokens.csrftoken;
} );
} );
}
}
Line 662: Line 657:
list:    'globalallusers',
list:    'globalallusers',
agulimit: '500',
agulimit: '500',
aguprop: 'lockinfo|registration',
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.
*
* @param {string}  username
* @param {string}  token    setglobalaccountstatus token
* @param {string}  reason
* @param {boolean} hide      Apply "lists" (hidden from global user list) flag
* @param {boolean} suppress  Apply "suppressed" flag (overrides hide)
*/
*/
function lockAccount( username, token, reason, hide, suppress ) {
function lockAccount( username, token, reason, hide, suppress ) {
// hidden param: '' = not hidden, 'lists' = hidden from list, 'suppressed' = suppressed
const payload = {
let hidden = '';
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
action: 'setglobalaccountstatus',
userusername,
locked: 'lock',
hidden: hidden,
reason: reason,
token:  token,
format: 'json'
}
} );
} );
}
}
Line 760: Line 750:
const $tbody = $( '<tbody>' );
const $tbody = $( '<tbody>' );
users.forEach( function ( u ) {
users.forEach( function ( u ) {
const isLocked = !!u.locked;
// MW API boolean flags return an empty string ("") if true. Must check for property existence.
const $row = $( '<tr>' ).attr( 'id', rowId( u.name ) );
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>' ).text( u.registration ? u.registration.slice( 0, 10 ) : '—' ),
$( '<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( 'Registered' ),
$( '<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' );


getSetGlobalAccountStatusToken()
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 = $( '#' + rowId( username ) + ' td:nth-child(4)' )
const wasLocked = $( 'tr[data-user="' + $.escapeSelector( username ) + '"] td:nth-child(3)' )
.text().trim() === 'Locked';
.text().trim() === 'Locked';


Cookies help us deliver our services. By using our services, you agree to our use of cookies.