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