gitscope
https://dev.kronoz.win/r/marzneshin.git
app/routes/subscription.py · v0.3.0-alpha6
Source
1 a47eb4c Dawsh 2y ago import re
2 a47eb4c Dawsh 2y ago
3 f7af2b3 Dawsh 2y ago from fastapi import APIRouter
4 c13a1aa Dawsh 2y ago from fastapi import Header, HTTPException, Path, Request, Response
5 a47eb4c Dawsh 2y ago from fastapi.responses import HTMLResponse
6 a47eb4c Dawsh 2y ago
7 c13a1aa Dawsh 2y ago from app.db import crud
8 41ba8cb Dawsh 2y ago from app.dependencies import DBDep, SubUserDep, StartDateDep, EndDateDep
9 a47eb4c Dawsh 2y ago from app.models.user import UserResponse
10 a47eb4c Dawsh 2y ago from app.templates import render_template
11 f7af2b3 Dawsh 2y ago from app.utils.share import encode_title, generate_subscription, generate_v2ray_links
12 a47eb4c Dawsh 2y ago from config import (
13 a47eb4c Dawsh 2y ago SUB_PROFILE_TITLE,
14 a47eb4c Dawsh 2y ago SUB_SUPPORT_URL,
15 a47eb4c Dawsh 2y ago SUB_UPDATE_INTERVAL,
16 01cdf2e Dawsh 2y ago SUBSCRIPTION_PAGE_TEMPLATE,
17 a47eb4c 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 c13a1aa Dawsh 2y ago def get_subscription_user_info(user: UserResponse) -> dict:
23 c13a1aa Dawsh 2y ago return {
24 c13a1aa Dawsh 2y ago "upload": 0,
25 c13a1aa Dawsh 2y ago "download": user.used_traffic,
26 c13a1aa Dawsh 2y ago "total": user.data_limit,
27 c13a1aa Dawsh 2y ago "expire": user.expire,
28 c13a1aa Dawsh 2y ago }
29 c13a1aa Dawsh 2y ago
30 c13a1aa Dawsh 2y ago
31 2de9d72 Dawsh 2y ago @router.get("/{username}/{key}")
32 01cdf2e Dawsh 2y ago def user_subscription(
33 01cdf2e Dawsh 2y ago db_user: SubUserDep,
34 01cdf2e Dawsh 2y ago request: Request,
35 01cdf2e Dawsh 2y ago db: DBDep,
36 01cdf2e Dawsh 2y ago user_agent: str = Header(default=""),
37 01cdf2e Dawsh 2y ago ):
38 a47eb4c Dawsh 2y ago """
39 a47eb4c Dawsh 2y ago Subscription link, V2ray and Clash supported
40 a47eb4c Dawsh 2y ago """
41 a47eb4c Dawsh 2y ago accept_header = request.headers.get("Accept", "")
42 a47eb4c Dawsh 2y ago
43 c13a1aa Dawsh 2y ago user: UserResponse = UserResponse.model_validate(db_user)
44 a47eb4c Dawsh 2y ago
45 a47eb4c Dawsh 2y ago if "text/html" in accept_header:
46 01cdf2e Dawsh 2y ago links = generate_v2ray_links(
47 01cdf2e Dawsh 2y ago user.inbounds,
48 01cdf2e Dawsh 2y ago user.key,
49 01cdf2e Dawsh 2y ago user.model_dump(
50 01cdf2e Dawsh 2y ago exclude={"subscription_url", "services", "inbounds", "links"}
51 01cdf2e Dawsh 2y ago ),
52 01cdf2e Dawsh 2y ago )
53 a47eb4c Dawsh 2y ago return HTMLResponse(
54 01cdf2e Dawsh 2y ago render_template(SUBSCRIPTION_PAGE_TEMPLATE, {"user": user, "links": links})
55 a47eb4c Dawsh 2y ago )
56 a47eb4c Dawsh 2y ago
57 a47eb4c Dawsh 2y ago response_headers = {
58 a47eb4c Dawsh 2y ago "content-disposition": f'attachment; filename="{user.username}"',
59 a47eb4c Dawsh 2y ago "profile-web-page-url": str(request.url),
60 a47eb4c Dawsh 2y ago "support-url": SUB_SUPPORT_URL,
61 a47eb4c Dawsh 2y ago "profile-title": encode_title(SUB_PROFILE_TITLE),
62 a47eb4c Dawsh 2y ago "profile-update-interval": SUB_UPDATE_INTERVAL,
63 a47eb4c Dawsh 2y ago "subscription-userinfo": "; ".join(
64 a47eb4c Dawsh 2y ago f"{key}={val}"
65 a47eb4c Dawsh 2y ago for key, val in get_subscription_user_info(user).items()
66 a47eb4c Dawsh 2y ago if val is not None
67 01cdf2e Dawsh 2y ago ),
68 a47eb4c Dawsh 2y ago }
69 a47eb4c Dawsh 2y ago
70 c13a1aa Dawsh 2y ago crud.update_user_sub(db, db_user, user_agent)
71 a47eb4c Dawsh 2y ago
72 01cdf2e Dawsh 2y ago if re.match("^([Cc]lash-verge|[Cc]lash-?[Mm]eta)", user_agent):
73 01cdf2e Dawsh 2y ago conf = generate_subscription(
74 01cdf2e Dawsh 2y ago user=user, config_format="clash-meta", as_base64=False
75 01cdf2e Dawsh 2y ago )
76 a47eb4c Dawsh 2y ago return Response(content=conf, media_type="text/yaml", headers=response_headers)
77 a47eb4c Dawsh 2y ago
78 01cdf2e Dawsh 2y ago elif re.match("^([Cc]lash|[Ss]tash)", user_agent):
79 a47eb4c Dawsh 2y ago conf = generate_subscription(user=user, config_format="clash", as_base64=False)
80 a47eb4c Dawsh 2y ago return Response(content=conf, media_type="text/yaml", headers=response_headers)
81 a47eb4c Dawsh 2y ago
82 01cdf2e Dawsh 2y ago elif re.match("^(SFA|SFI|SFM|SFT)", user_agent):
83 01cdf2e Dawsh 2y ago conf = generate_subscription(
84 01cdf2e Dawsh 2y ago user=user, config_format="sing-box", as_base64=False
85 01cdf2e Dawsh 2y ago )
86 01cdf2e Dawsh 2y ago return Response(
87 01cdf2e Dawsh 2y ago content=conf, media_type="application/json", headers=response_headers
88 01cdf2e Dawsh 2y ago )
89 a47eb4c Dawsh 2y ago
90 01cdf2e Dawsh 2y ago elif re.match("^(SS|SSR|SSD|SSS|Outline|Shadowsocks|SSconf)", user_agent):
91 01cdf2e Dawsh 2y ago conf = generate_subscription(
92 01cdf2e Dawsh 2y ago user=user, config_format="outline", as_base64=False
93 01cdf2e Dawsh 2y ago )
94 01cdf2e Dawsh 2y ago return Response(
95 01cdf2e Dawsh 2y ago content=conf, media_type="application/json", headers=response_headers
96 01cdf2e Dawsh 2y ago )
97 a47eb4c Dawsh 2y ago
98 a47eb4c Dawsh 2y ago else:
99 a47eb4c Dawsh 2y ago conf = generate_subscription(user=user, config_format="v2ray", as_base64=True)
100 a47eb4c Dawsh 2y ago return Response(content=conf, media_type="text/plain", headers=response_headers)
101 a47eb4c Dawsh 2y ago
102 a47eb4c Dawsh 2y ago
103 2de9d72 Dawsh 2y ago @router.get("/{username}/{key}/info", response_model=UserResponse)
104 c13a1aa Dawsh 2y ago def user_subscription_info(db_user: SubUserDep):
105 c13a1aa Dawsh 2y ago return db_user
106 a47eb4c Dawsh 2y ago
107 a47eb4c Dawsh 2y ago
108 2de9d72 Dawsh 2y ago @router.get("/{username}/{key}/usage")
109 01cdf2e Dawsh 2y ago def user_get_usage(
110 01cdf2e Dawsh 2y ago db_user: SubUserDep, db: DBDep, start_date: StartDateDep, end_date: EndDateDep
111 01cdf2e Dawsh 2y ago ):
112 c13a1aa Dawsh 2y ago usages = crud.get_user_usages(db, db_user, start_date, end_date)
113 a47eb4c Dawsh 2y ago
114 c13a1aa Dawsh 2y ago return {"usages": usages, "username": db_user.username}
115 a47eb4c Dawsh 2y ago
116 a47eb4c Dawsh 2y ago
117 2de9d72 Dawsh 2y ago @router.get("/{username}/{key}/{client_type}")
118 a47eb4c Dawsh 2y ago def user_subscription_with_client_type(
119 01cdf2e Dawsh 2y ago db_user: SubUserDep,
120 01cdf2e Dawsh 2y ago request: Request,
121 01cdf2e Dawsh 2y ago client_type: str = Path(..., regex="sing-box|clash-meta|clash|outline|v2ray"),
122 01cdf2e Dawsh 2y ago ):
123 a47eb4c Dawsh 2y ago """
124 a47eb4c Dawsh 2y ago Subscription link, v2ray, clash, sing-box, outline and clash-meta supported
125 a47eb4c Dawsh 2y ago """
126 a47eb4c Dawsh 2y ago
127 c13a1aa Dawsh 2y ago user: UserResponse = UserResponse.model_validate(db_user)
128 a47eb4c Dawsh 2y ago
129 a47eb4c Dawsh 2y ago response_headers = {
130 a47eb4c Dawsh 2y ago "content-disposition": f'attachment; filename="{user.username}"',
131 a47eb4c Dawsh 2y ago "profile-web-page-url": str(request.url),
132 a47eb4c Dawsh 2y ago "support-url": SUB_SUPPORT_URL,
133 a47eb4c Dawsh 2y ago "profile-title": encode_title(SUB_PROFILE_TITLE),
134 a47eb4c Dawsh 2y ago "profile-update-interval": SUB_UPDATE_INTERVAL,
135 a47eb4c Dawsh 2y ago "subscription-userinfo": "; ".join(
136 a47eb4c Dawsh 2y ago f"{key}={val}"
137 a47eb4c Dawsh 2y ago for key, val in get_subscription_user_info(user).items()
138 a47eb4c Dawsh 2y ago if val is not None
139 01cdf2e Dawsh 2y ago ),
140 a47eb4c Dawsh 2y ago }
141 a47eb4c Dawsh 2y ago
142 a47eb4c Dawsh 2y ago if client_type == "clash-meta":
143 01cdf2e Dawsh 2y ago conf = generate_subscription(
144 01cdf2e Dawsh 2y ago user=user, config_format="clash-meta", as_base64=False
145 01cdf2e Dawsh 2y ago )
146 a47eb4c Dawsh 2y ago return Response(content=conf, media_type="text/yaml", headers=response_headers)
147 a47eb4c Dawsh 2y ago
148 a47eb4c Dawsh 2y ago elif client_type == "sing-box":
149 01cdf2e Dawsh 2y ago conf = generate_subscription(
150 01cdf2e Dawsh 2y ago user=user, config_format="sing-box", as_base64=False
151 01cdf2e Dawsh 2y ago )
152 01cdf2e Dawsh 2y ago return Response(
153 01cdf2e Dawsh 2y ago content=conf, media_type="application/json", headers=response_headers
154 01cdf2e Dawsh 2y ago )
155 a47eb4c Dawsh 2y ago
156 a47eb4c Dawsh 2y ago elif client_type == "clash":
157 a47eb4c Dawsh 2y ago conf = generate_subscription(user=user, config_format="clash", as_base64=False)
158 a47eb4c Dawsh 2y ago return Response(content=conf, media_type="text/yaml", headers=response_headers)
159 a47eb4c Dawsh 2y ago
160 a47eb4c Dawsh 2y ago elif client_type == "v2ray":
161 a47eb4c Dawsh 2y ago conf = generate_subscription(user=user, config_format="v2ray", as_base64=True)
162 a47eb4c Dawsh 2y ago return Response(content=conf, media_type="text/plain", headers=response_headers)
163 a47eb4c Dawsh 2y ago
164 a47eb4c Dawsh 2y ago elif client_type == "outline":
165 01cdf2e Dawsh 2y ago conf = generate_subscription(
166 01cdf2e Dawsh 2y ago user=user, config_format="outline", as_base64=False
167 01cdf2e Dawsh 2y ago )
168 01cdf2e Dawsh 2y ago return Response(
169 01cdf2e Dawsh 2y ago content=conf, media_type="application/json", headers=response_headers
170 01cdf2e Dawsh 2y ago )
171 a47eb4c Dawsh 2y ago
172 a47eb4c Dawsh 2y ago else:
173 a47eb4c Dawsh 2y ago raise HTTPException(status_code=400, detail="Invalid subscription type")