1
0
Эх сурвалжийг харах

fix multiple minor issues

Markus-Rost 4 жил өмнө
parent
commit
102f81cabe

+ 1 - 1
RcGcDb

@@ -1 +1 @@
-Subproject commit 23284328ed59f5a9638f9bdd41ff391fdece7b91
+Subproject commit 5eb3057d6d73f585bc62fecf6a530188fda4d12f

+ 1 - 18
cmds/verify.js

@@ -1,6 +1,6 @@
-const htmlparser = require('htmlparser2');
 const cheerio = require('cheerio');
 const {MessageEmbed} = require('discord.js');
+const {htmlToPlain} = require('../util/functions.js');
 const toTitle = require('../util/wiki.js').toTitle;
 var db = require('../util/database.js');
 
@@ -379,23 +379,6 @@ function cmd_verify(lang, msg, args, line, wiki, old_username = '') {
 	} );
 }
 
-/**
- * Change HTML text to plain text.
- * @param {String} html - The text in HTML.
- * @returns {String}
- */
-function htmlToPlain(html) {
-	var text = '';
-	var parser = new htmlparser.Parser( {
-		ontext: (htmltext) => {
-			text += htmltext.escapeFormatting();
-		}
-	}, {decodeEntities:true} );
-	parser.write( html );
-	parser.end();
-	return text;
-};
-
 module.exports = {
 	name: 'verify',
 	everyone: true,

+ 1 - 1
cmds/wiki/fandom.js

@@ -186,7 +186,7 @@ function fandom_check_wiki(lang, msg, title, wiki, cmd, reaction, spoiler = '',
 												thumbnail = attribs.content;
 											}
 										}
-									}, {decodeEntities:true} );
+									} );
 									parser.write( descbody );
 									parser.end();
 									embed.setThumbnail( thumbnail );

+ 13 - 194
cmds/wiki/fandom/diff.js

@@ -1,7 +1,7 @@
-const htmlparser = require('htmlparser2');
 const {MessageEmbed} = require('discord.js');
 const {timeoptions} = require('../../../util/default.json');
-const {toFormatting} = require('../../../util/functions.js');
+const {toFormatting, htmlToPlain} = require('../../../util/functions.js');
+const diffParser = require('../../../util/edit_diff.js');
 
 /**
  * Processes a Fandom edit.
@@ -82,85 +82,9 @@ function fandom_diff(lang, msg, args, wiki, reaction, spoiler, embed) {
 								argids = [ids.to, ids.from];
 								var compare = ['', ''];
 								if ( ids['*'] !== undefined ) {
-									var more = '\n__' + lang.get('diff.info.more') + '__';
-									var current_tag = '';
-									var small_prev_ins = '';
-									var small_prev_del = '';
-									var ins_length = more.length;
-									var del_length = more.length;
-									var added = false;
-									var parser = new htmlparser.Parser( {
-										onopentag: (tagname, attribs) => {
-											if ( tagname === 'ins' || tagname == 'del' ) {
-												current_tag = tagname;
-											}
-											if ( tagname === 'td' && attribs.class === 'diff-addedline' ) {
-												current_tag = tagname+'a';
-											}
-											if ( tagname === 'td' && attribs.class === 'diff-deletedline' ) {
-												current_tag = tagname+"d";
-											}
-											if ( tagname === 'td' && attribs.class === 'diff-marker' ) {
-												added = true;
-											}
-										},
-										ontext: (htmltext) => {
-											if ( current_tag === 'ins' && ins_length <= 1000 ) {
-												ins_length += ( '**' + htmltext.escapeFormatting() + '**' ).length;
-												if ( ins_length <= 1000 ) small_prev_ins += '**' + htmltext.escapeFormatting() + '**';
-												else small_prev_ins += more;
-											}
-											if ( current_tag === 'del' && del_length <= 1000 ) {
-												del_length += ( '~~' + htmltext.escapeFormatting() + '~~' ).length;
-												if ( del_length <= 1000 ) small_prev_del += '~~' + htmltext.escapeFormatting() + '~~';
-												else small_prev_del += more;
-											}
-											if ( ( current_tag === 'afterins' || current_tag === 'tda') && ins_length <= 1000 ) {
-												ins_length += htmltext.escapeFormatting().length;
-												if ( ins_length <= 1000 ) small_prev_ins += htmltext.escapeFormatting();
-												else small_prev_ins += more;
-											}
-											if ( ( current_tag === 'afterdel' || current_tag === 'tdd') && del_length <= 1000 ) {
-												del_length += htmltext.escapeFormatting().length;
-												if ( del_length <= 1000 ) small_prev_del += htmltext.escapeFormatting();
-												else small_prev_del += more;
-											}
-											if ( added ) {
-												if ( htmltext === '+' && ins_length <= 1000 ) {
-													ins_length++;
-													if ( ins_length <= 1000 ) small_prev_ins += '\n';
-													else small_prev_ins += more;
-												}
-												if ( htmltext === '−' && del_length <= 1000 ) {
-													del_length++;
-													if ( del_length <= 1000 ) small_prev_del += '\n';
-													else small_prev_del += more;
-												}
-												added = false;
-											}
-										},
-										onclosetag: (tagname) => {
-											if ( tagname === 'ins' ) {
-												current_tag = 'afterins';
-											} else if ( tagname === 'del' ) {
-												current_tag = 'afterdel';
-											} else {
-												current_tag = '';
-											}
-										}
-									}, {decodeEntities:true} );
-									parser.write( ids['*'] );
-									parser.end();
-									if ( small_prev_del.length ) {
-										if ( small_prev_del.replace( /\~\~/g, '' ).trim().length ) {
-											compare[0] = small_prev_del.replace( /\~\~\~\~/g, '' );
-										} else compare[0] = '__' + lang.get('diff.info.whitespace') + '__';
-									}
-									if ( small_prev_ins.length ) {
-										if ( small_prev_ins.replace( /\*\*/g, '' ).trim().length ) {
-											compare[1] = small_prev_ins.replace( /\*\*\*\*/g, '' );
-										} else compare[1] = '__' + lang.get('diff.info.whitespace') + '__';
-									}
+									let more = '\n__' + lang.get('diff.info.more') + '__';
+									let whitespace = '__' + lang.get('diff.info.whitespace') + '__';
+									compare = diffParser( ids['*'], more, whitespace );
 								}
 							}
 							fandom_diff_send(lang, msg, argids, wiki, reaction, spoiler, compare);
@@ -258,27 +182,10 @@ function fandom_diff_send(lang, msg, args, wiki, reaction, spoiler, compare) {
 					}
 					if ( editor[1] === lang.get('diff.hidden') ) editorlink = editor[1];
 					var embed = new MessageEmbed().setAuthor( body.query.general.sitename ).setTitle( ( title + '?diff=' + diff + '&oldid=' + oldid ).escapeFormatting() ).setURL( pagelink ).addField( editor[0], editorlink, true ).addField( size[0], size[1], true ).addField( comment[0], comment[1] ).setFooter( timestamp[1] );
-					if ( tags ) {
-						var taglink = '';
-						var tagtext = '';
-						var tagparser = new htmlparser.Parser( {
-							onopentag: (tagname, attribs) => {
-								if ( tagname === 'a' ) taglink = attribs.href;
-							},
-							ontext: (htmltext) => {
-								if ( taglink ) tagtext += '[' + htmltext.escapeFormatting() + '](' + taglink + ')'
-								else tagtext += htmltext.escapeFormatting();
-							},
-							onclosetag: (tagname) => {
-								if ( tagname === 'a' ) taglink = '';
-							}
-						}, {decodeEntities:true} );
-						tagparser.write( tags[1] );
-						tagparser.end();
-						embed.addField( tags[0], tagtext );
-					}
+					if ( tags ) embed.addField( tags[0], htmlToPlain(tags[1]) );
 					
 					var more = '\n__' + lang.get('diff.info.more') + '__';
