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