util.js 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257
  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 sqlite3 = require('sqlite3').verbose();
  10. const mode = ( process.env.READONLY ? sqlite3.OPEN_READONLY : sqlite3.OPEN_READWRITE );
  11. const db = new sqlite3.Database( './wikibot.db', mode, dberror => {
  12. if ( dberror ) {
  13. console.log( '- Dashboard: Error while connecting to the database: ' + dberror );
  14. return dberror;
  15. }
  16. db.exec( 'PRAGMA foreign_keys = ON;', function (error) {
  17. if ( error ) {
  18. console.log( '- Dashboard: Error while enabling the foreign key constraint: ' + error );
  19. }
  20. console.log( '- Dashboard: Connected to the database.' );
  21. } );
  22. } );
  23. /**
  24. * @typedef Settings
  25. * @property {String} state
  26. * @property {String} access_token
  27. * @property {User} user
  28. * @property {Object} guilds
  29. * @property {Number} guilds.count
  30. * @property {Map<String, Guild>} guilds.isMember
  31. * @property {Map<String, Guild>} guilds.notMember
  32. */
  33. /**
  34. * @typedef User
  35. * @property {String} id
  36. * @property {String} username
  37. * @property {String} discriminator
  38. * @property {String} avatar
  39. * @property {String} locale
  40. */
  41. /**
  42. * @typedef Guild
  43. * @property {String} id
  44. * @property {String} name
  45. * @property {String} acronym
  46. * @property {String} [icon]
  47. * @property {String} userPermissions
  48. * @property {Boolean} [patreon]
  49. * @property {String} [botPermissions]
  50. * @property {{id: String, name: String, userPermissions: Number, botPermissions: Number}[]} [channels]
  51. * @property {{id: String, name: String, lower: Boolean}[]} [roles]
  52. */
  53. /**
  54. * @type {Map<String, Settings>}
  55. */
  56. const settingsData = new Map();
  57. /**
  58. * @type {Map<Number, PromiseConstructor>}
  59. */
  60. const messages = new Map();
  61. var messageId = 1;
  62. process.on( 'message', message => {
  63. if ( message.id ) {
  64. if ( message.data.error ) messages.get(message.id).reject(message.data.error);
  65. else messages.get(message.id).resolve(message.data.response);
  66. return messages.delete(message.id);
  67. }
  68. console.log( '- [Dashboard]: Message received!', message );
  69. } );
  70. /**
  71. * Send messages to the manager.
  72. * @param {Object} [message] - The message.
  73. * @returns {Promise<Object>}
  74. */
  75. function sendMsg(message) {
  76. var id = messageId++;
  77. var promise = new Promise( (resolve, reject) => {
  78. messages.set(id, {resolve, reject});
  79. process.send( {id, data: message} );
  80. } );
  81. return promise;
  82. }
  83. /**
  84. * Create a red notice
  85. * @param {import('cheerio')} $ - The cheerio static
  86. * @param {String} notice - The notice to create
  87. * @param {String[]} [args] - The arguments for the notice
  88. * @returns {import('cheerio')}
  89. */
  90. function createNotice($, notice, args = []) {
  91. if ( !notice ) return;
  92. var type = 'info';
  93. var title = $('<b>');
  94. var text = $('<div>');
  95. var note;
  96. switch (notice) {
  97. case 'unauthorized':
  98. type = 'info';
  99. title.text('Not logged in!');
  100. text.text('Please login before you can change any settings.');
  101. break;
  102. case 'save':
  103. type = 'success';
  104. title.text('Settings saved!');
  105. text.text('The settings have been updated successfully.');
  106. break;
  107. case 'nosettings':
  108. type = 'info';
  109. title.text('Server not set up yet!');
  110. text.text('Please define settings for the server first.');
  111. note = $('<a>').text('Change settings.').attr('href', `/guild/${args[0]}/settings`);
  112. break;
  113. case 'logout':
  114. type = 'success';
  115. title.text('Successfully logged out!');
  116. text.text('You have been successfully logged out. To change any settings you need to login again.');
  117. break;
  118. case 'refresh':
  119. type = 'success';
  120. title.text('Refresh successful!');
  121. text.text('Your server list has been successfully refeshed.');
  122. break;
  123. case 'missingperm':
  124. type = 'error';
  125. title.text('Missing permission!');
  126. text.append(
  127. escapeText('Either you or Wiki-Bot are missing the '),
  128. $('<code>').text(args[0]),
  129. escapeText(' permission for this function.')
  130. );
  131. break;
  132. case 'loginfail':
  133. type = 'error';
  134. title.text('Login failed!');
  135. text.text('An error occurred while logging you in, please try again.');
  136. break;
  137. case 'sysmessage':
  138. type = 'info';
  139. title.text('System message does not match!');
  140. text.append(
  141. escapeText('The page '),
  142. $('<a target="_blank">').append(
  143. $('<code>').text('MediaWiki:Custom-RcGcDw')
  144. ).attr('href', args[1]),
  145. escapeText(' needs to be the server id '),
  146. $('<code class="user-select">').text(args[0]),
  147. escapeText('.')
  148. );
  149. note = $('<a target="_blank">').text(args[1]).attr('href', args[1]);
  150. break;
  151. case 'mwversion':
  152. type = 'error';
  153. title.text('Outdated MediaWiki version!');
  154. text.text(`Requires at least MediaWiki 1.30, found ${args[0]} on ${args[1]}.`);
  155. note = $('<a target="_blank">').text('https://www.mediawiki.org/wiki/MediaWiki_1.30').attr('href', 'https://www.mediawiki.org/wiki/MediaWiki_1.30');
  156. break;
  157. case 'nochange':
  158. type = 'info';
  159. title.text('Save failed!');
  160. text.text('The settings matched the current default settings.');
  161. break;
  162. case 'invalidusergroup':
  163. type = 'error';
  164. title.text('Invalid user group!');
  165. text.text('The user group name was too long or you provided too many.');
  166. break;
  167. case 'wikiblocked':
  168. type = 'error';
  169. title.text('Wiki is blocked!');
  170. text.text(`${args[0]} has been blocked from being added as a recent changes webhook.`);
  171. if ( args[1] ) note = $('<div>').text(`Reason: ${args[1]}`);
  172. break;
  173. case 'savefail':
  174. type = 'error';
  175. title.text('Save failed!');
  176. text.text('The settings could not be saved, please try again.');
  177. break;
  178. case 'movefail':
  179. type = 'info';
  180. title.text('Settings partially saved!');
  181. text.text('The settings have only been partially updated.');
  182. note = $('<div>').text('The webhook channel could not be changed!');
  183. break;
  184. case 'refreshfail':
  185. type = 'error';
  186. title.text('Refresh failed!');
  187. text.text('You server list could not be refreshed, please try again.');
  188. break;
  189. case 'error':
  190. type = 'error';
  191. title.text('Unknown error!');
  192. text.text('An unknown error occured, please try again.');
  193. break;
  194. case 'readonly':
  195. type = 'info';
  196. title.text('Read-only database!');
  197. text.text('You can currently only view your settings, but not change them.');
  198. break;
  199. default:
  200. return;
  201. }
  202. return $(`<div class="notice notice-${type}">`).append(
  203. title,
  204. text,
  205. note
  206. ).appendTo('#text #notices');
  207. }
  208. /**
  209. * HTML escape text
  210. * @param {String} text - The text to escape
  211. * @returns {String}
  212. */
  213. function escapeText(text) {
  214. return text.replace( /&/g, '&amp;' ).replace( /</g, '&lt;' ).replace( />/g, '&gt;' );
  215. }
  216. const permissions = {
  217. ADMINISTRATOR: 1 << 3,
  218. MANAGE_CHANNELS: 1 << 4,
  219. MANAGE_GUILD: 1 << 5,
  220. ADD_REACTIONS: 1 << 6,
  221. VIEW_CHANNEL: 1 << 10,
  222. SEND_MESSAGES: 1 << 11,
  223. MANAGE_MESSAGES: 1 << 13,
  224. EMBED_LINKS: 1 << 14,
  225. ATTACH_FILES: 1 << 15,
  226. READ_MESSAGE_HISTORY: 1 << 16,
  227. USE_EXTERNAL_EMOJIS: 1 << 18,
  228. MANAGE_NICKNAMES: 1 << 27,
  229. MANAGE_ROLES: 1 << 28,
  230. MANAGE_WEBHOOKS: 1 << 29
  231. }
  232. /**
  233. * Check if a permission is included in the BitField
  234. * @param {String|Number} all - BitField of multiple permissions
  235. * @param {String[]} permission - Name of the permission to check for
  236. * @returns {Boolean}
  237. */
  238. function hasPerm(all = 0, ...permission) {
  239. if ( (all & permissions.ADMINISTRATOR) === permissions.ADMINISTRATOR ) return true;
  240. return permission.map( perm => {
  241. let bit = permissions[perm];
  242. return ( (all & bit) === bit );
  243. } ).every( perm => perm );
  244. }
  245. module.exports = {got, db, settingsData, sendMsg, createNotice, escapeText, hasPerm};