Bläddra i källkod

Add Phabricator support

Markus-Rost 4 år sedan
förälder
incheckning
281736399b
6 ändrade filer med 156 tillägg och 16 borttagningar
  1. 4 0
      .env.example
  2. 5 1
      cmds/link.js
  3. 6 2
      cmds/wiki/general.js
  4. 1 1
      cmds/wiki/search.js
  5. 92 0
      functions/phabricator.js
  6. 48 12
      util/functions.js

+ 4 - 0
.env.example

@@ -20,5 +20,9 @@ channel="464098946894004224"
 invite="https://discord.gg/v77RTk5"
 # Link to the patreon page for the bot
 patreon="https://www.patreon.com/WikiBot"
+# API token for phabricator.wikimedia.org
+phabricator-wikimedia="<token>"
+# API token for phabricator.miraheze.org
+phabricator-miraheze="<token>"
 # Path to a log file for usage statistics
 usagelog=""

+ 5 - 1
cmds/link.js

@@ -4,6 +4,7 @@ const check_wiki = {
 	test: require('./test.js').run
 };
 const help_setup = require('../functions/helpsetup.js');
+const phabricator = require('../functions/phabricator.js');
 
 /**
  * Processes the wiki linking command.
@@ -20,7 +21,10 @@ function cmd_link(lang, msg, title, wiki, cmd = '') {
 		var spoiler = '||';
 	}
 	msg.reactEmoji('⏳').then( reaction => {
-		check_wiki.general(lang, msg, title, wiki, cmd, reaction, spoiler);
+		if ( /^phabricator\.(wikimedia|miraheze)\.org$/.test(wiki.hostname) ) {
+			return phabricator(lang, msg, wiki, new URL('/' + title, wiki), reaction, spoiler);
+		}
+		else check_wiki.general(lang, msg, title, wiki, cmd, reaction, spoiler);
 	} );
 }
 

+ 6 - 2
cmds/wiki/general.js

@@ -1,5 +1,6 @@
 const {MessageEmbed} = require('discord.js');
 const parse_page = require('../../functions/parse_page.js');
+const phabricator = require('../../functions/phabricator.js');
 const logging = require('../../util/logging.js');
 const {parse_infobox, htmlToPlain, htmlToDiscord, partialURIdecode} = require('../../util/functions.js');
 const extract_desc = require('../../util/extract_desc.js');
@@ -187,7 +188,7 @@ function gamepedia_check_wiki(lang, msg, title, wiki, cmd, reaction, spoiler = '
 				if ( reaction ) reaction.removeEmoji();
 				return;
 			}
-			if ( ( querypage.missing !== undefined && querypage.known === undefined && !( noRedirect || querypage.categoryinfo ) ) || querypage.invalid !== undefined ) return got.get( wiki + 'api.php?action=query&prop=categoryinfo|info|pageprops|pageimages|extracts&piprop=original|name&ppprop=description|displaytitle|page_image_free|infoboxes&explaintext=true&exsectionformat=raw&exlimit=1&generator=search&gsrnamespace=4|12|14|' + querypage.ns + '|' + Object.values(body.query.namespaces).filter( ns => ns.content !== undefined ).map( ns => ns.id ).join('|') + '&gsrlimit=1&gsrsearch=' + encodeURIComponent( title ) + '&format=json' ).then( srresponse => {
+			if ( ( querypage.missing !== undefined && querypage.known === undefined && !( noRedirect || querypage.categoryinfo ) ) || querypage.invalid !== undefined ) return got.get( wiki + 'api.php?action=query&prop=categoryinfo|info|pageprops|pageimages|extracts&piprop=original|name&ppprop=description|displaytitle|page_image_free|infoboxes&explaintext=true&exsectionformat=raw&exlimit=1&generator=search&gsrnamespace=4|12|14|' + ( querypage.ns >= 0 ? querypage.ns + '|' : '' ) + Object.values(body.query.namespaces).filter( ns => ns.content !== undefined ).map( ns => ns.id ).join('|') + '&gsrlimit=1&gsrsearch=' + encodeURIComponent( title ) + '&format=json' ).then( srresponse => {
 				logging(wiki, msg.guild?.id, 'general', 'search');
 				var srbody = srresponse.body;
 				if ( srbody && srbody.warnings ) log_warn(srbody.warnings);
@@ -381,13 +382,16 @@ function gamepedia_check_wiki(lang, msg, title, wiki, cmd, reaction, spoiler = '
 				console.log( '- Aborted, paused.' );
 				return;
 			}
-			logging(wiki, msg.guild?.id, 'interwiki');
 			var iw = new URL(body.query.interwiki[0].url.replace( /\\/g, '%5C' ).replace( /@(here|everyone)/g, '%40$1' ), wiki);
 			querystring.forEach( (value, name) => {
 				iw.searchParams.append(name, value);
 			} );
 			if ( fragment ) iw.hash = Wiki.toSection(fragment);
 			else fragment = iw.hash.substring(1);
+			if ( /^phabricator\.(wikimedia|miraheze)\.org$/.test(iw.hostname) ) {
+				return phabricator(lang, msg, wiki, iw, reaction, spoiler);
+			}
+			logging(wiki, msg.guild?.id, 'interwiki');
 			var maxselfcall = interwikiLimit[( patreons[msg.guild?.id] ? 'patreon' : 'default' )];
 			if ( selfcall < maxselfcall && ['http:','https:'].includes( iw.protocol ) ) {
 				selfcall++;

+ 1 - 1
cmds/wiki/search.js

@@ -25,7 +25,7 @@ function gamepedia_search(lang, msg, searchterm, wiki, query, reaction, spoiler)
 	var querypage = ( Object.values(( query.pages || {} ))?.[0] || {title:'',ns:0,invalid:''} );
 	var description = [];
 	var limit = searchLimit[( patreons[msg.guild?.id] ? 'patreon' : 'default' )];
-	got.get( wiki + 'api.php?action=query&titles=Special:Search&list=search&srinfo=totalhits&srprop=redirecttitle|sectiontitle&srnamespace=4|12|14|' + querypage.ns + '|' + Object.values(query.namespaces).filter( ns => ns.content !== undefined ).map( ns => ns.id ).join('|') + '&srlimit=' + limit + '&srsearch=' + encodeURIComponent( ( searchterm || ' ' ) ) + '&format=json' ).then( response => {
+	got.get( wiki + 'api.php?action=query&titles=Special:Search&list=search&srinfo=totalhits&srprop=redirecttitle|sectiontitle&srnamespace=4|12|14|' + ( querypage.ns >= 0 ? querypage.ns + '|' : '' ) + Object.values(query.namespaces).filter( ns => ns.content !== undefined ).map( ns => ns.id ).join('|') + '&srlimit=' + limit + '&srsearch=' + encodeURIComponent( ( searchterm || ' ' ) ) + '&format=json' ).then( response => {
 		var body = response.body;
 		if ( body && body.warnings ) log_warn(body.warnings);
 		if ( response.statusCode !== 200 || !body || !body.query || !body.query.search || body.batchcomplete === undefined ) {

+ 92 - 0
functions/phabricator.js

@@ -0,0 +1,92 @@
+const {MessageEmbed} = require('discord.js');
+const logging = require('../util/logging.js');
+
+/**
+ * Sends a Phabricator task.
+ * @param {import('../util/i18n.js')} lang - The user language.
+ * @param {import('discord.js').Message} msg - The Discord message.
+ * @param {import('../util/wiki.js')} wiki - The wiki.
+ * @param {URL} link - The link.
+ * @param {import('discord.js').MessageReaction} reaction - The reaction on the message.
+ * @param {String} [spoiler] - If the response is in a spoiler.
+ */
+function phabricator_task(lang, msg, wiki, link, reaction, spoiler = '') {
+	var regex = /^(?:https?:)?\/\/phabricator\.(wikimedia|miraheze)\.org\/T(\d+)(?:#|$)/.exec(link.href);
+	if ( !regex || !process.env['phabricator-' + regex[1]] ) {
+		logging(wiki, msg.guild?.id, 'interwiki');
+		msg.sendChannel( spoiler + ' ' + link + ' ' + spoiler );
+		if ( reaction ) reaction.removeEmoji();
+		return;
+	}
+	logging(link.origin, msg.guild?.id, 'phabricator', regex[1]);
+	got.get( 'https://phabricator.' + regex[1] + '.org/api/maniphest.search?api.token=' + process.env['phabricator-' + regex[1]] + '&constraints[ids][0]=' + regex[2] ).then( response => {
+		var body = response.body;
+		if ( response.statusCode !== 200 || !body?.result?.data || body.error_code ) {
+			console.log( '- ' + response.statusCode + ': Error while getting the Phabricator task: ' + body?.error_info );
+			msg.sendChannelError( spoiler + ' ' + link + ' ' + spoiler );
+
+			if ( reaction ) reaction.removeEmoji();
+			return;
+		}
+		if ( !body.result.data.length ) {
+			msg.sendChannel( spoiler + ' ' + link + ' ' + spoiler );
+
+			if ( reaction ) reaction.removeEmoji();
+			return;
+		}
+		var task = body.result.data[0];
+		if ( !msg.showEmbed() ) {
+			var status = '**' + task.fields.status.name + ':** ' + task.fields.name.escapeFormatting();
+			msg.sendChannel( spoiler + status + '\n<' + link + '>' + spoiler );
+			
+			if ( reaction ) reaction.removeEmoji();
+			return;
+		}
+		var embed = new MessageEmbed().setAuthor( 'Phabricator' ).setTitle( task.fields.name.escapeFormatting() ).setURL( link ).addField( 'Status', task.fields.status.name, true ).addField( 'Priority', task.fields.priority.name, true );
+		if ( task.fields.subtype !== 'default' ) embed.addField( 'Subtype', task.fields.subtype, true );;
+		var description = task.fields.description.raw.replace( /```lang=/g, '```' );
+		if ( description.length > 2000 ) description = description.substring(0, 2000) + '\u2026';
+		embed.setDescription( parse_links( description ) );
+
+		if ( /^#\d+$/.test( link.hash ) ) return got.get( 'https://phabricator.' + regex[1] + '.org/api/transaction.search?api.token=' + process.env['phabricator-' + regex[1]] + '&objectIdentifier=' + task.phid ).then( response => {
+			var body = response.body;
+			if ( response.statusCode !== 200 || !body?.result?.data || body.error_code ) {
+				console.log( '- ' + response.statusCode + ': Error while getting the task transactions: ' + body?.error_info );
+				return;
+			}
+			var comment = body.result.data.find( transaction => '#' + transaction.id === link.hash );
+			if ( comment.type === 'comment' ) {
+				var content = comment.comments[0].content.raw;
+				if ( content.length > 1000 ) content = content.substring(0, 1000) + '\u2026';
+				embed.spliceFields( 0, 0, {name: 'Comment', value: parse_links( content )} );
+			}
+		}, error => {
+			console.log( '- Error while getting the task transactions: ' + error );
+		} ).finally( () => {
+			msg.sendChannel( spoiler + '<' + link + '>' + spoiler, {embed} );
+			
+			if ( reaction ) reaction.removeEmoji();
+		} );
+
+		msg.sendChannel( spoiler + '<' + link + '>' + spoiler, {embed} );
+		
+		if ( reaction ) reaction.removeEmoji();
+	}, error => {
+		console.log( '- Error while getting the Phabricator task: ' + error );
+		msg.sendChannelError( spoiler + ' ' + link + ' ' + spoiler );
+
+		if ( reaction ) reaction.removeEmoji();
+	} );
+}
+
+/**
+ * Parse Phabricator links.
+ * @param {String} text - The text to parse.
+ * @returns {String}
+ */
+function parse_links(text) {
+	text = text.replace( /\[\[ *(.+?) *\| *(.+?) *\]\]/g, '[$2]($1)' );
+	return text;
+}
+
+module.exports = phabricator_task;

+ 48 - 12
util/functions.js

@@ -183,12 +183,30 @@ function htmlToPlain(html) {
 				if ( !text.endsWith( '\n' ) ) text += '\n';
 				if ( listlevel > -1 ) text += '\u200b '.repeat(4 * (listlevel + 1));
 			}
-			if ( tagname === 'h1' ) text += '***__';
-			if ( tagname === 'h2' ) text += '**__';
-			if ( tagname === 'h3' ) text += '**';
-			if ( tagname === 'h4' ) text += '__';
-			if ( tagname === 'h5' ) text += '*';
-			if ( tagname === 'h6' ) text += '';
+			if ( tagname === 'h1' ) {
+				if ( !text.endsWith( '\n' ) ) text += '\n';
+				text += '***__';
+			}
+			if ( tagname === 'h2' ) {
+				if ( !text.endsWith( '\n' ) ) text += '\n';
+				text += '**__';
+			}
+			if ( tagname === 'h3' ) {
+				if ( !text.endsWith( '\n' ) ) text += '\n';
+				text += '**';
+			}
+			if ( tagname === 'h4' ) {
+				if ( !text.endsWith( '\n' ) ) text += '\n';
+				text += '__';
+			}
+			if ( tagname === 'h5' ) {
+				if ( !text.endsWith( '\n' ) ) text += '\n';
+				text += '*';
+			}
+			if ( tagname === 'h6' ) {
+				if ( !text.endsWith( '\n' ) ) text += '\n';
+				text += '';
+			}
 		},
 		ontext: (htmltext) => {
 			if ( !reference ) {
@@ -266,12 +284,30 @@ function htmlToDiscord(html, serverpath = '', ...escapeArgs) {
 				if ( !text.endsWith( '\n' ) ) text += '\n';
 				if ( listlevel > -1 ) text += '\u200b '.repeat(4 * (listlevel + 1));
 			}
-			if ( tagname === 'h1' ) text += '***__';
-			if ( tagname === 'h2' ) text += '**__';
-			if ( tagname === 'h3' ) text += '**';
-			if ( tagname === 'h4' ) text += '__';
-			if ( tagname === 'h5' ) text += '*';
-			if ( tagname === 'h6' ) text += '';
+			if ( tagname === 'h1' ) {
+				if ( !text.endsWith( '\n' ) ) text += '\n';
+				text += '***__';
+			}
+			if ( tagname === 'h2' ) {
+				if ( !text.endsWith( '\n' ) ) text += '\n';
+				text += '**__';
+			}
+			if ( tagname === 'h3' ) {
+				if ( !text.endsWith( '\n' ) ) text += '\n';
+				text += '**';
+			}
+			if ( tagname === 'h4' ) {
+				if ( !text.endsWith( '\n' ) ) text += '\n';
+				text += '__';
+			}
+			if ( tagname === 'h5' ) {
+				if ( !text.endsWith( '\n' ) ) text += '\n';
+				text += '*';
+			}
+			if ( tagname === 'h6' ) {
+				if ( !text.endsWith( '\n' ) ) text += '\n';
+				text += '';
+			}
 			if ( tagname === 'a' && attribs.href && attribs.class !== 'new' && /^(?:(?:https?:)?\/)?\//.test(attribs.href) ) {
 				href = new URL(attribs.href, serverpath).href;
 				text += '[';