1
0

util.js 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324
  1. const got = require('got').extend( {
  2. throwHttpErrors: false,
  3. timeout: 5000,
  4. headers: {
  5. 'User-Agent': 'Wiki-Bot/dashboard (Discord; ' + process.env.npm_package_name + ')'
  6. },
  7. responseType: 'json'
  8. } );
  9. const {Pool} = require('pg');
  10. const db = new Pool();
  11. db.on( 'error', dberror => {
  12. console.log( '- Dashboard: Error while connecting to the database: ' + dberror );
  13. } );
  14. /**
  15. * @typedef Settings
  16. * @property {String} state
  17. * @property {String} access_token
  18. * @property {User} user
  19. * @property {Object} guilds
  20. * @property {Number} guilds.count
  21. * @property {Map<String, Guild>} guilds.isMember
  22. * @property {Map<String, Guild>} guilds.notMember
  23. */
  24. /**
  25. * @typedef User
  26. * @property {String} id
  27. * @property {String} username
  28. * @property {String} discriminator
  29. * @property {String} avatar
  30. * @property {String} locale
  31. */
  32. /**
  33. * @typedef Guild
  34. * @property {String} id
  35. * @property {String} name
  36. * @property {String} acronym
  37. * @property {String} [icon]
  38. * @property {String} userPermissions
  39. * @property {Boolean} [patreon]
  40. * @property {String} [botPermissions]
  41. * @property {Channel[]} [channels]
  42. * @property {Role[]} [roles]
  43. * @property {String} [locale]
  44. */
  45. /**
  46. * @typedef Channel
  47. * @property {String} id
  48. * @property {String} name
  49. * @property {Boolean} isCategory
  50. * @property {Number} userPermissions
  51. * @property {Number} botPermissions
  52. */
  53. /**
  54. * @typedef Role
  55. * @property {String} id
  56. * @property {String} name
  57. * @property {Boolean} lower
  58. */
  59. /**
  60. * @type {Map<String, Settings>}
  61. */
  62. const settingsData = new Map();
  63. /**
  64. * @type {Map<Number, PromiseConstructor>}
  65. */
  66. const messages = new Map();
  67. var messageId = 1;
  68. process.on( 'message', message => {
  69. if ( message.id ) {
  70. if ( message.data.error ) messages.get(message.id).reject(message.data.error);
  71. else messages.get(message.id).resolve(message.data.response);
  72. return messages.delete(message.id);
  73. }
  74. if ( message === 'toggleDebug' ) global.isDebug = !global.isDebug;
  75. console.log( '- [Dashboard]: Message received!', message );
  76. } );
  77. /**
  78. * Send messages to the manager.
  79. * @param {Object} [message] - The message.
  80. * @returns {Promise<Object>}
  81. */
  82. function sendMsg(message) {
  83. var id = messageId++;
  84. var promise = new Promise( (resolve, reject) => {
  85. messages.set(id, {resolve, reject});
  86. process.send( {id, data: message} );
  87. } );
  88. return promise;
  89. }
  90. var botLists = [];
  91. if ( process.env.botlist ) {
  92. let supportedLists = {
  93. 'blist.xyz': {
  94. link: 'https://blist.xyz/bot/' + process.env.bot,
  95. widget: 'https://blist.xyz/api/v2/bot/' + process.env.bot + '/widget'
  96. },
  97. 'botlists.com': {
  98. link: 'https://botlists.com/bot/' + process.env.bot,
  99. widget: 'https://botlists.com/bot/' + process.env.bot + '/widget'
  100. },
  101. 'bots.ondiscord.xyz': {
  102. link: 'https://bots.ondiscord.xyz/bots/' + process.env.bot,
  103. widget: 'https://bots.ondiscord.xyz/bots/' + process.env.bot + '/embed?theme=dark&showGuilds=true'
  104. },
  105. 'botsfordiscord.com': {
  106. link: 'https://botsfordiscord.com/bots/' + process.env.bot,
  107. widget: 'https://botsfordiscord.com/api/bot/' + process.env.bot + '/widget?theme=dark'
  108. },
  109. 'discord.boats': {
  110. link: 'https://discord.boats/bot/' + process.env.bot,
  111. widget: 'https://discord.boats/api/widget/' + process.env.bot
  112. },
  113. 'infinitybotlist.com': {
  114. link: 'https://infinitybotlist.com/bots/' + process.env.bot,
  115. widget: 'https://infinitybotlist.com/bots/' + process.env.bot + '/widget?size=medium'
  116. },
  117. 'top.gg': {
  118. link: 'https://top.gg/bot/' + process.env.bot,
  119. widget: 'https://top.gg/api/widget/' + process.env.bot + '.svg'
  120. },
  121. 'voidbots.net': {
  122. link: 'https://voidbots.net/bot/' + process.env.bot,
  123. widget: 'https://voidbots.net/api/embed/' + process.env.bot + '?theme=dark'
  124. }
  125. };
  126. botLists = Object.keys(JSON.parse(process.env.botlist)).filter( botList => {
  127. return supportedLists.hasOwnProperty(botList);
  128. } ).map( botList => {
  129. return `<a href="${supportedLists[botList].link}" target="_blank">
  130. <img src="${supportedLists[botList].widget}" alt="${botList}" height="150px" loading="lazy" />
  131. </a>`;
  132. } );
  133. }
  134. /**
  135. * Add bot list widgets.
  136. * @param {import('cheerio')} $ - The cheerio static
  137. * @param {import('./i18n.js')} dashboardLang - The user language
  138. * @returns {import('cheerio')}
  139. */
  140. function addWidgets($, dashboardLang) {
  141. if ( !botLists.length ) return;
  142. return $('<div class="widgets">').append(
  143. $('<h3 id="bot-lists">').text(dashboardLang.get('general.botlist.title')),
  144. $('<p>').text(dashboardLang.get('general.botlist.text')),
  145. ...botLists
  146. ).appendTo('#text');
  147. }
  148. /**
  149. * Create a red notice
  150. * @param {import('cheerio')} $ - The cheerio static
  151. * @param {String} notice - The notice to create
  152. * @param {import('./i18n.js')} dashboardLang - The user language
  153. * @param {String[]} [args] - The arguments for the notice
  154. * @returns {import('cheerio')}
  155. */
  156. function createNotice($, notice, dashboardLang, args = []) {
  157. if ( !notice ) return;
  158. var type = 'info';
  159. var title = $('<b>');
  160. var text = $('<div>');
  161. var note;
  162. switch (notice) {
  163. case 'unauthorized':
  164. type = 'info';
  165. title.text(dashboardLang.get('notice.unauthorized.title'));
  166. text.text(dashboardLang.get('notice.unauthorized.text'));
  167. break;
  168. case 'save':
  169. type = 'success';
  170. title.text(dashboardLang.get('notice.save.title'));
  171. text.text(dashboardLang.get('notice.save.text'));
  172. break;
  173. case 'nosettings':
  174. type = 'info';
  175. title.text(dashboardLang.get('notice.nosettings.title'));
  176. text.text(dashboardLang.get('notice.nosettings.text'));
  177. note = $('<a>').text(dashboardLang.get('notice.nosettings.note')).attr('href', `/guild/${args[0]}/settings`);
  178. break;
  179. case 'logout':
  180. type = 'success';
  181. title.text(dashboardLang.get('notice.logout.title'));
  182. text.text(dashboardLang.get('notice.logout.text'));
  183. break;
  184. case 'refresh':
  185. type = 'success';
  186. title.text(dashboardLang.get('notice.refresh.title'));
  187. text.text(dashboardLang.get('notice.refresh.text'));
  188. break;
  189. case 'missingperm':
  190. type = 'error';
  191. title.text(dashboardLang.get('notice.missingperm.title'));
  192. text.html(dashboardLang.get('notice.missingperm.text', true, $('<code>').text(args[0])));
  193. break;
  194. case 'loginfail':
  195. type = 'error';
  196. title.text(dashboardLang.get('notice.loginfail.title'));
  197. text.text(dashboardLang.get('notice.loginfail.text'));
  198. break;
  199. case 'sysmessage':
  200. type = 'info';
  201. title.text(dashboardLang.get('notice.sysmessage.title'));
  202. text.html(dashboardLang.get('notice.sysmessage.text', true, $('<a target="_blank">').append(
  203. $('<code>').text('MediaWiki:Custom-RcGcDw')
  204. ).attr('href', args[1]), $('<code class="user-select">').text(args[0])));
  205. note = $('<a target="_blank">').text(args[1]).attr('href', args[1]);
  206. break;
  207. case 'mwversion':
  208. type = 'error';
  209. title.text(dashboardLang.get('notice.mwversion.title'));
  210. text.text(dashboardLang.get('notice.mwversion.text', false, args[0], args[1]));
  211. note = $('<a target="_blank">').text('https://www.mediawiki.org/wiki/MediaWiki_1.30').attr('href', 'https://www.mediawiki.org/wiki/MediaWiki_1.30');
  212. break;
  213. case 'nochange':
  214. type = 'info';
  215. title.text(dashboardLang.get('notice.nochange.title'));
  216. text.text(dashboardLang.get('notice.nochange.text'));
  217. break;
  218. case 'invalidusergroup':
  219. type = 'error';
  220. title.text(dashboardLang.get('notice.invalidusergroup.title'));
  221. text.text(dashboardLang.get('notice.invalidusergroup.text'));
  222. break;
  223. case 'wikiblocked':
  224. type = 'error';
  225. title.text(dashboardLang.get('notice.wikiblocked.title'));
  226. text.text(dashboardLang.get('notice.wikiblocked.text', false, args[0]));
  227. if ( args[1] ) note = $('<div>').append(
  228. dashboardLang.get('notice.wikiblocked.note', true) + ' ',
  229. $('<code>').text(args[1])
  230. );
  231. break;
  232. case 'savefail':
  233. type = 'error';
  234. title.text(dashboardLang.get('notice.savefail.title'));
  235. text.text(dashboardLang.get('notice.savefail.text'));
  236. if ( typeof args[0] === 'string' ) {
  237. note = $('<div>').text(dashboardLang.get('notice.savefail.note_' + args[0]));
  238. }
  239. break;
  240. case 'movefail':
  241. type = 'info';
  242. title.text(dashboardLang.get('notice.movefail.title'));
  243. text.text(dashboardLang.get('notice.movefail.text'));
  244. note = $('<div>').text(dashboardLang.get('notice.movefail.note'));
  245. break;
  246. case 'refreshfail':
  247. type = 'error';
  248. title.text(dashboardLang.get('notice.refreshfail.title'));
  249. text.text(dashboardLang.get('notice.refreshfail.text'));
  250. break;
  251. case 'error':
  252. type = 'error';
  253. title.text(dashboardLang.get('notice.error.title'));
  254. text.text(dashboardLang.get('notice.error.text'));
  255. break;
  256. case 'readonly':
  257. type = 'info';
  258. title.text(dashboardLang.get('notice.readonly.title'));
  259. text.text(dashboardLang.get('notice.readonly.text'));
  260. break;
  261. default:
  262. return;
  263. }
  264. return $(`<div class="notice notice-${type}">`).append(
  265. title,
  266. text,
  267. note
  268. ).appendTo('#text #notices');
  269. }
  270. /**
  271. * HTML escape text
  272. * @param {String} text - The text to escape
  273. * @returns {String}
  274. */
  275. function escapeText(text) {
  276. return text.replace( /&/g, '&amp;' ).replace( /</g, '&lt;' ).replace( />/g, '&gt;' );
  277. }
  278. const permissions = {
  279. ADMINISTRATOR: 1 << 3,
  280. MANAGE_CHANNELS: 1 << 4,
  281. MANAGE_GUILD: 1 << 5,
  282. ADD_REACTIONS: 1 << 6,
  283. VIEW_CHANNEL: 1 << 10,
  284. SEND_MESSAGES: 1 << 11,
  285. MANAGE_MESSAGES: 1 << 13,
  286. EMBED_LINKS: 1 << 14,
  287. ATTACH_FILES: 1 << 15,
  288. READ_MESSAGE_HISTORY: 1 << 16,
  289. MENTION_EVERYONE: 1 << 17,
  290. USE_EXTERNAL_EMOJIS: 1 << 18,
  291. MANAGE_NICKNAMES: 1 << 27,
  292. MANAGE_ROLES: 1 << 28,
  293. MANAGE_WEBHOOKS: 1 << 29
  294. }
  295. /**
  296. * Check if a permission is included in the BitField
  297. * @param {String|Number} all - BitField of multiple permissions
  298. * @param {String[]} permission - Name of the permission to check for
  299. * @returns {Boolean}
  300. */
  301. function hasPerm(all = 0, ...permission) {
  302. if ( (all & permissions.ADMINISTRATOR) === permissions.ADMINISTRATOR ) return true;
  303. return permission.map( perm => {
  304. let bit = permissions[perm];
  305. return ( (all & bit) === bit );
  306. } ).every( perm => perm );
  307. }
  308. module.exports = {got, db, settingsData, sendMsg, addWidgets, createNotice, escapeText, hasPerm};