+					var whitespace = '__' + lang.get('diff.info.whitespace') + '__';
 					if ( !compare && oldid ) got.get( wiki + 'api.php?action=query&prop=revisions&rvprop=&revids=' + oldid + '&rvdiffto=' + diff + '&format=json' ).then( cpresponse => {
 						var cpbody = cpresponse.body;
 						if ( cpbody && cpbody.warnings ) log_warn(cpbody.warnings);
@@ -288,83 +195,12 @@ function fandom_diff_send(lang, msg, args, wiki, reaction, spoiler, compare) {
 						else {
 							var revision = Object.values(cpbody.query.pages)[0].revisions[0];
 							if ( revision.texthidden === undefined && revision.diff && revision.diff['*'] !== undefined ) {
-								var current_tag = '';
-								var small_prev_ins = '';
-								var small_prev_del = '';
-								var ins_length = more.length;
-								var del_length = more.length;
-								var added = false;
-								var parser = new htmlparser.Parser( {
-									onopentag: (tagname, attribs) => {
-										if ( tagname === 'ins' || tagname == 'del' ) {
-											current_tag = tagname;
-										}
-										if ( tagname === 'td' && attribs.class === 'diff-addedline' ) {
-											current_tag = tagname+'a';
-										}
-										if ( tagname === 'td' && attribs.class === 'diff-deletedline' ) {
-											current_tag = tagname+"d";
-										}
-										if ( tagname === 'td' && attribs.class === 'diff-marker' ) {
-											added = true;
-										}
-									},
-									ontext: (htmltext) => {
-										if ( current_tag === 'ins' && ins_length <= 1000 ) {
-											ins_length += ( '**' + htmltext.escapeFormatting() + '**' ).length;
-											if ( ins_length <= 1000 ) small_prev_ins += '**' + htmltext.escapeFormatting() + '**';
-											else small_prev_ins += more;
-										}
-										if ( current_tag === 'del' && del_length <= 1000 ) {
-											del_length += ( '~~' + htmltext.escapeFormatting() + '~~' ).length;
-											if ( del_length <= 1000 ) small_prev_del += '~~' + htmltext.escapeFormatting() + '~~';
-											else small_prev_del += more;
-										}
-										if ( ( current_tag === 'afterins' || current_tag === 'tda') && ins_length <= 1000 ) {
-											ins_length += htmltext.escapeFormatting().length;
-											if ( ins_length <= 1000 ) small_prev_ins += htmltext.escapeFormatting();
-											else small_prev_ins += more;
-										}
-										if ( ( current_tag === 'afterdel' || current_tag === 'tdd') && del_length <= 1000 ) {
-											del_length += htmltext.escapeFormatting().length;
-											if ( del_length <= 1000 ) small_prev_del += htmltext.escapeFormatting();
-											else small_prev_del += more;
-										}
-										if ( added ) {
-											if ( htmltext === '+' && ins_length <= 1000 ) {
-												ins_length++;
-												if ( ins_length <= 1000 ) small_prev_ins += '\n';
-												else small_prev_ins += more;
-											}
-											if ( htmltext === '−' && del_length <= 1000 ) {
-												del_length++;
-												if ( del_length <= 1000 ) small_prev_del += '\n';
-												else small_prev_del += more;
-											}
-											added = false;
-										}
-									},
-									onclosetag: (tagname) => {
-										if ( tagname === 'ins' ) {
-											current_tag = 'afterins';
-										} else if ( tagname === 'del' ) {
-											current_tag = 'afterdel';
-										} else {
-											current_tag = '';
-										}
-									}
-								}, {decodeEntities:true} );
-								parser.write( revision.diff['*'] );
-								parser.end();
-								if ( small_prev_del.length ) {
-									if ( small_prev_del.replace( /\~\~/g, '' ).trim().length ) {
-										embed.addField( lang.get('diff.info.removed'), small_prev_del.replace( /\~\~\~\~/g, '' ), true );
-									} else embed.addField( lang.get('diff.info.removed'), '__' + lang.get('diff.info.whitespace') + '__', true );
+								let edit_diff = diffParser( revision.diff['*'], more, whitespace )
+								if ( edit_diff[0].length ) {
+									embed.addField( lang.get('diff.info.removed'), edit_diff[0], true );
 								}
-								if ( small_prev_ins.length ) {
-									if ( small_prev_ins.replace( /\*\*/g, '' ).trim().length ) {
-										embed.addField( lang.get('diff.info.added'), small_prev_ins.replace( /\*\*\*\*/g, '' ), true );
-									} else embed.addField( lang.get('diff.info.added'), '__' + lang.get('diff.info.whitespace') + '__', true );
+								if ( edit_diff[1].length ) {
+									embed.addField( lang.get('diff.info.added'), edit_diff[1], true );
 								}
 							}
 							else if ( revision.texthidden !== undefined ) {
@@ -395,7 +231,7 @@ function fandom_diff_send(lang, msg, args, wiki, reaction, spoiler, compare) {
 									content = '**' + content.substring(0, content.lastIndexOf('\n')) + '**' + more;
 								}
 								embed.addField( lang.get('diff.info.added'), content, true );
-							} else embed.addField( lang.get('diff.info.added'), '__' + lang.get('diff.info.whitespace') + '__', true );
+							} else embed.addField( lang.get('diff.info.added'), whitespace, true );
 						}
 						
 						msg.sendChannel( spoiler + text + spoiler, {embed} );
@@ -433,23 +269,6 @@ function fandom_diff_send(lang, msg, args, wiki, reaction, spoiler, compare) {
 	} );
 }
 
-/**
- * Change HTML text to plain text.
- * @param {String} html - The text in HTML.
- * @returns {String}
- */
-function htmlToPlain(html) {
-	var text = '';
-	var parser = new htmlparser.Parser( {
-		ontext: (htmltext) => {
-			text += htmltext.escapeFormatting();
-		}
-	}, {decodeEntities:true} );
-	parser.write( html );
-	parser.end();
-	return text;
-};
-
 module.exports = {
 	name: 'diff',
 	run: fandom_diff

+ 0 - 1
cmds/wiki/fandom/overview.js

@@ -1,4 +1,3 @@
-const htmlparser = require('htmlparser2');
 const {MessageEmbed} = require('discord.js');
 const gamepedia_overview = require('../gamepedia/overview.js').run;
 const {timeoptions} = require('../../../util/default.json');

+ 1 - 1
cmds/wiki/fandom/random.js

@@ -60,7 +60,7 @@ function fandom_random(lang, msg, wiki, reaction, spoiler) {
 								thumbnail = attribs.content;
 							}
 						}
-					}, {decodeEntities:true} );
+					} );
 					parser.write( descbody );
 					parser.end();
 					embed.setThumbnail( thumbnail );

+ 3 - 20
cmds/wiki/fandom/user.js

@@ -2,7 +2,7 @@ const htmlparser = require('htmlparser2');
 const {MessageEmbed} = require('discord.js');
 const global_block = require('../../../functions/global_block.js');
 const {timeoptions, usergroups} = require('../../../util/default.json');
-const {toMarkdown, toPlaintext} = require('../../../util/functions.js');
+const {toMarkdown, toPlaintext, htmlToPlain} = require('../../../util/functions.js');
 
 /**
  * Processes a Fandom user.
@@ -52,7 +52,7 @@ function fandom_user(lang, msg, namespace, username, wiki, querystring, fragment
 											thumbnail = attribs.content;
 										}
 									}
-								}, {decodeEntities:true} );
+								} );
 								parser.write( descbody );
 								parser.end();
 								embed.setThumbnail( thumbnail );
@@ -212,7 +212,7 @@ function fandom_user(lang, msg, namespace, username, wiki, querystring, fragment
 											thumbnail = attribs.content;
 										}
 									}
-								}, {decodeEntities:true} );
+								} );
 								parser.write( descbody );
 								parser.end();
 								embed.setThumbnail( thumbnail );
@@ -363,23 +363,6 @@ function fandom_user(lang, msg, namespace, username, wiki, querystring, fragment
 	}
 }
 
-/**
- * Change HTML text to plain text.
- * @param {String} html - The text in HTML.
- * @returns {String}
- */
-function htmlToPlain(html) {
-	var text = '';
-	var parser = new htmlparser.Parser( {
-		ontext: (htmltext) => {
-			text += htmltext.escapeFormatting();
-		}
-	}, {decodeEntities:true} );
-	parser.write( html );
-	parser.end();
-	return text;
-};
-
 module.exports = {
 	name: 'user',
 	run: fandom_user

+ 1 - 67
cmds/wiki/gamepedia.js

@@ -1,6 +1,6 @@
-const htmlparser = require('htmlparser2');
 const {MessageEmbed} = require('discord.js');
 const parse_page = require('../../functions/parse_page.js');
+const {htmlToPlain, htmlToDiscord} = require('../../util/functions.js');
 const extract_desc = require('../../util/extract_desc.js');
 const {limit: {interwiki: interwikiLimit}, wikiProjects} = require('../../util/default.json');
 const Wiki = require('../../util/wiki.js');
@@ -416,70 +416,4 @@ function gamepedia_check_wiki(lang, msg, title, wiki, cmd, reaction, spoiler = '
 	} );
 }
 
