def post(self, request, *args, **kwargs): """ post will handle the endpoint that allow a group admin to create and send an invite to a user to join his group endpoint : api/groups/:id/members/invite/ """ group = self.get_object() to_be_member = request.data.get("username") try: user = User.objects.get(username=to_be_member) except User.DoesNotExist: return Response(data={ "message": "The User you wanted to add as member doesnt exist" }, status=status.HTTP_400_BAD_REQUEST) membership = Membership.objects.create(group=group, user=user, status='I') encrypted_details = encrypt(str(membership.id), SECRET_KEY) url = "{}/api/groups/members/invite/accept/{}/".format( DOMAIN, encrypted_details.decode()) group_invite = "<h3>Dear {}, you have been invited by {}, to join the esusu group {} on Esusu confam<a href='{}'>Esusu confam</a>!</h3><br />Please kindly click on the url to accept and activate your membership.{} May the force be with you!".format( user.username, group.admin.username, group.name, DOMAIN, url) logger.info("invite details: {}".format({ "invite url": url, "user": user.username, "email": user.email })) mail_subject = "Esusu Group Invite" text_heading = "Hello, Dear" mail_resp = send_mail(mail_subject, user, text_heading, group_invite) logger.debug("email response : {}".format(mail_resp.json())) return Response(data={"url": url}, status=status.HTTP_201_CREATED)
def store(self, obj: object) -> str: """储存一个对象,返回其 key""" hash_str = str(id(obj)) key = md5(hash_str.encode("utf-8")).hexdigest() if key not in self._db: logger.debug(f"Store {obj} -> {key}") self._db[key] = obj return key
def get(self, request, token): try: data = int(decrypt(token, SECRET_KEY).decode()) membership = Membership.objects.get(id=int(data)) except (ValueError, Membership.DoesNotExist) as e: logger.debug("something went wrong: {}".format(e.__str__())) return Response(status=status.HTTP_400_BAD_REQUEST) membership.status = 'A' membership.save() return Response(status=status.HTTP_200_OK)
def head(url: str, params=None, allow_redirects=True, **kwargs) -> requests.Response: """封装 HEAD 方法, 默认开启 302 重定向, 用于获取目标直链""" try: logger.debug(f"url: {url}, params: {params}, allow_redirects: {allow_redirects}") kwargs.setdefault("timeout", 10) kwargs.setdefault("headers", HtmlParseHelper._headers) return requests.head(url, params=params, verify=False, allow_redirects=allow_redirects, **kwargs) except requests.Timeout as e: logger.warning(e) return requests.Response() except requests.RequestException: return requests.Response()
def get(url: str, params=None, html_encoding="utf-8", **kwargs) -> requests.Response: """封装 GET 方法, 默认网页编码为 utf-8""" try: logger.debug(f"url: {url}, params: {params}") kwargs.setdefault("timeout", 5) kwargs.setdefault("headers", HtmlParseHelper._headers) ret = requests.get(url, params, verify=False, **kwargs) ret.encoding = html_encoding # 有些网页仍然使用 gb2312/gb18030 之类的编码, 需要单独设置 return ret except requests.Timeout as e: logger.warning(e) return requests.Response() except requests.RequestException: return requests.Response()
def post(url: str, data=None, html_encoding="utf-8", **kwargs) -> requests.Response: """"封装 POST 方法, 默认网页编码为 utf-8""" try: logger.debug(f"url: {url}, data: {data}") kwargs.setdefault("timeout", 5) kwargs.setdefault("headers", HtmlParseHelper._headers) ret = requests.post(url, data, verify=False, **kwargs) ret.encoding = html_encoding return ret except requests.Timeout as e: logger.warning(e) return requests.Response() except requests.RequestException: return requests.Response()
def get_real_url(self): """通过视频的 play_id 获取视频链接""" play_api = "http://service-agbhuggw-1259251677.gz.apigw.tencentcs.com/android/video/newplay" play_id = self.get_raw_url() secret_key = "zandroidzz" now = int(time.time() * 1000) # 13 位时间戳 sing = secret_key + str(now) sing = md5(sing.encode("utf-8")).hexdigest() logger.info(f"Parsing real url for {play_id}") payload = {"playid": play_id, "userid": "", "apptoken": "", "sing": sing, "map": now} resp = self.post(play_api, data=payload) if resp.status_code != 200: logger.warning(f"Response error: {resp.status_code} {play_api}") logger.debug(f"POST params: {payload}") return "error" real_url = resp.json()["data"]["videoplayurl"] logger.info(f"Video real url: {real_url}") return real_url
def put(self, request, *args, **kwargs): user_id = request.data.get("user_id") if user_id is None: return Response(data={"message": "a user is required to be added"}, status=status.HTTP_400_BAD_REQUEST) try: grp = self.get_object() except Group.DoesNotExist: return Response( data={"message": "The requested group does not exist"}, status=status.HTTP_404_NOT_FOUND) try: usr = User.objects.get(id=user_id) except User.DoesNotExist: return Response(data={ "message": "The User you wanted to add as member doesnt exist" }, status=status.HTTP_400_BAD_REQUEST) mbr = grp.members.all().filter(user__id=usr.id) if len(mbr) > 0: return Response( data={"message": "The User is already a member of this group"}, status=status.HTTP_400_BAD_REQUEST) membership = Membership.objects.create(group=grp, user=usr, status="A") membership.save() logger.debug(membership) grp.members.add(membership) mbr = [i.user.username for i in grp.members.all()] if self.check_group_is_ready(): tenure = Tenure.objects.create(group=grp) return Response(data={ "message": "The amount of active " + "members are complete and the tenure" + " should start now, below is the sequence of collection", "collections": tenure.collection_sequences }, status=status.HTTP_200_OK) return Response(data={"members": mbr}, status=status.HTTP_201_CREATED)
def get_real_url(self) -> str: url = "http://www.yhdm.tv/" + self.get_raw_url() logger.info(f"Parsing real url for {url}") resp = self.get(url) if resp.status_code != 200: logger.warning(f"Response error: {resp.status_code} {url}") return "error" video_url = self.xpath( resp.text, '//div[@id="playbox"]/@data-vid')[0] # "url$format" video_url = video_url.split( "$" )[0] # "http://quan.qq.com/video/1098_ae4be38407bf9d8227748e145a8f97a5" if not video_url.startswith("http"): # 偶尔出现一些无效视频 logger.warning(f"This video is not valid: {video_url}") return "error" logger.debug(f"Redirect for {video_url}") resp = self.head(video_url, allow_redirects=True) # 获取直链时会重定向 2 次 logger.info(f"Video real url: {resp.url}") return resp.url # 重定向之后的视频直链
def set_cookie(self): # 计算 k2 的值 # https://www.agefans.tv/age/static/js/s_runtimelib.js?ver=202008211700 __getplay_pck() t1 = self._client.cookies.get("t1") logger.debug(f"Get cookie t1={t1}") t = (int(t1) // 1000) >> 5 k2 = (t * (t % 4096) + 39382) * (t % 4096) + t k2 = str(k2) # 计算 t2 的值, 生成一个后三位包含 k2 最后一位的数时间戳 # https://www.agefans.tv/age/static/js/s_dett.js?ver=202008211700 __getplay_pck2() k2_last = k2[-1] t2 = "" while True: now = str(int(time.time() * 1000)) last_3w = now[-3:] if 0 <= last_3w.find(k2_last): t2 = now break logger.debug(f"Set cookies, k2={k2}, t2={t2}") self._client.cookies.update({"k2": k2, "t2": t2})
def post(self, request, *args, **kwargs): """ post: process the log in request """ username = request.data.get("username", "") password = request.data.get("password", "") logger.debug("log in credentials", extra={ "username": username, "password": password }) user = BasicAuthenticationBackend().authenticate(request=request, username=username, password=password) if user is not None: login(request, user) serializer = TokenSerializer( data={"token": jwt_encode_handler(jwt_payload_handler(user))}) serializer.is_valid() return Response(serializer.data) return Response(status=status.HTTP_401_UNAUTHORIZED)
def post(self, request, *args, **kwargs): grp = self.get_object() user = request.user mbr = Membership.objects.create(group=grp, user=user, status="I") mbr.save() grp.members.add(mbr) mbrship_request = "<h3>Dear {}, a user just requested to join your co-operative group on Esusu confam, kindly log in to accept!".format( grp.admin.username) logger.info("new member details: name: {}, email: {}".format( user.username, user.email)) mail_subject = "Esusu Group Membership request" text_heading = "Hello, Dear" mail_resp = send_mail(mail_subject, grp.admin, text_heading, mbrship_request) logger.debug("email response : {}".format(mail_resp.json())) Response(data={ "message": "A request has been sent to the group admin to confirm your membership. thanks" }, status=status.HTTP_201_CREATED)
def search(self, keyword: str) -> List[DanmakuMetaInfo]: """搜索番剧信息""" logger.info(f"Searching for danmaku: {keyword}") ret = [] params = {"keyword": keyword, "search_type": "media_bangumi"} params2 = { "keyword": keyword, "search_type": "video", "tids": 13, "order": "dm", "page": 1, "duration": 4 } task_list = [ (self.get, (self._search_api, params), {}), (self.get, (self._search_api, params2), {} ) # 用户上传的60 分钟以上的视频, 按弹幕数量排序 ] resp_list = self.submit_tasks(task_list) # 多线程同时搜索 for resp in resp_list: if resp.status_code != 200: continue data = resp.json() if data["code"] != 0 or data["data"]["numResults"] == 0: continue for item in data["data"]["result"]: if '<em class="keyword">' not in item[ "title"]: # 没有匹配关键字, 是B站的推广视频 continue dm_mate = DanmakuMetaInfo() dm_mate.title = item["title"].replace(r'<em class="keyword">', "").replace("</em>", "") # 番剧标题 dm_mate.play_page_url = item.get("goto_url") or item.get( "arcurl") # 番剧播放页链接 dm_mate.num = int(item.get("ep_size") or -1) logger.debug(f"Match danmaku: {dm_mate}") ret.append(dm_mate) return ret
def get_real_url(self): headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4033.0 Safari/537.36 Edg/81.0.403.1", "Referer": self._base_url } play_page_url = self._base_url + self.get_raw_url( ) # "https://www.agefans.tv/play/20170172?playid=1_1" logger.info(f"Parse page: {play_page_url}") aid = play_page_url.split("?")[0].split("/")[-1] playindex, epindex = play_page_url.split("=")[-1].split("_") params = { "aid": aid, "playindex": playindex, "epindex": epindex, "r": random() } self._client.head(play_page_url, headers=headers) # 接受服务器设置的 cookie, 不需要 body, 加快速度 self.set_cookie() # 否则返回的数据与视频对不上 resp = self._client.get(self._play_api, params=params, headers=headers, verify=False) while "err:timeout" in resp.text: logger.debug("Response : err:timeout") self.set_cookie() resp = self._client.get(self._play_api, params=params, headers=headers, verify=False) logger.debug(resp.status_code) logger.debug(resp.text) try: data = resp.json() real_url = data["purlf"] + data["vurl"] real_url = real_url.split("?")[-1].replace("url=", "") real_url = requests.utils.unquote(real_url) if real_url.startswith("//"): real_url = "http:" + real_url logger.debug(f"real_url={real_url}") except Exception: return "error, try agin" return real_url
def detect_video_format(self) -> str: """判断视频真正的格式, url 可能没有视频后缀""" # 尝试从 url 提取后缀 url = self._get_real_url() try: ext = url.split("?")[0].split(".")[-1].lower() if ext in ["mp4", "flv"]: return ext if ext == "m3u8": return "hls" except (IndexError, AttributeError): pass # 视频的元数据中包含了视频的格式信息, 在视频开头寻找十六进制标识符推断视频格式 format_hex = { "mp4": ["69736F6D", "70617663", "6D703432", "4D50454734", "4C617666"], "flv": ["464C56"], "hls": ["4558544D3355"] } _, data_iter = self._get_stream_from_server(0, 512) if not data_iter: logger.warning("Could not get video stream from server") return "unknown" logger.debug("Detecting video format from binary stream") video_meta = next(data_iter).hex().upper() for format_, hex_list in format_hex.items(): for hex_sign in hex_list: if hex_sign in video_meta: logger.debug(f"Video format: {format_}") return format_ logger.error("Could not detect video format from stream") logger.debug("Video raw binary stream (512byte):") logger.debug(video_meta) return "unknown"
def update(self, key: str, value: object): if key in self._db: logger.debug(f"Update {key} -> {value}") self._db[key] = value return key
def fetch(self, key: str): ret = self._db.get(key) logger.debug(f"Fetch {key} -> {ret}") return ret
def post(self, request, *args, **kwargs): serializer = UserSerializer(data=request.data) logger.debug(request.data) serializer.is_valid(raise_exception=True) serializer.save() return Response(status=status.HTTP_201_CREATED)
def _get_real_url(self): """获取视频直链, 如果有缓存的话, 使用缓存的值""" if self._real_url: # 缓存解析完成的直链, 防止重复解析 logger.debug(f"Using cached real url: {self._real_url}") return self._real_url return self.get_real_url()