This commit is contained in:
Alex Ling 2021-05-22 07:24:30 +00:00
parent a571d21cba
commit e0713ccde8
5 changed files with 282 additions and 22 deletions

View File

@ -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);
}
};
};

View File

@ -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,11 +246,15 @@ class Plugin
def select_chapter(id : String)
json = eval_json "selectChapter('#{id}')"
begin
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"
end
end
rescue e
raise Error.new e.message
end
@ -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,10 +462,16 @@ class Plugin
end
sbx.put_prop_string -2, "storage"
if info.version > 1
sbx.push_proc 1 do |ptr|
env = Duktape::Sandbox.new ptr
key = env.require_string 0
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
@ -425,6 +481,7 @@ class Plugin
env.call_success
end
sbx.put_prop_string -2, "settings"
end
sbx.put_prop_string -2, "mango"
end

View File

@ -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

View File

@ -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

View File

@ -0,0 +1,61 @@
<div x-data="component()" x-init="init()">
<div class="uk-grid-small" uk-grid style="margin-bottom:40px;">
<div class="uk-container uk-text-center" x-show="plugins.length === 0">
<h2>No Plugins Found</h2>
<p>We could't find any plugins in the directory <code><%= Config.current.plugin_path %></code>.</p>
<p>You can download official plugins from the <a href="https://github.com/hkalexling/mango-plugins">Mango plugins repository</a>.</p>
</div>
<div x-show="plugins.length > 0" style="width:100%">
<h2 class=uk-title>Download with Plugins</h2>
<template x-if="info !== undefined">
<div>
<div class="uk-grid-small" uk-grid>
<div class="uk-width-3-4@m uk-child-width-1-1">
<div class="uk-margin">
<div class="uk-form-controls">
<label class="uk-form-label">&nbsp;</label>
<input class="uk-input" type="text" :placeholder="info.placeholder">
</div>
</div>
</div>
<div class="uk-width-expand">
<div class="uk-margin">
<label class="uk-form-label">Choose a plugin</label>
<div class="uk-form-controls">
<select class="uk-select" x-model="pid" @change="pluginChanged()">
<template x-for="p in plugins" :key="p">
<option :value="p.id" x-text="p.title"></option>
</template>
</select>
</div>
</div>
</div>
<div class="uk-width-auto">
<div class="uk-margin">
<label class="uk-form-label">&nbsp;</label>
<div class="uk-form-controls" style="padding-top: 10px;">
<span uk-icon="info" uk-toggle="target: #toggle"></span>
</div>
</div>
</div>
</div>
<template x-for="entry, idx in Object.entries(info).filter(tp => !['id', 'settings'].includes(tp[0]))" :key="idx">
<dl class="uk-description-list" id="toggle" hidden>
<dt x-text="entry[0]"></dt>
<dd x-text="entry[1]"></dd>
</dl>
</template>
</div>
</template>
</div>
</div>
</div>
<% content_for "script" do %>
<%= render_component "moment" %>
<%= render_component "jquery-ui" %>
<script src="<%= base_url %>js/alert.js"></script>
<script src="<%= base_url %>js/plugin-download-2.js"></script>
<% end %>