-/**
- * Change HTML text to plain text.
- * @param {String} html - The text in HTML.
- * @returns {String}
- */
-function htmlToPlain(html) {
-	var text = '';
-	var parser = new htmlparser.Parser( {
-		ontext: (htmltext) => {
-			text += htmltext.escapeFormatting();
-		}
-	}, {decodeEntities:true} );
-	parser.write( html );
-	parser.end();
-	return text;
-};
-
-/**
- * Change HTML text to markdown text.
- * @param {String} html - The text in HTML.
- * @returns {String}
- */
-function htmlToDiscord(html) {
-	var text = '';
-	var parser = new htmlparser.Parser( {
-		onopentag: (tagname, attribs) => {
-			switch (tagname) {
-				case 'b':
-					text += '**';
-					break;
-				case 'i':
-					text += '*';
-					break;
-				case 's':
-					text += '~~';
-					break;
-				case 'u':
-					text += '__';
-					break;
-			}
-		},
-		ontext: (htmltext) => {
-			text += htmltext.escapeFormatting();
-		},
-		onclosetag: (tagname) => {
-			switch (tagname) {
-				case 'b':
-					text += '**';
-					break;
-				case 'i':
-					text += '*';
-					break;
-				case 's':
-					text += '~~';
-					break;
-				case 'u':
-					text += '__';
-					break;
-			}
-		}
-	}, {decodeEntities:true} );
-	parser.write( html );
-	parser.end();
-	return text;
-};
-
 module.exports = gamepedia_check_wiki;

+ 13 - 194
cmds/wiki/gamepedia/diff.js

@@ -1,7 +1,7 @@
-const htmlparser = require('htmlparser2');
 const {MessageEmbed} = require('discord.js');
 const {timeoptions} = require('../../../util/default.json');
-const {toFormatting} = require('../../../util/functions.js');
+const {toFormatting, htmlToPlain} = require('../../../util/functions.js');
+const diffParser = require('../../../util/edit_diff.js');
 
 /**
  * Processes a Gamepedia edit.
@@ -103,85 +103,9 @@ function gamepedia_diff(lang, msg, args, wiki, reaction, spoiler, embed) {
 							argids = [ids.torevid, ids.fromrevid];
 							var compare = ['', ''];
 							if ( ids.fromtexthidden === undefined && ids.totexthidden === undefined && ids['*'] !== undefined ) {
-								var more = '\n__' + lang.get('diff.info.more') + '__';
-								var current_tag = '';
-								var small_prev_ins = '';
-								var small_prev_del = '';
-								var ins_length = more.length;
-								var del_length = more.length;
-								var added = false;
-								var parser = new htmlparser.Parser( {
-									onopentag: (tagname, attribs) => {
-										if ( tagname === 'ins' || tagname == 'del' ) {
-											current_tag = tagname;
-										}
-										if ( tagname === 'td' && attribs.class === 'diff-addedline' ) {
-											current_tag = tagname+'a';
-										}
-										if ( tagname === 'td' && attribs.class === 'diff-deletedline' ) {
-											current_tag = tagname+"d";
-										}
-										if ( tagname === 'td' && attribs.class === 'diff-marker' ) {
-											added = true;
-										}
-									},
-									ontext: (htmltext) => {
-										if ( current_tag === 'ins' && ins_length <= 1000 ) {
-											ins_length += ( '**' + htmltext.escapeFormatting() + '**' ).length;
-											if ( ins_length <= 1000 ) small_prev_ins += '**' + htmltext.escapeFormatting() + '**';
-											else small_prev_ins += more;
-										}
-										if ( current_tag === 'del' && del_length <= 1000 ) {
-											del_length += ( '~~' + htmltext.escapeFormatting() + '~~' ).length;
-											if ( del_length <= 1000 ) small_prev_del += '~~' + htmltext.escapeFormatting() + '~~';
-											else small_prev_del += more;
-										}
-										if ( ( current_tag === 'afterins' || current_tag === 'tda') && ins_length <= 1000 ) {
-											ins_length += htmltext.escapeFormatting().length;
-											if ( ins_length <= 1000 ) small_prev_ins += htmltext.escapeFormatting();
-											else small_prev_ins += more;
-										}
-										if ( ( current_tag === 'afterdel' || current_tag === 'tdd') && del_length <= 1000 ) {
-											del_length += htmltext.escapeFormatting().length;
-											if ( del_length <= 1000 ) small_prev_del += htmltext.escapeFormatting();
-											else small_prev_del += more;
-										}
-										if ( added ) {
-											if ( htmltext === '+' && ins_length <= 1000 ) {
-												ins_length++;
-												if ( ins_length <= 1000 ) small_prev_ins += '\n';
-												else small_prev_ins += more;
-											}
-											if ( htmltext === '−' && del_length <= 1000 ) {
-												del_length++;
-												if ( del_length <= 1000 ) small_prev_del += '\n';
-												else small_prev_del += more;
-											}
-											added = false;
-										}
-									},
-									onclosetag: (tagname) => {
-										if ( tagname === 'ins' ) {
-											current_tag = 'afterins';
-										} else if ( tagname === 'del' ) {
-											current_tag = 'afterdel';
-										} else {
-											current_tag = '';
-										}
-									}
-								}, {decodeEntities:true} );
-								parser.write( ids['*'] );
-								parser.end();
-								if ( small_prev_del.length ) {
-									if ( small_prev_del.replace( /\~\~/g, '' ).trim().length ) {
-										compare[0] = small_prev_del.replace( /\~\~\~\~/g, '' );
-									} else compare[0] = '__' + lang.get('diff.info.whitespace') + '__';
-								}
-								if ( small_prev_ins.length ) {
-									if ( small_prev_ins.replace( /\*\*/g, '' ).trim().length ) {
-										compare[1] = small_prev_ins.replace( /\*\*\*\*/g, '' );
-									} else compare[1] = '__' + lang.get('diff.info.whitespace') + '__';
-								}
+								let more = '\n__' + lang.get('diff.info.more') + '__';
+								let whitespace = '__' + lang.get('diff.info.whitespace') + '__';
+								compare = diffParser( ids['*'], more, whitespace );
 							}
 							else if ( ids.fromtexthidden !== undefined ) compare[0] = '__' + lang.get('diff.hidden') + '__';
 							else if ( ids.totexthidden !== undefined ) compare[1] = '__' + lang.get('diff.hidden') + '__';
@@ -271,27 +195,10 @@ function gamepedia_diff_send(lang, msg, args, wiki, reaction, spoiler, compare)
 					}
 					if ( editor[1] === lang.get('diff.hidden') ) editorlink = editor[1];
 					var embed = new MessageEmbed().setAuthor( body.query.general.sitename ).setTitle( ( title + '?diff=' + diff + '&oldid=' + oldid ).escapeFormatting() ).setURL( pagelink ).addField( editor[0], editorlink, true ).addField( size[0], size[1], true ).addField( comment[0], comment[1] ).setFooter( timestamp[1] );
-					if ( tags ) {
-						var taglink = '';
-						var tagtext = '';
-						var tagparser = new htmlparser.Parser( {
-							onopentag: (tagname, attribs) => {
-								if ( tagname === 'a' ) taglink = attribs.href;
-							},
-							ontext: (htmltext) => {
-								if ( taglink ) tagtext += '[' + htmltext.escapeFormatting() + '](' + taglink + ')'
-								else tagtext += htmltext.escapeFormatting();
-							},
-							onclosetag: (tagname) => {
-								if ( tagname === 'a' ) taglink = '';
-							}
-						}, {decodeEntities:true} );
-						tagparser.write( tags[1] );
-						tagparser.end();
-						embed.addField( tags[0], tagtext );
-					}
+					if ( tags ) embed.addField( tags[0], htmlToPlain(tags[1]) );
 					
 					var more = '\n__' + lang.get('diff.info.more') + '__';
