Page Menu
Home
WickedGov Phorge
Search
Configure Global Search
Log In
Files
F4056880
ipReveal.js
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Size
27 KB
Referenced Files
None
Subscribers
None
ipReveal.js
View Options
const
BlockDetailsPopupButtonWidget
=
require
(
'./BlockDetailsPopupButtonWidget.js'
);
const
ipRevealUtils
=
require
(
'./ipRevealUtils.js'
);
const
{
performRevealRequest
,
performBatchRevealRequest
,
isRevisionLookup
,
isLogLookup
,
isAbuseFilterLogLookup
}
=
require
(
'./rest.js'
);
/**
* Replace a button with an IP address, or a message indicating that the IP address
* was not found.
*
* @param {jQuery} $element The button element
* @param {string|false|undefined} ip IP address, false if unavailable, undefined if expired
* @param {boolean} success The IP lookup was successful. Indicates how to interpret
* a value of `false` for the IP address. If the lookup was successful but the IP
* is `false`, then the IP address is legitimately missing.
* @return {void}
*/
function
replaceButton
(
$element
,
ip
,
success
)
{
const
$span
=
$
(
'<span>'
)
.
addClass
(
'ext-checkuser-tempaccount-reveal-ip'
);
if
(
!
success
)
{
$element
.
replaceWith
(
$span
.
text
(
mw
.
msg
(
'checkuser-tempaccount-reveal-ip-error'
)
)
);
}
else
if
(
typeof
ip
===
'undefined'
)
{
$element
.
replaceWith
(
$span
.
text
(
mw
.
msg
(
'checkuser-tempaccount-reveal-ip-expired'
)
)
);
}
else
if
(
ip
)
{
$element
.
replaceWith
(
$span
.
append
(
$
(
'<a>'
)
.
attr
(
'href'
,
mw
.
util
.
getUrl
(
'Special:IPContributions/'
+
ip
)
)
.
addClass
(
'ext-checkuser-tempaccount-reveal-ip-anchor'
)
.
text
(
ip
)
)
);
}
else
{
$element
.
replaceWith
(
$span
.
text
(
mw
.
msg
(
'checkuser-tempaccount-reveal-ip-missing'
)
)
);
}
}
/**
* Make a button for revealing IP addresses and add a handler for the 'ipReveal'
* event. The handler will perform an API lookup and replace the button with some
* resulting information.
*
* If the current user is blocked, the result will include
* an additional info widget that surfaces block details.
*
* @param {string} target
* @param {Object} revIds Object used to perform the API request, containing:
* - targetId: revision ID for the passed-in element
* - allIds: array of all revision IDs for the passed-in target
* @param {Object} logIds Object used to perform the API request, containing:
* - targetId: log ID for the passed-in element
* - allIds: array of all log IDs for the passed-in target
* @param {Object} aflIds Object used to perform the API request, containing:
* - targetId: AbuseFilter log ID for the passed-in element
* - allIds: array of all AbuseFilter log IDs for the passed-in target
* @param {string|*} documentRoot A Document or selector to use as the context
* for firing the 'userRevealed' event, handled by buttons within that context.
* @return {jQuery[]}
*/
function
makeButton
(
target
,
revIds
,
logIds
,
aflIds
,
documentRoot
)
{
if
(
!
documentRoot
)
{
documentRoot
=
document
;
}
const
isPerformerBlocked
=
mw
.
config
.
get
(
'wgCheckUserIsPerformerBlocked'
);
const
button
=
new
OO
.
ui
.
ButtonWidget
(
{
label
:
mw
.
msg
(
'checkuser-tempaccount-reveal-ip-button-label'
),
framed
:
false
,
quiet
:
true
,
flags
:
[
'progressive'
],
classes
:
[
'ext-checkuser-tempaccount-reveal-ip-button'
],
disabled
:
isPerformerBlocked
}
);
button
.
$element
.
data
(
'target'
,
target
);
button
.
$element
.
data
(
'revIds'
,
revIds
);
button
.
$element
.
data
(
'logIds'
,
logIds
);
button
.
$element
.
data
(
'aflIds'
,
aflIds
);
button
.
once
(
'click'
,
()
=>
{
button
.
$element
.
trigger
(
'revealIp'
);
button
.
$element
.
off
(
'revealIp'
);
}
);
button
.
$element
.
on
(
'revealIp'
,
(
_
,
ip
,
batchResponse
)
=>
{
button
.
$element
.
off
(
'revealIp'
);
if
(
batchResponse
)
{
if
(
!
ipRevealUtils
.
getRevealedStatus
(
target
)
&&
!
batchResponse
.
autoReveal
)
{
ipRevealUtils
.
setRevealedStatus
(
target
);
}
replaceButton
(
button
.
$element
,
ip
,
true
);
let
ips
=
{};
if
(
isRevisionLookup
(
revIds
)
)
{
revIds
.
allIds
.
forEach
(
(
revId
)
=>
{
ips
[
revId
]
=
batchResponse
[
target
].
revIps
[
revId
];
}
);
}
else
if
(
isLogLookup
(
logIds
)
)
{
logIds
.
allIds
.
forEach
(
(
logId
)
=>
{
ips
[
logId
]
=
batchResponse
[
target
].
logIps
[
logId
];
}
);
}
else
if
(
isAbuseFilterLogLookup
(
aflIds
)
)
{
aflIds
.
allIds
.
forEach
(
(
aflLogId
)
=>
{
ips
[
aflLogId
]
=
batchResponse
[
target
].
abuseLogIps
[
aflLogId
];
}
);
}
else
{
ips
=
[
ip
];
}
$
(
documentRoot
).
trigger
(
'userRevealed'
,
[
target
,
ips
,
isRevisionLookup
(
revIds
),
isLogLookup
(
logIds
),
isAbuseFilterLogLookup
(
aflIds
),
batchResponse
]
);
return
;
}
performRevealRequest
(
target
,
revIds
,
logIds
,
aflIds
).
then
(
(
response
)
=>
{
const
index
=
(
revIds
.
targetId
||
logIds
.
targetId
||
aflIds
.
targetId
||
0
);
const
targetIp
=
response
.
ips
[
index
];
if
(
!
ipRevealUtils
.
getRevealedStatus
(
target
)
&&
!
response
.
autoReveal
)
{
ipRevealUtils
.
setRevealedStatus
(
target
);
}
replaceButton
(
button
.
$element
,
targetIp
,
true
);
$
(
documentRoot
).
trigger
(
'userRevealed'
,
[
target
,
response
.
ips
,
isRevisionLookup
(
revIds
),
isLogLookup
(
logIds
),
isAbuseFilterLogLookup
(
aflIds
)
]
);
}
).
catch
(
()
=>
{
replaceButton
(
button
.
$element
,
false
,
false
);
}
);
}
);
const
elements
=
[
button
.
$element
];
if
(
isPerformerBlocked
)
{
const
blockDetailsPopupButton
=
new
BlockDetailsPopupButtonWidget
();
elements
.
push
(
blockDetailsPopupButton
.
$element
);
}
return
elements
;
}
/**
* Get all temporary account user links inside $content that should have a "Show IP" button.
*
* @param {jQuery} $content
* @return {jQuery} The user links
*/
function
getUserLinks
(
$content
)
{
// Get the "normal" temp user links which are those which are not inside a log entry line.
const
$normalUserLinks
=
$content
.
find
(
'.mw-tempuserlink'
).
filter
(
function
()
{
return
$
(
this
).
closest
(
'.mw-logevent-loglines, .mw-changeslist-log-entry, .mw-changeslist-log'
).
length
===
0
;
}
);
// Get the log line temp user links which are inside log lines that are marked as being
// performed by a temporary account and support IP reveal.
const
$logLinePerformerUserLinks
=
$content
.
find
(
'.mw-changeslist-log, .mw-logevent-loglines, .mw-changeslist-log-entry'
)
.
find
(
'.ext-checkuser-log-line-supports-ip-reveal'
)
.
addBack
(
'.ext-checkuser-log-line-supports-ip-reveal'
)
.
map
(
function
()
{
return
$
(
this
).
find
(
'.mw-tempuserlink'
).
first
().
get
();
}
);
return
$normalUserLinks
.
add
(
$logLinePerformerUserLinks
);
}
/**
* Add IP reveal buttons next to temporary user links on a page. See getUserLinks for which
* links are excluded.
*
* @param {jQuery} $content
* @return {jQuery} The IP reveal buttons within $content
*/
function
addIpRevealButtons
(
$content
)
{
const
$userLinks
=
getUserLinks
(
$content
);
return
addButtonsToUserLinks
(
$userLinks
);
}
/**
* Add buttons to a collection of user links.
*
* @param {jQuery} $userLinks
* @return {jQuery} The IP reveal buttons
*/
function
addButtonsToUserLinks
(
$userLinks
)
{
const
allRevIds
=
{};
const
allLogIds
=
{};
const
allAflIds
=
{};
$userLinks
.
each
(
function
()
{
addToAllIds
(
$
(
this
),
allRevIds
,
getRevisionId
);
addToAllIds
(
$
(
this
),
allLogIds
,
getLogId
);
addToAllIds
(
$
(
this
),
allAflIds
,
getAbuseFilterLogId
);
}
);
$userLinks
.
each
(
function
()
{
const
target
=
$
(
this
).
attr
(
'data-mw-target'
);
if
(
$
(
this
).
next
().
is
(
'.ext-checkuser-tempaccount-reveal-ip-button'
)
)
{
return
;
}
$
(
this
).
after
(
function
()
{
const
revIds
=
getIdsForTarget
(
$
(
this
),
target
,
allRevIds
,
getRevisionId
);
const
logIds
=
getIdsForTarget
(
$
(
this
),
target
,
allLogIds
,
getLogId
);
const
aflIds
=
getIdsForTarget
(
$
(
this
),
target
,
allAflIds
,
getAbuseFilterLogId
);
return
makeButton
(
target
,
revIds
,
logIds
,
aflIds
);
}
);
}
);
return
$userLinks
.
next
(
'.ext-checkuser-tempaccount-reveal-ip-button'
);
}
/**
* Add the log or revision ID for a certain element to a map of each target on the page
* to all the IDs on the page that are relevant to that target.
*
* @param {jQuery} $element A user link
* @param {Object.<string, number[]>} allIds Map to be populated
* @param {function(jQuery):number|undefined} getId Callback that gets the ID associated
* with the $element (which may be undefined).
*/
function
addToAllIds
(
$element
,
allIds
,
getId
)
{
const
id
=
getId
(
$element
);
if
(
id
)
{
const
target
=
$element
.
attr
(
'data-mw-target'
);
if
(
!
allIds
[
target
]
)
{
allIds
[
target
]
=
[];
}
allIds
[
target
].
push
(
id
);
}
}
/**
* Get IDs of a certain type (e.g. revision, log) for a certain target user.
*
* @param {jQuery} $element
* @param {string} target
* @param {Object.<string, number[]>} allIds Map of all targets to their relevant IDs of
* one type (revision or log)
* @param {function(jQuery):number|undefined} getId Callback that gets the ID associated
* with the $element (which may be undefined).
* @return {Object} Object used to perform the API request, containing:
* - targetId: ID for the passed-in element
* - allIds: array of all IDs of one type for the passed-in target
*/
function
getIdsForTarget
(
$element
,
target
,
allIds
,
getId
)
{
const
id
=
getId
(
$element
);
let
ids
;
if
(
id
)
{
ids
=
[
...
new
Set
(
allIds
[
target
]
)
];
}
return
{
targetId
:
id
,
allIds
:
ids
};
}
/**
* Enable multi-reveal for a "typical" page, not including contributions pages, which are
* handled separately in SpecialContributions.js.
*
* "Multi-reveal" refers to replacing multiple lookup buttons for the same user with IPs.
*
* @param {jQuery} $element
*/
function
enableMultiReveal
(
$element
)
{
$element
.
on
(
'userRevealed'
,
/**
* @param {Event} _e
* @param {string} userLookup
* @param {string[]|object} ips An array of IPs from most recent to oldest, or a map of
* revision or log IDs to the IP address used while making the edit or performing the
* action.
* @param {boolean} isRev The map keys are revision IDs
* @param {boolean} isLog The map keys are log IDs
* @param {boolean} isAfLog The map keys are AbuseFilter log IDs
* @param {Object|undefined} batchResponse
*/
(
_e
,
userLookup
,
ips
,
isRev
,
isLog
,
isAfLog
,
batchResponse
)
=>
{
// Find all temp user links that share the username
const
$userLinks
=
$
(
'.mw-tempuserlink'
).
filter
(
function
()
{
return
$
(
this
).
attr
(
'data-mw-target'
)
===
userLookup
;
}
);
// Convert the user links into pointers to the IP reveal button
let
$userButtons
=
$userLinks
.
map
(
(
_i
,
el
)
=>
$
(
el
).
next
(
'.ext-checkuser-tempaccount-reveal-ip-button'
)
);
$userButtons
=
$userButtons
.
filter
(
function
()
{
return
$
(
this
).
length
>
0
;
}
);
// The lookup may have returned a map of IDs to IPs or an array of IPs. If it
// returned an array, but subsequent buttons have IDs, they will need to do
// another lookup to get the map. Needed for grouped recent changes: T369662
const
ipsIsRevMap
=
!
Array
.
isArray
(
ips
)
&&
isRev
;
const
ipsIsLogMap
=
!
Array
.
isArray
(
ips
)
&&
isLog
;
const
ipsIsAfLogMap
=
!
Array
.
isArray
(
ips
)
&&
isAfLog
;
const
isUnknownType
=
!
ipsIsRevMap
&&
!
ipsIsLogMap
&&
!
ipsIsAfLogMap
;
let
$triggerNext
;
$userButtons
.
each
(
function
()
{
if
(
!
ips
)
{
// If there's no IP information at all (i.e. ips is null),
// then the IP is considered unavailable.
replaceButton
(
$
(
this
),
false
,
true
);
}
else
if
(
ips
.
length
===
0
)
{
// If IPs are missing from the response, then they should be
// considered expired (if they were unavailable, the backend
// would have returned null for them instead).
replaceButton
(
$
(
this
),
undefined
,
true
);
}
else
{
const
revId
=
getRevisionId
(
$
(
this
)
);
const
logId
=
getLogId
(
$
(
this
)
);
const
afLogId
=
getAbuseFilterLogId
(
$
(
this
)
);
if
(
ipsIsRevMap
&&
revId
)
{
replaceButton
(
$
(
this
),
ips
[
revId
],
true
);
}
else
if
(
ipsIsLogMap
&&
logId
)
{
replaceButton
(
$
(
this
),
ips
[
logId
],
true
);
}
else
if
(
ipsIsAfLogMap
&&
afLogId
)
{
replaceButton
(
$
(
this
),
ips
[
afLogId
],
true
);
}
else
if
(
isUnknownType
&&
!
revId
&&
!
logId
&&
!
afLogId
)
{
replaceButton
(
$
(
this
),
ips
[
0
],
true
);
}
else
if
(
!
ipsIsRevMap
&&
revId
&&
batchResponse
)
{
// If the current button has a revId but the reveal
// didn't set ipsIsRevMap due to the reveal happening
// from another button without the revId, and we also
// have a batch response, we don't need to trigger a
// new lookup. The data we need should be in the batch
// response.
const
ip
=
batchResponse
[
userLookup
].
revIps
[
revId
];
replaceButton
(
$
(
this
),
ip
,
true
);
}
else
if
(
!
ipsIsLogMap
&&
logId
&&
batchResponse
)
{
// If the current button has a logId but the reveal
// didn't set ipsIsLogMap due to the reveal happening
// from another button without the logId, and we also
// have a batch response, we don't need to trigger a
// new lookup. The data we need should be in the batch
// response.
const
ip
=
batchResponse
[
userLookup
].
logIps
[
logId
];
replaceButton
(
$
(
this
),
ip
,
true
);
}
else
if
(
!
ipsIsAfLogMap
&&
afLogId
&&
batchResponse
)
{
// If the current button has an afLogId but the reveal
// didn't set ipsIsAfLogMap due to the reveal happening
// from another button without the afLogId, and we also
// have a batch response, we don't need to trigger a
// new lookup. The data we need should be in the batch
// response.
const
ip
=
batchResponse
[
userLookup
].
abuseLogIps
[
afLogId
];
replaceButton
(
$
(
this
),
ip
,
true
);
}
else
{
// There is a mismatch, so trigger a new lookup for this button.
// Each time revealIp is triggered, an API request is performed,
// so only trigger it for one button at a time, and allow those
// results to be shared to avoid extra lookups.
$triggerNext
=
$
(
this
);
}
}
}
);
if
(
$triggerNext
)
{
$triggerNext
.
trigger
(
'revealIp'
);
}
}
);
}
/**
* Lookup IP addresses for multiple temporary users in a single REST API call
* and reveal the respective buttons per reveal request.
*
* "Batch reveal" refers to looking up IPs for multiple different temporary users.
* "Multi-reveal" refers to replacing multiple lookup buttons for the same user with the looked-up
* IP addresses.
*
* @param {Object} request Object used to perform the API request. Keys are temporary user
* names and values are objects specifying which IP addresses to look up, containing:
* - revIds: array of revision IDs
* - logIds: array of log IDs
* - lastUsedIp: boolean, whether to look up the most recently used IP
* @param {jQuery} $ipRevealButtons The buttons to replace with IP addresses
*/
function
batchRevealIps
(
request
,
$ipRevealButtons
)
{
performBatchRevealRequest
(
request
).
then
(
(
response
)
=>
{
// Replace the lookup buttons with the IPs by triggering 'revealIp'.
$ipRevealButtons
.
each
(
function
()
{
const
target
=
$
(
this
).
data
(
'target'
);
// Skip buttons that got revealed by multi-reveal.
const
$button
=
$
(
this
);
if
(
!
$button
.
get
(
0
)
)
{
return
;
}
if
(
Object
.
prototype
.
hasOwnProperty
.
call
(
response
,
target
)
)
{
const
revId
=
$button
.
data
(
'revIds'
).
targetId
;
const
logId
=
$button
.
data
(
'logIds'
).
targetId
;
const
aflId
=
$button
.
data
(
'aflIds'
).
targetId
;
let
ip
=
null
;
if
(
revId
&&
response
[
target
].
revIps
!==
null
)
{
ip
=
response
[
target
].
revIps
[
revId
];
}
else
if
(
logId
&&
response
[
target
].
logIps
!==
null
)
{
ip
=
response
[
target
].
logIps
[
logId
];
}
else
if
(
aflId
&&
response
[
target
].
abuseLogIps
!==
null
)
{
ip
=
response
[
target
].
abuseLogIps
[
aflId
];
}
else
if
(
response
[
target
].
lastUsedIp
)
{
ip
=
response
[
target
].
lastUsedIp
;
}
if
(
ip
!==
null
)
{
$button
.
trigger
(
'revealIp'
,
[
ip
,
response
]
);
}
}
}
);
}
).
catch
(
()
=>
{
$ipRevealButtons
.
each
(
function
()
{
const
target
=
$
(
this
).
data
(
'target'
);
if
(
Object
.
prototype
.
hasOwnProperty
.
call
(
request
,
target
)
)
{
replaceButton
(
$
(
this
),
false
,
false
);
}
}
);
}
);
}
/**
* Automatically reveal IPs for the given buttons.
*
* Note that this uses the `batch-temporaryaccount` API endpoint.
*
* @param {jQuery} $ipRevealButtons
* @param {boolean} autoRevealStatus Whether auto-reveal mode is on
*/
function
automaticallyRevealUsersInternal
(
$ipRevealButtons
,
autoRevealStatus
)
{
const
request
=
{};
const
usersToReveal
=
[];
let
$buttonsToReveal
;
if
(
autoRevealStatus
)
{
$buttonsToReveal
=
$ipRevealButtons
;
}
else
{
$buttonsToReveal
=
$ipRevealButtons
.
filter
(
function
()
{
return
ipRevealUtils
.
getRevealedStatus
(
$
(
this
).
data
(
'target'
)
);
}
);
}
$buttonsToReveal
.
each
(
function
()
{
const
target
=
$
(
this
).
data
(
'target'
);
const
$button
=
$
(
this
);
if
(
!
Object
.
prototype
.
hasOwnProperty
.
call
(
request
,
target
)
)
{
request
[
target
]
=
{
revIds
:
[],
logIds
:
[],
lastUsedIp
:
false
};
}
if
(
$button
.
data
(
'revIds'
).
allIds
&&
request
[
target
].
revIds
.
length
===
0
)
{
request
[
target
].
revIds
=
request
[
target
].
revIds
.
concat
(
$button
.
data
(
'revIds'
).
allIds
.
map
(
(
x
)
=>
String
(
x
)
)
);
}
if
(
$button
.
data
(
'logIds'
).
allIds
&&
request
[
target
].
logIds
.
length
===
0
)
{
request
[
target
].
logIds
=
request
[
target
].
logIds
.
concat
(
$button
.
data
(
'logIds'
).
allIds
.
map
(
(
x
)
=>
String
(
x
)
)
);
}
let
isEmpty
=
(
request
[
target
].
revIds
.
length
===
0
&&
request
[
target
].
logIds
.
length
===
0
);
// Checking for AbuseFilter is required so that an (empty) aflIds property is
// not added to the payload (doing so when AF is not loaded would make the
// request fail).
if
(
mw
.
loader
.
getState
(
'ext.abuseFilter'
)
===
'ready'
)
{
if
(
!
Object
.
prototype
.
hasOwnProperty
.
call
(
request
[
target
],
'abuseLogIds'
)
)
{
request
[
target
].
abuseLogIds
=
[];
}
if
(
$button
.
data
(
'aflIds'
).
allIds
&&
request
[
target
].
abuseLogIds
.
length
===
0
)
{
request
[
target
].
abuseLogIds
=
request
[
target
].
abuseLogIds
.
concat
(
$button
.
data
(
'aflIds'
).
allIds
.
map
(
(
x
)
=>
String
(
x
)
)
);
}
isEmpty
=
isEmpty
&&
(
request
[
target
].
abuseLogIds
.
length
===
0
);
}
if
(
isEmpty
)
{
request
[
target
].
lastUsedIp
=
true
;
}
usersToReveal
.
push
(
target
);
}
);
// Trigger a batch lookup for all revealed users.
if
(
usersToReveal
.
length
>
0
)
{
batchRevealIps
(
request
,
$buttonsToReveal
);
}
}
/**
* Automatically reveal IPs for temporary users, where appropriate. This means:
* - Users who have been revealed less than `CheckUserTemporaryAccountMaxAge` seconds ago
* - All users if auto-reveal mode is on
*
* This is an outer wrapper for #automaticallyRevealUsersInternal, to accommodate a possible
* async look-up for the auto-reveal status.
*
* @param {jQuery} $ipRevealButtons
* @param {boolean|undefined} autoRevealStatus Whether auto-reveal mode is on. If not
* given, it will be looked up.
*/
function
automaticallyRevealUsers
(
$ipRevealButtons
,
autoRevealStatus
)
{
if
(
autoRevealStatus
===
undefined
)
{
ipRevealUtils
.
getAutoRevealStatus
().
then
(
(
status
)
=>
{
automaticallyRevealUsersInternal
(
$ipRevealButtons
,
status
);
}
);
}
else
{
automaticallyRevealUsersInternal
(
$ipRevealButtons
,
autoRevealStatus
);
}
}
/**
* Enable auto-reveal mode, then show all the IPs on the page.
*
* @param {number} relativeExpiry
* @param {jQuery|undefined} $content
* @return {Promise}
*/
function
enableAutoReveal
(
relativeExpiry
,
$content
)
{
const
deferred
=
$
.
Deferred
();
$content
=
$content
||
$
(
document
);
ipRevealUtils
.
setAutoRevealStatus
(
relativeExpiry
).
then
(
()
=>
{
showAllIps
(
$content
,
true
);
deferred
.
resolve
();
},
(
error
)
=>
{
deferred
.
reject
(
error
);
}
);
return
deferred
.
promise
();
}
/**
* Reveal IPs for all users inside $content who should be revealed. Similar to
* automaticallyRevealUsers, but takes the containing element, rather than the buttons.
*
* @param {jQuery} $content
* @param {boolean} autoRevealStatus Whether auto-reveal mode is on
*/
function
showAllIps
(
$content
,
autoRevealStatus
)
{
$content
=
$content
||
$
(
document
);
// Handle contributions pages with temp user targets separately, as they do not have user links
const
pageTitle
=
mw
.
config
.
get
(
'wgCanonicalSpecialPageName'
);
const
relevantUser
=
mw
.
config
.
get
(
'wgRelevantUserName'
);
if
(
(
pageTitle
===
'Contributions'
||
pageTitle
===
'DeletedContributions'
)
&&
relevantUser
&&
mw
.
util
.
isTemporaryUser
(
relevantUser
)
)
{
if
(
ipRevealUtils
.
getRevealedStatus
(
relevantUser
)
)
{
// The user was recently manually revealed, so there is nothing to do
return
;
}
else
{
// Reveal the IPs
$
(
'.ext-checkuser-tempaccount-reveal-ip-button'
).
first
().
trigger
(
'revealIp'
);
return
;
}
}
// On all other pages, find all buttons and reveal all their users
const
$ipRevealButtons
=
$content
.
find
(
'.ext-checkuser-tempaccount-reveal-ip-button'
);
automaticallyRevealUsers
(
$ipRevealButtons
,
autoRevealStatus
);
}
/**
* Disable auto-reveal mode, then remove auto-revealed IPs from the page.
*
* @param {jQuery|undefined} $content
* @return {Promise}
*/
function
disableAutoReveal
(
$content
)
{
const
deferred
=
$
.
Deferred
();
$content
=
$content
||
$
(
document
);
ipRevealUtils
.
setAutoRevealStatus
().
then
(
()
=>
{
hideAllIps
(
$content
,
false
);
deferred
.
resolve
();
},
(
error
)
=>
{
deferred
.
reject
(
error
);
}
);
return
deferred
.
promise
();
}
/**
* Remove all revealed IPs inside $content and replace them with "IP reveal" buttons.
*
* @param {jQuery} $content
* @param {boolean} autoRevealStatus Whether auto-reveal mode is on
*/
function
hideAllIps
(
$content
,
autoRevealStatus
)
{
$content
=
$content
||
$
(
document
);
// Handle contributions pages with temp user targets separately, as they do not have user links
const
pageTitle
=
mw
.
config
.
get
(
'wgCanonicalSpecialPageName'
);
const
relevantUser
=
mw
.
config
.
get
(
'wgRelevantUserName'
);
if
(
(
pageTitle
===
'Contributions'
||
pageTitle
===
'DeletedContributions'
)
&&
relevantUser
&&
mw
.
util
.
isTemporaryUser
(
relevantUser
)
)
{
if
(
ipRevealUtils
.
getRevealedStatus
(
relevantUser
)
)
{
// The user was recently manually revealed, so keep them revealed
return
;
}
else
{
// Remove the revealed IPs and add the buttons again
$
(
'.ext-checkuser-tempaccount-reveal-ip'
).
remove
();
enableIpRevealForContributionsPage
(
$content
,
pageTitle
,
autoRevealStatus
);
return
;
}
}
// On all other pages, replace IPs that are not pre-revealed with buttons
const
$userLinks
=
getUserLinks
(
$content
);
const
$userLinksToHide
=
$userLinks
.
filter
(
function
()
{
if
(
ipRevealUtils
.
getRevealedStatus
(
$
(
this
).
attr
(
'data-mw-target'
)
)
)
{
return
false
;
}
if
(
$
(
this
).
next
(
'.ext-checkuser-tempaccount-reveal-ip'
).
length
===
0
)
{
return
false
;
}
return
true
;
}
);
$userLinksToHide
.
next
(
'.ext-checkuser-tempaccount-reveal-ip'
).
remove
();
addButtonsToUserLinks
(
$userLinksToHide
);
}
/**
* Get revision ID from the surrounding DOM. Look in ancestors, then siblings.
*
* @param {jQuery} $element
* @return {number|undefined}
*/
function
getRevisionId
(
$element
)
{
let
id
=
$element
.
closest
(
'[data-mw-revid]'
).
data
(
'mw-revid'
);
if
(
id
===
undefined
)
{
id
=
$element
.
siblings
(
'[data-mw-revid]'
).
eq
(
0
).
data
(
'mw-revid'
);
}
return
id
;
}
/**
* Get log ID from the surrounding DOM. Look in ancestors, then siblings.
*
* @param {jQuery} $element
* @return {number|undefined}
*/
function
getLogId
(
$element
)
{
let
id
=
$element
.
closest
(
'[data-mw-logid]'
).
data
(
'mw-logid'
);
if
(
id
===
undefined
)
{
id
=
$element
.
siblings
(
'[data-mw-logid]'
).
eq
(
0
).
data
(
'mw-logid'
);
}
return
id
;
}
/**
* Get AbuseFilter log ID from the surrounding DOM. This looks only in ancestors.
*
* @param {jQuery} $element
* @return {number|undefined}
*/
function
getAbuseFilterLogId
(
$element
)
{
return
$element
.
closest
(
'[data-afl-log-id]'
).
data
(
'afl-log-id'
);
}
/**
* Reveals the first button within $content. This may trigger further reveals, if multi-reveal
* is enabled.
*
* @param {jQuery} $content
*/
function
revealFirstIp
(
$content
)
{
$content
.
find
(
'.ext-checkuser-tempaccount-reveal-ip-button'
).
first
().
trigger
(
'revealIp'
);
}
/**
* Add IP reveal functionality to contributions pages that show contributions made by a single
* temporary user. There are no user links on these pages.
*
* This is similar to initOnLoad except with the following customizations:
* - Since there are no user links, add the buttons for revealing IPs to a specified place
* within each revision line.
* - Use simpler, customized logic for enabling multi-reveal and automatically revealing users,
* since the page does not have multiple users, and all buttons are related to revisions.
*
* @param {string|*} documentRoot A Document or selector to use as the root of the
* search for elements
* @param {string} pageTitle Declare what page this is being run on.
* This is for compatibility across Special:Contributions and Special:DeletedContributions,
* as they have different guaranteed existing elements.
* @param {boolean|undefined} autoRevealStatus Whether auto-reveal mode is on. If not
* given, it will be looked up.
*/
function
enableIpRevealForContributionsPage
(
documentRoot
,
pageTitle
,
autoRevealStatus
)
{
if
(
!
documentRoot
)
{
documentRoot
=
document
;
}
// Define the class name of the element that the "Show IP" button should be appended after.
// This can't point to the element yet as it'll be the child of a container revision line.
let
revAppendAfter
;
if
(
pageTitle
===
'Contributions'
)
{
revAppendAfter
=
'.mw-diff-bytes'
;
}
else
if
(
pageTitle
===
'DeletedContributions'
)
{
revAppendAfter
=
'.mw-deletedcontribs-tools'
;
}
const
target
=
mw
.
config
.
get
(
'wgRelevantUserName'
);
const
revIds
=
[];
const
$userLinks
=
$
(
'#bodyContent'
,
documentRoot
).
find
(
'.mw-contributions-list [data-mw-revid]'
);
$userLinks
.
each
(
function
()
{
const
revId
=
getRevisionId
(
$
(
this
)
);
revIds
.
push
(
revId
);
}
);
$userLinks
.
each
(
function
()
{
const
revId
=
getRevisionId
(
$
(
this
)
);
$
(
this
).
find
(
revAppendAfter
).
after
(
()
=>
{
const
ids
=
{
targetId
:
revId
,
allIds
:
revIds
};
return
[
' '
,
$
(
'<span>'
).
addClass
(
'mw-changeslist-separator'
)
].
concat
(
makeButton
(
target
,
ids
,
undefined
,
undefined
,
documentRoot
)
);
}
);
}
);
$
(
documentRoot
).
on
(
'userRevealed'
,
(
_e
,
_userLookup
,
ips
)
=>
{
$
(
'.ext-checkuser-tempaccount-reveal-ip-button'
).
each
(
function
()
{
const
id
=
$
(
this
).
closest
(
'[data-mw-revid]'
).
data
(
'mw-revid'
);
const
ip
=
(
ips
&&
ips
[
id
]
)
?
ips
[
id
]
:
false
;
replaceButton
(
$
(
this
),
ip
,
true
);
}
);
}
);
if
(
autoRevealStatus
||
ipRevealUtils
.
getRevealedStatus
(
mw
.
config
.
get
(
'wgRelevantUserName'
)
)
)
{
// If the user has been revealed lately or auto-reveal mode is on, trigger a lookup
// from the first button
revealFirstIp
(
$
(
documentRoot
)
);
}
else
if
(
autoRevealStatus
===
undefined
)
{
// If auto-reveal status is unknown, look it up.
ipRevealUtils
.
getAutoRevealStatus
().
then
(
(
expiry
)
=>
{
if
(
expiry
)
{
revealFirstIp
(
$
(
documentRoot
)
);
}
}
);
}
}
module
.
exports
=
{
makeButton
:
makeButton
,
addIpRevealButtons
:
addIpRevealButtons
,
replaceButton
:
replaceButton
,
enableMultiReveal
:
enableMultiReveal
,
enableAutoReveal
:
enableAutoReveal
,
disableAutoReveal
:
disableAutoReveal
,
automaticallyRevealUsers
:
automaticallyRevealUsers
,
batchRevealIps
:
batchRevealIps
,
getRevisionId
:
getRevisionId
,
getLogId
:
getLogId
,
enableIpRevealForContributionsPage
:
enableIpRevealForContributionsPage
};
File Metadata
Details
Attached
Mime Type
text/x-asm
Expires
Aug 16 2026, 03:42 (12 w, 2 d ago)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
94/69/08aef31db79d5bccf28bb48dc349
Default Alt Text
ipReveal.js (27 KB)
Attached To
Mode
rMWPROD MediaWiki Production
Attached
Detach File
Event Timeline
Log In to Comment