gitscope
https://dev.kronoz.win/r/marzneshin.git
app/routes/subscription.py · v0.6.2
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 fb4b69d Dawsh 2y ago "clash-meta": "text/yaml",
144 fb4b69d Dawsh 2y ago "clash": "text/yaml",
145 fb4b69d Dawsh 2y ago "xray": "application/json",
146 fb4b69d Dawsh 2y ago "v2ray": "text/plain",
147 fb4b69d Dawsh 2y ago }
148 fb4b69d Dawsh 2y ago
149 fb4b69d Dawsh 2y ago
150 2de9d72 Dawsh 2y ago @router.get("/{username}/{key}/{client_type}")
151 a47eb4c Dawsh 2y ago def user_subscription_with_client_type(
152 0178858 Dawsh 2y ago db: DBDep,
153 01cdf2e Dawsh 2y ago db_user: SubUserDep,
154 01cdf2e Dawsh 2y ago request: Request,
155 d6600c5 Dawsh 2y ago client_type: str = Path(regex="sing-box|clash-meta|clash|xray|v2ray"),
156 01cdf2e Dawsh 2y ago ):
157 a47eb4c Dawsh 2y ago """
158 d6600c5 Dawsh 2y ago Subscription by client type; v2ray, xray, sing-box, clash and clash-meta formats supported
159 a47eb4c Dawsh 2y ago """
160 a47eb4c Dawsh 2y ago
161 c13a1aa Dawsh 2y ago user: UserResponse = UserResponse.model_validate(db_user)
162 a47eb4c Dawsh 2y ago
163 0178858 Dawsh 2y ago subscription_settings = SubscriptionSettings.model_validate(
164 0178858 Dawsh 2y ago db.query(Settings.subscription).first()[0]
165 0178858 Dawsh 2y ago )
166 0178858 Dawsh 2y ago
167 a47eb4c Dawsh 2y ago response_headers = {
168 a47eb4c Dawsh 2y ago "content-disposition": f'attachment; filename="{user.username}"',
169 a47eb4c Dawsh 2y ago "profile-web-page-url": str(request.url),
170 0178858 Dawsh 2y ago "support-url": subscription_settings.support_link,
171 0178858 Dawsh 2y ago "profile-title": encode_title(subscription_settings.profile_title),
172 31f8dc7 Dawsh 2y ago "profile-update-interval": str(subscription_settings.update_interval),
173 a47eb4c Dawsh 2y ago "subscription-userinfo": "; ".join(
174 a47eb4c Dawsh 2y ago f"{key}={val}"
175 a47eb4c Dawsh 2y ago for key, val in get_subscription_user_info(user).items()
176 01cdf2e Dawsh 2y ago ),
177 a47eb4c Dawsh 2y ago }
178 a47eb4c Dawsh 2y ago
179 fb4b69d Dawsh 2y ago conf = generate_subscription(
180 fb4b69d Dawsh 2y ago user=db_user,
181 fb4b69d Dawsh 2y ago config_format="links" if client_type == "v2ray" else client_type,
182 fb4b69d Dawsh 2y ago as_base64=client_type == "v2ray",
183 fb4b69d Dawsh 2y ago use_placeholder=not user.is_active
184 fb4b69d Dawsh 2y ago and subscription_settings.placeholder_if_disabled,
185 fb4b69d Dawsh 2y ago placeholder_remark=subscription_settings.placeholder_remark,
186 fb4b69d Dawsh 2y ago shuffle=subscription_settings.shuffle_configs,
187 fb4b69d Dawsh 2y ago )
188 fb4b69d Dawsh 2y ago return Response(
189 fb4b69d Dawsh 2y ago content=conf,
190 fb4b69d Dawsh 2y ago media_type=client_type_mime_type[client_type],
191 fb4b69d Dawsh 2y ago headers=response_headers,
192 fb4b69d Dawsh 2y ago )