+					var whitespace = '__' + lang.get('diff.info.whitespace') + '__';
 					if ( !compare && oldid ) got.get( wiki + 'api.php?action=compare&prop=diff&fromrev=' + oldid + '&torev=' + diff + '&format=json' ).then( cpresponse => {
 						var cpbody = cpresponse.body;
 						if ( cpbody && cpbody.warnings ) log_warn(cpbody.warnings);
@@ -312,83 +219,12 @@ function gamepedia_diff_send(lang, msg, args, wiki, reaction, spoiler, compare)
 							if ( !noerror ) console.log( '- ' + cpresponse.statusCode + ': Error while getting the diff: ' + ( cpbody && cpbody.error && cpbody.error.info ) );
 						}
 						else if ( cpbody.compare.fromtexthidden === undefined && cpbody.compare.totexthidden === undefined && cpbody.compare.fromarchive === undefined && cpbody.compare.toarchive === undefined ) {
-							var current_tag = '';
-							var small_prev_ins = '';
-							var small_prev_del = '';
-							var ins_length = more.length;
-							var del_length = more.length;
-							var added = false;
-							var parser = new htmlparser.Parser( {
-								onopentag: (tagname, attribs) => {
-									if ( tagname === 'ins' || tagname == 'del' ) {
-										current_tag = tagname;
-									}
-									if ( tagname === 'td' && attribs.class === 'diff-addedline' ) {
-										current_tag = tagname+'a';
-									}
-									if ( tagname === 'td' && attribs.class === 'diff-deletedline' ) {
-										current_tag = tagname+"d";
-									}
-									if ( tagname === 'td' && attribs.class === 'diff-marker' ) {
-										added = true;
-									}
-								},
-								ontext: (htmltext) => {
-									if ( current_tag === 'ins' && ins_length <= 1000 ) {
-										ins_length += ( '**' + htmltext.escapeFormatting() + '**' ).length;
-										if ( ins_length <= 1000 ) small_prev_ins += '**' + htmltext.escapeFormatting() + '**';
-										else small_prev_ins += more;
-									}
-									if ( current_tag === 'del' && del_length <= 1000 ) {
-										del_length += ( '~~' + htmltext.escapeFormatting() + '~~' ).length;
-										if ( del_length <= 1000 ) small_prev_del += '~~' + htmltext.escapeFormatting() + '~~';
-										else small_prev_del += more;
-									}
-									if ( ( current_tag === 'afterins' || current_tag === 'tda') && ins_length <= 1000 ) {
-										ins_length += htmltext.escapeFormatting().length;
-										if ( ins_length <= 1000 ) small_prev_ins += htmltext.escapeFormatting();
-										else small_prev_ins += more;
-									}
-									if ( ( current_tag === 'afterdel' || current_tag === 'tdd') && del_length <= 1000 ) {
-										del_length += htmltext.escapeFormatting().length;
-										if ( del_length <= 1000 ) small_prev_del += htmltext.escapeFormatting();
-										else small_prev_del += more;
-									}
-									if ( added ) {
-										if ( htmltext === '+' && ins_length <= 1000 ) {
-											ins_length++;
-											if ( ins_length <= 1000 ) small_prev_ins += '\n';
-											else small_prev_ins += more;
-										}
-										if ( htmltext === '−' && del_length <= 1000 ) {
-											del_length++;
-											if ( del_length <= 1000 ) small_prev_del += '\n';
-											else small_prev_del += more;
-										}
-										added = false;
-									}
-								},
-								onclosetag: (tagname) => {
-									if ( tagname === 'ins' ) {
-										current_tag = 'afterins';
-									} else if ( tagname === 'del' ) {
-										current_tag = 'afterdel';
-									} else {
-										current_tag = '';
-									}
-								}
-							}, {decodeEntities:true} );
-							parser.write( cpbody.compare['*'] );
-							parser.end();
-							if ( small_prev_del.length ) {
-								if ( small_prev_del.replace( /\~\~/g, '' ).trim().length ) {
-									embed.addField( lang.get('diff.info.removed'), small_prev_del.replace( /\~\~\~\~/g, '' ), true );
-								} else embed.addField( lang.get('diff.info.removed'), '__' + lang.get('diff.info.whitespace') + '__', true );
+							let edit_diff = diffParser( cpbody.compare['*'], more, whitespace )
+							if ( edit_diff[0].length ) {
+								embed.addField( lang.get('diff.info.removed'), edit_diff[0], true );
 							}
-							if ( small_prev_ins.length ) {
-								if ( small_prev_ins.replace( /\*\*/g, '' ).trim().length ) {
-									embed.addField( lang.get('diff.info.added'), small_prev_ins.replace( /\*\*\*\*/g, '' ), true );
-								} else embed.addField( lang.get('diff.info.added'), '__' + lang.get('diff.info.whitespace') + '__', true );
+							if ( edit_diff[1].length ) {
+								embed.addField( lang.get('diff.info.added'), edit_diff[1], true );
 							}
 						}
 						else if ( cpbody.compare.fromtexthidden !== undefined ) {
@@ -418,7 +254,7 @@ function gamepedia_diff_send(lang, msg, args, wiki, reaction, spoiler, compare)
 									content = '**' + content.substring(0, content.lastIndexOf('\n')) + '**' + more;
 								}
 								embed.addField( lang.get('diff.info.added'), content, true );
-							} else embed.addField( lang.get('diff.info.added'), '__' + lang.get('diff.info.whitespace') + '__', true );
+							} else embed.addField( lang.get('diff.info.added'), whitespace, true );
 						}
 						
 						msg.sendChannel( spoiler + text + spoiler, {embed} );
@@ -456,23 +292,6 @@ function gamepedia_diff_send(lang, msg, args, wiki, reaction, spoiler, compare)
 	} );
 }
 
-/**
- * Change HTML text to plain text.
- * @param {String} html - The text in HTML.
- * @returns {String}
- */
-function htmlToPlain(html) {
-	var text = '';
-	var parser = new htmlparser.Parser( {
-		ontext: (htmltext) => {
-			text += htmltext.escapeFormatting();
-		}
-	}, {decodeEntities:true} );
-	parser.write( html );
-	parser.end();
-	return text;
-};
-
 module.exports = {
 	name: 'diff',
 	run: gamepedia_diff

+ 1 - 67
cmds/wiki/gamepedia/random.js

@@ -1,6 +1,6 @@
-const htmlparser = require('htmlparser2');
 const {MessageEmbed} = require('discord.js');
 const parse_page = require('../../../functions/parse_page.js');
+const {htmlToPlain, htmlToDiscord} = require('../../../util/functions.js');
 const extract_desc = require('../../../util/extract_desc.js');
 
 /**
@@ -70,72 +70,6 @@ function gamepedia_random(lang, msg, wiki, reaction, spoiler) {
 	} );
 }
 
-/**
- * Change HTML text to plain text.
- * @param {String} html - The text in HTML.
- * @returns {String}
- */
-function htmlToPlain(html) {
-	var text = '';
-	var parser = new htmlparser.Parser( {
-		ontext: (htmltext) => {
-			text += htmltext.escapeFormatting();
-		}
-	}, {decodeEntities:true} );
-	parser.write( html );
-	parser.end();
-	return text;
-};
-
-/**
- * Change HTML text to markdown text.
- * @param {String} html - The text in HTML.
- * @returns {String}
- */
-function htmlToDiscord(html) {
-	var text = '';
-	var parser = new htmlparser.Parser( {
-		onopentag: (tagname, attribs) => {
-			switch (tagname) {
-				case 'b':
-					text += '**';
-					break;
-				case 'i':
-					text += '*';
-					break;
-				case 's':
-					text += '~~';
-					break;
-				case 'u':
-					text += '__';
-					break;
-			}
-		},
-		ontext: (htmltext) => {
-			text += htmltext.escapeFormatting();
-		},
-		onclosetag: (tagname) => {
-			switch (tagname) {
-				case 'b':
-					text += '**';
-					break;
-				case 'i':
-					text += '*';
-					break;
-				case 's':
-					text += '~~';
-					break;
-				case 'u':
-					text += '__';
-					break;
-			}
-		}
-	}, {decodeEntities:true} );
-	parser.write( html );
-	parser.end();
-	return text;
-};
-
 module.exports = {
 	name: 'random',
 	run: gamepedia_random

+ 1 - 68
cmds/wiki/gamepedia/user.js

@@ -1,10 +1,9 @@
-const htmlparser = require('htmlparser2');
 const {MessageEmbed} = require('discord.js');
 const global_block = require('../../../functions/global_block.js');
 const parse_page = require('../../../functions/parse_page.js');
 const extract_desc = require('../../../util/extract_desc.js');
 const {timeoptions, usergroups} = require('../../../util/default.json');
-const {toMarkdown, toPlaintext} = require('../../../util/functions.js');
+const {toMarkdown, toPlaintext, htmlToPlain, htmlToDiscord} = require('../../../util/functions.js');
 
 var allSites = [];
 const getAllSites = require('../../../util/allSites.js');
@@ -473,72 +472,6 @@ function gamepedia_user(lang, msg, namespace, username, wiki, querystring, fragm
 	} );
 }
 
