More languages
More actions
Notification system for ConfirmAccount extension
The following is to detail how the Discord extension works, in hopes to implement that on the ConfirmAccount extension.
Discord.php
Uses a MediaWiki hook to trigger the Discord event. So we'd need to check for ConfirmAccount hooks, or worse, create one.
/**
* Called when a page is created or edited
* @see https://www.mediawiki.org/wiki/Manual:Hooks/PageSaveComplete
*/
public static function onPageSaveComplete( WikiPage $wikiPage, UserIdentity $userIdentity, string $summary, int $flags, RevisionRecord $revision, EditResult $editResult ) {
global $wgDiscordNoBots, $wgDiscordNoMinor, $wgDiscordNoNull;
$hookName = 'PageContentSaveComplete';
$user = MediaWikiServices::getInstance()->getUserFactory()->newFromUserIdentity( $userIdentity );
if ( DiscordUtils::isDisabled( $hookName, $wikiPage->getTitle()->getNamespace(), $user ) ) {
return true;
}
if ( $wgDiscordNoBots && $user->isBot() ) {
// Don't continue, this is a bot edit
return true;
}
if ( $wgDiscordNoMinor && $revision->isMinor() ) {
// Don't continue, this is a minor edit
return true;
}
if ( $wgDiscordNoNull && $editResult->isNullEdit() ) {
// Don't continue, this is a null edit
return true;
}
$isNew = $editResult->isNew();
if ( $wikiPage->getTitle()->inNamespace( NS_FILE ) && $isNew ) {
// Don't continue, it's a new file which onUploadComplete will handle instead
return true;
}
$msgKey = 'discord-edit';
if ( $isNew ) { // is a new page
$msgKey = 'discord-create';
}
$msg = wfMessage( $msgKey, DiscordUtils::createUserLinks( $user ),
DiscordUtils::createMarkdownLink( $wikiPage->getTitle(), $wikiPage->getTitle()->getFullURL( '', false, PROTO_CANONICAL ) ),
DiscordUtils::createRevisionText( $revision ),
( $summary ? ('`' . DiscordUtils::sanitiseText( DiscordUtils::truncateText( $summary ) ) . '`' ) : '' ) )->plain();
DiscordUtils::handleDiscord($hookName, $msg);
return true;
}
Utils.php
Further detailing of the "handleDiscord" method, which contains the logic for communication with Discord
/**
* Handles sending a webhook to Discord using cURL
*/
public static function handleDiscord ($hookName, $msg) {
global $wgDiscordWebhookURL, $wgDiscordEmojis, $wgDiscordUseEmojis, $wgDiscordPrependTimestamp, $wgDiscordUseFileGetContents;
if ( !$wgDiscordWebhookURL ) {
// There's nothing in here, so we won't do anything
return false;
}
$urls = [];
if ( is_array( $wgDiscordWebhookURL ) ) {
$urls = array_merge($urls, $wgDiscordWebhookURL);
} else if ( is_string($wgDiscordWebhookURL) ) {
$urls[] = $wgDiscordWebhookURL;
} else {
wfDebugLog( 'discord', 'The value of $wgDiscordWebhookURL is not valid and therefore no webhooks could be sent.' );
return false;
}
// Strip whitespace to just one space
$stripped = preg_replace('/\s+/', ' ', $msg);
if ( $wgDiscordPrependTimestamp ) {
// Add timestamp
$dateString = gmdate( wfMessage( 'discord-timestampformat' )->text() );
$stripped = $dateString . ' ' . $stripped;
}
if ( $wgDiscordUseEmojis ) {
// Add emoji
$emoji = $wgDiscordEmojis[$hookName];
$stripped = $emoji . ' ' . $stripped;
}
DeferredUpdates::addCallableUpdate( function() use ( $stripped, $urls, $wgDiscordUseFileGetContents ) {
$user_agent = 'mw-discord/1.0 (github.com/jaydenkieran)';
$json_data = [
'content' => "$stripped",
'allowed_mentions' => [
'parse' => []
]
];
$json = json_encode($json_data);
if ( $wgDiscordUseFileGetContents ) {
// They want to use file_get_contents
foreach ($urls as &$value) {
$contextOpts = [
'http' => [
'header' => 'Content-Type: application/x-www-form-urlencoded',
'method' => 'POST', // Send as a POST request
'user_agent' => $user_agent, // Add a unique user agent
'content' => $json, // Send the JSON in the POST request
'ignore_errors' => true // If the call fails, let's not do anything with it
]
];
$context = stream_context_create( $contextOpts );
$result = file_get_contents( $value, false, $context );
}
} else {
// By default, we use cURL
// Set up cURL multi handlers
$c_handlers = [];
$result = [];
$mh = curl_multi_init();
foreach ($urls as &$value) {
$c_handlers[$value] = curl_init( $value );
curl_setopt( $c_handlers[$value], CURLOPT_POST, 1 ); // Send as a POST request
curl_setopt( $c_handlers[$value], CURLOPT_POSTFIELDS, $json ); // Send the JSON in the POST request
curl_setopt( $c_handlers[$value], CURLOPT_FOLLOWLOCATION, 1 );
curl_setopt( $c_handlers[$value], CURLOPT_HEADER, 0 );
curl_setopt( $c_handlers[$value], CURLOPT_RETURNTRANSFER, 1 );
curl_setopt( $c_handlers[$value], CURLOPT_CONNECTTIMEOUT, 10 ); // Add a timeout for connecting to the site
curl_setopt( $c_handlers[$value], CURLOPT_TIMEOUT, 10 ); // Do not allow cURL to run for a long time
curl_setopt( $c_handlers[$value], CURLOPT_USERAGENT, $user_agent ); // Add a unique user agent
curl_setopt( $c_handlers[$value], CURLOPT_HTTPHEADER, array(
'Content-Type: application/json'
));
curl_multi_add_handle( $mh, $c_handlers[$value] );
}
$running = null;
do {
curl_multi_exec($mh, $running);
} while ($running);
// Remove all handlers and then close the multi handler
foreach($c_handlers as $k => $ch) {
$result[$k] = curl_multi_getcontent($ch);
wfDebugLog( 'discord', 'Result of cURL was: ' . $result[$k] );
curl_multi_remove_handle($mh, $ch);
}
curl_multi_close($mh);
}
} );
return true;
}
Module pages in English and Portuguese instances produces errors
Full backtrace:
LogicException: This ParserOutput contains no text! Backtrace: from /var/www/prolewiki/includes/parser/ParserOutput.php(382) #0 /var/www/prolewiki/extensions/Scribunto/includes/ScribuntoContentHandler.php(224): ParserOutput->getRawText() #1 /var/www/prolewiki/extensions/Scribunto/includes/ScribuntoContentHandler.php(193): MediaWiki\Extension\Scribunto\ScribuntoContentHandler->highlight() #2 /var/www/prolewiki/includes/content/ContentHandler.php(1759): MediaWiki\Extension\Scribunto\ScribuntoContentHandler->fillParserOutput() #3 /var/www/prolewiki/includes/content/Renderer/ContentRenderer.php(47): ContentHandler->getParserOutput() #4 /var/www/prolewiki/includes/Revision/RenderedRevision.php(259): MediaWiki\Content\Renderer\ContentRenderer->getParserOutput() #5 /var/www/prolewiki/includes/Revision/RenderedRevision.php(232): MediaWiki\Revision\RenderedRevision->getSlotParserOutputUncached() #6 /var/www/prolewiki/includes/Revision/RevisionRenderer.php(223): MediaWiki\Revision\RenderedRevision->getSlotParserOutput() #7 /var/www/prolewiki/includes/Revision/RevisionRenderer.php(164): MediaWiki\Revision\RevisionRenderer->combineSlotOutput() #8 [internal function]: MediaWiki\Revision\RevisionRenderer->MediaWiki\Revision\{closure}() #9 /var/www/prolewiki/includes/Revision/RenderedRevision.php(199): call_user_func() #10 /var/www/prolewiki/includes/poolcounter/PoolWorkArticleView.php(84): MediaWiki\Revision\RenderedRevision->getRevisionParserOutput() #11 /var/www/prolewiki/includes/poolcounter/PoolWorkArticleView.php(69): PoolWorkArticleView->renderRevision() #12 /var/www/prolewiki/includes/poolcounter/PoolCounterWork.php(167): PoolWorkArticleView->doWork() #13 /var/www/prolewiki/includes/page/ParserOutputAccess.php(304): PoolCounterWork->execute() #14 /var/www/prolewiki/includes/page/Article.php(746): MediaWiki\Page\ParserOutputAccess->getParserOutput() #15 /var/www/prolewiki/includes/page/Article.php(550): Article->generateContentOutput() #16 /var/www/prolewiki/includes/actions/ViewAction.php(78): Article->view() #17 /var/www/prolewiki/includes/MediaWiki.php(583): ViewAction->show() #18 /var/www/prolewiki/includes/MediaWiki.php(363): MediaWiki->performAction() #19 /var/www/prolewiki/includes/MediaWiki.php(960): MediaWiki->performRequest() #20 /var/www/prolewiki/includes/MediaWiki.php(613): MediaWiki->main() #21 /var/www/prolewiki/index.php(50): MediaWiki->run() #22 /var/www/prolewiki/index.php(46): wfIndexMain() #23 {main}
from /var/www/prolewiki/includes/parser/ParserOutput.php (382)
public function getRawText() {
if ( $this->mText === null ) {
throw new LogicException( 'This ParserOutput contains no text!' );
}
return $this->mText;
}
Further context on mText
private $mText = null;
public function __construct( $text = null, $languageLinks = [], $categoryLinks = [],
$unused = false, $titletext = ''
) {
$this->mText = $text;
$this->mLanguageLinks = $languageLinks;
$this->mCategories = $categoryLinks;
$this->mTitleText = $titletext;
}
public function setText( $text ) {
return wfSetVar( $this->mText, $text, true );
}
#0 /var/www/prolewiki/extensions/Scribunto/includes/ScribuntoContentHandler.php(224): ParserOutput->getRawText()
protected function highlight( $text, ParserOutput $parserOutput, ScribuntoEngineBase $engine ) {
global $wgScribuntoUseGeSHi;
$language = $engine->getGeSHiLanguage();
if (
$wgScribuntoUseGeSHi && $language && ExtensionRegistry::getInstance()->isLoaded( 'SyntaxHighlight' )
) {
$status = SyntaxHighlight::highlight( $text, $language, [ 'line' => true, 'linelinks' => 'L' ] );
if ( $status->isGood() ) {
// @todo replace addModuleStyles line with the appropriate call on
// SyntaxHighlight once one is created
$parserOutput->addModuleStyles( [ 'ext.pygments' ] );
$parserOutput->addModules( [ 'ext.pygments.linenumbers' ] );
$parserOutput->setText( $parserOutput->getRawText() . $status->getValue() );
return true;
}
}
return false;
}