Prechádzať zdrojové kódy

Add category overwrites

close #130
Markus-Rost 4 rokov pred
rodič
commit
828d276ce4
12 zmenil súbory, kde vykonal 145 pridanie a 48 odobranie
  1. 3 3
      bot.js
  2. 7 0
      cmds/eval.js
  3. 7 6
      cmds/get.js
  4. 14 0
      cmds/patreon.js
  5. 2 2
      cmds/verification.js
  6. 1 0
      dashboard/i18n.js
  7. 75 25
      dashboard/settings.js
  8. 7 0
      dashboard/src/index.css
  9. 16 11
      dashboard/verification.js
  10. 10 0
      i18n/en.json
  11. 2 1
      main.js
  12. 1 0
      util/i18n.js

+ 3 - 3
bot.js

@@ -205,7 +205,7 @@ client.on( 'message', msg => {
 		if ( msg.content === process.env.prefix + 'help' && ( msg.isAdmin() || msg.isOwner() ) ) {
 			if ( msg.channel.permissionsFor(msg.client.user).has('SEND_MESSAGES') ) {
 				console.log( msg.guild.name + ': ' + msg.content );
-				db.get( 'SELECT lang FROM discord WHERE guild = ? AND (channel = ? OR channel IS NULL) ORDER BY channel DESC', [msg.guild.id, msg.channel.id], (dberror, row) => {
+				db.get( 'SELECT lang FROM discord WHERE guild = ? AND (channel = ? OR channel = ? OR channel IS NULL) ORDER BY channel DESC', [msg.guild.id, msg.channel.id, '#' + msg.channel.parentID], (dberror, row) => {
 					if ( dberror ) console.log( '- Error while getting the lang: ' + dberror );
 					msg.replyMsg( new Lang(( row || defaultSettings ).lang).get('general.prefix', patreons[msg.guild.id]), {}, true );
 				} );
@@ -220,7 +220,7 @@ client.on( 'message', msg => {
 			if ( msg.isAdmin() || msg.isOwner() ) {
 				console.log( msg.guild.id + ': Missing permissions - ' + missing.join(', ') );
 				if ( !missing.includes( 'SEND_MESSAGES' ) ) {
-					db.get( 'SELECT lang FROM discord WHERE guild = ? AND (channel = ? OR channel IS NULL) ORDER BY channel DESC', [msg.guild.id, msg.channel.id], (dberror, row) => {
+					db.get( 'SELECT lang FROM discord WHERE guild = ? AND (channel = ? OR channel = ? OR channel IS NULL) ORDER BY channel DESC', [msg.guild.id, msg.channel.id, '#' + msg.channel.parentID], (dberror, row) => {
 						if ( dberror ) console.log( '- Error while getting the lang: ' + dberror );
 						if ( msg.content.hasPrefix(( patreons[msg.guild.id] || process.env.prefix ), 'm') ) {
 							msg.replyMsg( new Lang(( row || defaultSettings ).lang).get('general.missingperm') + ' `' + missing.join('`, `') + '`', {}, true );
@@ -230,7 +230,7 @@ client.on( 'message', msg => {
 			}
 			return;
 		}
-		db.get( 'SELECT wiki, lang, role, inline FROM discord WHERE guild = ? AND (channel = ? OR channel IS NULL) ORDER BY channel DESC', [msg.guild.id, msg.channel.id], (dberror, row) => {
+		db.get( 'SELECT wiki, lang, role, inline FROM discord WHERE guild = ? AND (channel = ? OR channel = ? OR channel IS NULL) ORDER BY channel DESC', [msg.guild.id, msg.channel.id, '#' + msg.channel.parentID], (dberror, row) => {
 			if ( dberror ) {
 				console.log( '- Error while getting the wiki: ' + dberror );
 				if ( permissions.has('SEND_MESSAGES') ) {

+ 7 - 0
cmds/eval.js

@@ -213,6 +213,13 @@ function removePatreons(guild, msg) {
 				console.log( '- Error while removing the patreon features: ' + tryerror );
 			}
 		} );
+		db.run( 'DELETE FROM discord WHERE guild = ? AND channel LIKE ?', [guild, '#%'], function (dberror) {
+			if ( dberror ) {
+				console.log( '- Error while deleting the channel categories: ' + dberror );
+				return dberror;
+			}
+			if ( this.changes ) console.log( '- Channel categories successfully deleted.' );
+		} );
 		db.all( 'SELECT configid FROM verification WHERE guild = ? ORDER BY configid ASC', [guild], (dberror, rows) => {
 			if ( dberror ) {
 				console.log( '- Error while getting the verifications: ' + dberror );

+ 7 - 6
cmds/get.js

@@ -66,10 +66,10 @@ async function cmd_get(lang, msg, args, line, wiki) {
 			} );
 		}
 		
-		var channel = await msg.client.shard.broadcastEval( `if ( this.channels.cache.filter( channel => channel.isGuild() ).has('${id}') ) {
-			var {name, id, guild: {name: guild, id: guildID, me}} = this.channels.cache.get('${id}');
+		var channel = await msg.client.shard.broadcastEval( `if ( this.channels.cache.filter( channel => channel.isGuild() || channel.type === 'category' ).has('${id}') ) {
+			var {name, id, type, parentID, guild: {name: guild, id: guildID, me}} = this.channels.cache.get('${id}');
 			( {
-				name, id, guild, guildID,
+				name, id, type, parentID, guild, guildID,
 				permissions: me.permissionsIn(id).missing(${defaultPermissions}),
 				pause: guildID in global.pause,
 				shardId: global.shardId
@@ -78,13 +78,14 @@ async function cmd_get(lang, msg, args, line, wiki) {
 		if ( channel ) {
 			var channelguild = ['Guild:', channel.guild.escapeFormatting() + ' `' + channel.guildID + '`' + ( channel.pause ? '\\*' : '' )];
 			var channelname = ['Channel:', '#' + channel.name.escapeFormatting() + ' `' + channel.id + '` <#' + channel.id + '>'];
+			var channeldetails = ['Details:', '`' + channel.type + '`' + ( channel.parentID ? ' – `' + channel.parentID + '` <#' + channel.parentID + '>' : '' )];
 			var channelpermissions = ['Missing permissions:', ( channel.permissions.length ? '`' + channel.permissions.join('`, `') + '`' : '*none*' )];
 			var channellang = ['Language:', '*unknown*'];
 			var channelwiki = ['Default Wiki:', '*unknown*'];
 			var channelrole = ['Minimal Role:', '*unknown*'];
 			var channelinline = ['Inline commands:', '*unknown*'];
 			
-			return db.get( 'SELECT wiki, lang, role, inline FROM discord WHERE guild = ? AND (channel = ? OR channel IS NULL) ORDER BY channel DESC', [channel.guildID, channel.id], (dberror, row) => {
+			return db.get( 'SELECT wiki, lang, role, inline FROM discord WHERE guild = ? AND (channel = ? OR channel = ? OR channel IS NULL) ORDER BY channel DESC', [channel.guildID, channel.id, '#' + ( channel.type === 'category' ? channel.id : channel.parentID )], (dberror, row) => {
 				if ( dberror ) {
 					console.log( '- Error while getting the settings: ' + dberror );
 				}
@@ -103,11 +104,11 @@ async function cmd_get(lang, msg, args, line, wiki) {
 				
 				if ( msg.showEmbed() ) {
 					var text = '';
-					var embed = new MessageEmbed().addField( channelguild[0], channelguild[1] ).addField( channelname[0], channelname[1] ).addField( channelpermissions[0], channelpermissions[1] ).addField( channellang[0], channellang[1] ).addField( channelwiki[0], channelwiki[1] ).addField( channelrole[0], channelrole[1] ).addField( channelinline[0], channelinline[1] );
+					var embed = new MessageEmbed().addField( channelguild[0], channelguild[1] ).addField( channelname[0], channelname[1] ).addField( channeldetails[0], channeldetails[1] ).addField( channelpermissions[0], channelpermissions[1] ).addField( channellang[0], channellang[1] ).addField( channelwiki[0], channelwiki[1] ).addField( channelrole[0], channelrole[1] ).addField( channelinline[0], channelinline[1] );
 				}
 				else {
 					var embed = {};
-					var text = channelguild.join(' ') + '\n' + channelname.join(' ') + '\n' + channelpermissions.join(' ') + '\n' + channellang.join(' ') + '\n' + channelwiki[0] + ' <' + channelwiki[1] + '>\n' + channelrole.join(' ') + '\n' + channelinline.join(' ');
+					var text = channelguild.join(' ') + '\n' + channelname.join(' ') + '\n' + channeldetails.join(' ') + '\n' + channelpermissions.join(' ') + '\n' + channellang.join(' ') + '\n' + channelwiki[0] + ' <' + channelwiki[1] + '>\n' + channelrole.join(' ') + '\n' + channelinline.join(' ');
 				}
 				msg.sendChannel( text, {embed}, true );
 			} );

+ 14 - 0
cmds/patreon.js

@@ -77,6 +77,13 @@ function cmd_patreon(lang, msg, args, line, wiki) {
 				msg.client.shard.broadcastEval( `delete global.patreons['${args[1]}']` );
 				msg.replyMsg( 'the patreon features are now disabled on "' + guild + '".', {}, true );
 			} );
+			db.run( 'DELETE FROM discord WHERE guild = ? AND channel LIKE ?', [args[1], '#%'], function (dberror) {
+				if ( dberror ) {
+					console.log( '- Error while deleting the channel categories: ' + dberror );
+					return dberror;
+				}
+				if ( this.changes ) console.log( '- Channel categories successfully deleted.' );
+			} );
 			db.all( 'SELECT configid FROM verification WHERE guild = ? ORDER BY configid ASC', [args[1]], (dberror, rows) => {
 				if ( dberror ) {
 					console.log( '- Error while getting the verifications: ' + dberror );
@@ -206,6 +213,13 @@ function cmd_patreon(lang, msg, args, line, wiki) {
 				}
 				msg.replyMsg( '<@' + args[1] + '> is no longer a patreon.', {}, true );
 			} );
+			db.run( 'DELETE FROM discord WHERE guild IN (' + guilds.map( guild => '?' ).join(', ') + ') AND channel LIKE ?', [...guilds, '#%'], function (uperror) {
+				if ( uperror ) {
+					console.log( '- Error while deleting the channel categories: ' + uperror );
+					return uperror;
+				}
+				if ( this.changes ) console.log( '- Channel categories successfully deleted.' );
+			} );
 			db.each( 'SELECT a.guild, GROUP_CONCAT(DISTINCT a.configid) configids FROM verification a LEFT JOIN verification b ON a.guild = b.guild WHERE a.guild IN (' + guilds.map( guild => '?' ).join(', ') + ') GROUP BY a.guild', guilds, (eacherror, eachrow) => {
 				if ( eacherror ) {
 					console.log( '- Error while getting the verifications: ' + eacherror );

+ 2 - 2
cmds/verification.js

@@ -141,14 +141,14 @@ function cmd_verification(lang, msg, args, line, wiki) {
 				var roles = args[2].replace( /\s*>?\s*[,|]\s*<?\s*/g, '|' ).split('|').filter( role => role.length );
 				if ( roles.length > 10 ) return msg.replyMsg( lang.get('verification.role_max'), {}, true );
 				roles = roles.map( role => {
-					var new_role = '';
+					var new_role = null;
 					if ( /^\d+$/.test(role) ) new_role = msg.guild.roles.cache.get(role);
 					if ( !new_role ) new_role = msg.guild.roles.cache.find( gc => gc.name === role.replace( /^@/, '' ) );
 					if ( !new_role ) new_role = msg.guild.roles.cache.find( gc => gc.name.toLowerCase() === role.toLowerCase().replace( /^@/, '' ) );
 					return new_role;
 				} );
 				if ( roles.some( role => !role ) ) return msg.replyMsg( lang.get('verification.role_missing'), {}, true );
-				if ( roles.some( role => role.managed ) ) return msg.replyMsg( lang.get('verification.role_managed'), {}, true );
+				if ( roles.some( role => role.managed || role.id === msg.guild.id ) ) return msg.replyMsg( lang.get('verification.role_managed'), {}, true );
 				roles = roles.map( role => role.id ).join('|');
 				if ( roles.length ) return db.run( 'UPDATE verification SET role = ? WHERE guild = ? AND configid = ?', [roles, msg.guild.id, row.configid], function (dberror) {
 					if ( dberror ) {

+ 1 - 0
dashboard/i18n.js

@@ -134,6 +134,7 @@ function plural(lang, number, args) {
 		case 'th':
 		case 'tr':
 		case 'ja':
+		case 'ko':
 		case 'zh-hans':
 		case 'zh-hant':
 		default:

+ 75 - 25
dashboard/settings.js

@@ -61,20 +61,58 @@ function createForm($, header, dashboardLang, settings, guildRoles, guildChannel
 	if ( settings.channel ) {
 		let channel = $('<div>').append(fieldset.channel);
 		channel.find('label').text(dashboardLang.get('settings.form.channel'));
-		channel.find('#wb-settings-channel').append(
-			...guildChannels.map( guildChannel => {
-				return $(`<option id="wb-settings-channel-${guildChannel.id}">`).val(guildChannel.id).text(`${guildChannel.id} – #${guildChannel.name}`)
-			} )
-		);
-		if ( guildChannels.length === 1 ) {
+		if ( settings.channel === 'new' ) {
+			let curCat = null;
+			channel.find('#wb-settings-channel').append(
+				$(`<option id="wb-settings-channel-default" selected hidden>`).val('').text(dashboardLang.get('settings.form.select_channel')),
+				...guildChannels.filter( guildChannel => {
+					return ( hasPerm(guildChannel.userPermissions, 'VIEW_CHANNEL', 'SEND_MESSAGES') || guildChannel.isCategory );
+				} ).map( guildChannel => {
+					if ( settings.patreon ) {
+						var optionChannel = $(`<option id="wb-settings-channel-${guildChannel.id}">`).val(guildChannel.id).text(`${guildChannel.id} – ` + ( guildChannel.isCategory ? '' : '#' ) + guildChannel.name);
+						if ( guildChannel.isCategory ) {
+							curCat = true;
+							optionChannel.addClass('wb-settings-optgroup');
+							if ( !guildChannel.allowedCat ) {
+								optionChannel.attr('disabled', '').val('');
+							}
+						}
+						else if ( curCat === true ) {
+							optionChannel.prepend('&nbsp; &nbsp; ');
+						}
+						return optionChannel;
+					}
+					if ( guildChannel.isCategory ) {
+						curCat = $('<optgroup>').attr('label', guildChannel.name);
+						return curCat;
+					}
+					var optionChannel = $(`<option id="wb-settings-channel-${guildChannel.id}">`).val(guildChannel.id).text(`${guildChannel.id} – #${guildChannel.name}`);
+					if ( !curCat ) return optionChannel;
+					optionChannel.appendTo(curCat);
+				} ).filter( (catChannel, i, guildChannelList) => {
+					if ( !catChannel ) return false;
+					if ( catChannel.is('optgroup') && !catChannel.children('option').length ) return false;
+					if ( catChannel.hasClass('wb-settings-optgroup') && guildChannelList[i + 1].hasClass('wb-settings-optgroup') ) {
+						if ( catChannel.attr('disabled') ) return false;
+						return guildChannels.some( guildChannel => {
+							return ( guildChannel.id === catChannel.val() && hasPerm(guildChannel.userPermissions, 'VIEW_CHANNEL', 'SEND_MESSAGES') );
+						} );
+					}
+					return true;
+				} )
+			);
+		}
+		else {
+			channel.find('#wb-settings-channel').append(
+				...guildChannels.map( guildChannel => {
+					return $(`<option id="wb-settings-channel-${guildChannel.id}">`).val(guildChannel.id).text(`${guildChannel.id} – ` + ( guildChannel.isCategory ? '' : '#' ) + guildChannel.name);
+				} )
+			);
 			channel.find(`#wb-settings-channel-${settings.channel}`).attr('selected', '');
 			if ( !hasPerm(guildChannels[0].userPermissions, 'VIEW_CHANNEL', 'SEND_MESSAGES') ) {
 				readonly = true;
 			}
 		}
-		else channel.find('#wb-settings-channel').prepend(
-			$(`<option id="wb-settings-channel-default" selected hidden>`).val('').text(dashboardLang.get('settings.form.select_channel'))
-		);
 		fields.push(channel);
 	}
 	let wiki = $('<div>').append(fieldset.wiki);
@@ -162,8 +200,8 @@ function dashboard_settings(res, $, guild, args, dashboardLang) {
 		}
 		let isPatreon = rows.some( row => row.patreon );
 		let channellist = rows.filter( row => row.channel ).map( row => {
-			let channel = guild.channels.find( channel => channel.id === row.channel );
-			return ( channel || {id: row.channel, name: 'UNKNOWN', userPermissions: 0} );
+			let channel = guild.channels.find( channel => channel.id === row.channel.replace( /^#/, '' ) );
+			return ( channel || {id: row.channel.replace( /^#/, '' ), name: 'UNKNOWN', userPermissions: 0, isCategory: row.channel.startsWith( '#' )} );
 		} ).sort( (a, b) => {
 			return guild.channels.indexOf(a) - guild.channels.indexOf(b);
 		} );
@@ -171,12 +209,16 @@ function dashboard_settings(res, $, guild, args, dashboardLang) {
 		$('#channellist #settings').after(
 			...channellist.map( channel => {
 				return $('<a class="channel">').attr('id', `channel-${channel.id}`).append(
-					$('<img>').attr('src', '/src/channel.svg'),
-					$('<div>').text(channel.name)
+					...( channel.isCategory ? [
+						$('<div class="category">').text(channel.name)
+					] : [
+						$('<img>').attr('src', '/src/channel.svg'),
+						$('<div>').text(channel.name)]
+					)
 				).attr('href', `/guild/${guild.id}/settings/${channel.id}${suffix}`).attr('title', channel.id);
 			} ),
 			( process.env.READONLY || !guild.channels.filter( channel => {
-				return ( !channel.isCategory && hasPerm(channel.userPermissions, 'VIEW_CHANNEL', 'SEND_MESSAGES') && !rows.some( row => row.channel === channel.id ) );
+				return ( hasPerm(channel.userPermissions, 'VIEW_CHANNEL', 'SEND_MESSAGES') && !rows.some( row => row.channel === ( channel.isCategory ? '#' : '' ) + channel.id ) );
 			} ).length ? '' :
 			$('<a class="channel" id="channel-new">').append(
 				$('<img>').attr('src', '/src/channel.svg'),
@@ -189,14 +231,21 @@ function dashboard_settings(res, $, guild, args, dashboardLang) {
 				patreon: isPatreon,
 				channel: 'new'
 			}), guild.roles, guild.channels.filter( channel => {
-				return ( !channel.isCategory && hasPerm(channel.userPermissions, 'VIEW_CHANNEL', 'SEND_MESSAGES') && !rows.some( row => row.channel === channel.id ) );
+				return ( channel.isCategory || !rows.some( row => row.channel === ( channel.isCategory ? '#' : '' ) + channel.id ) );
+			} ).map( channel => {
+				if ( !channel.isCategory ) return channel;
+				let {id, name, userPermissions, isCategory} = channel;
+				return {
+					id, name, userPermissions, isCategory,
+					allowedCat: !rows.some( row => row.channel === '#' + channel.id )
+				};
 			} )).attr('action', `/guild/${guild.id}/settings/new`).appendTo('#text');
 		}
 		else if ( channellist.some( channel => channel.id === args[4] ) ) {
 			let channel = channellist.find( channel => channel.id === args[4] );
 			$(`.channel#channel-${channel.id}`).addClass('selected');
 			createForm($, dashboardLang.get('settings.form.overwrite', false, `#${channel.name}`), dashboardLang, Object.assign({}, rows.find( row => {
-				return row.channel === channel.id;
+				return row.channel === ( channel.isCategory ? '#' : '' ) + channel.id;
 			} ), {
 				patreon: isPatreon
 			}), guild.roles, [channel]).attr('action', `/guild/${guild.id}/settings/${channel.id}`).appendTo('#text');
@@ -241,7 +290,7 @@ function update_settings(res, userSettings, guild, type, settings) {
 			return res(`/guild/${guild}/settings/${type}`, 'savefail');
 		}
 		if ( settings.channel && !userSettings.guilds.isMember.get(guild).channels.some( channel => {
-			return ( channel.id === settings.channel && !channel.isCategory );
+			return ( channel.id === settings.channel && ( !channel.isCategory || userSettings.guilds.isMember.get(guild).patreon ) );
 		} ) ) return res(`/guild/${guild}/settings/${type}`, 'savefail');
 		if ( settings.role && !userSettings.guilds.isMember.get(guild).roles.some( role => {
 			return ( role.id === settings.role );
@@ -254,7 +303,8 @@ function update_settings(res, userSettings, guild, type, settings) {
 		type: 'getMember',
 		member: userSettings.user.id,
 		guild: guild,
-		channel: ( type === settings.channel ? type : undefined )
+		channel: ( type !== 'default' ? settings.channel : undefined ),
+		allowCategory: true
 	} ).then( response => {
 		if ( !response ) {
 			userSettings.guilds.notMember.set(guild, userSettings.guilds.isMember.get(guild));
@@ -265,7 +315,7 @@ function update_settings(res, userSettings, guild, type, settings) {
 			userSettings.guilds.isMember.delete(guild);
 			return res('/', 'savefail');
 		}
-		if ( response.message === 'noChannel' ) return db.run( 'DELETE FROM discord WHERE guild = ? AND channel = ?', [guild, type], function (delerror) {
+		if ( response.message === 'noChannel' ) return db.run( 'DELETE FROM discord WHERE guild = ? AND ( channel = ? OR channel = ? )', [guild, type, `#${type}`], function (delerror) {
 			if ( delerror ) {
 				console.log( '- Dashboard: Error while removing the settings: ' + delerror );
 				return res(`/guild/${guild}/settings`, 'savefail');
@@ -274,11 +324,11 @@ function update_settings(res, userSettings, guild, type, settings) {
 			if ( settings.delete_settings ) return res(`/guild/${guild}/settings`, 'save');
 			else return res(`/guild/${guild}/settings`, 'savefail');
 		} );
-		if ( type === settings.channel && !hasPerm(response.userPermissions, 'VIEW_CHANNEL', 'SEND_MESSAGES') ) {
+		if ( type !== 'default' && !hasPerm(response.userPermissions, 'VIEW_CHANNEL', 'SEND_MESSAGES') ) {
 			return res(`/guild/${guild}/settings/${type}`, 'savefail');
 		}
-		if ( settings.delete_settings ) return db.get( 'SELECT main.lang mainlang, main.patreon, main.lang mainwiki, main.role mainrole, main.inline maininline, old.wiki, old.lang, old.role, old.inline FROM discord main LEFT JOIN discord old ON main.guild = old.guild AND old.channel = ? WHERE main.guild = ? AND main.channel IS NULL', [type, guild], function(dberror, row) {
-			db.run( 'DELETE FROM discord WHERE guild = ? AND channel = ?', [guild, type], function (delerror) {
+		if ( settings.delete_settings ) return db.get( 'SELECT main.lang mainlang, main.patreon, main.wiki mainwiki, main.role mainrole, main.inline maininline, old.wiki, old.lang, old.role, old.inline FROM discord main LEFT JOIN discord old ON main.guild = old.guild AND old.channel = ? WHERE main.guild = ? AND main.channel IS NULL', [( response.isCategory ? '#' : '' ) + type, guild], function(dberror, row) {
+			db.run( 'DELETE FROM discord WHERE guild = ? AND channel = ?', [guild, ( response.isCategory ? '#' : '' ) + type], function (delerror) {
 				if ( delerror ) {
 					console.log( '- Dashboard: Error while removing the settings: ' + delerror );
 					return res(`/guild/${guild}/settings/${type}`, 'savefail');
@@ -490,7 +540,7 @@ function update_settings(res, userSettings, guild, type, settings) {
 				if ( type === 'new' ) {
 					return res(`/guild/${guild}/settings/${type}`, 'nochange');
 				}
-				return db.run( 'DELETE FROM discord WHERE guild = ? AND channel = ?', [guild, type], function (delerror) {
+				return db.run( 'DELETE FROM discord WHERE guild = ? AND channel = ?', [guild, ( response.isCategory ? '#' : '' ) + type], function (delerror) {
 					if ( delerror ) {
 						console.log( '- Dashboard: Error while removing the settings: ' + delerror );
 						return res(`/guild/${guild}/settings/${type}`, 'savefail');
@@ -506,7 +556,7 @@ function update_settings(res, userSettings, guild, type, settings) {
 					} );
 				} );
 			}
-			return db.get( 'SELECT lang, wiki, role, inline FROM discord WHERE guild = ? AND channel = ?', [guild, settings.channel], function(curerror, channel) {
+			return db.get( 'SELECT lang, wiki, role, inline FROM discord WHERE guild = ? AND channel = ?', [guild, ( response.isCategory ? '#' : '' ) + settings.channel], function(curerror, channel) {
 				if ( curerror ) {
 					console.log( '- Dashboard: Error while getting the channel settings: ' + curerror );
 					return res(`/guild/${guild}/settings/${type}`, 'savefail');
@@ -534,7 +584,7 @@ function update_settings(res, userSettings, guild, type, settings) {
 					return res(`/guild/${guild}/settings/${settings.channel}`, 'save');
 				}
 				let sql = 'UPDATE discord SET wiki = ?, lang = ?, role = ?, inline = ? WHERE guild = ? AND channel = ?';
-				let sqlargs = [wiki.href, ( settings.lang || channel.lang ), ( response.patreon ? ( settings.role || null ) : channel.role ), ( response.patreon ? ( settings.inline ? null : 1 ) : channel.inline ), guild, settings.channel];
+				let sqlargs = [wiki.href, ( settings.lang || channel.lang ), ( response.patreon ? ( settings.role || null ) : channel.role ), ( response.patreon ? ( settings.inline ? null : 1 ) : channel.inline ), guild, ( response.isCategory ? '#' : '' ) + settings.channel];
 				if ( channel === row ) {
 					sql = 'INSERT INTO discord(wiki, lang, role, inline, guild, channel, prefix) VALUES(?, ?, ?, ?, ?, ?, ?)';
 					sqlargs.push(row.prefix);

+ 7 - 0
dashboard/src/index.css

@@ -277,6 +277,9 @@ code {
 	white-space: nowrap;
 	overflow: hidden;
 }
+.channel div.category {
+	margin-left: 6px;
+}
 .channel:hover {
 	background: rgba(79,84,92,0.16);
 }
@@ -328,6 +331,9 @@ button.addmore:not([hidden]) {
 	display: block;
 	margin-left: 20%;
 }
+.wb-settings-optgroup {
+	font-weight: bold;
+}
 .wb-settings-error {
 	color: red;
 }
@@ -341,6 +347,7 @@ button.addmore:not([hidden]) {
 }
 #wb-settings-lang-widget {
 	vertical-align: top;
+	margin-left: 5px;
 }
 #login-button {
 	display: flex;

+ 16 - 11
dashboard/verification.js

@@ -1,6 +1,6 @@
 const {limit: {verification: verificationLimit}, usergroups} = require('../util/default.json');
 const Lang = require('../util/i18n.js');
-const {got, db, sendMsg, createNotice, hasPerm} = require('./util.js');
+const {got, db, sendMsg, createNotice, escapeText, hasPerm} = require('./util.js');
 
 const fieldset = {
 	channel: '<label for="wb-settings-channel">Channel:</label>'
@@ -195,20 +195,12 @@ function createForm($, header, dashboardLang, settings, guildChannels, guildRole
  * @param {import('./i18n.js')} dashboardLang - The user language
  */
 function dashboard_verification(res, $, guild, args, dashboardLang) {
-	if ( !hasPerm(guild.botPermissions, 'MANAGE_ROLES') ) {
-		createNotice($, 'missingperm', dashboardLang, ['Manage Roles']);
-		$('#text .description').html(dashboardLang.get('verification.explanation'));
-		$('.channel#verification').addClass('selected');
-		let body = $.html();
-		res.writeHead(200, {'Content-Length': body.length});
-		res.write( body );
-		return res.end();
-	}
-	db.all( 'SELECT wiki, discord.role defaultrole, configid, verification.channel, verification.role, editcount, usergroup, accountage, rename FROM discord LEFT JOIN verification ON discord.guild = verification.guild WHERE discord.guild = ? AND discord.channel IS NULL ORDER BY configid ASC', [guild.id], function(dberror, rows) {
+	db.all( 'SELECT wiki, discord.role defaultrole, prefix, configid, verification.channel, verification.role, editcount, usergroup, accountage, rename FROM discord LEFT JOIN verification ON discord.guild = verification.guild WHERE discord.guild = ? AND discord.channel IS NULL ORDER BY configid ASC', [guild.id], function(dberror, rows) {
 		if ( dberror ) {
 			console.log( '- Dashboard: Error while getting the verifications: ' + dberror );
 			createNotice($, 'error', dashboardLang);
 			$('#text .description').html(dashboardLang.get('verification.explanation'));
+			$('#text code.prefix').prepend(escapeText(process.env.prefix));
 			$('.channel#verification').addClass('selected');
 			let body = $.html();
 			res.writeHead(200, {'Content-Length': body.length});
@@ -218,6 +210,17 @@ function dashboard_verification(res, $, guild, args, dashboardLang) {
 		if ( rows.length === 0 ) {
 			createNotice($, 'nosettings', dashboardLang, [guild.id]);
 			$('#text .description').html(dashboardLang.get('verification.explanation'));
+			$('#text code.prefix').prepend(escapeText(process.env.prefix));
+			$('.channel#verification').addClass('selected');
+			let body = $.html();
+			res.writeHead(200, {'Content-Length': body.length});
+			res.write( body );
+			return res.end();
+		}
+		if ( !hasPerm(guild.botPermissions, 'MANAGE_ROLES') ) {
+			createNotice($, 'missingperm', dashboardLang, ['Manage Roles']);
+			$('#text .description').html(dashboardLang.get('verification.explanation'));
+			$('#text code.prefix').prepend(escapeText(rows[0].prefix));
 			$('.channel#verification').addClass('selected');
 			let body = $.html();
 			res.writeHead(200, {'Content-Length': body.length});
@@ -226,6 +229,7 @@ function dashboard_verification(res, $, guild, args, dashboardLang) {
 		}
 		var wiki = rows[0].wiki;
 		var defaultrole = rows[0].defaultrole;
+		var prefix = rows[0].prefix;
 		if ( rows.length === 1 && rows[0].configid === null ) rows.pop();
 		$('<p>').html(dashboardLang.get('verification.desc', true, $('<code>').text(guild.name))).appendTo('#text .description');
 		let suffix = ( args[0] === 'owner' ? '?owner=true' : '' );
@@ -261,6 +265,7 @@ function dashboard_verification(res, $, guild, args, dashboardLang) {
 		else {
 			$('.channel#verification').addClass('selected');
 			$('#text .description').html(dashboardLang.get('verification.explanation'));
+			$('#text code.prefix').prepend(escapeText(prefix));
 		}
 		let body = $.html();
 		res.writeHead(200, {'Content-Length': body.length});

+ 10 - 0
i18n/en.json

@@ -315,6 +315,10 @@
                     "cmd": "settings prefix <prefix>",
                     "desc": "I will change the prefix for this server."
                 },
+                "role": {
+                    "cmd": "settings role <role>",
+                    "desc": "I will change the minimal required role to use commands for this server."
+                },
                 "wiki": {
                     "cmd": "settings wiki <wiki>",
                     "desc": "I will change the default wiki for this server."
@@ -533,6 +537,8 @@
         "channel current": "these are the current settings for this channel:",
         "channel lang": "the language for this channel is:",
         "channel langchanged": "you changed the language for this channel to:",
+        "channel role": "the minimal required role for this channel is:",
+        "channel rolechanged": "you changed the minimal required role for this channel to:",
         "channel wiki": "the default wiki for this channel is:",
         "channel wikichanged": "you changed the default wiki for this channel to:",
         "current": "these are the current settings for this server:",
@@ -572,6 +578,10 @@
         "prefixchanged": "you changed the prefix for this server to:",
         "prefixhelp": "Use `$1 <prefix>` to change the prefix.\nUse `_` at the end to indicate a space at the end of the prefix.\nThe prefix may not include mentions!",
         "prefixinvalid": "the specified prefix is not supported!",
+        "role": "the minimal required role for this server is:",
+        "rolechanged": "you changed the minimal required role for this server to:",
+        "rolehelp": "Use `$1 <role>` to change the minimal required role.",
+        "roleinvalid": "the specified role does not exist!",
         "save_failed": "sadly the settings couldn't be saved, please try again later.",
         "wiki": "the default wiki for this server is:",
         "wikichanged": "you changed the default wiki for this server to:",

+ 2 - 1
main.js

@@ -152,9 +152,10 @@ if ( process.env.dashboard ) {
 							};
 							if ( '${( message.data.channel || '' )}' ) {
 								let channel = guild.channels.cache.get('${message.data.channel}');
-								if ( channel?.isText() ) {
+								if ( channel?.isText() || ( response.patreon && ${message.data.allowCategory} && channel?.type === 'category' ) ) {
 									response.userPermissions = channel.permissionsFor(member).bitfield;
 									response.botPermissions = channel.permissionsFor(guild.me).bitfield;
+									response.isCategory = ( channel.type === 'category' );
 								}
 								else response.message = 'noChannel';
 							}

+ 1 - 0
util/i18n.js

@@ -198,6 +198,7 @@ function plural(lang, number, args) {
 		case 'th':
 		case 'tr':
 		case 'ja':
+		case 'ko':
 		case 'zh-hans':
 		case 'zh-hant':
 		default: