subscription.py 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191
  1. from datetime import datetime
  2. from typing import Dict, List, Optional
  3. from sqlmodel import SQLModel, Field,Session,select,Relationship,func,update
  4. from sqlalchemy.engine import Engine
  5. from sqlalchemy.dialects.postgresql import JSON
  6. from pydantic import BaseModel
  7. import yaml
  8. from database.engine import get_session,create_engine,engine
  9. from config.logu import logger
  10. class SubscriptFile(SQLModel, table=True):
  11. id: Optional[int] = Field(default=None, primary_key=True)
  12. name: str = Field()
  13. url: str = Field(index=True)
  14. file_path: str = Field()
  15. updated_at: datetime = Field(default_factory=datetime.now)
  16. error: int = Field(default=0)
  17. detail: dict = Field(default={}, sa_type=JSON)
  18. mihomo_meta: List["MihomoMeta"] = Relationship(back_populates="subscript_file")
  19. class MihomoMeta(SQLModel, table=True):
  20. id: Optional[int] = Field(default=None, primary_key=True)
  21. provider_name: str = Field(default="")
  22. proxy_name: str = Field(default="")
  23. mixed_port: Optional[int] = Field(default=None)
  24. external_controller: Optional[str] = Field(default=None)
  25. temp_file_path: Optional[str] = Field(default=None)
  26. pid: Optional[int] = Field(default=None)
  27. running: Optional[bool] = False
  28. updated_at: datetime = Field(default_factory=datetime.now)
  29. delay: Optional[int] = Field(default=None) # 新增延迟字段
  30. subscript_file_id: Optional[int] = Field(default=None, foreign_key="subscriptfile.id")
  31. subscript_file: SubscriptFile | None = Relationship(back_populates="mihomo_meta")
  32. class SubscriptionManager:
  33. def __init__(self, db:Engine=None):
  34. self.engine:Engine = db or engine
  35. def add_subscription_meta(self, sub_model: SubscriptFile, proxies, overwrite:bool=False):
  36. with Session(self.engine) as session:
  37. exist_sub = session.exec(select(SubscriptFile).where(SubscriptFile.url == sub_model.url)).first()
  38. if exist_sub and not overwrite:
  39. logger.info(f"{sub_model.url} already exist, skip add")
  40. return exist_sub
  41. logger.info(f"exist_sub {exist_sub} overwrite {overwrite} proxies {len(proxies)}")
  42. if overwrite:
  43. # 删除与 exist_sub 相关的 MihomoMeta 记录
  44. session.exec(select(MihomoMeta).where(MihomoMeta.subscript_file_id == exist_sub.id)).all()
  45. for proxy in session.exec(select(MihomoMeta).where(MihomoMeta.subscript_file_id == exist_sub.id)).all():
  46. session.delete(proxy)
  47. # 删除旧的 SubscriptFile 记录
  48. session.delete(exist_sub)
  49. session.commit()
  50. logger.info(f"add {sub_model}")
  51. # 添加新的 SubscriptFile
  52. session.add(sub_model)
  53. session.commit()
  54. session.refresh(sub_model)
  55. # 添加 MihomoMeta 记录
  56. for proxy in proxies:
  57. miho = MihomoMeta(
  58. provider_name=sub_model.name,
  59. proxy_name=proxy["name"],
  60. subscript_file_id=sub_model.id # 使用 sub_model.id
  61. )
  62. logger.info(f"miho {miho}")
  63. session.add(miho)
  64. session.commit()
  65. session.refresh(sub_model)
  66. return sub_model
  67. def get_subscription_meta(self) -> List[SubscriptFile]:
  68. with Session(self.engine) as session:
  69. return session.exec(select(SubscriptFile)).all()
  70. def get_proxies(self) -> List[MihomoMeta]:
  71. with Session(self.engine) as session:
  72. return session.exec(select(MihomoMeta)).all()
  73. def get_running_proxies(self) -> List[MihomoMeta]:
  74. with Session(self.engine) as session:
  75. all = session.exec(
  76. select(MihomoMeta)
  77. .where(MihomoMeta.pid.is_not(None))
  78. ).all()
  79. return all
  80. def update_proxy_delays(self, provider_name: str, delays: dict):
  81. """更新指定服务商下所有代理的延迟"""
  82. with Session(self.engine) as session:
  83. # 先清空该provider下所有代理的延迟值
  84. session.exec(
  85. update(MihomoMeta)
  86. .where(MihomoMeta.provider_name == provider_name)
  87. .values(delay=None)
  88. )
  89. # 更新有延迟值的代理
  90. for proxy_name, delay in delays.items():
  91. if isinstance(delay, int): # 确保只更新有效的延迟值
  92. session.exec(
  93. update(MihomoMeta)
  94. .where(
  95. (MihomoMeta.provider_name == provider_name) &
  96. (MihomoMeta.proxy_name == proxy_name)
  97. )
  98. .values(delay=delay)
  99. )
  100. session.commit()
  101. def get_each_provider_proxies(self):
  102. with Session(self.engine) as session:
  103. # 子查询:获取每个 provider_name 的最小 id
  104. subquery = (
  105. select(
  106. MihomoMeta.provider_name,
  107. func.min(MihomoMeta.id).label("min_id")
  108. )
  109. .group_by(MihomoMeta.provider_name)
  110. .subquery()
  111. )
  112. # 主查询:通过联接到子查询获取每个 provider_name 的第一条记录
  113. stmt = (
  114. select(MihomoMeta)
  115. .join(subquery, MihomoMeta.id == subquery.c.min_id)
  116. )
  117. # 执行查询并获取结果
  118. return session.exec(stmt).all()
  119. def get_each_provider_running_proxies(self) -> List[MihomoMeta]:
  120. with Session(self.engine) as session:
  121. subquery = (
  122. select(
  123. MihomoMeta.provider_name,
  124. func.min(MihomoMeta.id).label("min_id")
  125. )
  126. .where(MihomoMeta.pid.is_not(None))
  127. .group_by(MihomoMeta.provider_name)
  128. .subquery()
  129. )
  130. # 主查询:通过联接到子查询获取每个 provider_name 的第一条记录
  131. stmt = (
  132. select(MihomoMeta)
  133. .join(subquery, MihomoMeta.id == subquery.c.min_id)
  134. )
  135. # 执行查询并获取结果
  136. return session.exec(stmt).all()
  137. def get_proxies_by_provider(self) -> Dict[str, List[MihomoMeta]]:
  138. """
  139. 返回一个字典,键是 provider_name,值是该 provider_name 对应的所有 MihomoMeta 记录列表。
  140. """
  141. with Session(self.engine) as session:
  142. all_proxies = session.exec(select(MihomoMeta)).all()
  143. # 使用字典来组织数据
  144. proxies_by_provider = {}
  145. for proxy in all_proxies:
  146. if proxy.provider_name not in proxies_by_provider:
  147. proxies_by_provider[proxy.provider_name] = []
  148. proxies_by_provider[proxy.provider_name].append(proxy)
  149. return proxies_by_provider
  150. def check_valid(self, sub_model:SubscriptFile):
  151. with open(sub_model.file_path, "r",encoding='utf-8') as f:
  152. sub_yaml = yaml.safe_load(f)
  153. groups = sub_yaml.get("proxy-groups", [])
  154. if not groups:
  155. raise ValueError("subscription file is not valid")
  156. name = groups[0].get("name", "")
  157. if not name:
  158. raise ValueError("subscription file is not valid")
  159. proxies = sub_yaml.get("proxies", [])
  160. if not proxies:
  161. raise ValueError("subscription file is not valid")
  162. fileter_proxies = []
  163. fileter_proxies = []
  164. keywords = ['流量', '套餐', '剩余', '测试']
  165. for proxy in proxies:
  166. if not any(keyword in proxy.get("name", "") for keyword in keywords):
  167. fileter_proxies.append(proxy)
  168. return name, groups,proxies