gitscope
https://dev.kronoz.win/r/marzneshin.git
app/routes/subscription.py · nightly
Source
1 a47eb4c Dawsh 2y ago import re
2 0178858 Dawsh 2y ago from collections import defaultdict
3 a47eb4c Dawsh 2y ago
4 f7af2b3 Dawsh 2y ago from fastapi import APIRouter
5 c13a1aa Dawsh 2y ago from fastapi import Header, HTTPException, Path, Request, Response
6 fb4b69d Dawsh 2y ago from starlette.responses import HTMLResponse
7 a47eb4c Dawsh 2y ago
8 c13a1aa Dawsh 2y ago from app.db import crud
9 0178858 Dawsh 2y ago from app.db.models import Settings
10 41ba8cb Dawsh 2y ago from app.dependencies import DBDep, SubUserDep, StartDateDep, EndDateDep
11 0178858 Dawsh 2y ago from app.models.settings import SubscriptionSettings
12 9ca312d Dawsh 2y ago from app.models.system import TrafficUsageSeries
13 a47eb4c Dawsh 2y ago from app.models.user import UserResponse
14 fb4b69d Dawsh 2y ago from app.utils.share import (
15 fb4b69d Dawsh 2y ago encode_title,
16 fb4b69d Dawsh 2y ago generate_subscription,
17 fb4b69d Dawsh 2y ago generate_subscription_template,
18 fb4b69d Dawsh 2y ago )
19 a47eb4c Dawsh 2y ago
20 01cdf2e Dawsh 2y ago router = APIRouter(prefix="/sub", tags=["Subscription"])
21 f7af2b3 Dawsh 2y ago
22 a47eb4c Dawsh 2y ago
23 0178858 Dawsh 2y ago config_mimetype = defaultdict(
24 0178858 Dawsh 2y ago lambda: "text/plain",
25 0178858 Dawsh 2y ago {
26 0178858 Dawsh 2y ago "links": "text/plain",
27 0178858 Dawsh 2y ago "base64-links": "text/plain",
28 0178858 Dawsh 2y ago "sing-box": "application/json",
29 0178858 Dawsh 2y ago "xray": "application/json",
30 0178858 Dawsh 2y ago "clash": "text/yaml",
31 0178858 Dawsh 2y ago "clash-meta": "text/yaml",
32 0178858 Dawsh 2y ago "template": "text/html",
33 0178858 Dawsh 2y ago "block": "text/plain",
34 0178858 Dawsh 2y ago },
35 0178858 Dawsh 2y ago )
36 0178858 Dawsh 2y ago
37 0178858 Dawsh 2y ago
38 c13a1aa Dawsh 2y ago def get_subscription_user_info(user: UserResponse) -> dict:
39 c13a1aa Dawsh 2y ago return {
40 c13a1aa Dawsh 2y ago "upload": 0,
41 c13a1aa Dawsh 2y ago "download": user.used_traffic,
42 46d0876 Dawsh 2y ago "total": user.data_limit or 0,
43 fb4b69d Dawsh 2y ago "expire": (
44 c04fef6 Dawsh 2y ago int(user.expire_date.timestamp())
45 c04fef6 Dawsh 2y ago if user.expire_strategy == "fixed_date"
46 46d0876 Dawsh 2y ago else 0
47 fb4b69d Dawsh 2y ago ),
48 c13a1aa Dawsh 2y ago }
49 c13a1aa Dawsh 2y ago
50 c13a1aa Dawsh 2y ago
51 2de9d72 Dawsh 2y ago @router.get("/{username}/{key}")
52 01cdf2e Dawsh 2y ago def user_subscription(
53 01cdf2e Dawsh 2y ago db_user: SubUserDep,
54 01cdf2e Dawsh 2y ago request: Request,
55 01cdf2e Dawsh 2y ago db: DBDep,
56 01cdf2e Dawsh 2y ago user_agent: str = Header(default=""),
57 01cdf2e Dawsh 2y ago ):
58 a47eb4c Dawsh 2y ago """
59 89eec32 Dawsh 2y ago Subscription link, result format depends on subscription settings
60 a47eb4c Dawsh 2y ago """
61 a47eb4c Dawsh 2y ago
62 c13a1aa Dawsh 2y ago user: UserResponse = UserResponse.model_validate(db_user)
63 a47eb4c Dawsh 2y ago
64 0178858 Dawsh 2y ago crud.update_user_sub(db, db_user, user_agent)
65 0178858 Dawsh 2y ago
66 0178858 Dawsh 2y ago subscription_settings = SubscriptionSettings.model_validate(
67 0178858 Dawsh 2y ago db.query(Settings.subscription).first()[0]
68 0178858 Dawsh 2y ago )
69 a47eb4c Dawsh 2y ago
70 89eec32 Dawsh 2y ago if (
71 89eec32 Dawsh 2y ago subscription_settings.template_on_acceptance
72 89eec32 Dawsh 2y ago and "text/html" in request.headers.get("Accept", [])
73 89eec32 Dawsh 2y ago ):
74 89eec32 Dawsh 2y ago return HTMLResponse(
75 fb4b69d Dawsh 2y ago generate_subscription_template(db_user, subscription_settings)
76 89eec32 Dawsh 2y ago )
77 89eec32 Dawsh 2y ago
78 a47eb4c Dawsh 2y ago response_headers = {
79 a47eb4c Dawsh 2y ago "content-disposition": f'attachment; filename="{user.username}"',
80 a47eb4c Dawsh 2y ago "profile-web-page-url": str(request.url),
81 0178858 Dawsh 2y ago "support-url": subscription_settings.support_link,
82 0178858 Dawsh 2y ago "profile-title": encode_title(subscription_settings.profile_title),
83 0178858 Dawsh 2y ago "profile-update-interval": str(subscription_settings.update_interval),
84 a47eb4c Dawsh 2y ago "subscription-userinfo": "; ".join(
85 a47eb4c Dawsh 2y ago f"{key}={val}"
86 a47eb4c Dawsh 2y ago for key, val in get_subscription_user_info(user).items()
87 01cdf2e Dawsh 2y ago ),
88 a47eb4c Dawsh 2y ago }
89 a47eb4c Dawsh 2y ago
90 0178858 Dawsh 2y ago for rule in subscription_settings.rules:
91 0178858 Dawsh 2y ago if re.match(rule.pattern, user_agent):
92 0178858 Dawsh 2y ago if rule.result.value == "template":
93 0178858 Dawsh 2y ago return HTMLResponse(
94 fb4b69d Dawsh 2y ago generate_subscription_template(
95 fb4b69d Dawsh 2y ago db_user, subscription_settings
96 0178858 Dawsh 2y ago )
97 0178858 Dawsh 2y ago )
98 0178858 Dawsh 2y ago elif rule.result.value == "block":
99 89eec32 Dawsh 2y ago raise HTTPException(404)
100 0178858 Dawsh 2y ago elif rule.result.value == "base64-links":
101 0178858 Dawsh 2y ago b64 = True
102 0178858 Dawsh 2y ago config_format = "links"
103 0178858 Dawsh 2y ago else:
104 0178858 Dawsh 2y ago b64 = False
105 0178858 Dawsh 2y ago config_format = rule.result.value
106 0178858 Dawsh 2y ago
107 0178858 Dawsh 2y ago conf = generate_subscription(
108 0178858 Dawsh 2y ago user=db_user,
109 0178858 Dawsh 2y ago config_format=config_format,
110 0178858 Dawsh 2y ago as_base64=b64,
111 fb4b69d Dawsh 2y ago use_placeholder=not user.is_active
112 fb4b69d Dawsh 2y ago and subscription_settings.placeholder_if_disabled,
113 fb4b69d Dawsh 2y ago placeholder_remark=subscription_settings.placeholder_remark,
114 fb4b69d Dawsh 2y ago shuffle=subscription_settings.shuffle_configs,
115 0178858 Dawsh 2y ago )
116 0178858 Dawsh 2y ago return Response(
117 0178858 Dawsh 2y ago content=conf,
118 0178858 Dawsh 2y ago media_type=config_mimetype[rule.result],
119 0178858 Dawsh 2y ago headers=response_headers,
120 0178858 Dawsh 2y ago )
121 a47eb4c Dawsh 2y ago
122 a47eb4c Dawsh 2y ago
123 2de9d72 Dawsh 2y ago @router.get("/{username}/{key}/info", response_model=UserResponse)
124 c13a1aa Dawsh 2y ago def user_subscription_info(db_user: SubUserDep):
125 c13a1aa Dawsh 2y ago return db_user
126 a47eb4c Dawsh 2y ago
127 a47eb4c Dawsh 2y ago
128 9ca312d Dawsh 2y ago @router.get("/{username}/{key}/usage", response_model=TrafficUsageSeries)
129 01cdf2e Dawsh 2y ago def user_get_usage(
130 f1ab16c Dawsh 2y ago db_user: SubUserDep,
131 f1ab16c Dawsh 2y ago db: DBDep,
132 f1ab16c Dawsh 2y ago start_date: StartDateDep,
133 f1ab16c Dawsh 2y ago end_date: EndDateDep,
134 01cdf2e Dawsh 2y ago ):
135 9ca312d Dawsh 2y ago per_day = (end_date - start_date).total_seconds() > 3 * 86400
136 9ca312d Dawsh 2y ago return crud.get_user_total_usage(
137 9ca312d Dawsh 2y ago db, db_user, start_date, end_date, per_day=per_day
138 9ca312d Dawsh 2y ago )
139 a47eb4c Dawsh 2y ago
140 a47eb4c Dawsh 2y ago
141 fb4b69d Dawsh 2y ago client_type_mime_type = {
142 fb4b69d Dawsh 2y ago "sing-box": "application/json",
143 0f3112a Dawsh 2y ago "wireguard": "application/json",
144 fb4b69d Dawsh 2y ago "clash-meta": "text/yaml",
145 fb4b69d Dawsh 2y ago "clash": "text/yaml",
146 fb4b69d Dawsh 2y ago "xray": "application/json",
147 fb4b69d Dawsh 2y ago "v2ray": "text/plain",
148 d136b48 Dawsh 1y ago "links": "text/plain",
149 fb4b69d Dawsh 2y ago }
150 fb4b69d Dawsh 2y ago
151 fb4b69d Dawsh 2y ago
152 2de9d72 Dawsh 2y ago @router.get("/{username}/{key}/{client_type}")
153 a47eb4c Dawsh 2y ago def user_subscription_with_client_type(
154 0178858 Dawsh 2y ago db: DBDep,
155 01cdf2e Dawsh 2y ago db_user: SubUserDep,
156 01cdf2e Dawsh 2y ago request: Request,
157 0f3112a Dawsh 2y ago client_type: str = Path(
158 0f3112a Dawsh 2y ago regex="^(sing-box|clash-meta|clash|xray|v2ray|links|wireguard)$"
159 0f3112a Dawsh 2y ago ),
160 01cdf2e Dawsh 2y ago ):
161 a47eb4c Dawsh 2y ago """
162 d6600c5 Dawsh 2y ago Subscription by client type; v2ray, xray, sing-box, clash and clash-meta formats supported
163 a47eb4c Dawsh 2y ago """
164 a47eb4c Dawsh 2y ago
165 c13a1aa Dawsh 2y ago user: UserResponse = UserResponse.model_validate(db_user)
166 a47eb4c Dawsh 2y ago
167 0178858 Dawsh 2y ago subscription_settings = SubscriptionSettings.model_validate(
168 0178858 Dawsh 2y ago db.query(Settings.subscription).first()[0]
169 0178858 Dawsh 2y ago )
170 0178858 Dawsh 2y ago
171 a47eb4c Dawsh 2y ago response_headers = {
172 a47eb4c Dawsh 2y ago "content-disposition": f'attachment; filename="{user.username}"',
173 a47eb4c Dawsh 2y ago "profile-web-page-url": str(request.url),
174 0178858 Dawsh 2y ago "support-url": subscription_settings.support_link,
175 0178858 Dawsh 2y ago "profile-title": encode_title(subscription_settings.profile_title),
176 31f8dc7 Dawsh 2y ago "profile-update-interval": str(subscription_settings.update_interval),
177 a47eb4c Dawsh 2y ago "subscription-userinfo": "; ".join(
178 a47eb4c Dawsh 2y ago f"{key}={val}"
179 a47eb4c Dawsh 2y ago for key, val in get_subscription_user_info(user).items()
180 01cdf2e Dawsh 2y ago ),
181 a47eb4c Dawsh 2y ago }
182 a47eb4c Dawsh 2y ago
183 fb4b69d Dawsh 2y ago conf = generate_subscription(
184 fb4b69d Dawsh 2y ago user=db_user,
185 fb4b69d Dawsh 2y ago config_format="links" if client_type == "v2ray" else client_type,
186 fb4b69d Dawsh 2y ago as_base64=client_type == "v2ray",
187 fb4b69d Dawsh 2y ago use_placeholder=not user.is_active
188 fb4b69d Dawsh 2y ago and subscription_settings.placeholder_if_disabled,
189 fb4b69d Dawsh 2y ago placeholder_remark=subscription_settings.placeholder_remark,
190 fb4b69d Dawsh 2y ago shuffle=subscription_settings.shuffle_configs,
191 fb4b69d Dawsh 2y ago )
192 fb4b69d Dawsh 2y ago return Response(
193 fb4b69d Dawsh 2y ago content=conf,
194 fb4b69d Dawsh 2y ago media_type=client_type_mime_type[client_type],
195 fb4b69d Dawsh 2y ago headers=response_headers,
196 fb4b69d Dawsh 2y ago )