1 module dscord.bot.plugin; 2 3 import std.path, 4 std.file; 5 6 import std.experimental.logger, 7 vibe.d : runTask; 8 9 import dscord.client, 10 dscord.bot.command, 11 dscord.bot.listener, 12 dscord.bot.bot, 13 dscord.util.storage; 14 15 /** 16 PluginOptions is a class that can be used to configure the base functionality 17 and utilties in use by a plugin. 18 */ 19 class PluginOptions { 20 /** Does this plugin load/require a configuration file? */ 21 bool useConfig = true; 22 23 /** Does this plugin load/require a JSON storage file? */ 24 bool useStorage = true; 25 } 26 27 /** 28 PluginState is a class the encapsulates all run-time state required for a 29 plugin to exist. It's purpose is to allow for hot-reloading and replacing 30 of plugin code, without destroy/rebuilding run-time data. 31 */ 32 class PluginState { 33 /** Plugin JSON Storage file (for data) */ 34 Storage storage; 35 36 /** Plugin JSON Config file */ 37 Storage config; 38 39 /** PluginOptions struct **/ 40 PluginOptions options; 41 42 this(Plugin plugin, PluginOptions opts) { 43 this.options = opts ? opts : new PluginOptions; 44 this.storage = new Storage(plugin.storagePath); 45 this.config = new Storage(plugin.configPath); 46 } 47 } 48 49 /** 50 A Plugin represents a modular, extendable class that encapsulates certain 51 Bot functionality into a logical slice. Plugins usually have a set of commands 52 and listeners attached to them, and are built to be dynamically loaded/reloaded 53 into a Bot. 54 */ 55 class Plugin { 56 /** Bot instance for this plugin. Should always be set */ 57 Bot bot; 58 59 /** Current runtime state for this plugin */ 60 PluginState state; 61 62 mixin Listenable; 63 mixin Commandable; 64 65 /** 66 The path to the dynamic library this plugin was loaded from. If set, this 67 signals this Plugin was loaded from a dynamic library, and can be reloaded 68 from the given path. 69 */ 70 string dynamicLibraryPath; 71 72 /** 73 Pointer to the dynamic library, used for cleaning up on shutdown. 74 */ 75 void* dynamicLibrary; 76 77 /** 78 Constructor for initial load. Usually called from the inherited constructor. 79 */ 80 this(this T)(PluginOptions opts = null) { 81 this.state = new PluginState(this, opts); 82 83 this.loadCommands!T(); 84 this.loadListeners!T(); 85 } 86 87 /** 88 Plugin log instance. 89 */ 90 @property Logger log() { 91 return this.bot.log; 92 } 93 94 /** 95 Used to load the Plugin, initially loading state if requred. 96 */ 97 void load(Bot bot, PluginState state = null) { 98 this.bot = bot; 99 100 // Make sure our storage directory exists 101 if (!exists(this.storageDirectoryPath)) { 102 mkdir(this.storageDirectoryPath); 103 } 104 105 // If we got state, assume this was a plugin reload and replace 106 if (state) { 107 this.state = state; 108 } else { 109 // If plugin uses storage, load the storage from disk 110 if (this.options.useStorage) { 111 this.storage.load(); 112 } 113 114 // If plugin uses config, load the config from disk 115 if (this.options.useConfig) { 116 this.config.load(); 117 } 118 } 119 } 120 121 /** 122 Used to unload the Plugin. Saves config/storage if required. 123 */ 124 void unload(Bot bot) { 125 if (this.options.useStorage) { 126 this.storage.save(); 127 } 128 129 if (this.options.useConfig) { 130 this.config.save(); 131 } 132 } 133 134 /** 135 Returns path to this plugins storage directory. 136 */ 137 @property string storageDirectoryPath() { 138 return "storage" ~ dirSeparator ~ this.name; 139 } 140 141 /** 142 Returns path to this plugins storage file. 143 */ 144 @property string storagePath() { 145 return this.storageDirectoryPath ~ dirSeparator ~ "storage.json"; 146 } 147 148 /** 149 Returns path to this plugins config file. 150 */ 151 @property string configPath() { 152 return "config" ~ dirSeparator ~ this.name ~ ".json"; 153 } 154 155 /** 156 Storage instance for the plugin. 157 */ 158 @property Storage storage() { 159 return this.state.storage; 160 } 161 162 /** 163 Config instance for the plugin. 164 */ 165 @property Storage config() { 166 return this.state.config; 167 } 168 169 /** 170 PluginOptions instance for the plugin. 171 */ 172 @property PluginOptions options() { 173 return this.state.options; 174 } 175 176 /** 177 Client instance for the plugin. 178 */ 179 @property Client client() { 180 return this.bot.client; 181 } 182 183 /** 184 Returns the name of this plugin. 185 */ 186 string name() { 187 return typeof(this).toString; 188 } 189 }