diff --git a/public/js/plugin-download-2.js b/public/js/plugin-download-2.js new file mode 100644 index 0000000..9874427 --- /dev/null +++ b/public/js/plugin-download-2.js @@ -0,0 +1,42 @@ +const component = () => { + return { + plugins: [], + info: undefined, + pid: undefined, + init() { + fetch(`${base_url}api/admin/plugin`) + .then(res => res.json()) + .then(data => { + if (!data.success) { + alert('danger', `Failed to list the available plugins. Error: ${data.error}`); + return; + } + this.plugins = data.plugins; + + const pid = localStorage.getItem('plugin'); + if (pid && this.plugins.map(p => p.id).includes(pid)) + return this.loadPlugin(pid); + + if (this.plugins.length > 0) + this.loadPlugin(this.plugins[0].id); + }); + }, + loadPlugin(pid) { + fetch(`${base_url}api/admin/plugin/info?${new URLSearchParams({ + plugin: pid + })}`) + .then(res => res.json()) + .then(data => { + if (!data.success) { + alert('danger', `Failed to get plugin metadata. Error: ${data.error}`); + return; + } + this.info = data.info; + this.pid = pid; + }); + }, + pluginChanged() { + this.loadPlugin(this.pid); + } + }; +}; diff --git a/src/plugin/plugin.cr b/src/plugin/plugin.cr index 28c756b..f8cb109 100644 --- a/src/plugin/plugin.cr +++ b/src/plugin/plugin.cr @@ -16,6 +16,8 @@ class Plugin end struct Info + include JSON::Serializable + {% for name in ["id", "title", "placeholder"] %} getter {{name.id}} = "" {% end %} @@ -24,6 +26,9 @@ class Plugin getter settings = {} of String => String? getter dir : String + @[JSON::Field(ignore: true)] + @json : JSON::Any + def initialize(@dir) info_path = File.join @dir, "info.json" @@ -150,6 +155,12 @@ class Plugin sbx.push_string path sbx.put_prop_string -2, "storage_path" + sbx.push_pointer info.dir.as(Void*) + path = sbx.require_pointer(-1).as String + sbx.pop + sbx.push_string path + sbx.put_prop_string -2, "info_dir" + def_helper_functions sbx end @@ -164,11 +175,36 @@ class Plugin {% end %} end + def assert_manga_type(obj : JSON::Any) + obj["id"].as_s && obj["title"].as_s + rescue e + raise Error.new "Missing required fields in the Manga type" + end + + def assert_chapter_type(obj : JSON::Any) + obj["id"].as_s && obj["title"].as_s && obj["pages"].as_i && + obj["manga_title"].as_s + rescue e + raise Error.new "Missing required fields in the Chapter type" + end + + def assert_page_type(obj : JSON::Any) + obj["url"].as_s && obj["filename"].as_s + rescue e + raise Error.new "Missing required fields in the Page type" + end + def search_manga(query : String) + if info.version == 1 + raise Error.new "Manga searching is only available for plugins targeting API " \ + "v2 or above" + end json = eval_json "searchManga('#{query}')" begin - check_fields ["id", "title"] - rescue + json.as_a.each do |obj| + assert_manga_type obj + end + rescue e raise Error.new e.message end json @@ -180,11 +216,7 @@ class Plugin if info.version > 1 # Since v2, listChapters returns an array json.as_a.each do |obj| - {% for field in %w(id title pages manga_title) %} - unless obj[{{field}}]? - raise "Field `{{field.id}}` is required in the chapter objects" - end - {% end %} + assert_chapter_type obj end else check_fields ["title", "chapters"] @@ -200,7 +232,9 @@ class Plugin end title = obj["title"]? - raise "Field `title` missing from `listChapters` outputs" if title.nil? + if title.nil? + raise "Field `title` missing from `listChapters` outputs" + end end end rescue e @@ -212,10 +246,14 @@ class Plugin def select_chapter(id : String) json = eval_json "selectChapter('#{id}')" begin - check_fields ["title", "pages"] + if info.version > 1 + assert_chapter_type json + else + check_fields ["title", "pages"] - if json["title"].to_s.empty? - raise "The `title` field of the chapter can not be empty" + if json["title"].to_s.empty? + raise "The `title` field of the chapter can not be empty" + end end rescue e raise Error.new e.message @@ -227,7 +265,19 @@ class Plugin json = eval_json "nextPage()" return if json.size == 0 begin - check_fields ["filename", "url"] + assert_page_type json + rescue e + raise Error.new e.message + end + json + end + + def new_chapters(manga_id : String, after : Int64) + json = eval_json "newChapters('#{manga_id}', #{after})" + begin + json.as_a.each do |obj| + assert_chapter_type obj + end rescue e raise Error.new e.message end @@ -412,19 +462,26 @@ class Plugin end sbx.put_prop_string -2, "storage" - sbx.push_proc 1 do |ptr| - env = Duktape::Sandbox.new ptr - key = env.require_string 0 + if info.version > 1 + sbx.push_proc 1 do |ptr| + env = Duktape::Sandbox.new ptr + key = env.require_string 0 - if value = info.settings[key]? - env.push_string value - else - env.push_undefined + env.get_global_string "info_dir" + info_dir = env.require_string -1 + env.pop + info = Info.new info_dir + + if value = info.settings[key]? + env.push_string value + else + env.push_undefined + end + + env.call_success end - - env.call_success + sbx.put_prop_string -2, "settings" end - sbx.put_prop_string -2, "settings" sbx.put_prop_string -2, "mango" end diff --git a/src/routes/api.cr b/src/routes/api.cr index a66210a..b1f0752 100644 --- a/src/routes/api.cr +++ b/src/routes/api.cr @@ -539,6 +539,97 @@ struct APIRouter end end + Koa.describe "Returns a list of available plugins" + Koa.tags ["admin", "downloader"] + Koa.query "plugin", schema: String + Koa.response 200, schema: { + "success" => Bool, + "error" => String?, + "plugins" => [{ + "id" => String, + "title" => String, + }], + } + get "/api/admin/plugin" do |env| + begin + send_json env, { + "success" => true, + "plugins" => Plugin.list, + }.to_json + rescue e + Logger.error e + send_json env, { + "success" => false, + "error" => e.message, + }.to_json + end + end + + Koa.describe "Returns the metadata of a plugin" + Koa.tags ["admin", "downloader"] + Koa.query "plugin", schema: String + Koa.response 200, schema: { + "success" => Bool, + "error" => String?, + "info" => { + "dir" => String, + "id" => String, + "title" => String, + "placeholder" => String, + "wait_seconds" => Int32, + "version" => Int32, + "settings" => {} of String => String, + }, + } + get "/api/admin/plugin/info" do |env| + begin + plugin = Plugin.new env.params.query["plugin"].as String + send_json env, { + "success" => true, + "info" => plugin.info, + }.to_json + rescue e + Logger.error e + send_json env, { + "success" => false, + "error" => e.message, + }.to_json + end + end + + Koa.describe "Searches for manga matching the given query from a plugin", <<-MD + Only available for plugins targeting API v2 or above. + MD + Koa.tags ["admin", "downloader"] + Koa.query "plugin", schema: String + Koa.query "query", schema: String + Koa.response 200, schema: { + "success" => Bool, + "error" => String?, + "manga" => [{ + "id" => String, + "title" => String, + }], + } + get "/api/admin/plugin/search" do |env| + begin + query = env.params.query["query"].as String + plugin = Plugin.new env.params.query["plugin"].as String + + manga_ary = plugin.search_manga(query).as_a + send_json env, { + "success" => true, + "manga" => manga_ary, + }.to_json + rescue e + Logger.error e + send_json env, { + "success" => false, + "error" => e.message, + }.to_json + end + end + Koa.describe "Lists the chapters in a title from a plugin" Koa.tags ["admin", "downloader"] Koa.query "plugin", schema: String diff --git a/src/routes/main.cr b/src/routes/main.cr index 54e3fba..79d135c 100644 --- a/src/routes/main.cr +++ b/src/routes/main.cr @@ -96,6 +96,15 @@ struct MainRouter end end + get "/download/plugins2" do |env| + begin + layout "plugin-download-2" + rescue e + Logger.error e + env.response.status_code = 500 + end + end + get "/download/subscription" do |env| mangadex_base_url = Config.current.mangadex["base_url"] username = get_username env diff --git a/src/views/plugin-download-2.html.ecr b/src/views/plugin-download-2.html.ecr new file mode 100644 index 0000000..a703341 --- /dev/null +++ b/src/views/plugin-download-2.html.ecr @@ -0,0 +1,61 @@ +
We could't find any plugins in the directory <%= Config.current.plugin_path %>
.
You can download official plugins from the Mango plugins repository.
+