eval.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324
  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 new Promise( function (resolve, reject) {
  45. db.all( sql, sqlargs, function(error, rows) {
  46. if (error) reject.call(this, error);
  47. else resolve.call(this, rows);
  48. } );
  49. } );
  50. }
  51. /**
  52. * Checks a wiki and it's recent changes webhooks.
  53. * @param {Wiki} wiki - The wiki to check.
  54. */
  55. function checkWiki(wiki) {
  56. wiki = Wiki.fromInput(wiki);
  57. 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 => {
  58. if ( response.statusCode === 404 && typeof response.body === 'string' ) {
  59. let api = cheerio.load(response.body)('head link[rel="EditURI"]').prop('href');
  60. if ( api ) {
  61. wiki = new Wiki(api.split('api.php?')[0], wiki);
  62. 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' );
  63. }
  64. }
  65. return response;
  66. } ).then( response => {
  67. var body = response.body;
  68. if ( response.statusCode !== 200 || !body?.query?.recentchanges ) {
  69. return response.statusCode + ': Error while getting the recent changes: ' + body?.error?.info;
  70. }
  71. wiki.updateWiki(body.query.general);
  72. var result = {
  73. wiki: wiki.href,
  74. activity: [],
  75. rcid: 0,
  76. postid: '-1'
  77. }
  78. var rc = body.query.recentchanges;
  79. if ( rc.length ) {
  80. result.rcid = rc[0].rcid;
  81. let text = '';
  82. let len = ( Date.parse(rc[0].timestamp) - Date.parse(rc[rc.length - 1].timestamp) ) / 60000;
  83. len = Math.round(len);
  84. let rdays = ( len / 1440 );
  85. let days = Math.floor(rdays);
  86. if ( days > 0 ) {
  87. if ( days === 1 ) text += ` ${days} day`;
  88. else text += ` ${days} days`;
  89. }
  90. let rhours = ( rdays - days ) * 24;
  91. let hours = Math.floor(rhours);
  92. if ( hours > 0 ) {
  93. if ( text.length ) text += ' and';
  94. if ( hours === 1 ) text += ` ${hours} hour`;
  95. else text += ` ${hours} hours`;
  96. }
  97. let rminutes = ( rhours - hours ) * 60;
  98. let minutes = Math.round(rminutes);
  99. if ( minutes > 0 ) {
  100. if ( text.length ) text += ' and';
  101. if ( minutes === 1 ) text += ` ${minutes} minute`;
  102. else text += ` ${minutes} minutes`;
  103. }
  104. result.activity.push(`${rc.length} edits in${text}`);
  105. }
  106. return Promise.all([
  107. database('SELECT guild, lang, display, rcid, postid FROM rcgcdw WHERE wiki = ?', [result.wiki]).then( rows => {
  108. result.rcgcdb = rows;
  109. }, error => {
  110. result.rcgcdb = error.toString();
  111. } ),
  112. ( 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(), {
  113. headers: {
  114. Accept: 'application/hal+json'
  115. }
  116. } ).then( dsresponse => {
  117. var dsbody = dsresponse.body;
  118. if ( dsresponse.statusCode !== 200 || !dsbody || dsbody.status === 404 ) {
  119. if ( dsbody?.status !== 404 ) result.postid = dsresponse.statusCode + ': Error while getting the discussions: ' + dsbody?.title;
  120. return;
  121. }
  122. var posts = dsbody._embedded?.['doc:posts'];
  123. result.postid = ( posts[0]?.id || '0' );
  124. if ( posts?.length ) {
  125. let text = '';
  126. let len = ( posts[0].creationDate.epochSecond - posts[posts.length - 1].creationDate.epochSecond ) / 60;
  127. len = Math.round(len);
  128. let rdays = ( len / 1440 );
  129. let days = Math.floor(rdays);
  130. if ( days > 0 ) {
  131. if ( days === 1 ) text += ` ${days} day`;
  132. else text += ` ${days} days`;
  133. }
  134. let rhours = ( rdays - days ) * 24;
  135. let hours = Math.floor(rhours);
  136. if ( hours > 0 ) {
  137. if ( text.length ) text += ' and';
  138. if ( hours === 1 ) text += ` ${hours} hour`;
  139. else text += ` ${hours} hours`;
  140. }
  141. let rminutes = ( rhours - hours ) * 60;
  142. let minutes = Math.round(rminutes);
  143. if ( minutes > 0 ) {
  144. if ( text.length ) text += ' and';
  145. if ( minutes === 1 ) text += ` ${minutes} minute`;
  146. else text += ` ${minutes} minutes`;
  147. }
  148. result.activity.push(`${posts.length} posts in${text}`);
  149. }
  150. }, error => {
  151. result.postid = 'Error while getting the discussions: ' + error;
  152. } ) : null )
  153. ]).then( () => {
  154. return result;
  155. } );
  156. }, error => {
  157. return 'Error while getting the recent changes: ' + error;
  158. } );
  159. }
  160. /**
  161. * Removes the patreon features for a guild.
  162. * @param {String} guild - The guild ID.
  163. * @param {Discord.Message} msg - The Discord message.
  164. */
  165. function removePatreons(guild, msg) {
  166. try {
  167. if ( !( typeof guild === 'string' || msg instanceof Discord.Message ) ) {
  168. return 'removePatreons(guild, msg) – No guild or message provided!';
  169. }
  170. db.get( 'SELECT lang, inline FROM discord WHERE guild = ? AND channel IS NULL', [guild], (dberror, row) => {
  171. try {
  172. if ( dberror ) {
  173. console.log( '- Error while getting the guild: ' + dberror );
  174. msg.replyMsg( 'I got an error while searching for the guild!', {}, true );
  175. return dberror;
  176. }
  177. if ( !row ) {
  178. msg.replyMsg( 'that guild doesn\'t exist!', {}, true );
  179. return;
  180. }
  181. db.run( 'UPDATE discord SET lang = ?, inline = ?, prefix = ?, patreon = NULL WHERE guild = ?', [row.lang, row.inline, process.env.prefix, guild], function (error) {
  182. try {
  183. if ( error ) {
  184. console.log( '- Error while updating the guild: ' + error );
  185. msg.replyMsg( 'I got an error while updating the guild!', {}, true );
  186. return error;
  187. }
  188. console.log( '- Guild successfully updated.' );
  189. msg.client.shard.broadcastEval( `delete global.patreons['${guild}']`);
  190. msg.replyMsg( 'the patreon features are now disabled on that guild.', {}, true );
  191. }
  192. catch ( tryerror ) {
  193. console.log( '- Error while removing the patreon features: ' + tryerror );
  194. }
  195. } );
  196. }
  197. catch ( tryerror ) {
  198. console.log( '- Error while removing the patreon features: ' + tryerror );
  199. }
  200. } );
  201. db.run( 'DELETE FROM discord WHERE guild = ? AND channel LIKE ?', [guild, '#%'], function (dberror) {
  202. if ( dberror ) {
  203. console.log( '- Error while deleting the channel categories: ' + dberror );
  204. return dberror;
  205. }
  206. if ( this.changes ) console.log( '- Channel categories successfully deleted.' );
  207. } );
  208. db.all( 'SELECT configid FROM verification WHERE guild = ? ORDER BY configid ASC', [guild], (dberror, rows) => {
  209. if ( dberror ) {
  210. console.log( '- Error while getting the verifications: ' + dberror );
  211. return dberror;
  212. }
  213. var ids = rows.slice(verificationLimit.default).map( row => row.configid );
  214. if ( ids.length ) db.run( 'DELETE FROM verification WHERE guild = ? AND configid IN (' + ids.map( configid => '?' ).join(', ') + ')', [guild, ...ids], function (error) {
  215. if ( error ) {
  216. console.log( '- Error while deleting the verifications: ' + error );
  217. return error;
  218. }
  219. console.log( '- Verifications successfully deleted.' );
  220. } );
  221. } );
  222. db.all( 'SELECT webhook FROM rcgcdw WHERE guild = ? ORDER BY configid ASC', [guild], (dberror, rows) => {
  223. if ( dberror ) {
  224. console.log( '- Error while getting the RcGcDw: ' + dberror );
  225. return dberror;
  226. }
  227. var webhooks = rows.slice(rcgcdwLimit.default).map( row => row.webhook );
  228. if ( webhooks.length ) db.run( 'DELETE FROM rcgcdw WHERE webhook IN (' + webhooks.map( webhook => '?' ).join(', ') + ')', webhooks, function (error) {
  229. if ( error ) {
  230. console.log( '- Error while deleting the RcGcDw: ' + error );
  231. return error;
  232. }
  233. console.log( '- RcGcDw successfully deleted.' );
  234. webhooks.forEach( hook => guild.client.fetchWebhook(...hook.split('/')).then( webhook => {
  235. webhook.delete('Removed extra recent changes webhook').catch(log_error);
  236. }, log_error ) );
  237. } );
  238. } );
  239. db.run( 'UPDATE rcgcdw SET display = ? WHERE guild = ? AND display > ?', [rcgcdwLimit.display, guild, rcgcdwLimit.display], function (dberror) {
  240. if ( dberror ) {
  241. console.log( '- Error while updating the RcGcDw: ' + dberror );
  242. return dberror;
  243. }
  244. console.log( '- RcGcDw successfully updated.' );
  245. } );
  246. }
  247. catch ( tryerror ) {
  248. console.log( '- Error while removing the patreon features: ' + tryerror );
  249. return 'removePatreons(guild, msg) – Error while removing the patreon features: ' + tryerror;
  250. }
  251. }
  252. /**
  253. * Removes the settings for deleted guilds and channels.
  254. * @param {Discord.Message} msg - The Discord message.
  255. */
  256. function removeSettings(msg) {
  257. if ( !msg ) return 'removeSettings(msg) – No message provided!';
  258. try {
  259. msg.client.shard.broadcastEval( `[[...this.guilds.cache.keys()], [...this.channels.cache.filter( channel => channel.isGuild() ).keys()]]` ).then( results => {
  260. var all_guilds = results.map( result => result[0] ).reduce( (acc, val) => acc.concat(val), [] );
  261. var all_channels = results.map( result => result[1] ).reduce( (acc, val) => acc.concat(val), [] );
  262. var guilds = [];
  263. var channels = [];
  264. db.each( 'SELECT guild, channel FROM discord', [], (dberror, row) => {
  265. if ( dberror ) {
  266. console.log( '- Error while getting the setting: ' + dberror );
  267. return dberror;
  268. }
  269. if ( !row.channel && !all_guilds.includes(row.guild) ) {
  270. if ( patreons.hasOwnProperty(row.guild) || voice.hasOwnProperty(row.guild) ) {
  271. msg.client.shard.broadcastEval( `delete global.patreons['${row.guild}'];
  272. delete global.voice['${row.guild}'];` );
  273. }
  274. return guilds.push(row.guild);
  275. }
  276. if ( row.channel && all_guilds.includes(row.guild) && !all_channels.includes(row.channel) ) return channels.push(row.channel);
  277. }, (error) => {
  278. if ( error ) {
  279. console.log( '- Error while getting the settings: ' + error );
  280. msg.replyMsg( 'I got an error while getting the settings!', {}, true );
  281. return error;
  282. }
  283. if ( guilds.length ) {
  284. db.run( 'DELETE FROM discord WHERE main IN (' + guilds.map( guild => '?' ).join(', ') + ')', guilds, function (dberror) {
  285. if ( dberror ) {
  286. console.log( '- Error while removing the guilds: ' + dberror );
  287. msg.replyMsg( 'I got an error while removing the guilds!', {}, true );
  288. return dberror;
  289. }
  290. console.log( '- Guilds successfully removed.' );
  291. } );
  292. }
  293. if ( channels.length ) db.run( 'DELETE FROM discord WHERE channel IN (' + channels.map( channel => '?' ).join(', ') + ')', channels, function (dberror) {
  294. if ( dberror ) {
  295. console.log( '- Error while removing the channels: ' + dberror );
  296. msg.replyMsg( 'I got an error while removing the channels!', {}, true );
  297. return dberror;
  298. }
  299. console.log( '- Channels successfully removed.' );
  300. } );
  301. if ( !guilds.length && !channels.length ) console.log( '- Settings successfully removed.' );
  302. } );
  303. } );
  304. }
  305. catch ( tryerror ) {
  306. console.log( '- Error while removing the settings: ' + tryerror );
  307. return 'removeSettings(msg) – Error while removing the settings: ' + tryerror;
  308. }
  309. }
  310. module.exports = {
  311. name: 'eval',
  312. everyone: false,
  313. pause: false,
  314. owner: true,
  315. run: cmd_eval
  316. };