Logo小幻影视

通用配置定义(草案)

跨客户端共享服务配置的 JSON + AES 加密格式定义

本章为草案

本格式当前仍在迭代,字段定义、加密参数与密钥分发约定都可能在后续版本中调整。在没有正式签发的"对接合作"前提下,请不要在第三方应用里硬编码本格式作为对外协议;接入前请通过 issue 或邮件与维护者确认当前版本细节。

格式本身被设计为可在多客户端之间互通,但密钥分发是受控的——是否能解析特定客户端导出的配置,取决于该客户端是否选择把密钥分享给你。

该 JSON 配置定义了一些通用的配置字段,遵循此格式以尽可能跨客户端共享配置,降低数据迁移阻碍。

格式自由度较高,加密密钥由各客户端开发者自行管理,并自主决定要把密钥分享给哪些客户端,从而实现定向配置共享。

数据定义与序列化流程

客户端把自身一份服务配置转换成下面的数据模型(以 C# 为例):

public sealed class CommonServiceConfig
{
    /// <summary>
    /// 服务类型,可选值:emby, jellyfin, webdav, alist, smb 等,全小写,无分隔符.
    /// </summary>
    [JsonPropertyName("type")]
    public string Type { get; set; }

    /// <summary>
    /// 配置标识符,全局唯一.
    /// </summary>
    [JsonPropertyName("id")]
    public string? Id { get; set; }

    /// <summary>
    /// 配置名称,用户自定义,用于区分不同配置.
    /// </summary>
    [JsonPropertyName("name")]
    public string Name { get; set; }

    /// <summary>
    /// 服务地址,支持 IP 和域名。应该是完整的包含 protocol 的链接.
    /// </summary>
    [JsonPropertyName("url")]
    public string Url { get; set; }

    /// <summary>
    /// 授权信息.
    /// </summary>
    [JsonPropertyName("authorization")]
    public CommonServiceAuthorization? Authorization { get; set; }

    /// <summary>
    /// 自定义排序序号,数值越小优先级越高,值大于等于 0.
    /// </summary>
    [JsonPropertyName("sort_order")]
    public uint? SortOrder { get; set; }

    /// <summary>
    /// 配置创建时间,Unix 时间戳,单位为秒.
    /// </summary>
    [JsonPropertyName("create_time")]
    public long? CreateTime { get; set; }

    /// <summary>
    /// 配置最后更新时间,Unix 时间戳,单位为秒.
    /// </summary>
    [JsonPropertyName("last_update_time")]
    public long? LastUpdateTime { get; set; }

    /// <summary>
    /// 配置最后一次播放的时间,Unix 时间戳,单位为秒.
    /// </summary>
    [JsonPropertyName("last_play_time")]
    public long? LastPlayTime { get; set; }

    /// <summary>
    /// 配置最后一次打开的时间,Unix 时间戳,单位为秒.
    /// </summary>
    [JsonPropertyName("last_open_time")]
    public long? LastOpenTime { get; set; }

    /// <summary>
    /// 图标,最好是网络图片.
    /// </summary>
    [JsonPropertyName("icon")]
    public string? Icon { get; set; }

    /// <summary>
    /// 线路信息列表.
    /// </summary>
    [JsonPropertyName("lines")]
    public List<CommonServiceLineInfo>? Lines { get; set; }

    /// <summary>
    /// 其他选项.
    /// </summary>
    [JsonPropertyName("options")]
    public Dictionary<string, string>? Options { get; set; }
}

public sealed class CommonServiceAuthorization
{
    [JsonPropertyName("user_name")]
    public string? UserName { get; set; }

    [JsonPropertyName("user_id")]
    public string? UserId { get; set; }

    /// <summary>明文存储.</summary>
    [JsonPropertyName("password")]
    public string? Password { get; set; }

    [JsonPropertyName("access_token")]
    public string? AccessToken { get; set; }

    [JsonPropertyName("data")]
    public Dictionary<string, string>? Data { get; set; }
}

public sealed class CommonServiceLineInfo
{
    [JsonPropertyName("url")]
    public string Url { get; set; }

    [JsonPropertyName("name")]
    public string? Name { get; set; }

    /// <summary>附加数据,用于存储其他未定义的信息.</summary>
    [JsonPropertyName("additional_data")]
    public Dictionary<string, string>? AdditionalData { get; set; }
}

可空类型表示该字段可根据客户端需求留空。序列化后的属性名按 [JsonPropertyName] 定义,统一使用蛇形命名(小写 + 下划线)。

完成数据映射后,将 JSON 字符串用 AES 加密:

  • Mode:CBC
  • Padding:PKCS7
  • Key:固定 32 字节(256 位)
  • IV:取 Key 的前 16 字节(128 位)

加密 / 解密示例(C#):

public static string ConvertToEncryptedString(YourConfig config, string key)
{
    var keyBytes = Encoding.UTF8.GetBytes(key);  // 32 字节 = 256 位
    var ivBytes = new byte[16];
    Array.Copy(keyBytes, ivBytes, 16);            // 16 字节 = 128 位(IV)

    var serviceConfig = new CommonServiceConfig
    {
        Id = config.Id,
        Type = "emby", // or "jellyfin", "webdav", etc.
        // ... other properties
    };

    var json = JsonSerializer.Serialize<CommonServiceConfig>(serviceConfig);
    using var aesAlg = Aes.Create();
    aesAlg.Mode = CipherMode.CBC;
    aesAlg.Padding = PaddingMode.PKCS7;
    aesAlg.Key = keyBytes;
    aesAlg.IV = ivBytes;

    var encryptor = aesAlg.CreateEncryptor(aesAlg.Key, aesAlg.IV);
    using var msEncrypt = new MemoryStream();
    using var csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write);
    using var swEncrypt = new StreamWriter(csEncrypt, Encoding.UTF8);
    swEncrypt.Write(json);
    swEncrypt.Flush();

    return Convert.ToBase64String(msEncrypt.ToArray());
}

public static string ConvertFromEncryptedString(string encrypted, string key)
{
    var keyBytes = Encoding.UTF8.GetBytes(key);
    var ivBytes = new byte[16];
    Array.Copy(keyBytes, ivBytes, 16);

    using var aesAlg = Aes.Create();
    aesAlg.Mode = CipherMode.CBC;
    aesAlg.Padding = PaddingMode.PKCS7;
    aesAlg.Key = keyBytes;
    aesAlg.IV = ivBytes;

    var decryptor = aesAlg.CreateDecryptor(aesAlg.Key, aesAlg.IV);
    var ciphertextBytes = Convert.FromBase64String(encrypted);

    using var msDecrypt = new MemoryStream(ciphertextBytes);
    using var csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read);
    using var srDecrypt = new StreamReader(csDecrypt, Encoding.UTF8);

    return srDecrypt.ReadToEnd();
    // 如需反序列化:JsonSerializer.Deserialize<CommonServiceConfig>(json);
}

