gitscope
https://dev.kronoz.win/r/marzneshin.git
app/utils/share.py · v0.3.0-alpha11
Source
1 a47eb4c Dawsh 2y ago import base64
2 a47eb4c Dawsh 2y ago import json
3 a47eb4c Dawsh 2y ago import random
4 a47eb4c Dawsh 2y ago import secrets
5 a47eb4c Dawsh 2y ago from datetime import datetime as dt, timedelta
6 a47eb4c Dawsh 2y ago from typing import TYPE_CHECKING, Literal, Union, List
7 a47eb4c Dawsh 2y ago from uuid import UUID
8 a47eb4c Dawsh 2y ago
9 a47eb4c Dawsh 2y ago from jdatetime import date as jd
10 f446990 Dawsh 2y ago from v2share import V2Data, SingBoxConfig, ClashConfig, ClashMetaConfig
11 a47eb4c Dawsh 2y ago
12 f446990 Dawsh 2y ago from app.db import GetDB, crud
13 01cdf2e Dawsh 2y ago from app.models.proxy import (
14 01cdf2e Dawsh 2y ago InboundHostSecurity,
15 01cdf2e Dawsh 2y ago )
16 f446990 Dawsh 2y ago from app.utils.keygen import gen_uuid, gen_password
17 a47eb4c Dawsh 2y ago from app.utils.system import get_public_ip, readable_size
18 a47eb4c Dawsh 2y ago
19 a47eb4c Dawsh 2y ago if TYPE_CHECKING:
20 a47eb4c Dawsh 2y ago from app.models.user import UserResponse
21 a47eb4c Dawsh 2y ago
22 a47eb4c Dawsh 2y ago SERVER_IP = get_public_ip()
23 a47eb4c Dawsh 2y ago
24 a47eb4c Dawsh 2y ago STATUS_EMOJIS = {
25 a47eb4c Dawsh 2y ago "active": "✅",
26 a47eb4c Dawsh 2y ago "expired": "⌛️",
27 a47eb4c Dawsh 2y ago "limited": "🪫",
28 a47eb4c Dawsh 2y ago "disabled": "❌",
29 a47eb4c Dawsh 2y ago "connect_to_start": "🔌",
30 a47eb4c Dawsh 2y ago }
31 a47eb4c Dawsh 2y ago
32 01cdf2e Dawsh 2y ago
33 a47eb4c Dawsh 2y ago def generate_v2ray_links(inbounds: list, key: str, extra_data: dict) -> list:
34 a47eb4c Dawsh 2y ago format_variables = setup_format_variables(extra_data)
35 a47eb4c Dawsh 2y ago return process_inbounds_and_tags(inbounds, key, format_variables, mode="v2ray")
36 a47eb4c Dawsh 2y ago
37 a47eb4c Dawsh 2y ago
38 a47eb4c Dawsh 2y ago def generate_clash_subscription(
39 f446990 Dawsh 2y ago inbounds: list, key: str, extra_data: dict, is_meta: bool = False
40 a47eb4c Dawsh 2y ago ) -> str:
41 a47eb4c Dawsh 2y ago if is_meta is True:
42 f446990 Dawsh 2y ago conf = ClashMetaConfig()
43 a47eb4c Dawsh 2y ago else:
44 f446990 Dawsh 2y ago conf = ClashConfig()
45 a47eb4c Dawsh 2y ago
46 a47eb4c Dawsh 2y ago format_variables = setup_format_variables(extra_data)
47 a47eb4c Dawsh 2y ago return process_inbounds_and_tags(
48 a47eb4c Dawsh 2y ago inbounds, key, format_variables, mode="clash", conf=conf
49 a47eb4c Dawsh 2y ago )
50 a47eb4c Dawsh 2y ago
51 a47eb4c Dawsh 2y ago
52 f446990 Dawsh 2y ago def generate_singbox_subscription(inbounds: list, key: str, extra_data: dict) -> str:
53 f446990 Dawsh 2y ago conf = SingBoxConfig()
54 a47eb4c Dawsh 2y ago
55 a47eb4c Dawsh 2y ago format_variables = setup_format_variables(extra_data)
56 a47eb4c Dawsh 2y ago return process_inbounds_and_tags(
57 a47eb4c Dawsh 2y ago inbounds, key, format_variables, mode="sing-box", conf=conf
58 a47eb4c Dawsh 2y ago )
59 a47eb4c Dawsh 2y ago
60 a47eb4c Dawsh 2y ago
61 a47eb4c Dawsh 2y ago def generate_v2ray_subscription(links: list) -> str:
62 a47eb4c Dawsh 2y ago return base64.b64encode("\n".join(links).encode()).decode()
63 a47eb4c Dawsh 2y ago
64 a47eb4c Dawsh 2y ago
65 a47eb4c Dawsh 2y ago def generate_subscription(
66 a47eb4c Dawsh 2y ago user: "UserResponse",
67 f446990 Dawsh 2y ago config_format: Literal["v2ray", "clash-meta", "clash", "sing-box"],
68 a47eb4c Dawsh 2y ago as_base64: bool,
69 a47eb4c Dawsh 2y ago ) -> str:
70 a47eb4c Dawsh 2y ago kwargs = {
71 a47eb4c Dawsh 2y ago # "proxies": user.proxies,
72 a47eb4c Dawsh 2y ago "inbounds": user.inbounds,
73 a47eb4c Dawsh 2y ago "key": user.key,
74 a47eb4c Dawsh 2y ago "extra_data": user.__dict__,
75 a47eb4c Dawsh 2y ago }
76 a47eb4c Dawsh 2y ago
77 a47eb4c Dawsh 2y ago if config_format == "v2ray":
78 a47eb4c Dawsh 2y ago config = "\n".join(generate_v2ray_links(**kwargs))
79 a47eb4c Dawsh 2y ago elif config_format == "clash-meta":
80 a47eb4c Dawsh 2y ago config = generate_clash_subscription(**kwargs, is_meta=True)
81 a47eb4c Dawsh 2y ago elif config_format == "clash":
82 a47eb4c Dawsh 2y ago config = generate_clash_subscription(**kwargs)
83 a47eb4c Dawsh 2y ago elif config_format == "sing-box":
84 a47eb4c Dawsh 2y ago config = generate_singbox_subscription(**kwargs)
85 a47eb4c Dawsh 2y ago else:
86 a47eb4c Dawsh 2y ago raise ValueError(f'Unsupported format "{config_format}"')
87 a47eb4c Dawsh 2y ago
88 a47eb4c Dawsh 2y ago if as_base64:
89 a47eb4c Dawsh 2y ago config = base64.b64encode(config.encode()).decode()
90 a47eb4c Dawsh 2y ago
91 a47eb4c Dawsh 2y ago return config
92 a47eb4c Dawsh 2y ago
93 a47eb4c Dawsh 2y ago
94 a47eb4c Dawsh 2y ago def format_time_left(seconds_left: int) -> str:
95 a47eb4c Dawsh 2y ago if not seconds_left or seconds_left <= 0:
96 a47eb4c Dawsh 2y ago return "∞"
97 a47eb4c Dawsh 2y ago
98 a47eb4c Dawsh 2y ago minutes, seconds = divmod(seconds_left, 60)
99 a47eb4c Dawsh 2y ago hours, minutes = divmod(minutes, 60)
100 a47eb4c Dawsh 2y ago days, hours = divmod(hours, 24)
101 a47eb4c Dawsh 2y ago months, days = divmod(days, 30)
102 a47eb4c Dawsh 2y ago
103 a47eb4c Dawsh 2y ago result = []
104 a47eb4c Dawsh 2y ago if months:
105 a47eb4c Dawsh 2y ago result.append(f"{months}m")
106 a47eb4c Dawsh 2y ago if days:
107 a47eb4c Dawsh 2y ago result.append(f"{days}d")
108 a47eb4c Dawsh 2y ago if hours and (days < 7):
109 a47eb4c Dawsh 2y ago result.append(f"{hours}h")
110 a47eb4c Dawsh 2y ago if minutes and not (months or days):
111 a47eb4c Dawsh 2y ago result.append(f"{minutes}m")
112 a47eb4c Dawsh 2y ago if seconds and not (months or days):
113 a47eb4c Dawsh 2y ago result.append(f"{seconds}s")
114 a47eb4c Dawsh 2y ago return " ".join(result)
115 a47eb4c Dawsh 2y ago
116 01cdf2e Dawsh 2y ago
117 a47eb4c Dawsh 2y ago from app.models.user import UserStatus
118 a47eb4c Dawsh 2y ago
119 01cdf2e Dawsh 2y ago
120 a47eb4c Dawsh 2y ago def setup_format_variables(extra_data: dict) -> dict:
121 a47eb4c Dawsh 2y ago
122 a47eb4c Dawsh 2y ago user_status = extra_data.get("status")
123 f446990 Dawsh 2y ago expire_datetime = extra_data.get("expire")
124 a47eb4c Dawsh 2y ago on_hold_expire_duration = extra_data.get("on_hold_expire_duration")
125 a47eb4c Dawsh 2y ago
126 a47eb4c Dawsh 2y ago if user_status != UserStatus.on_hold:
127 f446990 Dawsh 2y ago if expire_datetime is not None:
128 f446990 Dawsh 2y ago seconds_left = (expire_datetime - dt.utcnow()).total_seconds()
129 a47eb4c Dawsh 2y ago expire_date = expire_datetime.date()
130 a47eb4c Dawsh 2y ago jalali_expire_date = jd.fromgregorian(
131 a47eb4c Dawsh 2y ago year=expire_date.year, month=expire_date.month, day=expire_date.day
132 a47eb4c Dawsh 2y ago ).strftime("%Y-%m-%d")
133 a47eb4c Dawsh 2y ago days_left = (expire_datetime - dt.utcnow()).days + 1
134 a47eb4c Dawsh 2y ago time_left = format_time_left(seconds_left)
135 a47eb4c Dawsh 2y ago else:
136 a47eb4c Dawsh 2y ago days_left = "∞"
137 a47eb4c Dawsh 2y ago time_left = "∞"
138 a47eb4c Dawsh 2y ago expire_date = "∞"
139 a47eb4c Dawsh 2y ago jalali_expire_date = "∞"
140 a47eb4c Dawsh 2y ago else:
141 a47eb4c Dawsh 2y ago if on_hold_expire_duration is not None and on_hold_expire_duration >= 0:
142 a47eb4c Dawsh 2y ago days_left = timedelta(seconds=on_hold_expire_duration).days
143 a47eb4c Dawsh 2y ago time_left = format_time_left(on_hold_expire_duration)
144 a47eb4c Dawsh 2y ago expire_date = "-"
145 a47eb4c Dawsh 2y ago jalali_expire_date = "-"
146 a47eb4c Dawsh 2y ago else:
147 a47eb4c Dawsh 2y ago days_left = "∞"
148 a47eb4c Dawsh 2y ago time_left = "∞"
149 a47eb4c Dawsh 2y ago expire_date = "∞"
150 a47eb4c Dawsh 2y ago jalali_expire_date = "∞"
151 a47eb4c Dawsh 2y ago
152 a47eb4c Dawsh 2y ago if extra_data.get("data_limit"):
153 a47eb4c Dawsh 2y ago data_limit = readable_size(extra_data["data_limit"])
154 a47eb4c Dawsh 2y ago data_left = extra_data["data_limit"] - extra_data["used_traffic"]
155 a47eb4c Dawsh 2y ago if data_left < 0:
156 a47eb4c Dawsh 2y ago data_left = 0
157 a47eb4c Dawsh 2y ago data_left = readable_size(data_left)
158 a47eb4c Dawsh 2y ago else:
159 a47eb4c Dawsh 2y ago data_limit = "∞"
160 a47eb4c Dawsh 2y ago data_left = "∞"
161 a47eb4c Dawsh 2y ago
162 a47eb4c Dawsh 2y ago status_emoji = STATUS_EMOJIS.get(extra_data.get("status")) or ""
163 a47eb4c Dawsh 2y ago
164 a47eb4c Dawsh 2y ago format_variables = {
165 a47eb4c Dawsh 2y ago "SERVER_IP": SERVER_IP,
166 a47eb4c Dawsh 2y ago "USERNAME": extra_data.get("username", "{USERNAME}"),
167 a47eb4c Dawsh 2y ago "DATA_USAGE": readable_size(extra_data.get("used_traffic")),
168 a47eb4c Dawsh 2y ago "DATA_LIMIT": data_limit,
169 a47eb4c Dawsh 2y ago "DATA_LEFT": data_left,
170 a47eb4c Dawsh 2y ago "DAYS_LEFT": days_left,
171 a47eb4c Dawsh 2y ago "EXPIRE_DATE": expire_date,
172 a47eb4c Dawsh 2y ago "JALALI_EXPIRE_DATE": jalali_expire_date,
173 a47eb4c Dawsh 2y ago "TIME_LEFT": time_left,
174 a47eb4c Dawsh 2y ago "STATUS_EMOJI": status_emoji,
175 a47eb4c Dawsh 2y ago }
176 a47eb4c Dawsh 2y ago
177 a47eb4c Dawsh 2y ago return format_variables
178 a47eb4c Dawsh 2y ago
179 a47eb4c Dawsh 2y ago
180 a47eb4c Dawsh 2y ago def process_inbounds_and_tags(
181 a47eb4c Dawsh 2y ago inbounds: list,
182 a47eb4c Dawsh 2y ago ukey: str,
183 a47eb4c Dawsh 2y ago format_variables: dict,
184 a47eb4c Dawsh 2y ago mode: str = "v2ray",
185 a47eb4c Dawsh 2y ago conf=None,
186 a47eb4c Dawsh 2y ago ) -> Union[List, str]:
187 a47eb4c Dawsh 2y ago salt = secrets.token_hex(8)
188 a47eb4c Dawsh 2y ago results = []
189 01cdf2e Dawsh 2y ago
190 a47eb4c Dawsh 2y ago for inb in inbounds:
191 01cdf2e Dawsh 2y ago inb_id, inbound, protocol, tag = (
192 01cdf2e Dawsh 2y ago inb.id,
193 01cdf2e Dawsh 2y ago json.loads(inb.config),
194 01cdf2e Dawsh 2y ago inb.protocol,
195 01cdf2e Dawsh 2y ago inb.tag,
196 01cdf2e Dawsh 2y ago )
197 a47eb4c Dawsh 2y ago
198 a47eb4c Dawsh 2y ago format_variables.update({"PROTOCOL": protocol.name})
199 a47eb4c Dawsh 2y ago if not inbound:
200 a47eb4c Dawsh 2y ago continue
201 a47eb4c Dawsh 2y ago
202 a47eb4c Dawsh 2y ago format_variables.update({"TRANSPORT": inbound["network"]})
203 a47eb4c Dawsh 2y ago with GetDB() as db:
204 a47eb4c Dawsh 2y ago hosts = crud.get_inbound_hosts(db, inb_id)
205 f446990 Dawsh 2y ago
206 a47eb4c Dawsh 2y ago for host in hosts:
207 fe923dc Dawsh 2y ago host_snis = host.sni.split(",") if host.sni else []
208 fe923dc Dawsh 2y ago sni_list = host_snis or inbound["sni"]
209 a47eb4c Dawsh 2y ago if sni_list:
210 a47eb4c Dawsh 2y ago sni = random.choice(sni_list).replace("*", salt)
211 fe923dc Dawsh 2y ago else:
212 fe923dc Dawsh 2y ago sni = ""
213 a47eb4c Dawsh 2y ago
214 fe923dc Dawsh 2y ago host_hosts = host.host.split(",") if host.host else []
215 fe923dc Dawsh 2y ago req_host_list = host_hosts or inbound["host"]
216 a47eb4c Dawsh 2y ago if req_host_list:
217 a47eb4c Dawsh 2y ago req_host = random.choice(req_host_list).replace("*", salt)
218 fe923dc Dawsh 2y ago else:
219 fe923dc Dawsh 2y ago req_host = ""
220 01cdf2e Dawsh 2y ago
221 01cdf2e Dawsh 2y ago host_tls = (
222 01cdf2e Dawsh 2y ago None
223 01cdf2e Dawsh 2y ago if host.security == InboundHostSecurity.inbound_default
224 01cdf2e Dawsh 2y ago else host.security.value
225 01cdf2e Dawsh 2y ago )
226 f446990 Dawsh 2y ago data = V2Data(
227 f446990 Dawsh 2y ago protocol.value,
228 f446990 Dawsh 2y ago host.remark.format_map(format_variables),
229 f446990 Dawsh 2y ago host.address.format_map(format_variables),
230 f446990 Dawsh 2y ago host.port or inbound["port"],
231 a9f8ac2 Dawsh 2y ago transport_type=inbound["network"],
232 f446990 Dawsh 2y ago sni=sni,
233 f446990 Dawsh 2y ago host=req_host,
234 f446990 Dawsh 2y ago tls=host_tls or inbound["tls"],
235 f446990 Dawsh 2y ago alpn=host.alpn.value or inbound.get("alpn", ""),
236 f446990 Dawsh 2y ago path=(
237 f446990 Dawsh 2y ago host.path.format_map(format_variables)
238 f446990 Dawsh 2y ago if host.path
239 f446990 Dawsh 2y ago else inbound.get("path", "")
240 f446990 Dawsh 2y ago ),
241 f446990 Dawsh 2y ago fingerprint=host.fingerprint.value,
242 f446990 Dawsh 2y ago allow_insecure=host.allowinsecure,
243 f446990 Dawsh 2y ago uuid=UUID(gen_uuid(ukey)),
244 f446990 Dawsh 2y ago password=gen_password(ukey),
245 a47eb4c Dawsh 2y ago )
246 a47eb4c Dawsh 2y ago if mode == "v2ray":
247 f446990 Dawsh 2y ago results.append(data.to_link())
248 f446990 Dawsh 2y ago elif mode in ["clash", "sing-box"]:
249 f446990 Dawsh 2y ago conf.add_proxies([data])
250 f446990 Dawsh 2y ago
251 f446990 Dawsh 2y ago if mode in ["clash", "sing-box"]:
252 a47eb4c Dawsh 2y ago return conf.render()
253 a47eb4c Dawsh 2y ago return results
254 a47eb4c Dawsh 2y ago
255 a47eb4c Dawsh 2y ago
256 a47eb4c Dawsh 2y ago def encode_title(text: str) -> str:
257 a47eb4c Dawsh 2y ago return f"base64:{base64.b64encode(text.encode()).decode()}"