Skip to content

Commit

Permalink
first
Browse files Browse the repository at this point in the history
  • Loading branch information
xmdhs committed Apr 8, 2023
0 parents commit 628a350
Show file tree
Hide file tree
Showing 12 changed files with 568 additions and 0 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
bbolt.db
116 changes: 116 additions & 0 deletions config.json.template
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
{
"log": {},
"dns": {
"servers": [
{
"tag": "remote",
"address": "https://1.1.1.1/dns-query",
"detour": "select"
},
{
"tag": "local",
"address": "https://223.5.5.5/dns-query",
"detour": "direct"
}
],
"rules": [
{
"domain": [],
"geosite": "cn",
"server": "local"
}
],
"strategy": "ipv4_only"
},
"inbounds": [
{
"type": "tun",
"inet4_address": "172.19.0.1/30",
"sniff": true,
"sniff_override_destination": true,
"domain_strategy": "ipv4_only",
"strict_route": true,
"mtu": 9000,
"stack": "gvisor",
"endpoint_independent_nat": true,
"auto_route": true
},
{
"type": "socks",
"tag": "socks-in",
"listen": "127.0.0.1",
"sniff": true,
"sniff_override_destination": true,
"domain_strategy": "ipv4_only",
"listen_port": 2333,
"users": []
},
{
"type": "mixed",
"tag": "mixed-in",
"sniff": true,
"sniff_override_destination": true,
"domain_strategy": "ipv4_only",
"listen": "127.0.0.1",
"listen_port": 2334,
"users": []
}
],
"outbounds": [
{
"type": "direct",
"tag": "direct"
},
{
"type": "block",
"tag": "block"
},
{
"type": "dns",
"tag": "dns-out"
}
],
"route": {
"rules": [
{
"geosite": "category-ads-all",
"outbound": "block"
},
{
"protocol": "dns",
"outbound": "dns-out"
},
{
"clash_mode": "direct",
"outbound": "direct"
},
{
"clash_mode": "global",
"outbound": "select"
},
{
"geoip": [
"cn",
"private"
],
"outbound": "direct"
},
{
"geosite": "geolocation-!cn",
"outbound": "select"
},
{
"geosite": "cn",
"outbound": "direct"
}
],
"auto_detect_interface": true
},
"experimental": {
"clash_api": {
"external_controller": "127.0.0.1:9090",
"store_selected": true,
"secret": ""
}
}
}
61 changes: 61 additions & 0 deletions db/bbolt.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package db

import (
"context"
"encoding/json"
"fmt"

"github.com/xmdhs/clash2sfa/model"
"go.etcd.io/bbolt"
)

type BBolt struct {
db *bbolt.DB
}

func NewBBolt(path string) (*BBolt, error) {
db, err := bbolt.Open(path, 0666, bbolt.DefaultOptions)
if err != nil {
return nil, fmt.Errorf("NewBBolt: %w", err)
}
err = db.Update(func(tx *bbolt.Tx) error {
_, err := tx.CreateBucketIfNotExists([]byte("arg"))
return err
})
if err != nil {
return nil, fmt.Errorf("NewBBolt: %w", err)
}
return &BBolt{db: db}, nil
}

func (b *BBolt) GetArg(cxt context.Context, blake3 string) (model.ConvertArg, error) {
m := model.ConvertArg{}
err := b.db.View(func(tx *bbolt.Tx) error {
buc := tx.Bucket([]byte("arg"))
b := buc.Get([]byte(blake3))
err := json.Unmarshal(b, &m)
if err != nil {
panic(err)
}
return nil
})
if err != nil {
return m, fmt.Errorf("GetArg: %w", err)
}
return m, nil
}

func (b *BBolt) PutArg(cxt context.Context, blake3 string, arg model.ConvertArg) error {
rb, err := json.Marshal(arg)
if err != nil {
return fmt.Errorf("PutArg: %w", err)
}
err = b.db.Update(func(tx *bbolt.Tx) error {
buc := tx.Bucket([]byte("arg"))
return buc.Put([]byte(blake3), rb)
})
if err != nil {
return fmt.Errorf("PutArg: %w", err)
}
return nil
}
12 changes: 12 additions & 0 deletions db/interface.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package db

import (
"context"

"github.com/xmdhs/clash2sfa/model"
)

type DB interface {
GetArg(cxt context.Context, blake3 string) (model.ConvertArg, error)
PutArg(cxt context.Context, blake3 string, arg model.ConvertArg) error
}
106 changes: 106 additions & 0 deletions frontend.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
<!DOCTYPE html>
<html lang="zh-CN">

<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>clash 订阅链接转 sing-box</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/picocss/1.5.7/pico.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/3.2.47/vue.global.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/pako/2.1.0/pako.min.js"
integrity="sha512-g2TeAWw5GPnX7z0Kn8nFbYfeHcvAu/tx6d6mrLe/90mkCxO+RcptyYpksUz35EO337F83bZwcmUyHiHamspkfg=="
crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/base64.min.js"></script>
<style type="text/css">
header {
border-bottom: 1px solid #e5e5e5;
margin-bottom: 30px;
}
</style>
</head>