将每条服务配置依次加密,得到一个加密字符串数组,最后包装成 CommonConfig

public sealed class CommonConfig
{
    /// <summary>标识配置来源.</summary>
    [JsonPropertyName("from")]
    public string? From { get; set; }

    /// <summary>配置版本号,当前为 1.0.</summary>
    [JsonPropertyName("version")]
    public string Version { get; set; } = "1.0";

    /// <summary>导出时间,Unix 时间戳,单位为秒.</summary>
    [JsonPropertyName("export_time")]
    public long? ExportTime { get; set; }

    /// <summary>加密后的服务配置 JSON 字符串列表.</summary>
    [JsonPropertyName("configs")]
    public List<string> Configs { get; set; }

    /// <summary>图标包,键为标识符,值为图标包地址.</summary>
    [JsonPropertyName("icon_packages")]
    public Dictionary<string, string>? IconPackages { get; set; }

    /// <summary>设置项列表,键为名称,值为序列化值.</summary>
    [JsonPropertyName("settings")]
    public Dictionary<string, string>? Settings { get; set; }

    /// <summary>附加数据,由对接双方自行约定结构.</summary>
    [JsonPropertyName("additional_data")]
    public Dictionary<string, string>? AdditionalData { get; set; }

    /// <summary>解密密钥(仅在希望对所有客户端公开时填写).</summary>
    [JsonPropertyName("_key")]
    public string? Key { get; set; }
}

各字段含义:

属性说明
from客户端标识,例如小幻影视导出时写 RodelPlayer。接收方据此决定使用哪一份密钥解密
version配置版本,目前固定 1.0
export_time导出时间,可选
configs加密后的服务配置字符串数组
icon_packages可选的图标包字典
settings跨端共享的用户设置字典
additional_data自定义附加数据,由双方协商定义
_key解密所需密钥;仅当你希望让所有客户端无差别解析此配置时才填写,否则务必留空

通常服务配置在客户端之间流动时,互信的客户端通过私下分享的密钥彼此解密。如果某条配置确实需要被任意客户端解析,可以把密钥写入 _key 字段;接收方应优先检查 _key 是否存在,存在则直接使用,不存在则回落到本地保存的密钥表。

目录