-/**
- * Change HTML text to plain text.
- * @param {String} html - The text in HTML.
- * @returns {String}
- */
-function htmlToPlain(html) {
-	var text = '';
-	var parser = new htmlparser.Parser( {
-		ontext: (htmltext) => {
-			text += htmltext.escapeFormatting();
-		}
-	}, {decodeEntities:true} );
-	parser.write( html );
-	parser.end();
-	return text;
-};
-
-/**
- * Change HTML text to markdown text.
- * @param {String} html - The text in HTML.
- * @returns {String}
- */
-function htmlToDiscord(html) {
-	var text = '';
-	var parser = new htmlparser.Parser( {
-		onopentag: (tagname, attribs) => {
-			switch (tagname) {
-				case 'b':
-					text += '**';
-					break;
-				case 'i':
-					text += '*';
-					break;
-				case 's':
-					text += '~~';
-					break;
-				case 'u':
-					text += '__';
-					break;
-			}
-		},
-		ontext: (htmltext) => {
-			text += htmltext.escapeFormatting();
-		},
-		onclosetag: (tagname) => {
-			switch (tagname) {
-				case 'b':
-					text += '**';
-					break;
-				case 'i':
-					text += '*';
-					break;
-				case 's':
-					text += '~~';
-					break;
-				case 'u':
-					text += '__';
-					break;
-			}
-		}
-	}, {decodeEntities:true} );
-	parser.write( html );
-	parser.end();
-	return text;
-};
-
 module.exports = {
 	name: 'user',
 	run: gamepedia_user

+ 21 - 23
dashboard/index.js

@@ -43,14 +43,18 @@ const files = new Map(fs.readdirSync( './dashboard/src' ).map( file => {
 process.env.READONLY = 'true';
 
 const server = http.createServer((req, res) => {
-	if ( req.method === 'POST' && req.url.startsWith( '/guild/' ) && !process.env.READONLY ) {
+	if ( req.method === 'POST' && req.url.startsWith( '/guild/' ) ) {
 		let args = req.url.split('/');
 		let state = req.headers.cookie?.split('; ')?.filter( cookie => {
 			return cookie.split('=')[0] === 'wikibot';
 		} )?.map( cookie => cookie.replace( /^wikibot="(\w*(?:-\d+)?)"$/, '$1' ) )?.join();
 
-		if ( args.length === 5 && ['settings', 'verification', 'rcscript'].includes( args[3] ) 
-		&& settingsData.has(state) && settingsData.get(state).guilds.isMember.has(args[2]) ) {
+		if ( args.length === 5 && ['settings', 'verification', 'rcscript'].includes( args[3] )
+		&& /^(?:default|new|\d+)$/.test(args[4]) && settingsData.has(state)
+		&& settingsData.get(state).guilds.isMember.has(args[2]) ) {
+			if ( process.env.READONLY ) {
+				return dashboard(res, state, new URL(`${req.url}?save=failed`, process.env.dashboard));
+			}
 			let body = '';
 			req.on( 'data', chunk => {
 				body += chunk.toString();
@@ -60,7 +64,7 @@ const server = http.createServer((req, res) => {
 				res.end('error');
 			} );
 			return req.on( 'end', () => {
-				return posts[args[3]](res, settingsData.get(state).user.id, args[2], parse(body));
+				return posts[args[3]](res, settingsData.get(state), args[2], args[4], parse(body));
 			} );
 		}
 	}
@@ -106,21 +110,15 @@ const server = http.createServer((req, res) => {
 
 	if ( reqURL.pathname === '/logout' ) {
 		settingsData.delete(state);
-		res.writeHead(302, {
-			Location: '/login?action=logout',
-			'Set-Cookie': [
-				...( res.getHeader('Set-Cookie') || [] ),
-				'wikibot=""; HttpOnly; Path=/; Max-Age=0'
-			]
-		});
-		return res.end();
+		res.setHeader('Set-Cookie', [
+			...( res.getHeader('Set-Cookie') || [] ),
+			'wikibot=""; HttpOnly; Path=/; Max-Age=0'
+		]);
+		return pages.login(res, state, 'logout');
 	}
 
 	if ( !state ) {
-		res.writeHead(302, {
-			Location: ( reqURL.pathname === '/' ? '/login' : '/login?action=unauthorized' )
-		});
-		return res.end();
+		return pages.login(res, state, ( reqURL.pathname === '/' ? '' : 'unauthorized' ));
 	}
 
 	if ( reqURL.pathname === '/oauth' ) {
@@ -128,22 +126,22 @@ const server = http.createServer((req, res) => {
 	}
 
 	if ( !settingsData.has(state) ) {
-		res.writeHead(302, {
-			Location: ( reqURL.pathname === '/' ? '/login' : '/login?action=unauthorized' )
-		});
-		return res.end();
+		return pages.login(res, state, ( reqURL.pathname === '/' ? '' : 'unauthorized' ));
 	}
 
 	if ( reqURL.pathname === '/refresh' ) {
-		return pages.refresh(res, state, reqURL.searchParams.get('return'));
+		let returnLocation = reqURL.searchParams.get('return');
+		if ( returnLocation && ( !returnLocation.startsWith('/') || returnLocation.startsWith('//') ) ) {
+			returnLocation = '/';
+		}
+		return pages.refresh(res, state, returnLocation);
 	}
 
 	if ( reqURL.pathname === '/' || reqURL.pathname.startsWith( '/guild/' ) ) {
 		return dashboard(res, state, reqURL);
 	}
 
-	res.writeHead(302, {Location: '/'});
-	return res.end();
+	return dashboard(res, state, new URL('/', process.env.dashboard));
 });
 
 server.listen(8080, 'localhost', () => {

+ 6 - 6
dashboard/oauth.js

@@ -27,11 +27,6 @@ function dashboard_login(res, state, action) {
 		settingsData.delete(state);
 	}
 	var $ = cheerio.load(file);
-	let invite = oauth.generateAuthUrl( {
-		scope: ['identify', 'guilds', 'bot'],
-		permissions: defaultPermissions, state
-	} );
-	$('.guild#invite a, .channel#invite-wikibot').attr('href', invite);
 	let responseCode = 200;
 	let prompt = 'none';
 	if ( action === 'unauthorized' ) {
@@ -68,6 +63,11 @@ function dashboard_login(res, state, action) {
 	while ( settingsData.has(state) ) {
 		state = crypto.randomBytes(16).toString("hex");
 	}
+	let invite = oauth.generateAuthUrl( {
+		scope: ['identify', 'guilds', 'bot'],
+		permissions: defaultPermissions, state
+	} );
+	$('.guild#invite a, .channel#invite-wikibot').attr('href', invite);
 	let url = oauth.generateAuthUrl( {
 		scope: ['identify', 'guilds'],
 		prompt, state
@@ -156,7 +156,7 @@ function dashboard_oauth(res, state, searchParams, lastGuild) {
 				} );
 				settingsData.set(settings.state, settings);
 				res.writeHead(302, {
-					Location: ( lastGuild ? '/guild/' + lastGuild : '/settings' ),
+					Location: ( lastGuild ? `/guild/${lastGuild}/settings` : '/' ),
 					'Set-Cookie': [`wikibot="${settings.state}"; HttpOnly; Path=/`]
 				});
 				return res.end();

+ 10 - 14
dashboard/rcscript.js

@@ -1,12 +1,7 @@
-const got = require('got').extend( {
-	headers: {
-		'User-Agent': 'Wiki-Bot/dashboard (Discord; ' + process.env.npm_package_name + ')'
-	},
-	responseType: 'json'
-} );
 const {defaultSettings, limit: {rcgcdw: rcgcdwLimit}} = require('../util/default.json');
-const {RcGcDw: {names: allLangs}} = require('../i18n/allLangs.json');
-const {db, settingsData, sendMsg, createNotice, hasPerm} = require('./util.js');
+const {RcGcDw: allLangs} = require('../i18n/allLangs.json');
+const {got, db, sendMsg, hasPerm} = require('./util.js');
+const dashboard = require('./guilds.js');
 
 const fieldset = {
 	channel: '<label for="wb-settings-channel">Channel:</label>'
@@ -19,8 +14,8 @@ const fieldset = {
 	//+ '</fieldset>',
 	lang: '<label for="wb-settings-lang">Language:</label>'
 	+ '<select id="wb-settings-lang" name="lang" required>'
-	+ Object.keys(allLangs).map( lang => {
-		return `<option id="wb-settings-lang-${lang}" value="${lang}">${allLangs[lang]}</option>`
+	+ Object.keys(allLangs.names).map( lang => {
+		return `<option id="wb-settings-lang-${lang}" value="${lang}">${allLangs.names[lang]}</option>`
 	} ).join('\n')
 	+ '</select>',
 	display: '<span>Display mode:</span>'
@@ -199,8 +194,9 @@ function dashboard_rcscript(res, $, guild, args) {
 /**
  * Change recent changes scripts
  * @param {import('http').ServerResponse} res - The server response
- * @param {String} user - The id of the user
+ * @param {import('./util.js').Settings} userSettings - The settings of the user
  * @param {String} guild - The id of the guild
+ * @param {String} type - The setting to change
  * @param {Object} settings - The new settings
  * @param {String} settings.channel
  * @param {String} settings.wiki
@@ -211,11 +207,11 @@ function dashboard_rcscript(res, $, guild, args) {
  * @param {String} [settings.save_settings]
  * @param {String} [settings.delete_settings]
  */
-function update_rcscript(res, user, guild, settings) {
+function update_rcscript(res, userSettings, guild, type, settings) {
 	
 	console.log( settings );
-	res.writeHead(302, {Location: req.url});
-	res.end();
+	return dashboard(res, userSettings.state,
+		new URL(`/guild/${guild}/rcscript/${type}?save=failed`, process.env.dashboard));
 }
 
 module.exports = {

+ 58 - 9
dashboard/settings.js

@@ -1,6 +1,7 @@
 const {defaultSettings} = require('../util/default.json');
-const {allLangs: {names: allLangs}} = require('../i18n/allLangs.json');
-const {db, settingsData, sendMsg, createNotice, hasPerm} = require('./util.js');
+const {allLangs} = require('../i18n/allLangs.json');
+const {got, db, sendMsg, hasPerm} = require('./util.js');
+const dashboard = require('./guilds.js');
 
 const fieldset = {
 	channel: '<label for="wb-settings-channel">Channel:</label>'
@@ -13,8 +14,8 @@ const fieldset = {
 	//+ '</fieldset>',
 	lang: '<label for="wb-settings-lang">Language:</label>'
 	+ '<select id="wb-settings-lang" name="lang" required>'
-	+ Object.keys(allLangs).map( lang => {
-		return `<option id="wb-settings-lang-${lang}" value="${lang}">${allLangs[lang]}</option>`
+	+ Object.keys(allLangs.names).map( lang => {
+		return `<option id="wb-settings-lang-${lang}" value="${lang}">${allLangs.names[lang]}</option>`
 	} ).join('\n')
 	+ '</select>',
 	prefix: '<label for="wb-settings-prefix">Prefix:</label>'
@@ -191,8 +192,9 @@ function dashboard_settings(res, $, guild, args) {
 /**
  * Change settings
  * @param {import('http').ServerResponse} res - The server response
- * @param {String} user - The id of the user
+ * @param {import('./util.js').Settings} userSettings - The settings of the user
  * @param {String} guild - The id of the guild
+ * @param {String} type - The setting to change
  * @param {Object} settings - The new settings
  * @param {String} [settings.channel]
  * @param {String} settings.wiki
@@ -204,11 +206,58 @@ function dashboard_settings(res, $, guild, args) {
  * @param {String} [settings.save_settings]
  * @param {String} [settings.delete_settings]
  */
-function update_settings(res, user, guild, settings) {
-	
+function update_settings(res, userSettings, guild, type, settings) {
 	console.log( settings );
-	res.writeHead(302, {Location: req.url});
-	res.end();
+	sendMsg( {
+		type: 'getMember',
+		member: userSettings.user.id,
+		guild: guild
+	} ).then( response => {
+		if ( !response ) {
+			userSettings.guilds.notMember.set(guild, userSettings.guilds.isMember.get(guild));
+			userSettings.guilds.isMember.delete(guild);
+			return dashboard(res, userSettings.state,
+				new URL(`/guild/${guild}?save=failed`, process.env.dashboard));
+		}
+		if ( response === 'noMember' || !hasPerm(response.permissions, 'MANAGE_SERVER') ) {
+			userSettings.guilds.isMember.delete(guild);
+			return dashboard(res, userSettings.state,
+				new URL('/?save=failed', process.env.dashboard));
+		}
+		if ( type === 'default' ) {
+			if ( !settings.save_settings || settings.channel
+			|| ( !response.patreon && settings.prefix ) ) {
+				return dashboard(res, userSettings.state,
+					new URL(`/guild/${guild}/settings?save=failed`, process.env.dashboard));
+			}
+			return dashboard(res, userSettings.state,
+				new URL(`/guild/${guild}/settings?save=success`, process.env.dashboard));
+		}
+		if ( ( !settings.save_settings && !settings.delete_settings )
+		|| !settings.channel || settings.voice || ( !response.patreon
+		&& ( settings.prefix || settings.lang || settings.inline ) ) ) {
+			return dashboard(res, userSettings.state,
+				new URL(`/guild/${guild}/settings/${type}?save=failed`, process.env.dashboard));
+		}
+		if ( type === 'new' ) {
+			if ( !settings.save_settings ) {
+				return dashboard(res, userSettings.state,
+					new URL(`/guild/${guild}/settings/new?save=failed`, process.env.dashboard));
+			}
+			return dashboard(res, userSettings.state,
+				new URL(`/guild/${guild}/settings/new?save=success`, process.env.dashboard));
+		}
+		if ( !settings.save_settings && settings.delete_settings ) {
+			return dashboard(res, userSettings.state,
+				new URL(`/guild/${guild}/settings?save=success`, process.env.dashboard));
+		}
+		return dashboard(res, userSettings.state,
+			new URL(`/guild/${guild}/settings/${type}?save=success`, process.env.dashboard));
+	}, error => {
+		console.log( '- Dashboard: Error while getting the member: ' + error );
+		return dashboard(res, userSettings.state,
+			new URL(`/guild/${guild}/settings/${type}?save=failed`, process.env.dashboard));
+	} );
 }
 
 module.exports = {

+ 7 - 1
dashboard/util.js

@@ -1,3 +1,9 @@
+const got = require('got').extend( {
+	headers: {
+		'User-Agent': 'Wiki-Bot/dashboard (Discord; ' + process.env.npm_package_name + ')'
+	},
+	responseType: 'json'
+} );
 const sqlite3 = require('sqlite3').verbose();
 const mode = ( process.env.READONLY ? sqlite3.OPEN_READONLY : sqlite3.OPEN_READWRITE | sqlite3.OPEN_CREATE );
 const db = new sqlite3.Database( './wikibot.db', mode, dberror => {
@@ -123,4 +129,4 @@ function hasPerm(all, ...permission) {
 	} ).every( perm => perm );
 }
 
-module.exports = {db, settingsData, sendMsg, createNotice, hasPerm};
+module.exports = {got, db, settingsData, sendMsg, createNotice, hasPerm};

+ 7 - 5
dashboard/verification.js

@@ -1,5 +1,6 @@
 const {limit: {verification: verificationLimit}} = require('../util/default.json');
-const {db, settingsData, sendMsg, createNotice, hasPerm} = require('./util.js');
+const {db, sendMsg, hasPerm} = require('./util.js');
+const dashboard = require('./guilds.js');
 
 const fieldset = {
 	channel: '<label for="wb-settings-channel">Channel:</label>'
@@ -207,8 +208,9 @@ function dashboard_verification(res, $, guild, args) {
 /**
  * Change verifications
  * @param {import('http').ServerResponse} res - The server response
- * @param {String} user - The id of the user
+ * @param {import('./util.js').Settings} userSettings - The settings of the user
  * @param {String} guild - The id of the guild
+ * @param {String} type - The setting to change
  * @param {Object} settings - The new settings
  * @param {String|String[]} settings.channel
  * @param {String|String[]} settings.role
@@ -219,11 +221,11 @@ function dashboard_verification(res, $, guild, args) {
  * @param {String} [settings.save_settings]
  * @param {String} [settings.delete_settings]
  */
-function update_verification(res, user, guild, settings) {
+function update_verification(res, userSettings, guild, type, settings) {
 	
 	console.log( settings );
-	res.writeHead(302, {Location: req.url});
-	res.end();
+	return dashboard(res, userSettings.state,
+		new URL(`/guild/${guild}/verification/${type}?save=failed`, process.env.dashboard));
 }
 
 module.exports = {

+ 2 - 2
functions/discussion.js

@@ -36,7 +36,7 @@ function fandom_discussion(lang, msg, wiki, title, query, reaction, spoiler) {
 							thumbnail = attribs.content;
 						}
 					}
-				}, {decodeEntities:true} );
+				} );
 				parser.write( descbody );
 				parser.end();
 				embed.setThumbnail( thumbnail );
@@ -342,7 +342,7 @@ function discussion_send(lang, msg, wiki, discussion, embed, spoiler) {
 						}
 						if ( tagname === 'p' ) description += '\n';
 					}
-				}, {decodeEntities:true} );
+				} );
 				parser.write( discussion.renderedContent );
 				parser.end();
 				if ( discussion._embedded.contentImages.length ) embed.setThumbnail( discussion._embedded.contentImages[0].url );

+ 3 - 0
functions/parse_page.js

@@ -6,6 +6,8 @@ const removeClasses = [
 	'script',
 	'input',
 	'style',
+	'script',
+	'noscript',
 	'ul.gallery',
 	'.mw-editsection',
 	'sup.reference',
@@ -40,6 +42,7 @@ function parse_page(msg, title, embed, wiki, thumbnail) {
 		}
 		var change = false;
 		var $ = cheerio.load(response.body.parse.text['*']);
+		console.log($.text())
 		if ( embed.thumbnail?.url === thumbnail ) {
 			var image = response.body.parse.images.find( pageimage => ( /\.(?:png|jpg|jpeg|gif)$/.test(pageimage.toLowerCase()) && pageimage.toLowerCase().includes( title.toLowerCase().replace( / /g, '_' ) ) ) );
 			if ( !image ) {

+ 1 - 1
functions/special_page.js

@@ -138,7 +138,7 @@ function special_page(lang, msg, title, specialpage, embed, wiki, reaction, spoi
 		}
 		else {
 			if ( body.query.allmessages[0]['*'] ) {
-				var description = toPlaintext(body.query.allmessages[0]['*']);
+				var description = toPlaintext(body.query.allmessages[0]['*'], true);
 				if ( description.length > 2000 ) description = description.substring(0, 2000) + '\u2026';
 				embed.setDescription( description );
 			}

+ 6 - 1
main.js

@@ -131,7 +131,12 @@ if ( process.env.dashboard ) {
 					return manager.broadcastEval(`if ( this.guilds.cache.has('${message.data.guild}') ) {
 						let guild = this.guilds.cache.get('${message.data.guild}');
 						guild.members.fetch('${message.data.member}').then( member => {
-							return member.permissions.bitfield;
+							return {
+								patreon: guild.id in global.patreons,
+								permissions: member.permissions.bitfield
+							};
+						}, error => {
+							return 'noMember';
 						} );
 					}`).then( results => {
 						data.response = results.find( result => result );

+ 26 - 25
package-lock.json

@@ -587,9 +587,9 @@
       }
     },
     "got": {
-      "version": "11.6.2",
-      "resolved": "https://registry.npmjs.org/got/-/got-11.6.2.tgz",
-      "integrity": "sha512-/21qgUePCeus29Jk7MEti8cgQUNXFSWfIevNIk4H7u1wmXNDrGPKPY6YsPY+o9CIT/a2DjCjRz0x1nM9FtS2/A==",
+      "version": "11.7.0",
+      "resolved": "https://registry.npmjs.org/got/-/got-11.7.0.tgz",
+      "integrity": "sha512-7en2XwH2MEqOsrK0xaKhbWibBoZqy+f1RSUoIeF1BLcnf+pyQdDsljWMfmOh+QKJwuvDIiKx38GtPh5wFdGGjg==",
       "requires": {
         "@sindresorhus/is": "^3.1.1",
         "@szmarczak/http-timer": "^4.0.5",
@@ -632,46 +632,47 @@
       "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk="
     },
     "htmlparser2": {
-      "version": "4.1.0",
-      "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-4.1.0.tgz",
-      "integrity": "sha512-4zDq1a1zhE4gQso/c5LP1OtrhYTncXNSpvJYtWJBtXAETPlMfi3IFNjGuQbYLuVY4ZR0QMqRVvo4Pdy9KLyP8Q==",
+      "version": "5.0.0",
+      "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-5.0.0.tgz",
+      "integrity": "sha512-/Cvz5RTj9q71kCL9No1u2jhFaAdoMtxpNy0YTwjmQB3iX2TZXfCojTm7tp3rM4NxcwaX1iAzvNgo8OFectXmrQ==",
       "requires": {
         "domelementtype": "^2.0.1",
-        "domhandler": "^3.0.0",
-        "domutils": "^2.0.0",
+        "domhandler": "^3.3.0",
+        "domutils": "^2.4.2",
         "entities": "^2.0.0"
       },
       "dependencies": {
         "dom-serializer": {
-          "version": "0.2.2",
-          "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.2.2.tgz",
-          "integrity": "sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g==",
+          "version": "1.1.0",
+          "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.1.0.tgz",
+          "integrity": "sha512-ox7bvGXt2n+uLWtCRLybYx60IrOlWL/aCebWJk1T0d4m3y2tzf4U3ij9wBMUb6YJZpz06HCCYuyCDveE2xXmzQ==",
           "requires": {
             "domelementtype": "^2.0.1",
+            "domhandler": "^3.0.0",
             "entities": "^2.0.0"
           }
         },
         "domelementtype": {
-          "version": "2.0.1",
-          "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.0.1.tgz",
-          "integrity": "sha512-5HOHUDsYZWV8FGWN0Njbr/Rn7f/eWSQi1v7+HsUVwXgn8nWWlL64zKDkS0n8ZmQ3mlWOMuXOnR+7Nx/5tMO5AQ=="
+          "version": "2.0.2",
+          "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.0.2.tgz",
+          "integrity": "sha512-wFwTwCVebUrMgGeAwRL/NhZtHAUyT9n9yg4IMDwf10+6iCMxSkVq9MGCVEH+QZWo1nNidy8kNvwmv4zWHDTqvA=="
         },
         "domhandler": {
-          "version": "3.0.0",
-          "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-3.0.0.tgz",
-          "integrity": "sha512-eKLdI5v9m67kbXQbJSNn1zjh0SDzvzWVWtX+qEI3eMjZw8daH9k8rlj1FZY9memPwjiskQFbe7vHVVJIAqoEhw==",
+          "version": "3.3.0",
+          "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-3.3.0.tgz",
+          "integrity": "sha512-J1C5rIANUbuYK+FuFL98650rihynUOEzRLxW+90bKZRWB6A1X1Tf82GxR1qAWLyfNPRvjqfip3Q5tdYlmAa9lA==",
           "requires": {
             "domelementtype": "^2.0.1"
           }
         },
         "domutils": {
-          "version": "2.1.0",
-          "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.1.0.tgz",
-          "integrity": "sha512-CD9M0Dm1iaHfQ1R/TI+z3/JWp/pgub0j4jIQKH89ARR4ATAV2nbaOQS5XxU9maJP5jHaPdDDQSEHuE2UmpUTKg==",
+          "version": "2.4.2",
+          "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.4.2.tgz",
+          "integrity": "sha512-NKbgaM8ZJOecTZsIzW5gSuplsX2IWW2mIK7xVr8hTQF2v1CJWTmLZ1HOCh5sH+IzVPAGE5IucooOkvwBRAdowA==",
           "requires": {
-            "dom-serializer": "^0.2.1",
+            "dom-serializer": "^1.0.1",
             "domelementtype": "^2.0.1",
-            "domhandler": "^3.0.0"
+            "domhandler": "^3.3.0"
           }
         },
         "entities": {
@@ -814,9 +815,9 @@
       }
     },
     "keyv": {
-      "version": "4.0.1",
-      "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.0.1.tgz",
-      "integrity": "sha512-xz6Jv6oNkbhrFCvCP7HQa8AaII8y8LRpoSm661NOKLr4uHuBwhX4epXrPQgF3+xdJnN4Esm5X0xwY4bOlALOtw==",
+      "version": "4.0.3",
+      "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.0.3.tgz",
+      "integrity": "sha512-zdGa2TOpSZPq5mU6iowDARnMBZgtCqJ11dJROFi6tg6kTn4nuUdU09lFyLFSaHrWqpIJ+EBq4E8/Dc0Vx5vLdA==",
       "requires": {
         "json-buffer": "3.0.1"
       }

+ 2 - 2
package.json

@@ -20,8 +20,8 @@
     "discord.js": "^12.3.1",
     "dotenv": "^8.2.0",
     "full-icu": "^1.3.1",
-    "got": "^11.6.2",
-    "htmlparser2": "^4.1.0",
+    "got": "^11.7.0",
+    "htmlparser2": "^5.0.0",
     "npm": "^6.14.8",
     "sqlite3": "^5.0.0"
   },

+ 86 - 0
util/edit_diff.js

@@ -0,0 +1,86 @@
+const htmlparser = require('htmlparser2');
+const {escapeFormatting} = require('./functions.js');
+
+/**
+ * Change edit diffs to markdown text.
+ * @param {String} html - The edit diff in HTML.
+ * @param {String} more - The localized string for more content.
+ * @param {String} whitespace - The localized string for only whitespace.
+ * @returns {String[]}
+ */
+function diffParser(html, more, whitespace) {
+	var current_tag = '';
+	var small_prev_ins = '';
+	var small_prev_del = '';
+	var ins_length = more.length;
+	var del_length = more.length;
+	var added = false;
+	var parser = new htmlparser.Parser( {
+		onopentag: (tagname, attribs) => {
+			if ( tagname === 'ins' || tagname == 'del' ) current_tag = tagname;
+			if ( tagname === 'td' && attribs.class === 'diff-addedline' ) current_tag = tagname+'a';
+			if ( tagname === 'td' && attribs.class === 'diff-deletedline' ) current_tag = tagname+"d";
+			if ( tagname === 'td' && attribs.class === 'diff-marker' ) added = true;
+		},
+		ontext: (htmltext) => {
+			if ( current_tag === 'ins' && ins_length <= 1000 ) {
+				ins_length += ( '**' + escapeFormatting(htmltext) + '**' ).length;
+				if ( ins_length <= 1000 ) small_prev_ins += '**' + escapeFormatting(htmltext) + '**';
+				else small_prev_ins += more;
+			}
+			if ( current_tag === 'del' && del_length <= 1000 ) {
+				del_length += ( '~~' + escapeFormatting(htmltext) + '~~' ).length;
+				if ( del_length <= 1000 ) small_prev_del += '~~' + escapeFormatting(htmltext) + '~~';
+				else small_prev_del += more;
+			}
+			if ( ( current_tag === 'afterins' || current_tag === 'tda') && ins_length <= 1000 ) {
+				ins_length += escapeFormatting(htmltext).length;
+				if ( ins_length <= 1000 ) small_prev_ins += escapeFormatting(htmltext);
+				else small_prev_ins += more;
+			}
+			if ( ( current_tag === 'afterdel' || current_tag === 'tdd') && del_length <= 1000 ) {
+				del_length += escapeFormatting(htmltext).length;
+				if ( del_length <= 1000 ) small_prev_del += escapeFormatting(htmltext);
+				else small_prev_del += more;
+			}
+			if ( added ) {
+				if ( htmltext === '+' && ins_length <= 1000 ) {
+					ins_length++;
+					if ( ins_length <= 1000 ) small_prev_ins += '\n';
+					else small_prev_ins += more;
+				}
+				if ( htmltext === '−' && del_length <= 1000 ) {
+					del_length++;
+					if ( del_length <= 1000 ) small_prev_del += '\n';
+					else small_prev_del += more;
+				}
+				added = false;
+			}
+		},
+		onclosetag: (tagname) => {
+			if ( tagname === 'ins' ) {
+				current_tag = 'afterins';
+			} else if ( tagname === 'del' ) {
+				current_tag = 'afterdel';
+			} else {
+				current_tag = '';
+			}
+		}
+	} );
+	parser.write( html );
+	parser.end();
+	var compare = ['', ''];
+	if ( small_prev_del.length ) {
+		if ( small_prev_del.replace( /\~\~/g, '' ).trim().length ) {
+			compare[0] = small_prev_del.replace( /\~\~\~\~/g, '' );
+		} else compare[0] = whitespace;
+	}
+	if ( small_prev_ins.length ) {
+		if ( small_prev_ins.replace( /\*\*/g, '' ).trim().length ) {
+			compare[1] = small_prev_ins.replace( /\*\*\*\*/g, '' );
+		} else compare[1] = whitespace;
+	}
+	return compare;
+}
+
+module.exports = diffParser;

+ 86 - 2
util/functions.js

@@ -1,3 +1,13 @@
+const htmlparser = require('htmlparser2');
+const got = require('got').extend( {
+	throwHttpErrors: false,
+	timeout: 5000,
+	headers: {
+		'User-Agent': 'Wiki-Bot/' + ( isDebug ? 'testing' : process.env.npm_package_version ) + ' (Discord; ' + process.env.npm_package_name + ')'
+	},
+	responseType: 'json'
+} );
+
 /**
  * Make wikitext formatting usage.
  * @param {String} [text] - The text to modify.
@@ -36,10 +46,81 @@ function toMarkdown(text = '', wiki, title = '') {
 /**
  * Removes wikitext formatting.
  * @param {String} [text] - The text to modify.
+ * @param {Boolean} [canHTML] - If the text can contain HTML.
+ * @returns {String}
+ */
+function toPlaintext(text = '', fullWikitext = false) {
+	text = text.replace( /\[\[(?:[^\|\]]+\|)?([^\]]+)\]\]/g, '$1' ).replace( /\/\*\s*([^\*]+?)\s*\*\//g, '→$1:' );
+	if ( fullWikitext ) {
+		return htmlToPlain( text.replace( /\[(?:https?:)?\/\/(?:[^ ]+) ([^\]]+)\]/g, '$1' ) );
+	}
+	else return escapeFormatting(text);
+};
+
+/**
+ * Change HTML text to plain text.
+ * @param {String} html - The text in HTML.
+ * @returns {String}
+ */
+function htmlToPlain(html) {
+	var text = '';
+	var parser = new htmlparser.Parser( {
+		ontext: (htmltext) => {
+			text += escapeFormatting(htmltext);
+		}
+	} );
+	parser.write( html );
+	parser.end();
+	return text;
+};
+
+/**
+ * Change HTML text to markdown text.
+ * @param {String} html - The text in HTML.
  * @returns {String}
  */
-function toPlaintext(text = '') {
-	return escapeFormatting(text.replace( /\[\[(?:[^\|\]]+\|)?([^\]]+)\]\]/g, '$1' ).replace( /\/\*\s*([^\*]+?)\s*\*\//g, '→$1:' ));
+function htmlToDiscord(html) {
+	var text = '';
+	var parser = new htmlparser.Parser( {
+		onopentag: (tagname, attribs) => {
+			switch (tagname) {
+				case 'b':
+					text += '**';
+					break;
+				case 'i':
+					text += '*';
+					break;
+				case 's':
+					text += '~~';
+					break;
+				case 'u':
+					text += '__';
+					break;
+			}
+		},
+		ontext: (htmltext) => {
+			text += escapeFormatting(htmltext);
+		},
+		onclosetag: (tagname) => {
+			switch (tagname) {
+				case 'b':
+					text += '**';
+					break;
+				case 'i':
+					text += '*';
+					break;
+				case 's':
+					text += '~~';
+					break;
+				case 'u':
+					text += '__';
+					break;
+			}
+		}
+	} );
+	parser.write( html );
+	parser.end();
+	return text;
 };
 
 /**
@@ -54,8 +135,11 @@ function escapeFormatting(text = '', isMarkdown = false) {
 };
 
 module.exports = {
+	got,
 	toFormatting,
 	toMarkdown,
 	toPlaintext,
+	htmlToPlain,
+	htmlToDiscord,
 	escapeFormatting
 };

+ 1 - 1
util/newMessage.js

@@ -191,7 +191,7 @@ function newMessage(msg, lang, wiki = defaultSettings.wiki, prefix = process.env
 			}
 		} );
 		
-		if ( embeds.length ) got.get( wiki + 'api.php?action=query&meta=siteinfo&siprop=general' + ( wiki.isFandom() ? '' : '|variables' ) + '&titles=' + encodeURIComponent( embeds.map( embed => embed.title + '|Template:' + embed.title ).join('|') ) + '&format=json' ).then( response => {
+		if ( embeds.length ) got.get( wiki + 'api.php?action=query&meta=siteinfo&siprop=general' + ( wiki.isFandom() || wiki.isGamepedia() ? '' : '|variables' ) + '&titles=' + encodeURIComponent( embeds.map( embed => embed.title + '|Template:' + embed.title ).join('|') ) + '&format=json' ).then( response => {
 			var body = response.body;
 			if ( response.statusCode !== 200 || !body || !body.query ) {
 				if ( wiki.noWiki(response.url) || response.statusCode === 410 ) {