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