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