verify.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299
  1. const {randomBytes} = require('crypto');
  2. const {MessageEmbed} = require('discord.js');
  3. var db = require('../util/database.js');
  4. var verify = require('../functions/verify.js');
  5. const {got, oauthVerify, allowDelete, escapeFormatting} = require('../util/functions.js');
  6. /**
  7. * Processes the "verify" command.
  8. * @param {import('../util/i18n.js')} lang - The user language.
  9. * @param {import('discord.js').Message} msg - The Discord message.
  10. * @param {String[]} args - The command arguments.
  11. * @param {String} line - The command as plain text.
  12. * @param {import('../util/wiki.js')} wiki - The wiki for the message.
  13. */
  14. function cmd_verify(lang, msg, args, line, wiki) {
  15. if ( !msg.channel.isGuild() || msg.defaultSettings ) return this.LINK(lang, msg, line, wiki);
  16. if ( !msg.guild.me.permissions.has('MANAGE_ROLES') ) {
  17. if ( msg.isAdmin() ) {
  18. console.log( msg.guild.id + ': Missing permissions - MANAGE_ROLES' );
  19. msg.replyMsg( lang.get('general.missingperm') + ' `MANAGE_ROLES`' );
  20. }
  21. else if ( !msg.onlyVerifyCommand ) this.LINK(lang, msg, line, wiki);
  22. return;
  23. }
  24. db.query( 'SELECT logchannel, flags, onsuccess, onmatch, role, editcount, postcount, usergroup, accountage, rename FROM verification LEFT JOIN verifynotice ON verification.guild = verifynotice.guild WHERE verification.guild = $1 AND channel LIKE $2 ORDER BY configid ASC', [msg.guild.id, '%|' + msg.channel.id + '|%'] ).then( ({rows}) => {
  25. if ( !rows.length ) {
  26. if ( msg.onlyVerifyCommand ) return;
  27. return msg.replyMsg( lang.get('verify.missing') + ( msg.isAdmin() ? '\n`' + ( patreons[msg.guild.id] || process.env.prefix ) + 'verification`' : '' ) );
  28. }
  29. if ( wiki.hasOAuth2() && process.env.dashboard ) {
  30. let oauth = [wiki.hostname + wiki.pathname.slice(0, -1)];
  31. if ( wiki.isWikimedia() ) oauth.push('wikimedia');
  32. if ( wiki.isMiraheze() ) oauth.push('miraheze');
  33. if ( process.env['oauth_' + ( oauth[1] || oauth[0] )] && process.env['oauth_' + ( oauth[1] || oauth[0] ) + '_secret'] ) {
  34. return db.query( 'SELECT token FROM oauthusers WHERE userid = $1 AND site = $2', [msg.author.id, ( oauth[1] || oauth[0] )] ).then( ({rows: [row]}) => {
  35. if ( row?.token ) return got.post( wiki + 'rest.php/oauth2/access_token', {
  36. form: {
  37. grant_type: 'refresh_token', refresh_token: row.token,
  38. redirect_uri: new URL('/oauth/mw', process.env.dashboard).href,
  39. client_id: process.env['oauth_' + ( oauth[1] || oauth[0] )],
  40. client_secret: process.env['oauth_' + ( oauth[1] || oauth[0] ) + '_secret']
  41. }
  42. } ).then( response => {
  43. var body = response.body;
  44. if ( response.statusCode !== 200 || !body?.access_token ) {
  45. console.log( '- ' + response.statusCode + ': Error while refreshing the mediawiki token: ' + ( body?.message || body?.error ) );
  46. return Promise.reject(row);
  47. }
  48. if ( body?.refresh_token ) db.query( 'UPDATE oauthusers SET token = $1 WHERE userid = $2 AND site = $3', [body.refresh_token, msg.author.id, ( oauth[1] || oauth[0] )] ).then( () => {
  49. console.log( '- Dashboard: OAuth2 token for ' + msg.author.id + ' successfully updated.' );
  50. }, dberror => {
  51. console.log( '- Dashboard: Error while updating the OAuth2 token for ' + msg.author.id + ': ' + dberror );
  52. } );
  53. return global.verifyOauthUser('', body.access_token, {
  54. wiki: wiki.href,
  55. channel: msg.channel,
  56. user: msg.author.id,
  57. sourceMessage: msg
  58. });
  59. }, error => {
  60. console.log( '- Error while refreshing the mediawiki token: ' + error );
  61. return Promise.reject(row);
  62. } );
  63. return Promise.reject(row);
  64. }, dberror => {
  65. console.log( '- Error while getting the OAuth2 token: ' + dberror );
  66. return Promise.reject();
  67. } ).catch( row => {
  68. if ( row ) {
  69. if ( !row?.hasOwnProperty?.('token') ) console.log( '- Error while checking the OAuth2 refresh token: ' + row );
  70. else if ( row.token ) db.query( 'DELETE FROM oauthusers WHERE userid = $1 AND site = $2', [msg.author.id, ( oauth[1] || oauth[0] )] ).then( () => {
  71. console.log( '- Dashboard: OAuth2 token for ' + msg.author.id + ' successfully deleted.' );
  72. }, dberror => {
  73. console.log( '- Dashboard: Error while deleting the OAuth2 token for ' + msg.author.id + ': ' + dberror );
  74. } );
  75. }
  76. let state = `${oauth[0]} ${global.shardId}` + Date.now().toString(16) + randomBytes(16).toString('hex') + ( oauth[1] ? ` ${oauth[1]}` : '' );
  77. while ( oauthVerify.has(state) ) {
  78. state = `${oauth[0]} ${global.shardId}` + Date.now().toString(16) + randomBytes(16).toString('hex') + ( oauth[1] ? ` ${oauth[1]}` : '' );
  79. }
  80. oauthVerify.set(state, {
  81. state, wiki: wiki.href,
  82. channel: msg.channel,
  83. user: msg.author.id
  84. });
  85. msg.client.shard.send({id: 'verifyUser', state, user: ( row?.token === null ? '' : msg.author.id )});
  86. let oauthURL = wiki + 'rest.php/oauth2/authorize?' + new URLSearchParams({
  87. response_type: 'code', redirect_uri: new URL('/oauth/mw', process.env.dashboard).href,
  88. client_id: process.env['oauth_' + ( oauth[1] || oauth[0] )], state
  89. }).toString();
  90. return msg.member.send( lang.get('verify.oauth_message_dm', escapeFormatting(msg.guild.name)) + '\n<' + oauthURL + '>', {
  91. components: [
  92. {
  93. type: 1,
  94. components: [
  95. {
  96. type: 2,
  97. style: 5,
  98. label: lang.get('verify.oauth_button'),
  99. emoji: {id: null, name: '🔗'},
  100. url: oauthURL,
  101. disabled: false
  102. }
  103. ]
  104. }
  105. ]
  106. } ).then( message => {
  107. msg.reactEmoji('📩');
  108. allowDelete(message, msg.author.id);
  109. msg.delete({timeout: 60000, reason: lang.get('verify.footer')}).catch(log_error);
  110. }, error => {
  111. if ( error?.code === 50007 ) { // CANNOT_MESSAGE_USER
  112. return msg.replyMsg( lang.get('verify.oauth_private') );
  113. }
  114. log_error(error);
  115. msg.reactEmoji('error');
  116. } );
  117. } );
  118. }
  119. }
  120. var username = args.join(' ').replace( /_/g, ' ' ).trim().replace( /^<\s*(.*)\s*>$/, '$1' ).replace( /^@/, '' ).split('#')[0].substring(0, 250).trim();
  121. if ( /^(?:https?:)?\/\/([a-z\d-]{1,50})\.(?:gamepedia\.com\/|(?:fandom\.com|wikia\.org)\/(?:[a-z-]{1,8}\/)?(?:wiki\/)?)/.test(username) ) {
  122. username = decodeURIComponent( username.replace( /^(?:https?:)?\/\/([a-z\d-]{1,50})\.(?:gamepedia\.com\/|(?:fandom\.com|wikia\.org)\/(?:[a-z-]{1,8}\/)?(?:wiki\/)?)/, '' ) );
  123. }
  124. if ( wiki.isGamepedia() ) username = username.replace( /^userprofile\s*:\s*/i, '' );
  125. if ( !username.trim() ) {
  126. args[0] = line.split(' ')[0];
  127. if ( args[0] === 'verification' ) args[0] = ( lang.localNames.verify || 'verify' );
  128. return this.help(lang, msg, args, line, wiki);
  129. }
  130. msg.reactEmoji('⏳').then( reaction => {
  131. verify(lang, msg.channel, msg.member, username, wiki, rows).then( result => {
  132. if ( result.oauth.length ) {
  133. return db.query( 'SELECT token FROM oauthusers WHERE userid = $1 AND site = $2', [msg.author.id, ( result.oauth[1] || result.oauth[0] )] ).then( ({rows: [row]}) => {
  134. if ( row?.token ) return got.post( wiki + 'rest.php/oauth2/access_token', {
  135. form: {
  136. grant_type: 'refresh_token', refresh_token: row.token,
  137. redirect_uri: new URL('/oauth/mw', process.env.dashboard).href,
  138. client_id: process.env['oauth_' + ( result.oauth[1] || result.oauth[0] )],
  139. client_secret: process.env['oauth_' + ( result.oauth[1] || result.oauth[0] ) + '_secret']
  140. }
  141. } ).then( response => {
  142. var body = response.body;
  143. if ( response.statusCode !== 200 || !body?.access_token ) {
  144. console.log( '- ' + response.statusCode + ': Error while refreshing the mediawiki token: ' + ( body?.message || body?.error ) );
  145. return Promise.reject(row);
  146. }
  147. if ( body?.refresh_token ) db.query( 'UPDATE oauthusers SET token = $1 WHERE userid = $2 AND site = $3', [body.refresh_token, msg.author.id, ( result.oauth[1] || result.oauth[0] )] ).then( () => {
  148. console.log( '- Dashboard: OAuth2 token for ' + msg.author.id + ' successfully updated.' );
  149. }, dberror => {
  150. console.log( '- Dashboard: Error while updating the OAuth2 token for ' + msg.author.id + ': ' + dberror );
  151. } );
  152. return global.verifyOauthUser('', body.access_token, {
  153. wiki: wiki.href,
  154. channel: msg.channel,
  155. user: msg.author.id,
  156. sourceMessage: msg
  157. });
  158. }, error => {
  159. console.log( '- Error while refreshing the mediawiki token: ' + error );
  160. return Promise.reject(row);
  161. } );
  162. return Promise.reject(row);
  163. }, dberror => {
  164. console.log( '- Error while getting the OAuth2 token: ' + dberror );
  165. return Promise.reject();
  166. } ).catch( row => {
  167. if ( row ) {
  168. if ( !row?.hasOwnProperty?.('token') ) console.log( '- Error while checking the OAuth2 refresh token: ' + row );
  169. else if ( row.token ) db.query( 'DELETE FROM oauthusers WHERE userid = $1 AND site = $2', [msg.author.id, ( result.oauth[1] || result.oauth[0] )] ).then( () => {
  170. console.log( '- Dashboard: OAuth2 token for ' + msg.author.id + ' successfully deleted.' );
  171. }, dberror => {
  172. console.log( '- Dashboard: Error while deleting the OAuth2 token for ' + msg.author.id + ': ' + dberror );
  173. } );
  174. }
  175. let state = `${result.oauth[0]} ${global.shardId}` + Date.now().toString(16) + randomBytes(16).toString('hex') + ( result.oauth[1] ? ` ${result.oauth[1]}` : '' );
  176. while ( oauthVerify.has(state) ) {
  177. state = `${result.oauth[0]} ${global.shardId}` + Date.now().toString(16) + randomBytes(16).toString('hex') + ( result.oauth[1] ? ` ${result.oauth[1]}` : '' );
  178. }
  179. oauthVerify.set(state, {
  180. state, wiki: wiki.href,
  181. channel: msg.channel,
  182. user: msg.author.id
  183. });
  184. msg.client.shard.send({id: 'verifyUser', state, user: ( row?.token === null ? '' : msg.author.id )});
  185. let oauthURL = wiki + 'rest.php/oauth2/authorize?' + new URLSearchParams({
  186. response_type: 'code', redirect_uri: new URL('/oauth/mw', process.env.dashboard).href,
  187. client_id: process.env['oauth_' + ( result.oauth[1] || result.oauth[0] )], state
  188. }).toString();
  189. msg.member.send( lang.get('verify.oauth_message_dm', escapeFormatting(msg.guild.name)) + '\n<' + oauthURL + '>', {
  190. components: [
  191. {
  192. type: 1,
  193. components: [
  194. {
  195. type: 2,
  196. style: 5,
  197. label: lang.get('verify.oauth_button'),
  198. emoji: {id: null, name: '🔗'},
  199. url: oauthURL,
  200. disabled: false
  201. }
  202. ]
  203. }
  204. ]
  205. } ).then( message => {
  206. msg.reactEmoji('📩');
  207. allowDelete(message, msg.author.id);
  208. msg.delete({timeout: 60000, reason: lang.get('verify.footer')}).catch(log_error);
  209. }, error => {
  210. if ( error?.code === 50007 ) { // CANNOT_MESSAGE_USER
  211. return msg.replyMsg( lang.get('verify.oauth_private') );
  212. }
  213. log_error(error);
  214. msg.reactEmoji('error');
  215. } );
  216. } );
  217. }
  218. else if ( result.reaction ) msg.reactEmoji(result.reaction);
  219. else {
  220. var options = {embed: result.embed, components: []};
  221. if ( result.add_button ) options.components.push({
  222. type: 1,
  223. components: [
  224. {
  225. type: 2,
  226. style: 1,
  227. label: lang.get('verify.button_again'),
  228. emoji: {id: null, name: '🔂'},
  229. custom_id: 'verify_again',
  230. disabled: false
  231. }
  232. ]
  233. });
  234. if ( result.send_private ) {
  235. let dmEmbed = new MessageEmbed(options.embed);
  236. dmEmbed.fields.forEach( field => {
  237. field.value = field.value.replace( /<@&(\d+)>/g, (mention, id) => {
  238. if ( !msg.guild.roles.cache.has(id) ) return mention;
  239. return escapeFormatting('@' + msg.guild.roles.cache.get(id)?.name);
  240. } );
  241. } );
  242. msg.member.send( msg.channel.toString() + '; ' + result.content, {embed: dmEmbed, components: []} ).then( message => {
  243. msg.reactEmoji('📩');
  244. allowDelete(message, msg.author.id);
  245. msg.delete({timeout: 60000, reason: lang.get('verify.footer')}).catch(log_error);
  246. }, error => {
  247. if ( error?.code === 50007 ) { // CANNOT_MESSAGE_USER
  248. return msg.replyMsg( result.content, options, false, false );
  249. }
  250. log_error(error);
  251. msg.reactEmoji('error');
  252. } );
  253. if ( result.logging.channel && msg.guild.channels.cache.has(result.logging.channel) ) {
  254. msg.guild.channels.cache.get(result.logging.channel).send(result.logging.content, {
  255. embed: result.logging.embed,
  256. allowedMentions: {parse: []}
  257. }).catch(log_error);
  258. }
  259. }
  260. else msg.replyMsg( result.content, options, false, false ).then( message => {
  261. if ( !result.logging.channel || !msg.guild.channels.cache.has(result.logging.channel) ) return;
  262. if ( message ) {
  263. if ( result.logging.embed ) result.logging.embed.addField(message.url, '<#' + msg.channel.id + '>');
  264. else result.logging.content += '\n<#' + msg.channel.id + '> – <' + message.url + '>';
  265. }
  266. msg.guild.channels.cache.get(result.logging.channel).send(result.logging.content, {
  267. embed: result.logging.embed,
  268. allowedMentions: {parse: []}
  269. }).catch(log_error);
  270. } );
  271. }
  272. if ( reaction ) reaction.removeEmoji();
  273. }, error => {
  274. console.log( '- Error during the verifications: ' + error );
  275. msg.replyMsg( lang.get('verify.error_reply'), {}, false, false ).then( message => {
  276. if ( message ) message.reactEmoji('error');
  277. } );
  278. } );
  279. } );
  280. }, dberror => {
  281. console.log( '- Error while getting the verifications: ' + dberror );
  282. msg.replyMsg( lang.get('verify.error_reply'), {}, false, false ).then( message => {
  283. if ( message ) message.reactEmoji('error');
  284. } );
  285. } );
  286. }
  287. module.exports = {
  288. name: 'verify',
  289. everyone: true,
  290. pause: false,
  291. owner: false,
  292. run: cmd_verify
  293. };