diff --git a/InvisibleMan-XRay/Handlers/TemplateHandler.cs b/InvisibleMan-XRay/Handlers/TemplateHandler.cs
index 29f8071..1e53225 100644
--- a/InvisibleMan-XRay/Handlers/TemplateHandler.cs
+++ b/InvisibleMan-XRay/Handlers/TemplateHandler.cs
@@ -22,6 +22,8 @@ void RegisterTemplates()
{
templates.Add("vmess", typeof(Vmess));
templates.Add("vless", typeof(Vless));
+ templates.Add("trojan", typeof(Trojan));
+ templates.Add("ss", typeof(Shadowsocks));
}
}
diff --git a/InvisibleMan-XRay/InvisibleMan-XRay.csproj b/InvisibleMan-XRay/InvisibleMan-XRay.csproj
index 50d970e..7eeede0 100644
--- a/InvisibleMan-XRay/InvisibleMan-XRay.csproj
+++ b/InvisibleMan-XRay/InvisibleMan-XRay.csproj
@@ -9,8 +9,8 @@
InvisibleMan XRay
InvisibleMan
Copyright (C) 2023 Invisible Man
- 0.1.0.0
- 0.1.0.0
+ 0.3.0.0
+ 0.3.0.0
enable
0108;8600;8601;8602;8603;8604;8618;8625;8762
true
diff --git a/InvisibleMan-XRay/Models/Templates/Shadowsocks.cs b/InvisibleMan-XRay/Models/Templates/Shadowsocks.cs
new file mode 100644
index 0000000..3611433
--- /dev/null
+++ b/InvisibleMan-XRay/Models/Templates/Shadowsocks.cs
@@ -0,0 +1,102 @@
+using System;
+using System.Linq;
+using System.Text;
+
+namespace InvisibleManXRay.Models.Templates
+{
+ using Values;
+
+ public class Shadowsocks : Template
+ {
+ public class Data
+ {
+ public Uri uri;
+ public string security;
+ public string id;
+ public string remark;
+
+ public Data(string url)
+ {
+ this.uri = new Uri(url);
+ this.remark = url.Split("#")[1];
+ }
+ }
+
+ private Data data;
+ private readonly string[] validSecurity = new[] {
+ "aes-256-gcm",
+ "aes-128-gcm",
+ "chacha20-poly1305",
+ "chacha20-ietf-poly1305",
+ "xchacha20-poly1305",
+ "xchacha20-ietf-poly1305",
+ "none",
+ "plain",
+ "2022-blake3-aes-128-gcm",
+ "2022-blake3-aes-256-gcm",
+ "2022-blake3-chacha20-poly1305"
+ };
+
+ public override Status FetchDataFromLink(string link)
+ {
+ try
+ {
+ data = new Data(link);
+ FetchRemark();
+ MapDecodedLinkToData(decodedString: DecodeBase64Link());
+
+ return new Status(Code.SUCCESS, SubCode.SUCCESS, null);
+ }
+ catch(Exception)
+ {
+ return new Status(
+ code: Code.ERROR,
+ subCode: SubCode.INVALID_CONFIG,
+ content: Message.INVALID_CONFIG
+ );
+ }
+
+ string DecodeBase64Link()
+ {
+ string base64String = link.Split("@").FirstOrDefault().Replace("ss://", "");
+ byte[] dataBytes = Convert.FromBase64String(base64String);
+
+ return Encoding.UTF8.GetString(dataBytes);
+ }
+
+ void MapDecodedLinkToData(string decodedString)
+ {
+ string[] stringArray = decodedString.Split(":");
+ data.security = stringArray[0];
+ data.id = stringArray[1];
+ }
+
+ void FetchRemark()
+ {
+ data.remark = Uri.UnescapeDataString(data.uri.ToString().Split("#")[1]);
+ }
+ }
+
+ protected override Adapter Adapter => new Adapter() {
+ type = "shadowsocks",
+ remark = data.remark,
+ address = data.uri.IdnHost,
+ port = data.uri.Port,
+ id = data.id,
+ security = data.security
+ };
+
+ protected override V2Ray.Outbound.Settings OutboundSettings => new V2Ray.Outbound.Settings() {
+ servers = new V2Ray.Outbound.Settings.Server[] {
+ new V2Ray.Outbound.Settings.Server() {
+ address = Adapter.address,
+ port = Adapter.port,
+ password = Adapter.id,
+ ota = false,
+ level = 1,
+ method = validSecurity.Contains(Adapter.security) ? Adapter.security : "none"
+ }
+ }
+ };
+ }
+}
\ No newline at end of file
diff --git a/InvisibleMan-XRay/Models/Templates/Trojan.cs b/InvisibleMan-XRay/Models/Templates/Trojan.cs
new file mode 100644
index 0000000..3dab92a
--- /dev/null
+++ b/InvisibleMan-XRay/Models/Templates/Trojan.cs
@@ -0,0 +1,135 @@
+using System;
+using System.Web;
+using System.Collections.Specialized;
+
+namespace InvisibleManXRay.Models.Templates
+{
+ using Values;
+
+ public class Trojan : Template
+ {
+ public class Data
+ {
+ public Uri uri;
+ public NameValueCollection query;
+
+ public Data(string url)
+ {
+ this.uri = new Uri(url);
+ this.query = HttpUtility.ParseQueryString(uri.Query);
+ }
+ }
+
+ private Data data;
+
+ public override Status FetchDataFromLink(string link)
+ {
+ data = new Data(link);
+
+ if (IsInvalidLink())
+ return new Status(
+ code: Code.ERROR,
+ subCode: SubCode.INVALID_CONFIG,
+ content: Message.INVALID_CONFIG
+ );
+
+ return new Status(Code.SUCCESS, SubCode.SUCCESS, null);
+
+ bool IsInvalidLink() => data.query.Count == 0;
+ }
+
+ protected override Adapter Adapter
+ {
+ get
+ {
+ Adapter adapter = new Adapter() {
+ type = "trojan",
+ remark = data.uri.GetComponents(UriComponents.Fragment, UriFormat.Unescaped),
+ address = data.uri.IdnHost,
+ port = data.uri.Port,
+ id = data.uri.UserInfo,
+ security = data.query["encryption"] ?? "none",
+ streamNetwork = data.query["type"] ?? "tcp",
+ streamSecurity = data.query["security"] ?? "",
+ flow = data.query["flow"] ?? "",
+ sni = data.query["sni"] ?? "",
+ alpn = HttpUtility.UrlDecode(data.query["alpn"] ?? ""),
+ fingerprint = HttpUtility.UrlDecode(data.query["fp"] ?? "")
+ };
+
+ switch (adapter.streamNetwork)
+ {
+ case "tcp":
+ adapter.headerType = data.query["headerType"] ?? "none";
+ adapter.requestHost = HttpUtility.UrlDecode(data.query["host"] ?? "");
+ break;
+ case "kcp":
+ adapter.headerType = data.query["headerType"] ?? "none";
+ adapter.path = HttpUtility.UrlDecode(data.query["seed"] ?? "");
+ break;
+ case "ws":
+ adapter.requestHost = HttpUtility.UrlDecode(data.query["host"] ?? "");
+ adapter.path = HttpUtility.UrlDecode(data.query["path"] ?? "/");
+ break;
+ case "http":
+ case "h2":
+ adapter.streamNetwork = "h2";
+ adapter.requestHost = HttpUtility.UrlDecode(data.query["host"] ?? "");
+ adapter.path = HttpUtility.UrlDecode(data.query["path"] ?? "/");
+ break;
+ case "quic":
+ adapter.headerType = data.query["headerType"] ?? "none";
+ adapter.requestHost = data.query["quicSecurity"] ?? "none";
+ adapter.path = HttpUtility.UrlDecode(data.query["key"] ?? "");
+ break;
+ case "grpc":
+ adapter.path = HttpUtility.UrlDecode(data.query["serviceName"] ?? "");
+ adapter.headerType = HttpUtility.UrlDecode(data.query["mode"] ?? "gun");
+ break;
+ default:
+ break;
+ }
+
+ return adapter;
+ }
+ }
+
+ protected override V2Ray.Outbound.Settings OutboundSettings
+ {
+ get
+ {
+ if (Adapter.streamSecurity == Global.StreamSecurity.XTLS)
+ {
+ if (string.IsNullOrEmpty(Adapter.flow))
+ Adapter.flow = "xtls-rprx-origin";
+ else
+ Adapter.flow = Adapter.flow.Replace("splice", "direct");
+ }
+
+ return new V2Ray.Outbound.Settings() {
+ servers = new V2Ray.Outbound.Settings.Server[] {
+ new V2Ray.Outbound.Settings.Server() {
+ address = Adapter.address,
+ port = Adapter.port,
+ password = Adapter.id,
+ ota = false,
+ level = 1,
+ flow = SetServerFlow()
+ }
+ }
+ };
+
+ string SetServerFlow()
+ {
+ if (Adapter.streamSecurity != "xtls")
+ return string.Empty;
+
+ if (string.IsNullOrEmpty(Adapter.flow))
+ return "xtls-rprx-origin";
+
+ return Adapter.flow.Replace("splice", "direct");
+ }
+ }
+ }
+ }
+}
\ No newline at end of file