plugins.js 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384
  1. import { readdirSync, existsSync, readFileSync, watch } from 'fs'
  2. import { join, resolve } from 'path'
  3. import { format } from 'util'
  4. import syntaxerror from 'syntax-error'
  5. import importFile from './import.js'
  6. import Helper from './helper.js'
  7. const __dirname = Helper.__dirname(import.meta)
  8. const pluginFolder = Helper.__dirname(join(__dirname, '../plugins/index'))
  9. const pluginFilter = filename => /\.(mc)?js$/.test(filename)
  10. // inspired from https://github.com/Nurutomo/mahbod/blob/main/src/util/PluginManager.ts
  11. let watcher, plugins, pluginFolders = []
  12. watcher = plugins = {}
  13. async function filesInit(pluginFolder = pluginFolder, pluginFilter = pluginFilter, conn) {
  14. const folder = resolve(pluginFolder)
  15. if (folder in watcher) return
  16. pluginFolders.push(folder)
  17. await Promise.all(readdirSync(folder).filter(pluginFilter).map(async filename => {
  18. try {
  19. let file = global.__filename(join(folder, filename))
  20. const module = await import(file)
  21. if (module) plugins[filename] = 'default' in module ? module.default : module
  22. } catch (e) {
  23. conn?.logger.error(e)
  24. delete plugins[filename]
  25. }
  26. }))
  27. const watching = watch(folder, reload.bind(null, conn, folder, pluginFilter))
  28. watching.on('close', () => deletePluginFolder(folder, true))
  29. watcher[folder] = watching
  30. return plugins
  31. }
  32. function deletePluginFolder(folder, isAlreadyClosed = false) {
  33. const resolved = resolve(folder)
  34. if (!(resolved in watcher)) return
  35. if (!isAlreadyClosed) watcher[resolved].close()
  36. delete watcher[resolved]
  37. pluginFolders.splice(pluginFolders.indexOf(resolved), 1)
  38. }
  39. async function reload(conn, pluginFolder = pluginFolder, pluginFilter = pluginFilter, _ev, filename) {
  40. if (pluginFilter(filename)) {
  41. let dir = global.__filename(join(pluginFolder, filename), true)
  42. if (filename in plugins) {
  43. if (existsSync(dir)) conn.logger.info(` updated plugin - '${filename}'`)
  44. else {
  45. conn?.logger.warn(`deleted plugin - '${filename}'`)
  46. return delete plugins[filename]
  47. }
  48. } else conn?.logger.info(`new plugin - '${filename}'`)
  49. let err = syntaxerror(readFileSync(dir), filename, {
  50. sourceType: 'module',
  51. allowAwaitOutsideFunction: true
  52. })
  53. if (err) conn.logger.error(`syntax error while loading '${filename}'\n${format(err)}`)
  54. else try {
  55. const module = await importFile(global.__filename(dir)).catch(console.error)
  56. if (module) plugins[filename] = module
  57. } catch (e) {
  58. conn?.logger.error(`error require plugin '${filename}\n${format(e)}'`)
  59. } finally {
  60. plugins = Object.fromEntries(Object.entries(plugins).sort(([a], [b]) => a.localeCompare(b)))
  61. }
  62. }
  63. }
  64. export {
  65. pluginFolder,
  66. pluginFilter,
  67. plugins,
  68. watcher,
  69. pluginFolders,
  70. filesInit,
  71. deletePluginFolder,
  72. reload
  73. }