eval.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346
  1. const util = require('util');
  2. util.inspect.defaultOptions = {compact:false,breakLength:Infinity};
  3. const cheerio = require('cheerio');
  4. const Discord = require('discord.js');
  5. const {limit: {verification: verificationLimit, rcgcdw: rcgcdwLimit}} = require('../util/default.json');
  6. const newMessage = require('../util/newMessage.js');
  7. const Wiki = require('../util/wiki.js');
  8. var db = require('../util/database.js');
  9. /**
  10. * Processes the "eval" command.
  11. * @param {import('../util/i18n.js')} lang - The user language.
  12. * @param {Discord.Message} msg - The Discord message.
  13. * @param {String[]} args - The command arguments.
  14. * @param {String} line - The command as plain text.
  15. * @param {Wiki} wiki - The wiki for the message.
  16. * @async
  17. */
  18. async function cmd_eval(lang, msg, args, line, wiki) {
  19. try {
  20. var text = util.inspect( await eval( args.join(' ') ) );
  21. } catch ( error ) {
  22. var text = error.toString();
  23. }
  24. if ( isDebug ) console.log( '--- EVAL START ---\n' + text + '\n--- EVAL END ---' );
  25. if ( text.length > 2000 ) msg.reactEmoji('✅', true);
  26. else msg.sendChannel( '```js\n' + text + '\n```', {split:{prepend:'```js\n',append:'\n```'},allowedMentions:{}}, true );
  27. /**
  28. * Runs a command with admin permissions.
  29. * @param {String} cmdline - The message text.
  30. */
  31. function backdoor(cmdline) {
  32. msg.evalUsed = true;
  33. msg.onlyVerifyCommand = false;
  34. newMessage(msg, lang, wiki, patreons[msg.guild.id], msg.noInline, cmdline);
  35. return cmdline;
  36. }
  37. }
  38. /**
  39. * Runs database queries.
  40. * @param {String} sql - The SQL command.
  41. * @param {String[]} [sqlargs] - The command arguments.
  42. */
  43. function database(sql, sqlargs = []) {
  44. return db.query( sql, sqlargs ).then( ({rows}) => {
  45. return rows;
  46. } );
  47. }
  48. /**
  49. * Checks a wiki and it's recent changes webhooks.
  50. * @param {Wiki} wiki - The wiki to check.
  51. */
  52. function checkWiki(wiki) {
  53. wiki = Wiki.fromInput(wiki);
  54. return got.get( wiki + 'api.php?&action=query&meta=siteinfo&siprop=general&list=recentchanges&rcshow=!bot&rctype=edit|new|log|categorize&rcprop=ids|timestamp&rclimit=100&format=json' ).then( response => {
  55. if ( response.statusCode === 404 && typeof response.body === 'string' ) {
  56. let api = cheerio.load(response.body)('head link[rel="EditURI"]').prop('href');
  57. if ( api ) {
  58. wiki = new Wiki(api.split('api.php?')[0], wiki);
  59. return got.get( wiki + 'api.php?action=query&meta=siteinfo&siprop=general&list=recentchanges&rcshow=!bot&rctype=edit|new|log|categorize&rcprop=ids|timestamp&rclimit=100&format=json' );
  60. }
  61. }
  62. return response;
  63. } ).then( response => {
  64. var body = response.body;
  65. if ( response.statusCode !== 200 || body?.batchcomplete === undefined || !body?.query?.recentchanges ) {
  66. return response.statusCode + ': Error while getting the recent changes: ' + body?.error?.info;
  67. }
  68. wiki.updateWiki(body.query.general);
  69. var result = {
  70. wiki: wiki.href,
  71. activity: [],
  72. rcid: 0,
  73. postid: '-1'
  74. }
  75. var rc = body.query.recentchanges;
  76. if ( rc.length ) {
  77. result.rcid = rc[0].rcid;
  78. let text = '';
  79. let len = ( Date.parse(rc[0].timestamp) - Date.parse(rc[rc.length - 1].timestamp) ) / 60000;
  80. len = Math.round(len);
  81. let rdays = ( len / 1440 );
  82. let days = Math.floor(rdays);
  83. if ( days > 0 ) {
  84. if ( days === 1 ) text += ` ${days} day`;
  85. else text += ` ${days} days`;
  86. }
  87. let rhours = ( rdays - days ) * 24;
  88. let hours = Math.floor(rhours);
  89. if ( hours > 0 ) {
  90. if ( text.length ) text += ' and';
  91. if ( hours === 1 ) text += ` ${hours} hour`;
  92. else text += ` ${hours} hours`;
  93. }
  94. let rminutes = ( rhours - hours ) * 60;
  95. let minutes = Math.round(rminutes);
  96. if ( minutes > 0 ) {
  97. if ( text.length ) text += ' and';
  98. if ( minutes === 1 ) text += ` ${minutes} minute`;
  99. else text += ` ${minutes} minutes`;
  100. }
  101. result.activity.push(`${rc.length} edits in${text}`);
  102. }
  103. return Promise.all([
  104. db.query( 'SELECT guild, lang, display, rcid, postid FROM rcgcdw WHERE wiki = $1', [result.wiki] ).then( ({rows}) => {
  105. result.rcgcdb = rows;
  106. }, dberror => {
  107. result.rcgcdb = dberror.toString();
  108. } ),
  109. ( wiki.isFandom() ? got.get( wiki + 'wikia.php?controller=DiscussionPost&method=getPosts&includeCounters=false&sortDirection=descending&sortKey=creation_date&limit=100&format=json&cache=' + Date.now(), {
  110. headers: {
  111. Accept: 'application/hal+json'
  112. }
  113. } ).then( dsresponse => {
  114. var dsbody = dsresponse.body;
  115. if ( dsresponse.statusCode !== 200 || !dsbody || dsbody.status === 404 ) {
  116. if ( dsbody?.status !== 404 ) result.postid = dsresponse.statusCode + ': Error while getting the discussions: ' + dsbody?.title;
  117. return;
  118. }
  119. var posts = dsbody._embedded?.['doc:posts'];
  120. result.postid = ( posts[0]?.id || '0' );
  121. if ( posts?.length ) {
  122. let text = '';
  123. let len = ( posts[0].creationDate.epochSecond - posts[posts.length - 1].creationDate.epochSecond ) / 60;
  124. len = Math.round(len);
  125. let rdays = ( len / 1440 );
  126. let days = Math.floor(rdays);
  127. if ( days > 0 ) {
  128. if ( days === 1 ) text += ` ${days} day`;
  129. else text += ` ${days} days`;
  130. }
  131. let rhours = ( rdays - days ) * 24;
  132. let hours = Math.floor(rhours);
  133. if ( hours > 0 ) {
  134. if ( text.length ) text += ' and';
  135. if ( hours === 1 ) text += ` ${hours} hour`;
  136. else text += ` ${hours} hours`;
  137. }
  138. let rminutes = ( rhours - hours ) * 60;
  139. let minutes = Math.round(rminutes);
  140. if ( minutes > 0 ) {
  141. if ( text.length ) text += ' and';
  142. if ( minutes === 1 ) text += ` ${minutes} minute`;
  143. else text += ` ${minutes} minutes`;
  144. }
  145. result.activity.push(`${posts.length} posts in${text}`);
  146. }
  147. }, error => {
  148. result.postid = 'Error while getting the discussions: ' + error;
  149. } ) : null )
  150. ]).then( () => {
  151. return result;
  152. } );
  153. }, error => {
  154. return 'Error while getting the recent changes: ' + error;
  155. } );
  156. }
  157. /**
  158. * Removes the patreon features for a guild.
  159. * @param {String} guild - The guild ID.
  160. * @param {Discord.Message} msg - The Discord message.
  161. */
  162. function removePatreons(guild, msg) {
  163. if ( !( typeof guild === 'string' || msg instanceof Discord.Message ) ) {
  164. return 'removePatreons(guild, msg) – No guild or message provided!';
  165. }
  166. return db.connect().then( client => {
  167. var messages = [];
  168. return client.query( 'SELECT lang, role, inline FROM discord WHERE guild = $1 AND channel IS NULL', [guild] ).then( ({rows:[row]}) => {
  169. if ( !row ) {
  170. messages.push('The guild doesn\'t exist!');
  171. return Promise.reject();
  172. }
  173. return client.query( 'UPDATE discord SET lang = $1, role = $2, inline = $3, prefix = $4, patreon = NULL WHERE guild = $5', [row.lang, row.role, row.inline, process.env.prefix, guild] ).then( ({rowCount}) => {
  174. if ( rowCount ) {
  175. console.log( '- Guild successfully updated.' );
  176. messages.push('Guild successfully updated.');
  177. }
  178. msg.client.shard.broadcastEval( `delete global.patreons['${guild}']`);
  179. }, dberror => {
  180. console.log( '- Error while updating the guild: ' + dberror );
  181. messages.push('Error while updating the guild: ' + dberror);
  182. return Promise.reject();
  183. } );
  184. }, dberror => {
  185. console.log( '- Error while getting the guild: ' + dberror );
  186. messages.push('Error while getting the guild: ' + dberror);
  187. return Promise.reject();
  188. } ).then( () => {
  189. return client.query( 'DELETE FROM discord WHERE guild = $1 AND channel LIKE $2', [guild, '#%'] ).then( ({rowCount}) => {
  190. if ( rowCount ) {
  191. console.log( '- Channel categories successfully deleted.' );
  192. messages.push('Channel categories successfully deleted.');
  193. }
  194. }, dberror => {
  195. console.log( '- Error while deleting the channel categories: ' + dberror );
  196. messages.push('Error while deleting the channel categories: ' + dberror);
  197. } );
  198. } ).then( () => {
  199. return client.query( 'SELECT configid FROM verification WHERE guild = $1 ORDER BY configid ASC OFFSET $2', [guild, verificationLimit.default] ).then( ({rows}) => {
  200. if ( rows.length ) {
  201. return client.query( 'DELETE FROM verification WHERE guild = $1 AND configid IN (' + rows.map( (row, i) => '$' + ( i + 2 ) ).join(', ') + ')', [guild, ...rows.map( row => row.configid )] ).then( () => {
  202. console.log( '- Verifications successfully deleted.' );
  203. messages.push('Verifications successfully deleted.');
  204. }, dberror => {
  205. console.log( '- Error while deleting the verifications: ' + dberror );
  206. messages.push('Error while deleting the verifications: ' + dberror);
  207. } );
  208. }
  209. }, dberror => {
  210. console.log( '- Error while getting the verifications: ' + dberror );
  211. messages.push('Error while getting the verifications: ' + dberror);
  212. } );
  213. } ).then( () => {
  214. return client.query( 'SELECT webhook FROM rcgcdw WHERE guild = $1 ORDER BY configid ASC OFFSET $2', [guild, rcgcdwLimit.default] ).then( ({rows}) => {
  215. if ( rows.length ) {
  216. return client.query( 'DELETE FROM rcgcdw WHERE webhook IN (' + rows.map( (row, i) => '$' + ( i + 1 ) ).join(', ') + ')', rows.map( row => row.webhook ) ).then( () => {
  217. console.log( '- RcGcDw successfully deleted.' );
  218. messages.push('RcGcDw successfully deleted.');
  219. rows.forEach( row => msg.client.fetchWebhook(...row.webhook.split('/')).then( webhook => {
  220. webhook.delete('Removed extra recent changes webhook').catch(log_error);
  221. }, log_error ) );
  222. }, dberror => {
  223. console.log( '- Error while deleting the RcGcDw: ' + dberror );
  224. messages.push('Error while deleting the RcGcDw: ' + dberror);
  225. } );
  226. }
  227. }, dberror => {
  228. console.log( '- Error while getting the RcGcDw: ' + dberror );
  229. messages.push('Error while getting the RcGcDw: ' + dberror);
  230. } );
  231. } ).then( () => {
  232. return client.query( 'UPDATE rcgcdw SET display = $1 WHERE guild = $2 AND display > $1', [rcgcdwLimit.display, guild] ).then( () => {
  233. console.log( '- RcGcDw successfully updated.' );
  234. messages.push('RcGcDw successfully updated.');
  235. }, dberror => {
  236. console.log( '- Error while updating the RcGcDw: ' + dberror );
  237. messages.push('Error while updating the RcGcDw: ' + dberror);
  238. } );
  239. } ).then( () => {
  240. if ( !messages.length ) messages.push('No settings found that had to be removed.');
  241. return messages;
  242. }, error => {
  243. if ( error ) {
  244. console.log( '- Error while removing the patreon features: ' + error );
  245. messages.push('Error while removing the patreon features: ' + error);
  246. }
  247. if ( !messages.length ) messages.push('No settings found that had to be removed.');
  248. return messages;
  249. } ).finally( () => {
  250. client.release();
  251. } );
  252. }, dberror => {
  253. console.log( '- Error while connecting to the database client: ' + dberror );
  254. return 'Error while connecting to the database client: ' + dberror;
  255. } );
  256. }
  257. /**
  258. * Removes the settings for deleted guilds and channels.
  259. * @param {Discord.Message} msg - The Discord message.
  260. */
  261. function removeSettings(msg) {
  262. if ( !( msg instanceof Discord.Message ) ) return 'removeSettings(msg) – No message provided!';
  263. return db.connect().then( client => {
  264. var messages = [];
  265. return msg.client.shard.broadcastEval( `[
  266. [...this.guilds.cache.keys()],
  267. this.channels.cache.filter( channel => {
  268. return ( channel.isGuild() || ( channel.type === 'category' && global.patreons.hasOwnProperty(channel.guild.id) ) );
  269. } ).map( channel => ( channel.type === 'category' ? '#' : '' ) + channel.id )
  270. ]` ).then( results => {
  271. var all_guilds = results.map( result => result[0] ).reduce( (acc, val) => acc.concat(val), [] );
  272. var all_channels = results.map( result => result[1] ).reduce( (acc, val) => acc.concat(val), [] );
  273. var guilds = [];
  274. var channels = [];
  275. return client.query( 'SELECT guild, channel FROM discord' ).then( ({rows}) => {
  276. return rows.forEach( row => {
  277. if ( !all_guilds.includes(row.guild) ) {
  278. if ( !row.channel ) {
  279. if ( patreons.hasOwnProperty(row.guild) || voice.hasOwnProperty(row.guild) ) {
  280. msg.client.shard.broadcastEval( `delete global.patreons['${row.guild}'];
  281. delete global.voice['${row.guild}'];` );
  282. }
  283. return guilds.push(row.guild);
  284. }
  285. }
  286. else if ( row.channel && !all_channels.includes(row.channel) ) {
  287. return channels.push(row.channel);
  288. }
  289. } );
  290. }, dberror => {
  291. console.log( '- Error while getting the settings: ' + dberror );
  292. messages.push('Error while getting the settings: ' + dberror);
  293. } ).then( () => {
  294. if ( guilds.length ) {
  295. return client.query( 'DELETE FROM discord WHERE main IN (' + guilds.map( (guild, i) => '$' + ( i + 1 ) ).join(', ') + ')', guilds ).then( ({rowCount}) => {
  296. console.log( '- Guilds successfully removed: ' + rowCount );
  297. messages.push('Guilds successfully removed: ' + rowCount);
  298. }, dberror => {
  299. console.log( '- Error while removing the guilds: ' + dberror );
  300. messages.push('Error while removing the guilds: ' + dberror);
  301. } );
  302. }
  303. } ).then( () => {
  304. if ( channels.length ) {
  305. return client.query( 'DELETE FROM discord WHERE channel IN (' + channels.map( (channel, i) => '$' + ( i + 1 ) ).join(', ') + ')', channels ).then( ({rowCount}) => {
  306. console.log( '- Channels successfully removed: ' + rowCount );
  307. messages.push('Channels successfully removed: ' + rowCount);
  308. }, dberror => {
  309. console.log( '- Error while removing the channels: ' + dberror );
  310. messages.push('Error while removing the channels: ' + dberror);
  311. } );
  312. }
  313. } );
  314. } ).then( () => {
  315. if ( !messages.length ) messages.push('No settings found that had to be removed.');
  316. return messages;
  317. }, error => {
  318. if ( error ) {
  319. console.log( '- Error while removing the settings: ' + error );
  320. messages.push('Error while removing the settings: ' + error);
  321. }
  322. if ( !messages.length ) messages.push('No settings found that had to be removed.');
  323. return messages;
  324. } ).finally( () => {
  325. client.release();
  326. } );
  327. }, dberror => {
  328. console.log( '- Error while connecting to the database client: ' + dberror );
  329. return 'Error while connecting to the database client: ' + dberror;
  330. } );
  331. }
  332. module.exports = {
  333. name: 'eval',
  334. everyone: false,
  335. pause: false,
  336. owner: true,
  337. run: cmd_eval
  338. };