verify.js 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511
  1. const {randomBytes} = require('crypto');
  2. var db = require('../util/database.js');
  3. var verify = require('../functions/verify.js');
  4. const {oauthVerify, sendMessage} = require('../util/functions.js');
  5. /**
  6. * Wiki user verification.
  7. * @param {Object} interaction - The interaction.
  8. * @param {import('discord.js').Client} interaction.client - The client of the interaction.
  9. * @param {import('../util/i18n.js')} lang - The user language.
  10. * @param {import('../util/wiki.js')} wiki - The wiki for the interaction.
  11. * @param {import('discord.js').TextChannel} [channel] - The channel for the interaction.
  12. */
  13. function slash_verify(interaction, lang, wiki, channel) {
  14. var reply = '<@' + ( interaction.member?.nick ? '!' : '' ) + interaction.user.id + '>, ';
  15. var allowed_mentions = {
  16. users: [interaction.user.id]
  17. };
  18. if ( !channel?.guild ) return interaction.client.api.interactions(interaction.id, interaction.token).callback.post( {
  19. data: {
  20. type: 4,
  21. data: {
  22. content: reply + lang.get('verify.missing'),
  23. allowed_mentions,
  24. flags: 64
  25. }
  26. }
  27. } ).catch(log_error);
  28. if ( !channel.guild.me.permissions.has('MANAGE_ROLES') ) {
  29. console.log( channel.guild.id + ': Missing permissions - MANAGE_ROLES' );
  30. return interaction.client.api.interactions(interaction.id, interaction.token).callback.post( {
  31. data: {
  32. type: 4,
  33. data: {
  34. content: reply + lang.get('general.missingperm') + ' `MANAGE_ROLES`',
  35. allowed_mentions,
  36. flags: 64
  37. }
  38. }
  39. } ).catch(log_error);
  40. }
  41. return 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', [interaction.guild_id, '%|' + interaction.channel_id + '|%'] ).then( ({rows}) => {
  42. if ( !rows.length ) return interaction.client.api.interactions(interaction.id, interaction.token).callback.post( {
  43. data: {
  44. type: 4,
  45. data: {
  46. content: reply + lang.get('verify.missing') + ( interaction.member.permissions.has('MANAGE_GUILD') && process.env.dashboard ? '\n' + new URL(`/guild/${interaction.guild_id}/verification`, process.env.dashboard).href : '' ),
  47. allowed_mentions,
  48. flags: 64
  49. }
  50. }
  51. } ).catch(log_error);
  52. if ( wiki.hasOAuth2() && process.env.dashboard ) {
  53. let oauth = [wiki.hostname + wiki.pathname.slice(0, -1)];
  54. if ( wiki.isWikimedia() ) oauth.push('wikimedia');
  55. if ( wiki.isMiraheze() ) oauth.push('miraheze');
  56. if ( process.env['oauth_' + ( oauth[1] || oauth[0] )] && process.env['oauth_' + ( oauth[1] || oauth[0] ) + '_secret'] ) {
  57. let state = `${oauth[0]} ${global.shardId}` + Date.now().toString(16) + randomBytes(16).toString('hex') + ( oauth[1] ? ` ${oauth[1]}` : '' );
  58. while ( oauthVerify.has(state) ) {
  59. state = `${oauth[0]} ${global.shardId}` + Date.now().toString(16) + randomBytes(16).toString('hex') + ( oauth[1] ? ` ${oauth[1]}` : '' );
  60. }
  61. oauthVerify.set(state, {
  62. state, wiki: wiki.href, channel,
  63. user: interaction.user.id,
  64. token: interaction.token
  65. });
  66. interaction.client.shard.send({id: 'verifyUser', state});
  67. let oauthURL = wiki + 'rest.php/oauth2/authorize?' + new URLSearchParams({
  68. response_type: 'code', redirect_uri: new URL('/oauth/mw', process.env.dashboard).href,
  69. client_id: process.env['oauth_' + ( oauth[1] || oauth[0] )], state
  70. }).toString();
  71. return interaction.client.api.interactions(interaction.id, interaction.token).callback.post( {
  72. data: {
  73. type: 4,
  74. data: {
  75. content: reply + lang.get('verify.oauth_message', '<' + oauthURL + '>'),
  76. allowed_mentions,
  77. components: [
  78. {
  79. type: 1,
  80. components: [
  81. {
  82. type: 2,
  83. style: 5,
  84. label: lang.get('verify.oauth_button'),
  85. emoji: {id: null, name: '🔗'},
  86. url: oauthURL,
  87. disabled: false
  88. }
  89. ]
  90. }
  91. ],
  92. flags: 64
  93. }
  94. }
  95. } ).catch(log_error);
  96. }
  97. }
  98. var username = ( interaction.data.options?.[0]?.value || '' ).replace( /^\s*<@!?(\d+)>\s*$/, (mention, id) => {
  99. if ( id === interaction.user.id ) {
  100. return ( interaction.member?.nick || interaction.user.username );
  101. }
  102. let user = channel.guild.members.cache.get(id);
  103. if ( user ) return user.displayName;
  104. else {
  105. user = interaction.client.users.cache.get(user);
  106. if ( user ) return user.username;
  107. }
  108. return mention;
  109. } ).replace( /_/g, ' ' ).trim().replace( /^<\s*(.*)\s*>$/, '$1' ).split('#')[0].substring(0, 250).trim();
  110. if ( /^(?:https?:)?\/\/([a-z\d-]{1,50})\.(?:gamepedia\.com\/|(?:fandom\.com|wikia\.org)\/(?:[a-z-]{1,8}\/)?(?:wiki\/)?)/.test(username) ) {
  111. username = decodeURIComponent( username.replace( /^(?:https?:)?\/\/([a-z\d-]{1,50})\.(?:gamepedia\.com\/|(?:fandom\.com|wikia\.org)\/(?:[a-z-]{1,8}\/)?(?:wiki\/)?)/, '' ) );
  112. }
  113. if ( wiki.isGamepedia() ) username = username.replace( /^userprofile\s*:\s*/i, '' );
  114. if ( !username.trim() ) return interaction.client.api.interactions(interaction.id, interaction.token).callback.post( {
  115. data: {
  116. type: 4,
  117. data: {
  118. content: lang.get('interaction.verify'),
  119. allowed_mentions,
  120. flags: 64
  121. }
  122. }
  123. } ).catch(log_error);
  124. return interaction.client.api.interactions(interaction.id, interaction.token).callback.post( {
  125. data: {
  126. type: 5,
  127. data: {
  128. allowed_mentions,
  129. flags: ( (rows[0].flags & 1 << 0) === 1 << 0 ? 64 : 0 )
  130. }
  131. }
  132. } ).then( () => {
  133. return channel.guild.members.fetch(interaction.user.id).then( member => {
  134. return verify(lang, channel, member, username, wiki, rows).then( result => {
  135. if ( result.oauth.length ) {
  136. let state = `${result.oauth[0]} ${global.shardId}` + Date.now().toString(16) + randomBytes(16).toString('hex') + ( result.oauth[1] ? ` ${result.oauth[1]}` : '' );
  137. while ( oauthVerify.has(state) ) {
  138. state = `${result.oauth[0]} ${global.shardId}` + Date.now().toString(16) + randomBytes(16).toString('hex') + ( result.oauth[1] ? ` ${result.oauth[1]}` : '' );
  139. }
  140. oauthVerify.set(state, {
  141. state, wiki: wiki.href, channel,
  142. user: interaction.user.id,
  143. token: interaction.token
  144. });
  145. interaction.client.shard.send({id: 'verifyUser', state});
  146. let oauthURL = wiki + 'rest.php/oauth2/authorize?' + new URLSearchParams({
  147. response_type: 'code', redirect_uri: new URL('/oauth/mw', process.env.dashboard).href,
  148. client_id: process.env['oauth_' + ( result.oauth[1] || result.oauth[0] )], state
  149. }).toString();
  150. let message = {
  151. content: reply + lang.get('verify.oauth_message', '<' + oauthURL + '>'),
  152. allowed_mentions,
  153. components: [
  154. {
  155. type: 1,
  156. components: [
  157. {
  158. type: 2,
  159. style: 5,
  160. label: lang.get('verify.oauth_button'),
  161. emoji: {id: null, name: '🔗'},
  162. url: oauthURL,
  163. disabled: false
  164. }
  165. ]
  166. }
  167. ]
  168. }
  169. if ( result.send_private ) return sendMessage(interaction, message, channel, false);
  170. message.flags = 64;
  171. return interaction.client.api.webhooks(interaction.application_id, interaction.token).messages('@original').delete().then( () => {
  172. return interaction.client.api.webhooks(interaction.application_id, interaction.token).post( {
  173. data: message
  174. } ).catch(log_error);
  175. }, log_error );
  176. }
  177. var message = {
  178. content: reply + result.content,
  179. embeds: [result.embed],
  180. allowed_mentions,
  181. components: []
  182. };
  183. if ( result.add_button && !result.send_private ) message.components.push({
  184. type: 1,
  185. components: [
  186. {
  187. type: 2,
  188. style: 1,
  189. label: lang.get('verify.button_again'),
  190. emoji: {id: null, name: '🔂'},
  191. custom_id: 'verify_again',
  192. disabled: false
  193. }
  194. ]
  195. });
  196. if ( result.reaction ) {
  197. if ( result.reaction === 'nowiki' ) message.content = lang.get('interaction.nowiki');
  198. else message.content = reply + lang.get('verify.error_reply');
  199. message.embeds = [];
  200. }
  201. return sendMessage(interaction, message, channel, false).then( msg => {
  202. if ( !result.logging.channel || !channel.guild.channels.cache.has(result.logging.channel) ) return;
  203. if ( msg && !result.send_private ) {
  204. if ( result.logging.embed ) result.logging.embed.addField(msg.url, '<#' + channel.id + '>');
  205. else result.logging.content += '\n<#' + channel.id + '> – <' + msg.url + '>';
  206. }
  207. channel.guild.channels.cache.get(result.logging.channel).send(result.logging.content, {
  208. embed: result.logging.embed,
  209. allowedMentions: {parse: []}
  210. }).catch(log_error);
  211. } );
  212. }, error => {
  213. console.log( '- Error during the verifications: ' + error );
  214. return sendMessage(interaction, {
  215. content: reply + lang.get('verify.error_reply'),
  216. allowed_mentions
  217. }, channel);
  218. } );
  219. }, error => {
  220. console.log( '- Error while getting the member: ' + error );
  221. return sendMessage(interaction, {
  222. content: reply + lang.get('verify.error_reply'),
  223. allowed_mentions
  224. }, channel);
  225. } );
  226. }, log_error );
  227. }, dberror => {
  228. console.log( '- Error while getting the verifications: ' + dberror );
  229. return interaction.client.api.interactions(interaction.id, interaction.token).callback.post( {
  230. data: {
  231. type: 4,
  232. data: {
  233. content: reply + lang.get('verify.error_reply'),
  234. allowed_mentions,
  235. flags: 64
  236. }
  237. }
  238. } ).catch(log_error);
  239. } );
  240. }
  241. /**
  242. * Wiki user verification.
  243. * @param {Object} interaction - The interaction.
  244. * @param {import('discord.js').Client} interaction.client - The client of the interaction.
  245. * @param {import('../util/i18n.js')} lang - The user language.
  246. * @param {import('../util/wiki.js')} wiki - The wiki for the interaction.
  247. * @param {import('discord.js').TextChannel} [channel] - The channel for the interaction.
  248. */
  249. function button_verify(interaction, lang, wiki, channel) {
  250. var username = interaction?.message?.embeds?.[0]?.title?.replace( /\\(\\)?/g, '$1' );
  251. if ( !username || !channel?.guild || !interaction.message?.mentions?.[0]?.id ) {
  252. interaction.message.allowed_mentions = {
  253. users: [interaction.user.id]
  254. };
  255. interaction.message.components = [];
  256. return interaction.client.api.interactions(interaction.id, interaction.token).callback.post( {
  257. data: {
  258. type: 7,
  259. data: interaction.message
  260. }
  261. } ).catch(log_error);
  262. }
  263. if ( interaction.user.id !== interaction.message.mentions[0].id ) {
  264. return interaction.client.api.interactions(interaction.id, interaction.token).callback.post( {
  265. data: {type: 6}
  266. } ).then( () => {
  267. interaction.client.api.webhooks(interaction.application_id, interaction.token).post( {
  268. data: {
  269. content: lang.get('verify.button_wrong_user', `<@${interaction.message.mentions[0].id}>`),
  270. allowed_mentions: {
  271. parse: []
  272. },
  273. flags: 64
  274. }
  275. } ).catch(log_error);
  276. }, log_error);
  277. }
  278. return 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', [interaction.guild_id, '%|' + interaction.channel_id + '|%'] ).then( ({rows}) => {
  279. if ( !rows.length || !channel.guild.me.permissions.has('MANAGE_ROLES') ) {
  280. return interaction.client.api.interactions(interaction.id, interaction.token).callback.post( {
  281. data: {type: 6}
  282. } ).catch(log_error);
  283. }
  284. var reply = '<@' + ( interaction.member?.nick ? '!' : '' ) + interaction.user.id + '>, ';
  285. var allowed_mentions = {
  286. users: [interaction.user.id]
  287. };
  288. interaction.message.allowed_mentions = allowed_mentions;
  289. if ( interaction?.message?.embeds?.[0]?.fields?.[1]?.value === lang.get('verify.oauth_used') && interaction?.message?.embeds?.[0]?.url?.startsWith( wiki.origin ) ) {
  290. console.log( interaction.guild_id + ': Button: ' + interaction.data.custom_id + ': OAuth2: ' + username );
  291. interaction.message.components[0].components[0].disabled = true;
  292. return interaction.client.api.interactions(interaction.id, interaction.token).callback.post( {
  293. data: {
  294. type: 7,
  295. data: interaction.message
  296. }
  297. } ).then( () => {
  298. return global.verifyOauthUser('', '', {
  299. channel, username, user: interaction.user.id,
  300. send: function(content, options) {
  301. if ( !content && !options ) {
  302. interaction.message.components = [];
  303. return sendMessage(interaction, interaction.message, channel, false);
  304. }
  305. var message = {
  306. content, allowed_mentions,
  307. embeds: ( options.embed ? [options.embed] : [] ),
  308. components: ( options.components ? options.components : [] )
  309. };
  310. var msg = sendMessage(interaction, message, channel, false);
  311. interaction.client.api.webhooks(interaction.application_id, interaction.token).post( {
  312. data: {
  313. content, allowed_mentions,
  314. embeds: ( options.embed ? [options.embed] : [] ),
  315. components: [],
  316. flags: 64
  317. }
  318. } ).catch(log_error);
  319. return msg;
  320. }
  321. });
  322. }, log_error );
  323. }
  324. if ( wiki.hasOAuth2() && process.env.dashboard ) {
  325. let oauth = [wiki.hostname + wiki.pathname.slice(0, -1)];
  326. if ( wiki.isWikimedia() ) oauth.push('wikimedia');
  327. if ( wiki.isMiraheze() ) oauth.push('miraheze');
  328. if ( process.env['oauth_' + ( oauth[1] || oauth[0] )] && process.env['oauth_' + ( oauth[1] || oauth[0] ) + '_secret'] ) {
  329. console.log( interaction.guild_id + ': Button: ' + interaction.data.custom_id + ': OAuth2' );
  330. let state = `${oauth[0]} ${global.shardId}` + Date.now().toString(16) + randomBytes(16).toString('hex') + ( oauth[1] ? ` ${oauth[1]}` : '' );
  331. while ( oauthVerify.has(state) ) {
  332. state = `${oauth[0]} ${global.shardId}` + Date.now().toString(16) + randomBytes(16).toString('hex') + ( oauth[1] ? ` ${oauth[1]}` : '' );
  333. }
  334. oauthVerify.set(state, {
  335. state, wiki: wiki.href, channel,
  336. user: interaction.user.id,
  337. token: interaction.token
  338. });
  339. interaction.client.shard.send({id: 'verifyUser', state});
  340. let oauthURL = wiki + 'rest.php/oauth2/authorize?' + new URLSearchParams({
  341. response_type: 'code', redirect_uri: new URL('/oauth/mw', process.env.dashboard).href,
  342. client_id: process.env['oauth_' + ( oauth[1] || oauth[0] )], state
  343. }).toString();
  344. interaction.message.components = [];
  345. interaction.client.api.interactions(interaction.id, interaction.token).callback.post( {
  346. data: {
  347. type: 7,
  348. data: interaction.message
  349. }
  350. } ).catch(log_error);
  351. return interaction.client.api.webhooks(interaction.application_id, interaction.token).post( {
  352. data: {
  353. content: reply + lang.get('verify.oauth_message', '<' + oauthURL + '>'),
  354. allowed_mentions,
  355. components: [
  356. {
  357. type: 1,
  358. components: [
  359. {
  360. type: 2,
  361. style: 5,
  362. label: lang.get('verify.oauth_button'),
  363. emoji: {id: null, name: '🔗'},
  364. url: oauthURL,
  365. disabled: false
  366. }
  367. ]
  368. }
  369. ],
  370. flags: 64
  371. }
  372. } ).catch(log_error);
  373. }
  374. }
  375. interaction.message.components[0].components[0].disabled = true;
  376. return interaction.client.api.interactions(interaction.id, interaction.token).callback.post( {
  377. data: {
  378. type: 7,
  379. data: interaction.message
  380. }
  381. } ).then( () => {
  382. return channel.guild.members.fetch(interaction.user.id).then( member => {
  383. console.log( interaction.guild_id + ': Button: ' + interaction.data.custom_id + ' ' + username );
  384. return verify(lang, channel, member, username, wiki, rows).then( result => {
  385. if ( result.oauth.length ) {
  386. let state = `${result.oauth[0]} ${global.shardId}` + Date.now().toString(16) + randomBytes(16).toString('hex') + ( result.oauth[1] ? ` ${result.oauth[1]}` : '' );
  387. while ( oauthVerify.has(state) ) {
  388. state = `${result.oauth[0]} ${global.shardId}` + Date.now().toString(16) + randomBytes(16).toString('hex') + ( result.oauth[1] ? ` ${result.oauth[1]}` : '' );
  389. }
  390. oauthVerify.set(state, {
  391. state, wiki: wiki.href, channel,
  392. user: interaction.user.id,
  393. token: interaction.token
  394. });
  395. interaction.client.shard.send({id: 'verifyUser', state});
  396. let oauthURL = wiki + 'rest.php/oauth2/authorize?' + new URLSearchParams({
  397. response_type: 'code', redirect_uri: new URL('/oauth/mw', process.env.dashboard).href,
  398. client_id: process.env['oauth_' + ( result.oauth[1] || result.oauth[0] )], state
  399. }).toString();
  400. interaction.message.components = [];
  401. interaction.client.api.interactions(interaction.id, interaction.token).callback.post( {
  402. data: {
  403. type: 7,
  404. data: interaction.message
  405. }
  406. } ).catch(log_error);
  407. return interaction.client.api.webhooks(interaction.application_id, interaction.token).post( {
  408. data: {
  409. content: reply + lang.get('verify.oauth_message', '<' + oauthURL + '>'),
  410. allowed_mentions,
  411. components: [
  412. {
  413. type: 1,
  414. components: [
  415. {
  416. type: 2,
  417. style: 5,
  418. label: lang.get('verify.oauth_button'),
  419. emoji: {id: null, name: '🔗'},
  420. url: oauthURL,
  421. disabled: false
  422. }
  423. ]
  424. }
  425. ],
  426. flags: 64
  427. }
  428. } ).catch(log_error);
  429. }
  430. var message = {
  431. content: reply + result.content,
  432. embeds: [result.embed],
  433. allowed_mentions,
  434. components: []
  435. };
  436. if ( result.reaction ) {
  437. if ( result.reaction === 'nowiki' ) message.content = lang.get('interaction.nowiki');
  438. else message.content = reply + lang.get('verify.error_reply');
  439. message.embeds = [];
  440. }
  441. else if ( result.add_button ) message.components.push({
  442. type: 1,
  443. components: [
  444. {
  445. type: 2,
  446. style: 1,
  447. label: lang.get('verify.button_again'),
  448. emoji: {id: null, name: '🔂'},
  449. custom_id: 'verify_again',
  450. disabled: false
  451. }
  452. ]
  453. });
  454. sendMessage(interaction, message, channel, false);
  455. if ( result.logging.channel && channel.guild.channels.cache.has(result.logging.channel) ) {
  456. if ( !result.send_private ) {
  457. let msg_url = `https://discord.com/channels/${channel.guild.id}/${channel.id}/${interaction.message.id}`;
  458. if ( result.logging.embed ) result.logging.embed.addField(msg_url, '<#' + channel.id + '>');
  459. else result.logging.content += '\n<#' + channel.id + '> – <' + msg_url + '>';
  460. }
  461. channel.guild.channels.cache.get(result.logging.channel).send(result.logging.content, {
  462. embed: result.logging.embed,
  463. allowedMentions: {parse: []}
  464. }).catch(log_error);
  465. }
  466. interaction.client.api.webhooks(interaction.application_id, interaction.token).post( {
  467. data: {
  468. content: message.content,
  469. embeds: message.embeds,
  470. allowed_mentions,
  471. components: [],
  472. flags: 64
  473. }
  474. } ).catch(log_error);
  475. }, error => {
  476. console.log( '- Error during the verifications: ' + error );
  477. return sendMessage(interaction, {
  478. content: reply + lang.get('verify.error_reply'),
  479. allowed_mentions
  480. }, channel);
  481. } );
  482. }, error => {
  483. console.log( '- Error while getting the member: ' + error );
  484. return sendMessage(interaction, {
  485. content: reply + lang.get('verify.error_reply'),
  486. allowed_mentions
  487. }, channel);
  488. } );
  489. }, log_error);
  490. }, dberror => {
  491. console.log( '- Error while getting the verifications: ' + dberror );
  492. return interaction.client.api.interactions(interaction.id, interaction.token).callback.post( {
  493. data: {type: 6}
  494. } ).catch(log_error);
  495. } );
  496. }
  497. module.exports = {
  498. name: 'verify',
  499. run: slash_verify,
  500. button: button_verify
  501. };