diff --git a/ContentGrabbingJi-framework-api/pom.xml b/ContentGrabbingJi-framework-api/pom.xml
index 6f9226d..8acbff6 100644
--- a/ContentGrabbingJi-framework-api/pom.xml
+++ b/ContentGrabbingJi-framework-api/pom.xml
@@ -28,5 +28,17 @@
ContentGrabbingJi-framework-api
+
+
+ org.pf4j
+ pf4j
+ 3.4.1
+
+
+ com.google.code.gson
+ gson
+ 2.8.6
+
+
\ No newline at end of file
diff --git a/ContentGrabbingJi-framework-api/src/main/java/net/lamgc/cgj/bot/framework/Author.java b/ContentGrabbingJi-framework-api/src/main/java/net/lamgc/cgj/bot/framework/Author.java
new file mode 100644
index 0000000..45c42d6
--- /dev/null
+++ b/ContentGrabbingJi-framework-api/src/main/java/net/lamgc/cgj/bot/framework/Author.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2020 LamGC
+ *
+ * ContentGrabbingJi is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * ContentGrabbingJi is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package net.lamgc.cgj.bot.framework;
+
+import java.util.Objects;
+
+/**
+ * 框架作者信息.
+ *
我觉得改成维护者会更好一些?
+ * @author LamGC
+ */
+public class Author {
+
+ private final String name;
+ private final String url;
+ private final String email;
+
+ public Author(String name, String url, String email) {
+ this.name = Objects.requireNonNull(name);
+ this.url = url;
+ this.email = email;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public String getUrl() {
+ return url;
+ }
+
+ public String getEmail() {
+ return email;
+ }
+
+ @Override
+ public String toString() {
+ return "Author{" +
+ "name='" + getName() + '\'' +
+ ", url='" + getUrl() + '\'' +
+ ", email='" + getEmail() + '\'' +
+ '}';
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ Author author = (Author) o;
+ return name.equals(author.name) &&
+ Objects.equals(url, author.url) &&
+ Objects.equals(email, author.email);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(name, url, email);
+ }
+}
diff --git a/ContentGrabbingJi-framework-api/src/main/java/net/lamgc/cgj/bot/framework/DefaultFrameworkDescriptor.java b/ContentGrabbingJi-framework-api/src/main/java/net/lamgc/cgj/bot/framework/DefaultFrameworkDescriptor.java
new file mode 100644
index 0000000..00c6f7a
--- /dev/null
+++ b/ContentGrabbingJi-framework-api/src/main/java/net/lamgc/cgj/bot/framework/DefaultFrameworkDescriptor.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2020 LamGC
+ *
+ * ContentGrabbingJi is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * ContentGrabbingJi is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package net.lamgc.cgj.bot.framework;
+
+import net.lamgc.cgj.bot.framework.message.BotCodeDescriptor;
+import org.pf4j.PluginDependency;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 默认框架描述对象.
+ * @author LamGC
+ */
+class DefaultFrameworkDescriptor implements FrameworkDescriptor {
+
+ private String id;
+ private String description;
+ private String version;
+ private String requiresVersion;
+ private String provider;
+ private String license;
+ private String frameworkClass;
+ private final List dependencies = new ArrayList<>();
+
+ private Platform platform;
+ private BotCodeDescriptor botCode;
+ private List authors;
+
+ @Override
+ public String getPluginId() {
+ return id;
+ }
+
+ @Override
+ public String getPluginDescription() {
+ return description;
+ }
+
+ @Override
+ public String getPluginClass() {
+ return frameworkClass;
+ }
+
+ @Override
+ public String getVersion() {
+ return version;
+ }
+
+ @Override
+ public String getRequires() {
+ return requiresVersion;
+ }
+
+ @Override
+ public String getProvider() {
+ return provider;
+ }
+
+ @Override
+ public String getLicense() {
+ return license;
+ }
+
+ @Override
+ public List getDependencies() {
+ return dependencies;
+ }
+
+ @Override
+ public Platform getPlatform() {
+ return platform;
+ }
+
+ @Override
+ public BotCodeDescriptor getBotCodeDescriptor() {
+ return botCode;
+ }
+
+ @Override
+ public List getAuthors() {
+ return authors;
+ }
+
+}
diff --git a/ContentGrabbingJi-framework-api/src/main/java/net/lamgc/cgj/bot/framework/Framework.java b/ContentGrabbingJi-framework-api/src/main/java/net/lamgc/cgj/bot/framework/Framework.java
new file mode 100644
index 0000000..b3dee4c
--- /dev/null
+++ b/ContentGrabbingJi-framework-api/src/main/java/net/lamgc/cgj/bot/framework/Framework.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2020 LamGC
+ *
+ * ContentGrabbingJi is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * ContentGrabbingJi is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package net.lamgc.cgj.bot.framework;
+
+import org.pf4j.Plugin;
+import org.pf4j.PluginDescriptor;
+import org.pf4j.PluginState;
+import org.pf4j.PluginWrapper;
+
+import java.io.File;
+
+/**
+ * 框架抽象类.
+ * @author LamGC
+ */
+public abstract class Framework extends Plugin {
+
+ private final File dataFolder;
+
+ /**
+ * 由 FrameworkManager 执行的构造方法.
+ * 不要在构造方法内做任何处理. 如果你需要, 请在 {@link #initial()} 进行初始化.
+ *
+ * @param wrapper 包含框架运行期间需要使用对象的包装器.
+ */
+ public Framework(PluginWrapper wrapper, File dataFolder) {
+ super(wrapper);
+ this.dataFolder = dataFolder;
+ try {
+ initial();
+ } catch (Throwable e) {
+ wrapper.setFailedException(e);
+ wrapper.setPluginState(PluginState.FAILED);
+ log.error("An exception occurred while initializing the framework", e);
+ }
+ }
+
+ /**
+ * 执行初始化操作.
+ */
+ protected abstract void initial();
+
+ /**
+ * 获取仅属于该框架的数据存储目录.
+ *
+ *
调用本方法将会检查目录是否存在, 并在目录不存在时尝试创建.
+ *
请不要在除数据存储目录外的其他位置存储数据, 这将使用户感到困扰!
+ *
+ * @return 返回数据存储目录.
+ */
+ public File getDataFolder() {
+ if (!dataFolder.exists() && !dataFolder.mkdirs()) {
+ log.warn("框架 {} 数据目录创建失败.", getDescriptor().getPluginId());
+ }
+ return dataFolder;
+ }
+
+ /**
+ * 获取框架描述对象.
+ * @return 返回框架描述对象.
+ */
+ public FrameworkDescriptor getDescriptor() {
+ PluginDescriptor descriptor = getWrapper().getDescriptor();
+ if (descriptor instanceof FrameworkDescriptor) {
+ return (FrameworkDescriptor) descriptor;
+ }
+ throw new IllegalStateException("无法转换 Descriptor 的类型, 框架管理器可能遭到修改!");
+ }
+
+}
diff --git a/ContentGrabbingJi-framework-api/src/main/java/net/lamgc/cgj/bot/framework/FrameworkDescriptor.java b/ContentGrabbingJi-framework-api/src/main/java/net/lamgc/cgj/bot/framework/FrameworkDescriptor.java
new file mode 100644
index 0000000..51557cf
--- /dev/null
+++ b/ContentGrabbingJi-framework-api/src/main/java/net/lamgc/cgj/bot/framework/FrameworkDescriptor.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2020 LamGC
+ *
+ * ContentGrabbingJi is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * ContentGrabbingJi is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package net.lamgc.cgj.bot.framework;
+
+import net.lamgc.cgj.bot.framework.message.BotCodeDescriptor;
+import org.pf4j.PluginDescriptor;
+
+import java.util.List;
+
+/**
+ * 框架描述对象.
+ * @author LamGC
+ */
+public interface FrameworkDescriptor extends PluginDescriptor {
+
+ /**
+ * 获取框架所属平台.
+ * @return 返回平台对象.
+ */
+ Platform getPlatform();
+
+ /**
+ * 获取 BotCode 描述.
+ * @return 返回 BotCode 描述对象.
+ */
+ BotCodeDescriptor getBotCodeDescriptor();
+
+ /**
+ * 获取框架作者信息.
+ * @return 返回存储了所有作者信息的 List.
+ */
+ List getAuthors();
+
+}
diff --git a/ContentGrabbingJi-framework-api/src/main/java/net/lamgc/cgj/bot/framework/FrameworkFactory.java b/ContentGrabbingJi-framework-api/src/main/java/net/lamgc/cgj/bot/framework/FrameworkFactory.java
new file mode 100644
index 0000000..2699819
--- /dev/null
+++ b/ContentGrabbingJi-framework-api/src/main/java/net/lamgc/cgj/bot/framework/FrameworkFactory.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2020 LamGC
+ *
+ * ContentGrabbingJi is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * ContentGrabbingJi is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package net.lamgc.cgj.bot.framework;
+
+import org.pf4j.Plugin;
+import org.pf4j.PluginFactory;
+import org.pf4j.PluginWrapper;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Modifier;
+
+/**
+ * 经过调整的, 针对 Framework 的实例工厂类.
+ * @author LamGC
+ */
+final class FrameworkFactory implements PluginFactory {
+
+ private final static Logger log = LoggerFactory.getLogger(FrameworkFactory.class);
+
+ private final File dataRootFolder;
+
+ public FrameworkFactory(File dataRootFolder) {
+ this.dataRootFolder = dataRootFolder;
+ if (!this.dataRootFolder.exists() && !this.dataRootFolder.mkdirs()) {
+ log.warn("框架数据目录创建异常, 可能会导致后续框架存取数据失败!");
+ }
+ }
+
+ @Override
+ public Plugin create(PluginWrapper pluginWrapper) {
+ String pluginClassName = pluginWrapper.getDescriptor().getPluginClass();
+ log.debug("Create instance for framework '{}'", pluginClassName);
+
+ Class> pluginClass;
+ try {
+ pluginClass = pluginWrapper.getPluginClassLoader().loadClass(pluginClassName);
+ } catch (ClassNotFoundException e) {
+ log.error(e.getMessage(), e);
+ return null;
+ }
+
+ // 如果成功获取类, 就需要对其检查, 以确保类符合框架主类的要求.
+ int modifiers = pluginClass.getModifiers();
+ if (Modifier.isAbstract(modifiers) || Modifier.isInterface(modifiers)
+ || (!Framework.class.isAssignableFrom(pluginClass))) {
+ log.error("The framework class '{}' is not valid", pluginClassName);
+ return null;
+ }
+
+ try {
+ // (PluginWrapper, DataFolder)
+ Constructor> constructor = pluginClass.getConstructor(PluginWrapper.class, File.class);
+ return (Plugin) constructor.newInstance(pluginWrapper,
+ new File(dataRootFolder, pluginWrapper.getPluginId()));
+ } catch (Exception e) {
+ log.error(e.getMessage(), e);
+ }
+
+ return null;
+ }
+}
diff --git a/ContentGrabbingJi-framework-api/src/main/java/net/lamgc/cgj/bot/framework/FrameworkManager.java b/ContentGrabbingJi-framework-api/src/main/java/net/lamgc/cgj/bot/framework/FrameworkManager.java
new file mode 100644
index 0000000..15cf9d0
--- /dev/null
+++ b/ContentGrabbingJi-framework-api/src/main/java/net/lamgc/cgj/bot/framework/FrameworkManager.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2020 LamGC
+ *
+ * ContentGrabbingJi is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * ContentGrabbingJi is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package net.lamgc.cgj.bot.framework;
+
+import org.pf4j.*;
+
+import java.io.File;
+
+/**
+ * 框架管理器.
+ * @author LamGC
+ */
+public class FrameworkManager extends JarPluginManager {
+
+ public FrameworkManager(String systemVersion, File frameworksDirectory) {
+ super(frameworksDirectory.toPath());
+ setSystemVersion(systemVersion);
+ }
+
+ @Override
+ protected PluginDescriptorFinder createPluginDescriptorFinder() {
+ return new JsonFrameworkDescriptorFinder();
+ }
+
+ @Override
+ protected PluginRepository createPluginRepository() {
+ return new CompoundPluginRepository()
+ .add(new DevelopmentPluginRepository(getPluginsRoot()), this::isDevelopment)
+ .add(new JarPluginRepository(getPluginsRoot()), this::isNotDevelopment)
+ .add(new DefaultPluginRepository(getPluginsRoot()), this::isNotDevelopment);
+ }
+
+ @Override
+ protected PluginFactory createPluginFactory() {
+ return new FrameworkFactory(getPluginsRoot().getParent().resolve("frameworkData").toFile());
+ }
+}
diff --git a/ContentGrabbingJi-framework-api/src/main/java/net/lamgc/cgj/bot/framework/JsonFrameworkDescriptorFinder.java b/ContentGrabbingJi-framework-api/src/main/java/net/lamgc/cgj/bot/framework/JsonFrameworkDescriptorFinder.java
new file mode 100644
index 0000000..133e7a0
--- /dev/null
+++ b/ContentGrabbingJi-framework-api/src/main/java/net/lamgc/cgj/bot/framework/JsonFrameworkDescriptorFinder.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2020 LamGC
+ *
+ * ContentGrabbingJi is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * ContentGrabbingJi is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package net.lamgc.cgj.bot.framework;
+
+import com.google.gson.Gson;
+import com.google.gson.JsonObject;
+import net.lamgc.cgj.bot.framework.message.BotCodeDescriptor;
+import net.lamgc.cgj.bot.framework.util.AuthorJsonSerializer;
+import net.lamgc.cgj.bot.framework.util.BotCodeDescriptorJsonSerializer;
+import net.lamgc.cgj.bot.framework.util.PlatformJsonSerializer;
+import net.lamgc.cgj.bot.framework.util.PluginDependencyJsonSerializer;
+import org.pf4j.PluginDependency;
+import org.pf4j.PluginDescriptor;
+import org.pf4j.PluginDescriptorFinder;
+import org.pf4j.PluginRuntimeException;
+import org.pf4j.util.FileUtils;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+/**
+ * Json格式的框架描述文件查找器.
+ * @author LamGC
+ */
+class JsonFrameworkDescriptorFinder implements PluginDescriptorFinder {
+
+ private final static String DESCRIPTOR_FILE_NAME = "framework.json";
+ private final Gson gson;
+
+ public JsonFrameworkDescriptorFinder() {
+ this(new Gson());
+ }
+
+ public JsonFrameworkDescriptorFinder(Gson gson) {
+ this.gson = gson.newBuilder()
+ .serializeNulls()
+ .registerTypeAdapter(Author.class, new AuthorJsonSerializer())
+ .registerTypeAdapter(BotCodeDescriptor.class, new BotCodeDescriptorJsonSerializer())
+ .registerTypeAdapter(Platform.class, new PlatformJsonSerializer())
+ .registerTypeAdapter(PluginDependency.class, new PluginDependencyJsonSerializer())
+ .create();
+ }
+
+ @Override
+ public boolean isApplicable(Path frameworkPath) {
+ return Files.exists(frameworkPath) && (Files.isDirectory(frameworkPath) || FileUtils.isJarFile(frameworkPath));
+ }
+
+ @Override
+ public PluginDescriptor find(Path frameworkPath) {
+ JsonObject descriptorObject = loadFrameworkDescriptorObject(frameworkPath);
+ return createFrameworkDescriptor(descriptorObject);
+ }
+
+ private Path getFrameworkDescriptorPath(Path frameworkPath) {
+ if (Files.isDirectory(frameworkPath)) {
+ return frameworkPath.resolve(Paths.get(DESCRIPTOR_FILE_NAME));
+ } else {
+ try {
+ return FileUtils.getPath(frameworkPath, DESCRIPTOR_FILE_NAME);
+ } catch (IOException e) {
+ throw new PluginRuntimeException(e);
+ }
+ }
+ }
+
+ private JsonObject loadFrameworkDescriptorObject(Path frameworkPath) {
+ Path descriptorPath = getFrameworkDescriptorPath(frameworkPath);
+ if (frameworkPath == null) {
+ throw new PluginRuntimeException("Cannot find the json path");
+ }
+
+ JsonObject descriptorObject;
+ try {
+ if (Files.notExists(descriptorPath)) {
+ throw new PluginRuntimeException("Cannot find '{}' path", descriptorPath);
+ }
+ try (InputStream input = Files.newInputStream(descriptorPath)) {
+ descriptorObject = gson.fromJson(
+ new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8)), JsonObject.class);
+ } catch (IOException e) {
+ throw new PluginRuntimeException("Exception loading descriptor", e);
+ }
+ } finally {
+ FileUtils.closePath(descriptorPath);
+ }
+ return descriptorObject;
+ }
+
+ private FrameworkDescriptor createFrameworkDescriptor(JsonObject descriptorObject) {
+ return gson.fromJson(descriptorObject, DefaultFrameworkDescriptor.class);
+ }
+
+}
diff --git a/ContentGrabbingJi-framework-api/src/main/java/net/lamgc/cgj/bot/framework/Platform.java b/ContentGrabbingJi-framework-api/src/main/java/net/lamgc/cgj/bot/framework/Platform.java
new file mode 100644
index 0000000..7a03733
--- /dev/null
+++ b/ContentGrabbingJi-framework-api/src/main/java/net/lamgc/cgj/bot/framework/Platform.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2020 LamGC
+ *
+ * ContentGrabbingJi is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * ContentGrabbingJi is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package net.lamgc.cgj.bot.framework;
+
+/**
+ * 框架平台DO.
+ * @author LamGC
+ */
+public class Platform {
+
+ private final String platformName;
+ private final String platformIdentify;
+
+ public Platform(String platformName, String platformIdentify) {
+ this.platformName = platformName;
+ this.platformIdentify = platformIdentify;
+ }
+
+ @Override
+ public String toString() {
+ return "Platform{" +
+ "platformName='" + platformName + '\'' +
+ ", platformIdentify='" + platformIdentify + '\'' +
+ '}';
+ }
+
+ /**
+ * 获取平台名称.
+ * @return 返回平台名称.
+ */
+ public String getPlatformName() {
+ return platformName;
+ }
+
+ /**
+ * 获取平台唯一标识.
+ * 注意, 该标识将应用于平台所属事件的处理相关.
+ * @return 返回平台标识.
+ */
+ public String getPlatformIdentify() {
+ return platformIdentify;
+ }
+
+}
diff --git a/ContentGrabbingJi-framework-api/src/main/java/net/lamgc/cgj/bot/framework/message/BotCode.java b/ContentGrabbingJi-framework-api/src/main/java/net/lamgc/cgj/bot/framework/message/BotCode.java
new file mode 100644
index 0000000..f3db467
--- /dev/null
+++ b/ContentGrabbingJi-framework-api/src/main/java/net/lamgc/cgj/bot/framework/message/BotCode.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2020 LamGC
+ *
+ * ContentGrabbingJi is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * ContentGrabbingJi is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package net.lamgc.cgj.bot.framework.message;
+
+import net.lamgc.cgj.bot.framework.Platform;
+import net.lamgc.cgj.bot.framework.message.exception.BuildBotCodeException;
+import net.lamgc.cgj.bot.framework.message.exception.InvalidBotCodeException;
+import net.lamgc.cgj.bot.framework.message.exception.UnsupportedBotCodeException;
+
+import java.util.Set;
+
+/**
+ * 功能码接口.
+ * @author LamGC
+ */
+public interface BotCode {
+
+ /**
+ * 获取 BotCode 实现所属平台.
+ * @return 返回所属平台.
+ */
+ Platform getPlatform();
+
+ /**
+ * 转换为平台或框架可识别并处理的BotCode字符串形式.
+ * @return 返回转换后的结果.
+ * @throws UnsupportedBotCodeException 当框架不支持该 BotCode 时抛出.
+ * @throws BuildBotCodeException 当 BotCode 无法构造出字符串形式时抛出, 包含原因.
+ */
+ String toBotCodeString() throws UnsupportedBotCodeException, BuildBotCodeException;
+
+ /**
+ * 从 BotCode 字符串转换成 BotCode 对象.
+ * @param botCodeString 传入的 BotCode 字符串.
+ * @throws InvalidBotCodeException 当传入的 BotCode 字符串无法转换成该实现对应的 BotCode 对象时可抛出该异常,
+ * 务必在异常中清晰说明异常原因.
+ * @throws UnsupportedBotCodeException 当框架不支持该 BotCode 时抛出.
+ */
+ void fromBotCodeString(String botCodeString) throws InvalidBotCodeException, UnsupportedBotCodeException;
+
+ /**
+ * 取功能函数名.
+ * @return 返回功能函数名.
+ */
+ String getFunctionName();
+
+ /**
+ * 设置功能函数名
+ * @param functionName 新的功能函数名.
+ */
+ void setFunctionName(String functionName);
+
+ /**
+ * 设置功能参数
+ * @param key 参数名
+ * @param value 参数值, 如果参数值为 {@code null} 则删除该参数.
+ * @throws NullPointerException 当 key 为 null 时抛出.
+ */
+ void setProperty(String key, String value);
+
+ /**
+ * 获取功能参数
+ * @param key 参数名
+ * @return 如果存在, 返回参数名, 否则返回 {@code null}.
+ * @throws NullPointerException 当 key 为 null 时抛出.
+ */
+ String getProperty(String key);
+
+ /**
+ * 取功能码参数键集.
+ * @return 返回存储了所有参数键名的Set.
+ */
+ Set getPropertiesKeys();
+
+}
diff --git a/ContentGrabbingJi-framework-api/src/main/java/net/lamgc/cgj/bot/framework/message/BotCodeDescriptor.java b/ContentGrabbingJi-framework-api/src/main/java/net/lamgc/cgj/bot/framework/message/BotCodeDescriptor.java
new file mode 100644
index 0000000..b7b3323
--- /dev/null
+++ b/ContentGrabbingJi-framework-api/src/main/java/net/lamgc/cgj/bot/framework/message/BotCodeDescriptor.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2020 LamGC
+ *
+ * ContentGrabbingJi is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * ContentGrabbingJi is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package net.lamgc.cgj.bot.framework.message;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.regex.Pattern;
+
+/**
+ * BotCode 描述对象.
+ * @author LamGC
+ */
+public final class BotCodeDescriptor {
+
+ private final List patterns;
+
+ public BotCodeDescriptor(List patternStrings) {
+ List patterns = new ArrayList<>();
+ for (String patternString : patternStrings) {
+ patterns.add(Pattern.compile(patternString));
+ }
+ this.patterns = patterns;
+ }
+
+ public List getPatterns() {
+ return patterns;
+ }
+
+}
diff --git a/ContentGrabbingJi-framework-api/src/main/java/net/lamgc/cgj/bot/framework/util/AuthorJsonSerializer.java b/ContentGrabbingJi-framework-api/src/main/java/net/lamgc/cgj/bot/framework/util/AuthorJsonSerializer.java
new file mode 100644
index 0000000..dc3ef7a
--- /dev/null
+++ b/ContentGrabbingJi-framework-api/src/main/java/net/lamgc/cgj/bot/framework/util/AuthorJsonSerializer.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2020 LamGC
+ *
+ * ContentGrabbingJi is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * ContentGrabbingJi is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package net.lamgc.cgj.bot.framework.util;
+
+import com.google.gson.*;
+import net.lamgc.cgj.bot.framework.Author;
+
+import java.lang.reflect.Type;
+
+/**
+ * {@link Author} Json 序列化工具.
+ * @see Author
+ * @author LamGC
+ */
+public class AuthorJsonSerializer implements JsonSerializer, JsonDeserializer {
+
+ private final static String FIELD_NAME = "name";
+ private final static String FIELD_URL = "url";
+ private final static String FIELD_EMAIL = "email";
+
+ @Override
+ public Author deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
+ throws JsonParseException {
+ if (!json.isJsonObject()) {
+ throw new JsonParseException("Not a JsonObject");
+ }
+
+ JsonObject authorObject = json.getAsJsonObject();
+ if (!authorObject.has(FIELD_NAME) || !authorObject.get(FIELD_NAME).isJsonPrimitive()) {
+ throw new JsonParseException("A required field is missing or the type is incorrect: " + FIELD_NAME);
+ }
+
+ String name = authorObject.get(FIELD_NAME).getAsString();
+ String url = authorObject.has(FIELD_URL) && authorObject.get(FIELD_URL).isJsonPrimitive() ?
+ authorObject.get(FIELD_URL).getAsString() : null;
+ String email = authorObject.has(FIELD_EMAIL) && authorObject.get(FIELD_EMAIL).isJsonPrimitive() ?
+ authorObject.get(FIELD_EMAIL).getAsString() : null;
+
+ return new Author(name, url, email);
+ }
+
+ @Override
+ public JsonElement serialize(Author src, Type typeOfSrc, JsonSerializationContext context) {
+ JsonObject result = new JsonObject();
+ result.addProperty(FIELD_NAME, src.getName());
+ result.addProperty(FIELD_URL, src.getUrl());
+ result.addProperty(FIELD_EMAIL, src.getEmail());
+ return result;
+ }
+}
diff --git a/ContentGrabbingJi-framework-api/src/main/java/net/lamgc/cgj/bot/framework/util/BotCodeDescriptorJsonSerializer.java b/ContentGrabbingJi-framework-api/src/main/java/net/lamgc/cgj/bot/framework/util/BotCodeDescriptorJsonSerializer.java
new file mode 100644
index 0000000..a84b6b1
--- /dev/null
+++ b/ContentGrabbingJi-framework-api/src/main/java/net/lamgc/cgj/bot/framework/util/BotCodeDescriptorJsonSerializer.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2020 LamGC
+ *
+ * ContentGrabbingJi is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * ContentGrabbingJi is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package net.lamgc.cgj.bot.framework.util;
+
+import com.google.gson.*;
+import net.lamgc.cgj.bot.framework.message.BotCodeDescriptor;
+
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.regex.Pattern;
+
+/**
+ * BotCode 描述对象序列化工具.
+ * @see BotCodeDescriptor
+ * @author LamGC
+ */
+public class BotCodeDescriptorJsonSerializer
+ implements JsonSerializer, JsonDeserializer {
+
+ private final static String FIELD_PATTERNS = "patterns";
+
+ @Override
+ public BotCodeDescriptor deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
+ throws JsonParseException {
+ if (!json.isJsonObject()) {
+ throw new JsonParseException("Not a JsonObject");
+ }
+
+ JsonObject descriptorObject = json.getAsJsonObject();
+ List patternStrings = new ArrayList<>();
+ if (descriptorObject.has(FIELD_PATTERNS) && descriptorObject.get(FIELD_PATTERNS).isJsonArray()) {
+ for (JsonElement jsonElement : descriptorObject.getAsJsonArray(FIELD_PATTERNS)) {
+ if (!jsonElement.isJsonPrimitive()) {
+ continue;
+ }
+
+ JsonPrimitive primitive = jsonElement.getAsJsonPrimitive();
+ if (!primitive.isString()) {
+ continue;
+ }
+ patternStrings.add(primitive.getAsString());
+ }
+ }
+
+ return new BotCodeDescriptor(patternStrings);
+ }
+
+ @Override
+ public JsonElement serialize(BotCodeDescriptor src, Type typeOfSrc, JsonSerializationContext context) {
+ JsonObject result = new JsonObject();
+
+ JsonArray patterns = new JsonArray();
+ for (Pattern pattern : src.getPatterns()) {
+ patterns.add(pattern.pattern());
+ }
+ result.add(FIELD_PATTERNS, patterns);
+
+ return result;
+ }
+}
diff --git a/ContentGrabbingJi-framework-api/src/main/java/net/lamgc/cgj/bot/framework/util/PlatformJsonSerializer.java b/ContentGrabbingJi-framework-api/src/main/java/net/lamgc/cgj/bot/framework/util/PlatformJsonSerializer.java
new file mode 100644
index 0000000..f28ddc8
--- /dev/null
+++ b/ContentGrabbingJi-framework-api/src/main/java/net/lamgc/cgj/bot/framework/util/PlatformJsonSerializer.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2020 LamGC
+ *
+ * ContentGrabbingJi is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * ContentGrabbingJi is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package net.lamgc.cgj.bot.framework.util;
+
+import com.google.gson.*;
+import net.lamgc.cgj.bot.framework.Platform;
+
+import java.lang.reflect.Type;
+
+/**
+ * {@link Platform} 序列化工具.
+ * @see Platform
+ * @author LamGC
+ */
+public final class PlatformJsonSerializer implements JsonSerializer, JsonDeserializer {
+
+ private final static String FIELD_NAME = "name";
+ private final static String FIELD_IDENTIFY = "identify";
+
+
+ @Override
+ public Platform deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
+ throws JsonParseException {
+ if (!json.isJsonObject()) {
+ throw new JsonParseException("Not a JsonObject");
+ }
+
+ JsonObject descriptorObject = json.getAsJsonObject();
+ if (!descriptorObject.has(FIELD_NAME) || !descriptorObject.has(FIELD_IDENTIFY)) {
+ throw new JsonParseException("A required field is missing");
+ }
+ return new Platform(descriptorObject.get(FIELD_NAME).getAsString(),
+ descriptorObject.get(FIELD_IDENTIFY).getAsString());
+ }
+
+ @Override
+ public JsonElement serialize(Platform src, Type typeOfSrc, JsonSerializationContext context) {
+ JsonObject result = new JsonObject();
+ result.addProperty(FIELD_NAME, src.getPlatformName());
+ result.addProperty(FIELD_IDENTIFY, src.getPlatformIdentify());
+ return result;
+ }
+}
diff --git a/ContentGrabbingJi-framework-api/src/main/java/net/lamgc/cgj/bot/framework/util/PluginDependencyJsonSerializer.java b/ContentGrabbingJi-framework-api/src/main/java/net/lamgc/cgj/bot/framework/util/PluginDependencyJsonSerializer.java
new file mode 100644
index 0000000..610cdc8
--- /dev/null
+++ b/ContentGrabbingJi-framework-api/src/main/java/net/lamgc/cgj/bot/framework/util/PluginDependencyJsonSerializer.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2020 LamGC
+ *
+ * ContentGrabbingJi is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * ContentGrabbingJi is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package net.lamgc.cgj.bot.framework.util;
+
+import com.google.gson.*;
+import org.pf4j.PluginDependency;
+
+import java.lang.reflect.Type;
+
+/**
+ * PluginDependency Json 序列化/反序列化 工具.
+ * @see PluginDependency
+ * @author LamGC
+ */
+public class PluginDependencyJsonSerializer implements JsonSerializer, JsonDeserializer {
+ private final static String PLUGIN_VERSION_SUPPORT_ALL = "*";
+
+ @Override
+ public JsonElement serialize(PluginDependency src, Type typeOfSrc, JsonSerializationContext context) {
+ StringBuilder builder = new StringBuilder(src.getPluginId());
+ String pluginVersionSupport = src.getPluginVersionSupport();
+ if (src.isOptional()) {
+ builder.append('?');
+ }
+ if (src.getPluginVersionSupport() != null || !PLUGIN_VERSION_SUPPORT_ALL.equals(pluginVersionSupport)) {
+ builder.append('@').append(pluginVersionSupport);
+ }
+ return new JsonPrimitive(builder.toString());
+ }
+
+ @Override
+ public PluginDependency deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
+ if (!json.isJsonPrimitive()) {
+ throw new JsonParseException("Only JsonPrimitive types are supported for conversion");
+ }
+
+ JsonPrimitive primitive = json.getAsJsonPrimitive();
+ if (!primitive.isString()) {
+ throw new JsonParseException("Only String is supported");
+ }
+
+ return new PluginDependency(primitive.getAsString());
+ }
+}