{ "id": "gogoanime-external", "name": "Gogoanime (External)", "description": "", "version": "0.0.1", "type": "onlinestream-provider", "manifestURI": "", "language": "go", "author": "Seanime", "payload": "package main\n\nimport (\n\t\"bytes\"\n\t\"crypto/aes\"\n\t\"crypto/cipher\"\n\t\"encoding/base64\"\n\t\"encoding/hex\"\n\t\"errors\"\n\t\"fmt\"\n\tbrowser \"github.com/EDDYCJY/fake-useragent\"\n\t\"github.com/goccy/go-json\"\n\t\"github.com/gocolly/colly\"\n\t\"github.com/rs/zerolog\"\n\t\"io\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"regexp\"\n\t\"strconv\"\n\t\"strings\"\n\n\thibikeonlinestream \"github.com/5rahim/hibike/pkg/extension/onlinestream\"\n)\n\nconst (\n\tDefaultServer = \"default\"\n\tGogoanimeProvider = \"gogoanime-external\"\n\tGogocdnServer = \"gogocdn\"\n\tVidstreamingServer = \"vidstreaming\"\n\tStreamSBServer = \"streamsb\"\n)\n\ntype Gogoanime struct {\n\tBaseURL string\n\tAjaxURL string\n\tClient http.Client\n\tUserAgent string\n\tlogger *zerolog.Logger\n}\n\nfunc NewProvider(logger *zerolog.Logger) hibikeonlinestream.Provider {\n\treturn &Gogoanime{\n\t\tBaseURL: \"https://anitaku.to\",\n\t\tAjaxURL: \"https://ajax.gogocdn.net\",\n\t\tClient: http.Client{},\n\t\tUserAgent: browser.Firefox(),\n\t\tlogger: logger,\n\t}\n}\n\nfunc (g *Gogoanime) GetEpisodeServers() []string {\n\treturn []string{GogocdnServer, VidstreamingServer}\n}\n\nfunc (g *Gogoanime) Search(query string, dubbed bool) ([]*hibikeonlinestream.SearchResult, error) {\n\tvar results []*hibikeonlinestream.SearchResult\n\n\tg.logger.Debug().Str(\"query\", query).Bool(\"dubbed\", dubbed).Msg(\"gogoanime: Searching anime\")\n\n\tc := colly.NewCollector(\n\t\tcolly.UserAgent(g.UserAgent),\n\t)\n\n\tc.OnHTML(\".last_episodes > ul > li\", func(e *colly.HTMLElement) {\n\t\tid := \"\"\n\t\tidParts := strings.Split(e.ChildAttr(\"p.name > a\", \"href\"), \"/\")\n\t\tif len(idParts) > 2 {\n\t\t\tid = idParts[2]\n\t\t}\n\t\ttitle := e.ChildText(\"p.name > a\")\n\t\turl := g.BaseURL + e.ChildAttr(\"p.name > a\", \"href\")\n\t\tsubOrDub := hibikeonlinestream.Sub\n\t\tif strings.Contains(strings.ToLower(e.ChildText(\"p.name > a\")), \"dub\") {\n\t\t\tsubOrDub = hibikeonlinestream.Dub\n\t\t}\n\t\tresults = append(results, &hibikeonlinestream.SearchResult{\n\t\t\tID: id,\n\t\t\tTitle: title,\n\t\t\tURL: url,\n\t\t\tSubOrDub: subOrDub,\n\t\t})\n\t})\n\n\tsearchURL := g.BaseURL + \"/search.html?keyword=\" + url.QueryEscape(query)\n\tif dubbed {\n\t\tsearchURL += \"%20(Dub)\"\n\t}\n\n\terr := c.Visit(searchURL)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tg.logger.Debug().Int(\"count\", len(results)).Msg(\"gogoanime: Fetched anime\")\n\n\treturn results, nil\n}\n\nfunc (g *Gogoanime) FindEpisode(id string) ([]*hibikeonlinestream.EpisodeDetails, error) {\n\tvar episodes []*hibikeonlinestream.EpisodeDetails\n\n\tg.logger.Debug().Str(\"id\", id).Msg(\"gogoanime: Fetching episodes\")\n\n\tif !strings.Contains(id, \"gogoanime\") {\n\t\tid = fmt.Sprintf(\"%s/category/%s\", g.BaseURL, id)\n\t}\n\n\tc := colly.NewCollector(\n\t\tcolly.UserAgent(g.UserAgent),\n\t)\n\n\tvar epStart, epEnd, movieID, alias string\n\n\tc.OnHTML(\"#episode_page > li > a\", func(e *colly.HTMLElement) {\n\t\tif epStart == \"\" {\n\t\t\tepStart = e.Attr(\"ep_start\")\n\t\t}\n\t\tepEnd = e.Attr(\"ep_end\")\n\t})\n\n\tc.OnHTML(\"#movie_id\", func(e *colly.HTMLElement) {\n\t\tmovieID = e.Attr(\"value\")\n\t})\n\n\tc.OnHTML(\"#alias\", func(e *colly.HTMLElement) {\n\t\talias = e.Attr(\"value\")\n\t})\n\n\terr := c.Visit(id)\n\tif err != nil {\n\t\tg.logger.Error().Err(err).Msg(\"gogoanime: Failed to fetch episodes\")\n\t\treturn nil, err\n\t}\n\n\tc2 := colly.NewCollector(\n\t\tcolly.UserAgent(g.UserAgent),\n\t)\n\n\tc2.OnHTML(\"#episode_related > li\", func(e *colly.HTMLElement) {\n\t\tepisodeIDParts := strings.Split(e.ChildAttr(\"a\", \"href\"), \"/\")\n\t\tif len(episodeIDParts) < 2 {\n\t\t\treturn\n\t\t}\n\t\tepisodeID := strings.TrimSpace(episodeIDParts[1])\n\t\tepisodeNumberStr := strings.TrimPrefix(e.ChildText(\"div.name\"), \"EP \")\n\t\tepisodeNumber, err := strconv.Atoi(episodeNumberStr)\n\t\tif err != nil {\n\t\t\tg.logger.Error().Err(err).Str(\"episodeID\", episodeID).Msg(\"failed to parse episode number\")\n\t\t\treturn\n\t\t}\n\t\tepisodes = append(episodes, &hibikeonlinestream.EpisodeDetails{\n\t\t\tProvider: GogoanimeProvider,\n\t\t\tID: episodeID,\n\t\t\tNumber: episodeNumber,\n\t\t\tURL: g.BaseURL + \"/\" + episodeID,\n\t\t})\n\t})\n\n\tajaxURL := fmt.Sprintf(\"%s/ajax/load-list-episode\", g.AjaxURL)\n\tajaxParams := url.Values{\n\t\t\"ep_start\": {epStart},\n\t\t\"ep_end\": {epEnd},\n\t\t\"id\": {movieID},\n\t\t\"alias\": {alias},\n\t\t\"default_ep\": {\"0\"},\n\t}\n\tajaxURLWithParams := fmt.Sprintf(\"%s?%s\", ajaxURL, ajaxParams.Encode())\n\n\terr = c2.Visit(ajaxURLWithParams)\n\tif err != nil {\n\t\tg.logger.Error().Err(err).Msg(\"gogoanime: Failed to fetch episodes\")\n\t\treturn nil, err\n\t}\n\n\tg.logger.Debug().Int(\"count\", len(episodes)).Msg(\"gogoanime: Fetched episodes\")\n\n\treturn episodes, nil\n}\n\nfunc (g *Gogoanime) FindEpisodeServer(episodeInfo *hibikeonlinestream.EpisodeDetails, server string) (*hibikeonlinestream.EpisodeServer, error) {\n\tvar source *hibikeonlinestream.EpisodeServer\n\n\tif server == DefaultServer {\n\t\tserver = GogocdnServer\n\t}\n\tg.logger.Debug().Str(\"server\", string(server)).Str(\"episodeID\", episodeInfo.ID).Msg(\"gogoanime: Fetching server sources\")\n\n\tc := colly.NewCollector()\n\n\tswitch server {\n\tcase VidstreamingServer:\n\t\tc.OnHTML(\".anime_muti_link > ul > li.vidcdn > a\", func(e *colly.HTMLElement) {\n\t\t\tsrc := e.Attr(\"data-video\")\n\t\t\tgogocdn := NewGogoCDN()\n\t\t\tvideoSources, err := gogocdn.Extract(src)\n\t\t\tif err == nil {\n\t\t\t\tsource = &hibikeonlinestream.EpisodeServer{\n\t\t\t\t\tProvider: GogoanimeProvider,\n\t\t\t\t\tServer: server,\n\t\t\t\t\tHeaders: map[string]string{\n\t\t\t\t\t\t\"Referer\": g.BaseURL + \"/\" + episodeInfo.ID,\n\t\t\t\t\t},\n\t\t\t\t\tVideoSources: videoSources,\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\tcase GogocdnServer, \"\":\n\t\tc.OnHTML(\"#load_anime > div > div > iframe\", func(e *colly.HTMLElement) {\n\t\t\tsrc := e.Attr(\"src\")\n\t\t\tgogocdn := NewGogoCDN()\n\t\t\tvideoSources, err := gogocdn.Extract(src)\n\t\t\tif err == nil {\n\t\t\t\tsource = &hibikeonlinestream.EpisodeServer{\n\t\t\t\t\tProvider: GogoanimeProvider,\n\t\t\t\t\tServer: server,\n\t\t\t\t\tHeaders: map[string]string{\n\t\t\t\t\t\t\"Referer\": g.BaseURL + \"/\" + episodeInfo.ID,\n\t\t\t\t\t},\n\t\t\t\t\tVideoSources: videoSources,\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\tcase StreamSBServer:\n\t\tc.OnHTML(\".anime_muti_link > ul > li.streamsb > a\", func(e *colly.HTMLElement) {\n\t\t\tsrc := e.Attr(\"data-video\")\n\t\t\tstreamsb := NewStreamSB()\n\t\t\tvideoSources, err := streamsb.Extract(src)\n\t\t\tif err == nil {\n\t\t\t\tsource = &hibikeonlinestream.EpisodeServer{\n\t\t\t\t\tProvider: GogoanimeProvider,\n\t\t\t\t\tServer: server,\n\t\t\t\t\tHeaders: map[string]string{\n\t\t\t\t\t\t\"Referer\": g.BaseURL + \"/\" + episodeInfo.ID,\n\t\t\t\t\t\t\"watchsb\": \"streamsb\",\n\t\t\t\t\t\t\"User-Agent\": g.UserAgent,\n\t\t\t\t\t},\n\t\t\t\t\tVideoSources: videoSources,\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n\n\terr := c.Visit(g.BaseURL + \"/\" + episodeInfo.ID)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif source == nil {\n\t\tg.logger.Warn().Str(\"server\", string(server)).Msg(\"gogoanime: No sources found\")\n\t\treturn nil, fmt.Errorf(\"no sources found\")\n\t}\n\n\tg.logger.Debug().Str(\"server\", string(server)).Int(\"videoSources\", len(source.VideoSources)).Msg(\"gogoanime: Fetched server sources\")\n\n\treturn source, nil\n\n}\n\ntype cdnKeys struct {\n\tkey []byte\n\tsecondKey []byte\n\tiv []byte\n}\n\ntype GogoCDN struct {\n\tclient *http.Client\n\tserverName string\n\tkeys cdnKeys\n\treferrer string\n}\n\nfunc NewGogoCDN() *GogoCDN {\n\treturn &GogoCDN{\n\t\tclient: &http.Client{},\n\t\tserverName: \"goload\",\n\t\tkeys: cdnKeys{\n\t\t\tkey: []byte(\"37911490979715163134003223491201\"),\n\t\t\tsecondKey: []byte(\"54674138327930866480207815084989\"),\n\t\t\tiv: []byte(\"3134003223491201\"),\n\t\t},\n\t}\n}\n\n// Extract fetches and extracts video sources from the provided URI.\nfunc (g *GogoCDN) Extract(uri string) (vs []*hibikeonlinestream.VideoSource, err error) {\n\tdefer func() {\n\t\tif r := recover(); r != nil {\n\t\t\terr = fmt.Errorf(\"failed to extract video sources\")\n\t\t}\n\t}()\n\n\t// Instantiate a new collector\n\tc := colly.NewCollector(\n\t\t// Allow visiting the same page multiple times\n\t\tcolly.AllowURLRevisit(),\n\t)\n\tur, err := url.Parse(uri)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// Variables to hold extracted values\n\tvar scriptValue, id string\n\n\tid = ur.Query().Get(\"id\")\n\n\t// Find and extract the script value and id\n\tc.OnHTML(\"script[data-name='episode']\", func(e *colly.HTMLElement) {\n\t\tscriptValue = e.Attr(\"data-value\")\n\n\t})\n\n\t// Start scraping\n\terr = c.Visit(uri)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// Check if scriptValue and id are found\n\tif scriptValue == \"\" || id == \"\" {\n\t\treturn nil, errors.New(\"script value or id not found\")\n\t}\n\n\t// Extract video sources\n\tajaxUrl := fmt.Sprintf(\"%s://%s/encrypt-ajax.php?%s\", ur.Scheme, ur.Host, g.generateEncryptedAjaxParams(id, scriptValue))\n\n\treq, err := http.NewRequest(\"GET\", ajaxUrl, nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treq.Header.Set(\"X-Requested-With\", \"XMLHttpRequest\")\n\treq.Header.Set(\"User-Agent\", \"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36\")\n\treq.Header.Set(\"Accept\", \"application/json, text/javascript, */*; q=0.01\")\n\n\tencryptedData, err := g.client.Do(req)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tdefer encryptedData.Body.Close()\n\n\tencryptedDataBytesRes, err := io.ReadAll(encryptedData.Body)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar encryptedDataBytes map[string]string\n\terr = json.Unmarshal(encryptedDataBytesRes, &encryptedDataBytes)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tdata, err := g.decryptAjaxData(encryptedDataBytes[\"data\"])\n\n\tsource, ok := data[\"source\"].([]interface{})\n\n\t// Check if source is found\n\tif !ok {\n\t\treturn nil, errors.New(\"source not found\")\n\t}\n\n\tvar results []*hibikeonlinestream.VideoSource\n\n\turls := make([]string, 0)\n\tfor _, src := range source {\n\t\ts := src.(map[string]interface{})\n\t\turls = append(urls, s[\"file\"].(string))\n\t}\n\n\tsourceBK, ok := data[\"source_bk\"].([]interface{})\n\tif ok {\n\t\tfor _, src := range sourceBK {\n\t\t\ts := src.(map[string]interface{})\n\t\t\turls = append(urls, s[\"file\"].(string))\n\t\t}\n\t}\n\n\tfor _, url := range urls {\n\n\t\tvs, ok := g.urlToVideoSource(url, source, sourceBK)\n\t\tif ok {\n\t\t\tresults = append(results, vs...)\n\t\t}\n\n\t}\n\n\treturn results, nil\n}\n\nfunc (g *GogoCDN) urlToVideoSource(url string, source []interface{}, sourceBK []interface{}) (vs []*hibikeonlinestream.VideoSource, ok bool) {\n\tdefer func() {\n\t\tif r := recover(); r != nil {\n\t\t\tok = false\n\t\t}\n\t}()\n\tret := make([]*hibikeonlinestream.VideoSource, 0)\n\tif strings.Contains(url, \".m3u8\") {\n\t\tresResult, err := http.Get(url)\n\t\tif err != nil {\n\t\t\treturn nil, false\n\t\t}\n\t\tdefer resResult.Body.Close()\n\n\t\tbodyBytes, err := io.ReadAll(resResult.Body)\n\t\tif err != nil {\n\t\t\treturn nil, false\n\t\t}\n\t\tbodyString := string(bodyBytes)\n\n\t\tresolutions := regexp.MustCompile(`(RESOLUTION=)(.*)(\\s*?)(\\s.*)`).FindAllStringSubmatch(bodyString, -1)\n\t\tbaseURL := url[:strings.LastIndex(url, \"/\")]\n\n\t\tfor _, res := range resolutions {\n\t\t\tquality := strings.Split(strings.Split(res[2], \"x\")[1], \",\")[0]\n\t\t\turl := fmt.Sprintf(\"%s/%s\", baseURL, strings.TrimSpace(res[4]))\n\t\t\tret = append(ret, &hibikeonlinestream.VideoSource{URL: url, Type: hibikeonlinestream.VideoSourceM3U8, Quality: quality + \"p\"})\n\t\t}\n\n\t\tret = append(ret, &hibikeonlinestream.VideoSource{URL: url, Type: hibikeonlinestream.VideoSourceM3U8, Quality: \"default\"})\n\t} else {\n\t\tfor _, src := range source {\n\t\t\ts := src.(map[string]interface{})\n\t\t\tif s[\"file\"].(string) == url {\n\t\t\t\tquality := strings.Split(s[\"label\"].(string), \" \")[0] + \"p\"\n\t\t\t\tret = append(ret, &hibikeonlinestream.VideoSource{URL: url, Type: hibikeonlinestream.VideoSourceMP4, Quality: quality})\n\t\t\t}\n\t\t}\n\t\tif sourceBK != nil {\n\t\t\tfor _, src := range sourceBK {\n\t\t\t\ts := src.(map[string]interface{})\n\t\t\t\tif s[\"file\"].(string) == url {\n\t\t\t\t\tret = append(ret, &hibikeonlinestream.VideoSource{URL: url, Type: hibikeonlinestream.VideoSourceMP4, Quality: \"backup\"})\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn ret, true\n}\n\n// generateEncryptedAjaxParams generates encrypted AJAX parameters.\nfunc (g *GogoCDN) generateEncryptedAjaxParams(id, scriptValue string) string {\n\tencryptedKey := g.encrypt(id, g.keys.iv, g.keys.key)\n\tdecryptedToken := g.decrypt(scriptValue, g.keys.iv, g.keys.key)\n\treturn fmt.Sprintf(\"id=%s&alias=%s\", encryptedKey, decryptedToken)\n}\n\n// encrypt encrypts the given text using AES CBC mode.\nfunc (g *GogoCDN) encrypt(text string, iv []byte, key []byte) string {\n\tblock, _ := aes.NewCipher(key)\n\ttextBytes := []byte(text)\n\ttextBytes = pkcs7Padding(textBytes, aes.BlockSize)\n\tcipherText := make([]byte, len(textBytes))\n\n\tmode := cipher.NewCBCEncrypter(block, iv)\n\tmode.CryptBlocks(cipherText, textBytes)\n\n\treturn base64.StdEncoding.EncodeToString(cipherText)\n}\n\n// decrypt decrypts the given text using AES CBC mode.\nfunc (g *GogoCDN) decrypt(text string, iv []byte, key []byte) string {\n\tblock, _ := aes.NewCipher(key)\n\tcipherText, _ := base64.StdEncoding.DecodeString(text)\n\tplainText := make([]byte, len(cipherText))\n\n\tmode := cipher.NewCBCDecrypter(block, iv)\n\tmode.CryptBlocks(plainText, cipherText)\n\tplainText = pkcs7Trimming(plainText)\n\n\treturn string(plainText)\n}\n\nfunc (g *GogoCDN) decryptAjaxData(encryptedData string) (map[string]interface{}, error) {\n\tdecodedData, err := base64.StdEncoding.DecodeString(encryptedData)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tblock, err := aes.NewCipher(g.keys.secondKey)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif len(decodedData) < aes.BlockSize {\n\t\treturn nil, fmt.Errorf(\"cipher text too short\")\n\t}\n\n\tiv := g.keys.iv\n\tmode := cipher.NewCBCDecrypter(block, iv)\n\tmode.CryptBlocks(decodedData, decodedData)\n\n\t// Remove padding\n\tdecodedData = pkcs7Trimming(decodedData)\n\n\tvar data map[string]interface{}\n\terr = json.Unmarshal(decodedData, &data)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn data, nil\n}\n\n// pkcs7Padding pads the text to be a multiple of blockSize using Pkcs7 padding.\nfunc pkcs7Padding(text []byte, blockSize int) []byte {\n\tpadding := blockSize - len(text)%blockSize\n\tpadText := bytes.Repeat([]byte{byte(padding)}, padding)\n\treturn append(text, padText...)\n}\n\n// pkcs7Trimming removes Pkcs7 padding from the text.\nfunc pkcs7Trimming(text []byte) []byte {\n\tlength := len(text)\n\tunpadding := int(text[length-1])\n\treturn text[:(length - unpadding)]\n}\n\ntype StreamSB struct {\n\tHost string\n\tHost2 string\n\tUserAgent string\n}\n\nfunc NewStreamSB() *StreamSB {\n\treturn &StreamSB{\n\t\tHost: \"https://streamsss.net/sources50\",\n\t\tHost2: \"https://watchsb.com/sources50\",\n\t\tUserAgent: \"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36\",\n\t}\n}\n\nfunc (s *StreamSB) Payload(hex string) string {\n\treturn \"566d337678566f743674494a7c7c\" + hex + \"7c7c346b6767586d6934774855537c7c73747265616d7362/6565417268755339773461447c7c346133383438333436313335376136323337373433383634376337633465366534393338373136643732373736343735373237613763376334363733353737303533366236333463353333363534366137633763373337343732363536313664373336327c7c6b586c3163614468645a47617c7c73747265616d7362\"\n}\n\nfunc (s *StreamSB) Extract(uri string) (vs []*hibikeonlinestream.VideoSource, err error) {\n\tdefer func() {\n\t\tif r := recover(); r != nil {\n\t\t\terr = errors.New(\"failed to extract video sources\")\n\t\t}\n\t}()\n\n\tvar ret []*hibikeonlinestream.VideoSource\n\n\tid := strings.Split(uri, \"/e/\")[1]\n\tif strings.Contains(id, \"html\") {\n\t\tid = strings.Split(id, \".html\")[0]\n\t}\n\n\tif id == \"\" {\n\t\treturn nil, errors.New(\"cannot find ID\")\n\t}\n\n\tclient := &http.Client{}\n\treq, _ := http.NewRequest(\"GET\", fmt.Sprintf(\"%s/%s\", s.Host, s.Payload(hex.EncodeToString([]byte(id)))), nil)\n\treq.Header.Add(\"watchsb\", \"sbstream\")\n\treq.Header.Add(\"User-Agent\", s.UserAgent)\n\treq.Header.Add(\"Referer\", uri)\n\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer res.Body.Close()\n\n\tbody, _ := io.ReadAll(res.Body)\n\n\tvar jsonResponse map[string]interface{}\n\terr = json.Unmarshal(body, &jsonResponse)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tstreamData, ok := jsonResponse[\"stream_data\"].(map[string]interface{})\n\tif !ok {\n\t\treturn nil, fmt.Errorf(\"stream data not found\")\n\t}\n\n\tm3u8Urls, err := client.Get(streamData[\"file\"].(string))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer m3u8Urls.Body.Close()\n\n\tm3u8Body, err := io.ReadAll(m3u8Urls.Body)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tvideoList := strings.Split(string(m3u8Body), \"#EXT-X-STREAM-INF:\")\n\n\tfor _, video := range videoList {\n\t\tif !strings.Contains(video, \"m3u8\") {\n\t\t\tcontinue\n\t\t}\n\n\t\turl := strings.Split(video, \"\\n\")[1]\n\t\tquality := strings.Split(strings.Split(video, \"RESOLUTION=\")[1], \",\")[0]\n\t\tquality = strings.Split(quality, \"x\")[1]\n\n\t\tret = append(ret, &hibikeonlinestream.VideoSource{\n\t\t\tURL: url,\n\t\t\tQuality: quality + \"p\",\n\t\t\tType: hibikeonlinestream.VideoSourceM3U8,\n\t\t})\n\t}\n\n\tret = append(ret, &hibikeonlinestream.VideoSource{\n\t\tURL: streamData[\"file\"].(string),\n\t\tQuality: \"auto\",\n\t\tType: map[bool]hibikeonlinestream.VideoSourceType{true: hibikeonlinestream.VideoSourceM3U8, false: hibikeonlinestream.VideoSourceMP4}[strings.Contains(streamData[\"file\"].(string), \".m3u8\")],\n\t})\n\n\treturn ret, nil\n}\n" }