gitscope
https://dev.kronoz.win/r/marzneshin.git
app/routes/subscription.py · v0.3.0
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 d6600c5 Dawsh 2y ago from app.utils.share import encode_title, generate_subscription
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 d6600c5 Dawsh 2y ago links = generate_subscription(user=user, config_format="v2ray").split()
47 a47eb4c Dawsh 2y ago return HTMLResponse(
48 01cdf2e Dawsh 2y ago render_template(SUBSCRIPTION_PAGE_TEMPLATE, {"user": user, "links": links})
49 a47eb4c Dawsh 2y ago )
50 a47eb4c Dawsh 2y ago
51 a47eb4c Dawsh 2y ago response_headers = {
52 a47eb4c Dawsh 2y ago "content-disposition": f'attachment; filename="{user.username}"',
53 a47eb4c Dawsh 2y ago "profile-web-page-url": str(request.url),
54 a47eb4c Dawsh 2y ago "support-url": SUB_SUPPORT_URL,
55 a47eb4c Dawsh 2y ago "profile-title": encode_title(SUB_PROFILE_TITLE),
56 a47eb4c Dawsh 2y ago "profile-update-interval": SUB_UPDATE_INTERVAL,
57 a47eb4c Dawsh 2y ago "subscription-userinfo": "; ".join(
58 a47eb4c Dawsh 2y ago f"{key}={val}"
59 a47eb4c Dawsh 2y ago for key, val in get_subscription_user_info(user).items()
60 a47eb4c Dawsh 2y ago if val is not None
61 01cdf2e Dawsh 2y ago ),
62 a47eb4c Dawsh 2y ago }
63 a47eb4c Dawsh 2y ago
64 c13a1aa Dawsh 2y ago crud.update_user_sub(db, db_user, user_agent)
65 a47eb4c Dawsh 2y ago
66 01cdf2e Dawsh 2y ago if re.match("^([Cc]lash-verge|[Cc]lash-?[Mm]eta)", user_agent):
67 d6600c5 Dawsh 2y ago conf = generate_subscription(user=user, config_format="clash-meta")
68 a47eb4c Dawsh 2y ago return Response(content=conf, media_type="text/yaml", headers=response_headers)
69 01cdf2e Dawsh 2y ago elif re.match("^([Cc]lash|[Ss]tash)", user_agent):
70 d6600c5 Dawsh 2y ago conf = generate_subscription(user=user, config_format="clash")
71 a47eb4c Dawsh 2y ago return Response(content=conf, media_type="text/yaml", headers=response_headers)
72 01cdf2e Dawsh 2y ago elif re.match("^(SFA|SFI|SFM|SFT)", user_agent):
73 d6600c5 Dawsh 2y ago conf = generate_subscription(user=user, config_format="sing-box")
74 01cdf2e Dawsh 2y ago return Response(
75 01cdf2e Dawsh 2y ago content=conf, media_type="application/json", headers=response_headers
76 01cdf2e Dawsh 2y ago )
77 a47eb4c Dawsh 2y ago else:
78 a47eb4c Dawsh 2y ago conf = generate_subscription(user=user, config_format="v2ray", as_base64=True)
79 a47eb4c Dawsh 2y ago return Response(content=conf, media_type="text/plain", headers=response_headers)
80 a47eb4c Dawsh 2y ago
81 a47eb4c Dawsh 2y ago
82 2de9d72 Dawsh 2y ago @router.get("/{username}/{key}/info", response_model=UserResponse)
83 c13a1aa Dawsh 2y ago def user_subscription_info(db_user: SubUserDep):
84 c13a1aa Dawsh 2y ago return db_user
85 a47eb4c Dawsh 2y ago
86 a47eb4c Dawsh 2y ago
87 2de9d72 Dawsh 2y ago @router.get("/{username}/{key}/usage")
88 01cdf2e Dawsh 2y ago def user_get_usage(
89 01cdf2e Dawsh 2y ago db_user: SubUserDep, db: DBDep, start_date: StartDateDep, end_date: EndDateDep
90 01cdf2e Dawsh 2y ago ):
91 c13a1aa Dawsh 2y ago usages = crud.get_user_usages(db, db_user, start_date, end_date)
92 a47eb4c Dawsh 2y ago
93 c13a1aa Dawsh 2y ago return {"usages": usages, "username": db_user.username}
94 a47eb4c Dawsh 2y ago
95 a47eb4c Dawsh 2y ago
96 2de9d72 Dawsh 2y ago @router.get("/{username}/{key}/{client_type}")
97 a47eb4c Dawsh 2y ago def user_subscription_with_client_type(
98 01cdf2e Dawsh 2y ago db_user: SubUserDep,
99 01cdf2e Dawsh 2y ago request: Request,
100 d6600c5 Dawsh 2y ago client_type: str = Path(regex="sing-box|clash-meta|clash|xray|v2ray"),
101 01cdf2e Dawsh 2y ago ):
102 a47eb4c Dawsh 2y ago """
103 d6600c5 Dawsh 2y ago Subscription by client type; v2ray, xray, sing-box, clash and clash-meta formats supported
104 a47eb4c Dawsh 2y ago """
105 a47eb4c Dawsh 2y ago
106 c13a1aa Dawsh 2y ago user: UserResponse = UserResponse.model_validate(db_user)
107 a47eb4c Dawsh 2y ago
108 a47eb4c Dawsh 2y ago response_headers = {
109 a47eb4c Dawsh 2y ago "content-disposition": f'attachment; filename="{user.username}"',
110 a47eb4c Dawsh 2y ago "profile-web-page-url": str(request.url),
111 a47eb4c Dawsh 2y ago "support-url": SUB_SUPPORT_URL,
112 a47eb4c Dawsh 2y ago "profile-title": encode_title(SUB_PROFILE_TITLE),
113 a47eb4c Dawsh 2y ago "profile-update-interval": SUB_UPDATE_INTERVAL,
114 a47eb4c Dawsh 2y ago "subscription-userinfo": "; ".join(
115 a47eb4c Dawsh 2y ago f"{key}={val}"
116 a47eb4c Dawsh 2y ago for key, val in get_subscription_user_info(user).items()
117 a47eb4c Dawsh 2y ago if val is not None
118 01cdf2e Dawsh 2y ago ),
119 a47eb4c Dawsh 2y ago }
120 a47eb4c Dawsh 2y ago
121 a47eb4c Dawsh 2y ago if client_type == "clash-meta":
122 d6600c5 Dawsh 2y ago conf = generate_subscription(user=user, config_format="clash-meta")
123 a47eb4c Dawsh 2y ago return Response(content=conf, media_type="text/yaml", headers=response_headers)
124 a47eb4c Dawsh 2y ago
125 a47eb4c Dawsh 2y ago elif client_type == "sing-box":
126 d6600c5 Dawsh 2y ago conf = generate_subscription(user=user, config_format="sing-box")
127 01cdf2e Dawsh 2y ago return Response(
128 01cdf2e Dawsh 2y ago content=conf, media_type="application/json", headers=response_headers
129 01cdf2e Dawsh 2y ago )
130 a47eb4c Dawsh 2y ago elif client_type == "clash":
131 d6600c5 Dawsh 2y ago conf = generate_subscription(user=user, config_format="clash")
132 a47eb4c Dawsh 2y ago return Response(content=conf, media_type="text/yaml", headers=response_headers)
133 a47eb4c Dawsh 2y ago
134 a47eb4c Dawsh 2y ago elif client_type == "v2ray":
135 a47eb4c Dawsh 2y ago conf = generate_subscription(user=user, config_format="v2ray", as_base64=True)
136 a47eb4c Dawsh 2y ago return Response(content=conf, media_type="text/plain", headers=response_headers)
137 d6600c5 Dawsh 2y ago elif client_type == "xray":
138 01cdf2e Dawsh 2y ago return Response(
139 d6600c5 Dawsh 2y ago content=generate_subscription(user=user, config_format="xray"),
140 d6600c5 Dawsh 2y ago headers=response_headers,
141 01cdf2e Dawsh 2y ago )
142 a47eb4c Dawsh 2y ago else:
143 a47eb4c Dawsh 2y ago raise HTTPException(status_code=400, detail="Invalid subscription type")