Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/plugin v2 #284

Merged
merged 42 commits into from
Mar 22, 2022
Merged
Changes from 1 commit
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
23541f4
Add "title_title" to slim JSON
hkalexling May 15, 2021
a571d21
WIP
hkalexling May 15, 2021
e0713cc
WIP
hkalexling May 22, 2021
87c479b
WIP
hkalexling May 30, 2021
59bcb4d
WIP
hkalexling Jun 5, 2021
9eb699e
Add plugin subscription types
hkalexling Jun 7, 2021
6844860
Revert "Subscription manager"
hkalexling Jun 7, 2021
3b19883
Use auto overflow tables
hkalexling Jun 7, 2021
259f6cb
Add endpoint for plugin subscription
hkalexling Jul 11, 2021
f56ce23
WIP
hkalexling Jul 11, 2021
b7aee1e
WIP
hkalexling Aug 17, 2021
ae1c362
Merge branch 'dev' into feature/plugin-v2
hkalexling Sep 4, 2021
238860d
Simplify subscription JSON parsing
hkalexling Sep 4, 2021
198913d
Remove MangaDex files that are no longer needed
hkalexling Sep 4, 2021
a45bea0
Fix linter
hkalexling Sep 4, 2021
87e54aa
Merge branch 'dev' into feature/plugin-v2
hkalexling Nov 18, 2021
e442139
Refactor date filtering and use native date picker
hkalexling Nov 20, 2021
352236a
Delete unnecessary raise for debugging
hkalexling Nov 20, 2021
5442d12
Subscription management API endpoints
hkalexling Nov 20, 2021
fe6f884
Store manga ID with subscriptions
hkalexling Nov 20, 2021
b76d464
Add subscription manager page (WIP)
hkalexling Nov 21, 2021
031ea7e
Finish subscription manager page
hkalexling Nov 25, 2021
748386f
WIP
hkalexling Dec 31, 2021
5fac8c6
Merge branch 'dev' into feature/plugin-v2
hkalexling Jan 21, 2022
031df3a
Finish plugin updater
hkalexling Jan 22, 2022
d862c38
Base64 encode chapter IDs
hkalexling Jan 23, 2022
968f624
Fix actions on download manager
hkalexling Jan 23, 2022
2c7c29f
Trigger subscription update from manager page
hkalexling Jan 23, 2022
cae8329
Fix timestamp precision issue in plugin
hkalexling Jan 23, 2022
be3babd
Show target API version
hkalexling Jan 23, 2022
f6bd3fa
Update last checked from manager page
hkalexling Jan 23, 2022
0adcd3a
Update last checked even when no chapters found
hkalexling Jan 23, 2022
c80855b
Merge branch 'dev' into feature/plugin-v2
hkalexling Feb 20, 2022
fd650bd
Fix null pid
hkalexling Feb 20, 2022
b56a9cb
Clean up
hkalexling Feb 20, 2022
2ade6eb
Document the subscription endpoints
hkalexling Feb 20, 2022
c290eee
Fix BigFloat conversion issue
hkalexling Mar 15, 2022
8dfe580
Merge branch 'dev' into feature/plugin-v2
hkalexling Mar 15, 2022
acefa00
Merge branch 'dev' into feature/plugin-v2
hkalexling Mar 18, 2022
95eb208
Confirmation before deleting subscriptions
hkalexling Mar 19, 2022
2cc1a06
Reset table sort options
hkalexling Mar 19, 2022
cdfc9f3
Show manga title on subscription manager
hkalexling Mar 19, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
WIP
hkalexling committed May 22, 2021
commit e0713ccde8a32d4a52b67812589f853873526e71
42 changes: 42 additions & 0 deletions public/js/plugin-download-2.js
Original file line number Diff line number Diff line change
@@ -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);
}
};
};
101 changes: 79 additions & 22 deletions src/plugin/plugin.cr
Original file line number Diff line number Diff line change
@@ -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
end
env.get_global_string "info_dir"
info_dir = env.require_string -1
env.pop
info = Info.new info_dir

env.call_success
if value = info.settings[key]?
env.push_string value
else
env.push_undefined
end

env.call_success
end
sbx.put_prop_string -2, "settings"
end
sbx.put_prop_string -2, "settings"

sbx.put_prop_string -2, "mango"
end
91 changes: 91 additions & 0 deletions src/routes/api.cr
Original file line number Diff line number Diff line change
@@ -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
9 changes: 9 additions & 0 deletions src/routes/main.cr
Original file line number Diff line number Diff line change
@@ -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
61 changes: 61 additions & 0 deletions src/views/plugin-download-2.html.ecr
Original file line number Diff line number Diff line change
@@ -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 %>