verify.js 18 KB

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