mirror of
https://github.com/hkalexling/Mango.git
synced 2026-03-17 00:03:36 -04:00
Finish plugin functionalities
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
require "duktape/runtime"
|
||||
require "myhtml"
|
||||
require "http"
|
||||
require "xml"
|
||||
|
||||
class Plugin
|
||||
class Error < ::Exception
|
||||
@@ -15,70 +16,164 @@ class Plugin
|
||||
class SyntaxError < Error
|
||||
end
|
||||
|
||||
{% for name in ["id", "title", "author", "version", "placeholder"] %}
|
||||
getter {{name.id}} = ""
|
||||
{% end %}
|
||||
getter wait_seconds : UInt64 = 0
|
||||
getter filename
|
||||
struct Info
|
||||
{% for name in ["id", "title", "author", "version", "placeholder"] %}
|
||||
getter {{name.id}} = ""
|
||||
{% end %}
|
||||
getter wait_seconds : UInt64 = 0
|
||||
getter dir : String
|
||||
|
||||
def self.list
|
||||
dir = Config.current.plugin_path
|
||||
Dir.mkdir_p dir unless Dir.exists? dir
|
||||
def initialize(@dir)
|
||||
info_path = File.join @dir, "info.json"
|
||||
|
||||
Dir.children(dir)
|
||||
.select do |f|
|
||||
fp = File.join dir, f
|
||||
File.file?(fp) && File.extname(fp) == ".js"
|
||||
unless File.exists? info_path
|
||||
raise MetadataError.new "File `info.json` not found in the " \
|
||||
"plugin directory #{dir}"
|
||||
end
|
||||
.map do |f|
|
||||
File.basename f, ".js"
|
||||
|
||||
json = JSON.parse File.read info_path
|
||||
|
||||
begin
|
||||
{% for name in ["id", "title", "author", "version", "placeholder"] %}
|
||||
@{{name.id}} = json[{{name}}].as_s
|
||||
{% end %}
|
||||
@wait_seconds = json["wait_seconds"].as_i.to_u64
|
||||
|
||||
unless @id.alphanumeric_underscore?
|
||||
raise "Plugin ID can only contain alphanumeric characters and " \
|
||||
"underscores"
|
||||
end
|
||||
rescue e
|
||||
raise MetadataError.new "Failed to retrieve metadata from plugin " \
|
||||
"at #{@dir}. Error: #{e.message}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def initialize(@filename : String)
|
||||
struct State
|
||||
@hash = {} of String => String
|
||||
|
||||
def initialize(@path : String)
|
||||
unless File.exists? @path
|
||||
save
|
||||
end
|
||||
|
||||
json = JSON.parse File.read @path
|
||||
json.as_h.each do |k, v|
|
||||
@hash[k] = v.as_s
|
||||
end
|
||||
end
|
||||
|
||||
def []?(key)
|
||||
@hash[key]?
|
||||
end
|
||||
|
||||
def []=(key, val : String)
|
||||
@hash[key] = val
|
||||
end
|
||||
|
||||
def save
|
||||
File.write @path, @hash.to_pretty_json
|
||||
end
|
||||
end
|
||||
|
||||
@@info_ary = [] of Info
|
||||
@info : Info?
|
||||
|
||||
getter js_path = ""
|
||||
getter state_path = ""
|
||||
|
||||
def self.build_info_ary
|
||||
return unless @@info_ary.empty?
|
||||
|
||||
dir = Config.current.plugin_path
|
||||
Dir.mkdir_p dir unless Dir.exists? dir
|
||||
|
||||
@path = File.join dir, "#{filename}.js"
|
||||
unless File.exists? @path
|
||||
raise Error.new "Plugin script not found at #{@path}"
|
||||
Dir.each_child dir do |f|
|
||||
path = File.join dir, f
|
||||
next unless File.directory? path
|
||||
|
||||
begin
|
||||
@@info_ary << Info.new path
|
||||
rescue e : MetadataError
|
||||
Logger.warn e
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def self.list
|
||||
self.build_info_ary
|
||||
|
||||
@@info_ary.map &.title
|
||||
end
|
||||
|
||||
def info
|
||||
@info.not_nil!
|
||||
end
|
||||
|
||||
def self.new_from_id(id : String)
|
||||
self.build_info_ary
|
||||
|
||||
info = @@info_ary.find { |i| i.id == id }
|
||||
raise Error.new "Plugin with id #{id} not found" unless info
|
||||
self.new info.title
|
||||
end
|
||||
|
||||
def initialize(title : String)
|
||||
Plugin.build_info_ary
|
||||
|
||||
@info = @@info_ary.find { |i| i.title == title }
|
||||
if @info.nil?
|
||||
raise Error.new "Plugin with title #{title} not found"
|
||||
end
|
||||
|
||||
@js_path = File.join info.dir, "main.js"
|
||||
@state_path = File.join info.dir, "state.json"
|
||||
|
||||
unless File.exists? @js_path
|
||||
raise Error.new "Plugin script not found at #{@js_path}"
|
||||
end
|
||||
|
||||
@rt = Duktape::Runtime.new do |sbx|
|
||||
sbx.push_global_object
|
||||
|
||||
sbx.del_prop_string -1, "print"
|
||||
sbx.del_prop_string -1, "alert"
|
||||
sbx.del_prop_string -1, "console"
|
||||
sbx.push_pointer @state_path.as(Void*)
|
||||
path = sbx.require_pointer(-1).as String
|
||||
sbx.pop
|
||||
sbx.push_string path
|
||||
sbx.put_prop_string -2, "state_path"
|
||||
|
||||
def_helper_functions sbx
|
||||
end
|
||||
|
||||
eval File.read @path
|
||||
eval File.read @js_path
|
||||
end
|
||||
|
||||
begin
|
||||
data = eval_json "metadata"
|
||||
{% for name in ["id", "title", "author", "version", "placeholder"] %}
|
||||
@{{name.id}} = data[{{name}}].as_s
|
||||
{% end %}
|
||||
@wait_seconds = data["wait_seconds"].as_i.to_u64
|
||||
rescue e
|
||||
raise MetadataError.new "Failed to retrieve metadata from plugin " \
|
||||
"at #{@path}. Error: #{e.message}"
|
||||
end
|
||||
macro check_fields(ary)
|
||||
{% for field in ary %}
|
||||
unless json[{{field}}]?
|
||||
raise "Field `{{field.id}}` is missing from the function outputs"
|
||||
end
|
||||
{% end %}
|
||||
end
|
||||
|
||||
def search(query : String)
|
||||
json = eval_json "search('#{query}')"
|
||||
begin
|
||||
ary = json.as_a
|
||||
check_fields ["title", "chapters"]
|
||||
|
||||
ary = json["chapters"].as_a
|
||||
ary.each do |obj|
|
||||
id = obj["id"]?
|
||||
raise "Field `id` missing from `search` outputs" if id.nil?
|
||||
|
||||
unless id.to_s.chars.all? &.number?
|
||||
raise "The `id` values must be numeric" unless id
|
||||
unless id.to_s.alphanumeric_underscore?
|
||||
raise "The `id` field can only contain alphanumeric characters and " \
|
||||
"underscores"
|
||||
end
|
||||
|
||||
title = obj["title"]?
|
||||
raise "Field `title` missing from `search` outputs" if title.nil?
|
||||
end
|
||||
rescue e
|
||||
raise Error.new e.message
|
||||
@@ -89,12 +184,11 @@ class Plugin
|
||||
def select_chapter(id : String)
|
||||
json = eval_json "selectChapter('#{id}')"
|
||||
begin
|
||||
{% for field in ["title", "pages"] %}
|
||||
unless json[{{field}}]?
|
||||
raise "Field `{{field.id}}` is missing from the " \
|
||||
"`selectChapter` outputs"
|
||||
end
|
||||
{% end %}
|
||||
check_fields ["title", "pages"]
|
||||
|
||||
if json["title"].to_s.empty?
|
||||
raise "The `title` field of the chapter can not be empty"
|
||||
end
|
||||
rescue e
|
||||
raise Error.new e.message
|
||||
end
|
||||
@@ -105,12 +199,7 @@ class Plugin
|
||||
json = eval_json "nextPage()"
|
||||
return if json.size == 0
|
||||
begin
|
||||
{% for field in ["filename", "url"] %}
|
||||
unless json[{{field}}]?
|
||||
raise "Field `{{field.id}}` is missing from the " \
|
||||
"`nextPage` outputs"
|
||||
end
|
||||
{% end %}
|
||||
check_fields ["filename", "url"]
|
||||
rescue e
|
||||
raise Error.new e.message
|
||||
end
|
||||
@@ -173,16 +262,28 @@ class Plugin
|
||||
env = Duktape::Sandbox.new ptr
|
||||
html = env.require_string 0
|
||||
|
||||
myhtml = Myhtml::Parser.new html
|
||||
root = myhtml.root
|
||||
|
||||
str = ""
|
||||
str = root.inner_text if root
|
||||
str = XML.parse(html).inner_text
|
||||
|
||||
env.push_string str
|
||||
env.call_success
|
||||
end
|
||||
sbx.put_prop_string -2, "innerText"
|
||||
sbx.put_prop_string -2, "text"
|
||||
|
||||
sbx.push_proc 2 do |ptr|
|
||||
env = Duktape::Sandbox.new ptr
|
||||
html = env.require_string 0
|
||||
name = env.require_string 1
|
||||
|
||||
begin
|
||||
attr = XML.parse(html).first_element_child.not_nil![name]
|
||||
env.push_string attr
|
||||
rescue
|
||||
env.push_undefined
|
||||
end
|
||||
|
||||
env.call_success
|
||||
end
|
||||
sbx.put_prop_string -2, "attribute"
|
||||
|
||||
sbx.push_proc 1 do |ptr|
|
||||
env = Duktape::Sandbox.new ptr
|
||||
@@ -193,6 +294,32 @@ class Plugin
|
||||
end
|
||||
sbx.put_prop_string -2, "raise"
|
||||
|
||||
sbx.push_proc LibDUK::VARARGS do |ptr|
|
||||
env = Duktape::Sandbox.new ptr
|
||||
key = env.require_string 0
|
||||
|
||||
env.get_global_string "state_path"
|
||||
state_path = env.require_string -1
|
||||
env.pop
|
||||
state = State.new state_path
|
||||
|
||||
if env.get_top == 2
|
||||
val = env.require_string 1
|
||||
state[key] = val
|
||||
state.save
|
||||
else
|
||||
val = state[key]?
|
||||
if val
|
||||
env.push_string val
|
||||
else
|
||||
env.push_undefined
|
||||
end
|
||||
end
|
||||
|
||||
env.call_success
|
||||
end
|
||||
sbx.put_prop_string -2, "state"
|
||||
|
||||
sbx.put_prop_string -2, "mango"
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user