<body>
<div id="app">
<header>
<nav class="container-fluid">
<ul>
<li><strong>clash 订阅链接转 sing-box</strong></li>
</ul>
<ul>
</ul>
</nav>
</header>
<main class="container">
<p>将 clash 格式的订阅链接转换为 sing-box 的工具,转换后的链接可用于安卓版本的 <a
href="https://sing-box.sagernet.org/installation/clients/sfa/">SFA</a>,ios 版本的未测试。</p>
<input placeholder="订阅链接" v-model="sub" />
<input placeholder="url test 中包含的节点名(可用正则)" v-model="include" />
<input placeholder="url test 中排除的节点名(可用正则)" v-model="exclude" />

<details>
<summary>配置文件模板</summary>
<textarea style="resize: none;height: 25em;" v-model="config"></textarea>
</details>
<button @click="click">获取 sing-box 格式订阅链接</button>
<code v-if="newsub">{{ newsub }}</code>
</main>
</div>
</body>

<script type="module">
Vue.createApp({
setup(props, context) {
const sub = Vue.ref('');
const newsub = Vue.ref('');
const include = Vue.ref('');
const exclude = Vue.ref('');
const config = Vue.ref('加载中');
let oldConfig = "";

(async () => {
const f = await fetch("/config")
config.value = await f.text()
oldConfig = config.value
})();


async function click() {
if (sub.value == "") {
return ""
}
const u = new URL(new URL(location.href).origin)
u.pathname = "/put"
if (config.value != oldConfig) {
const compressed = pako.gzip(config.value);
const base64String = Base64.fromUint8Array(compressed, true)
u.searchParams.set("config", base64String)
}
if (include.value != "") {
u.searchParams.set("include", include.value)
}
if (exclude.value != "") {
u.searchParams.set("exclude", exclude.value)
}
u.searchParams.set("sub", sub.value)
const url = u.toString()
const text = await (await fetch(url)).text()
const subUrl = new URL(new URL(location.href).origin)
subUrl.pathname = "/sub"
subUrl.searchParams.set("id",text)
newsub.value = subUrl.toString()
}

return {
sub,
config,
include,
exclude,
newsub,
click
}

},
}).mount('#app')
</script>

</html>
15 changes: 15 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
module github.com/xmdhs/clash2sfa

go 1.20

require (
github.com/xmdhs/clash2singbox v0.0.0-20230408094726-758016fd4c71
go.etcd.io/bbolt v1.3.7
gopkg.in/yaml.v3 v3.0.1
lukechampine.com/blake3 v1.1.7
)

require (
github.com/klauspost/cpuid/v2 v2.0.9 // indirect
golang.org/x/sys v0.4.0 // indirect
)
17 changes: 17 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
github.com/xmdhs/clash2singbox v0.0.0-20230408094726-758016fd4c71 h1:T3ZbnXBgwlIMQeiPuDCeC4D3Duob0q3T/pEbtIM6Ebc=
github.com/xmdhs/clash2singbox v0.0.0-20230408094726-758016fd4c71/go.mod h1:o8BYyqbnGtrfEDL4W+Zls3uMN/YnXi1Dji0UBOgTXDM=
go.etcd.io/bbolt v1.3.7 h1:j+zJOnnEjF/kyHlDDgGnVL/AIqIJPq8UoB2GSNfkUfQ=
go.etcd.io/bbolt v1.3.7/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw=
golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18=
golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
lukechampine.com/blake3 v1.1.7 h1:GgRMhmdsuK8+ii6UZFDL8Nb+VyMwadAgcJyfYHxG6n0=
lukechampine.com/blake3 v1.1.7/go.mod h1:tkKEOtDkNtklkXtLNEOGNq5tcV90tJiA1vAA12R78LA=
60 changes: 60 additions & 0 deletions handle/handel.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package handle

import (
"net/http"
"strconv"

"github.com/xmdhs/clash2sfa/db"
"github.com/xmdhs/clash2sfa/model"
"github.com/xmdhs/clash2sfa/service"
)

func PutArg(db db.DB) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
cxt := r.Context()
c := r.FormValue("config")
sub := r.FormValue("sub")
if sub == "" {
http.Error(w, "订阅链接不得为空", 400)
return
}
include := r.FormValue("include")
exclude := r.FormValue("exclude")

arg := model.ConvertArg{
Sub: sub,
Include: include,
Exclude: exclude,
Config: c,
}

s, err := service.PutArg(cxt, arg, db)
if err != nil {
http.Error(w, err.Error(), 500)
}
w.Write([]byte(s))
}
}

func Frontend(frontendByte []byte, age int) http.HandlerFunc {
sage := strconv.Itoa(age)
return func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Cache-Control", "max-age="+sage)
w.Write(frontendByte)
}
}

func Sub(c *http.Client, db db.DB, frontendByte []byte) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
id := r.FormValue("id")
if id == "" {
http.Error(w, "id 不得为空", 400)
return
}
b, err := service.GetSub(r.Context(), c, db, id, frontendByte)
if err != nil {
http.Error(w, err.Error(), 500)
}
w.Write(b)
}
}
Loading

0 comments on commit 628a350

Please sign in to comment.