Newer
Older
anime-fetcher / main.go
package main

import (
	"encoding/json"
	"errors"
	"fmt"
	"github.com/PuerkitoBio/goquery"
	"html"
	"io"
	"log"
	"net/http"
	"net/url"
	"os"
	"os/exec"
	"strings"
	"time"
)

func main() {
	seasonID, err := extractSeasonID("https://aniwaves.ru/watch/re-zero-kara-hajimeru-isekai-seikatsu-4th-season-82570/ep-1")
	if err != nil {
		panic(err)
	}

	episodeCount, err := getEpisodeList(seasonID)
	if err != nil {
		panic(err)
	}
	for i := 1; i < episodeCount+1; i++ {
		sourceID, err := getAniwaveSourceID(seasonID, i)
		if err != nil {
			panic(err)
		}

		sourceUrl, err := getAniwavesSource(sourceID)
		if err != nil {
			panic(err)
		}

		playEchoVideoSourceeID, err := extractSourceID(sourceUrl)
		if err != nil {
			panic(err)
		}

		playEchoVideoSourceUrl, err := getPlayEchoVideoSource(playEchoVideoSourceeID)
		if err != nil {
			panic(err)
		}

		name := fmt.Sprintf("%d", i) + ".%(ext)s"

		cmd := exec.Command("./yt-dlp_linux", "-o", name, "--referer", "https://play.echovideo.ru/", playEchoVideoSourceUrl)

		// Redirect binary I/O directly to the terminal
		cmd.Stdout = os.Stdout
		cmd.Stderr = os.Stderr
		cmd.Stdin = os.Stdin

		// Run blocks until the command completes
		if err := cmd.Run(); err != nil {
			log.Fatalf("Command error: %v", err)
		}
	}
}

func extractSeasonID(u string) (string, error) {
	parsed, err := url.Parse(u)
	if err != nil {
		return "", nil
	}

	path := parsed.Path

	resource := strings.Split(path, "/")

	seasonID := resource[2][strings.LastIndex(resource[2], "-")+1:]

	return seasonID, nil
}

type GetEpisodeListResponse struct {
	Status int    `json:"status"`
	Result string `json:"result"`
}

func getEpisodeList(seasonID string) (int, error) {
	client := &http.Client{Timeout: 10 * time.Second}
	req, err := http.NewRequest("GET", fmt.Sprintf("https://aniwaves.ru/ajax/episode/list/%s?vrf=", seasonID), nil)
	if err != nil {
		return 0, err
	}

	req.Header.Add("User-Agent", "Mozilla/5.0 (X11; Linux x86_64; rv:151.0) Gecko/20100101 Firefox/151.0")

	response, err := client.Do(req)
	if err != nil {
		return 0, err
	}

	defer response.Body.Close() // Essential: Close the body
	body, err := io.ReadAll(response.Body)
	if err != nil {
		return 0, err
	}

	lsr := GetEpisodeListResponse{}
	err = json.Unmarshal(body, &lsr)
	if err != nil {
		return 0, err
	}

	clean := html.UnescapeString(lsr.Result)

	wrapped := "<html><body>" + clean + "</body></html>"

	doc, err := goquery.NewDocumentFromReader(strings.NewReader(wrapped))
	if err != nil {
		return 0, err
	}

	count := doc.Find("ul[class='ep-range'] li").Length()

	return count, nil
}

type ListServersResponse struct {
	Status int    `json:"status"`
	Result string `json:"result"`
}

func getAniwaveSourceID(seasonID string, episode int) (string, error) {
	client := &http.Client{Timeout: 10 * time.Second}
	req, err := http.NewRequest("GET", fmt.Sprintf("https://aniwaves.ru/ajax/server/list?servers=%s&eps=%d", seasonID, episode), nil)
	if err != nil {
		return "", err
	}

	req.Header.Add("User-Agent", "Mozilla/5.0 (X11; Linux x86_64; rv:151.0) Gecko/20100101 Firefox/151.0")

	response, err := client.Do(req)
	if err != nil {
		return "", err
	}

	defer response.Body.Close() // Essential: Close the body
	body, err := io.ReadAll(response.Body)
	if err != nil {
		return "", err
	}

	lsr := ListServersResponse{}
	err = json.Unmarshal(body, &lsr)
	if err != nil {
		return "", err
	}

	clean := html.UnescapeString(lsr.Result)

	wrapped := "<html><body>" + clean + "</body></html>"

	doc, err := goquery.NewDocumentFromReader(strings.NewReader(wrapped))
	if err != nil {
		return "", err
	}

	id, exist := doc.Find("div.type[data-type='dub'] ul li[data-sv-id='4']").Attr("data-link-id")
	if !exist {
		return "", errors.New("failed to find")
	}
	fmt.Println(id)

	return id, nil
}

type SourceResponse struct {
	Status int `json:"status"`
	Result struct {
		Url      string `json:"url"`
		Server   int    `json:"server"`
		SkipData struct {
			Intro []int `json:"intro"`
			Outro []int `json:"outro"`
		} `json:"skip_data"`
		Sources   []interface{} `json:"sources"`
		Tracks    []interface{} `json:"tracks"`
		HtmlGuide string        `json:"htmlGuide"`
	} `json:"result"`
}

func getAniwavesSource(sourceID string) (string, error) {
	url := fmt.Sprintf("https://aniwaves.ru/ajax/sources?id=%s&asi=0&autoPlay=0", sourceID)

	client := &http.Client{Timeout: 10 * time.Second}
	req, err := http.NewRequest("GET", url, nil)
	if err != nil {
		return "", err
	}

	//GET /ajax/sources?id=UWxwb05ERkJXU1pUV1ZYT1k1b0FBQXQvLzl4TndCYk85aUtBc2tJeUpJd3d5Yzd3QzBCQzBnaWdVRWdIM2tzb0FnYUFvQUIwT0FBQUFHZ0FBQUFCazB4QWVtVFU5STlKNm1neWVvMEdtUU5BMDBCb0JvTkdnd2pSb1pQS0F5QTBCaHdCQU1NeUFja2pOTkxNcHRicTdmMkp4NFl4UDNFQXRKeUcxc3E0clpNTUl2bmhBdlphVzNJRDA5QTIvT2pqMTdBSmUySzkxVTBrUkVXYURKSmJjb1lXUWtYWUEzQXA0V1VRWFNHSWhoSno4ZnMzb3BTc1o3dnhFMjM2eit1ZnhkeVJUaFFrRlhPWTVvQT0=&asi=0&autoPlay=0 HTTP/2
	//Host: aniwaves.ru
	//User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:151.0) Gecko/20100101 Firefox/151.0
	//Accept: application/json, text/javascript, */*; q=0.01
	//Accept-Language: en-US,en;q=0.9
	//Accept-Encoding: gzip, deflate, br, zstd
	//Referer: https://aniwaves.ru/watch/re-zero-kara-hajimeru-isekai-seikatsu-4th-season-82570/ep-1
	//X-Requested-With: XMLHttpRequest
	//Sec-Fetch-Dest: empty
	//Sec-Fetch-Mode: cors
	//Sec-Fetch-Site: same-origin
	//Connection: keep-alive
	//Cookie: cf_clearance=E7EaYC.6t5eW.AiGWFA4dGhrVUHTO7X8IiaRkfJTAco-1780105219-1.2.1.1-MJ0WuQfXydhynpkJCtY1Q719ENTQz4VLOfYm3pIdw7pfD41mgVQaml14Ca3gIw7pb.YmpANb8l5LPr9Boav4XmsH0pr0Ip58lVK5hkowCFzvMSFqJDdu_a43B9Km.FA5Ql3uPbXZeNU3rSlJuJ1wBDvKhhVFo4yIaxaWoIcfsqwNdmwuz81b_yWoIge0K0ciDAVKcwV.8uv9XfHP1T4Rrs_OKdKFRZ6cp0TBYjiLOe4NwFbWBviDIxsR8tDEhxKTof0UEVq9R0mBosRPelxr3YxIxH5QyFrCKdpghW.kDOeLXGfP13cYVkwZFwT0jXVhkLZvCkHEgpEZ9stzMERj0Y2P2xUx9WMKJRQaRyPMJWKUCn9FtZSvHrvuW23TaWOx9ll00D_ZPzmTHdtESQrc8MTgZ5wBDvPaXbyH1ksv2RA; prefered_server_id=4; prefered_server_type=dub; _ga_2BSQBMWMM9=GS2.1.s1780942892$o1$g1$t1780942910$j42$l0$h0; _ga=GA1.1.764185913.1780942893
	//Priority: u=0
	//Pragma: no-cache
	//Cache-Control: no-cache
	//TE: trailers

	req.Header.Add("Referer", "https://aniwaves.ru/watch/re-zero-kara-hajimeru-isekai-seikatsu-4th-season-82570/ep-1")
	req.Header.Add("User-Agent", "Mozilla/5.0 (X11; Linux x86_64; rv:151.0) Gecko/20100101 Firefox/151.0")

	resp, err := client.Do(req)
	if err != nil {
		return "", err
	}
	defer resp.Body.Close() // Essential: Close the body
	body, err := io.ReadAll(resp.Body)
	if err != nil {
		return "", err
	}

	sr := SourceResponse{}
	err = json.Unmarshal(body, &sr)
	if err != nil {
		return "", err
	}

	return sr.Result.Url, nil
}

func extractSourceID(u string) (string, error) {
	parsedURL, err := url.Parse(u)
	if err != nil {
		return "", err
	}
	path := parsedURL.Path
	id := path[strings.LastIndex(path, "/")+1:]
	return id, nil
}

type PlayEchoVideoSourceResponse struct {
	Sources string `json:"sources"`
	Intro   struct {
		Start int `json:"start"`
		End   int `json:"end"`
	} `json:"intro"`
	Outro struct {
		Start int `json:"start"`
		End   int `json:"end"`
	} `json:"outro"`
}

func getPlayEchoVideoSource(id string) (string, error) {
	url := fmt.Sprintf("https://play.echovideo.ru/embed-1/getSources?id=%s", id)

	//GET /embed-1/getSources?id=KPpOQdEl-krSDv4eXEEESOGQxIyn39hxhDVL-a2XPou3CoyWNh4VQmK3H0rMoVhfkd0GovoHTaEZ6Sm5lyMSpt5yt-Dn2lZCmvH1yzTQSxaKaYl25DzAviI4tquisv4m4sdprfru0X7FRb516Rwgo1lBQipOxMdBWJAVmU3pVFbH-FCBQorpYyUMwrsEZeectZ9x8EhnJ_wYs7Sd2a1lnficzNBqKBC3zWOvZLXKnwY HTTP/2
	//Host: play.echovideo.ru
	//User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:151.0) Gecko/20100101 Firefox/151.0
	//Accept: */*
	//Accept-Language: en-US,en;q=0.9
	//Accept-Encoding: gzip, deflate, br, zstd
	//Referer: https://play.echovideo.ru/embed-1/KPpOQdEl-krSDv4eXEEESOGQxIyn39hxhDVL-a2XPou3CoyWNh4VQmK3H0rMoVhfkd0GovoHTaEZ6Sm5lyMSpt5yt-Dn2lZCmvH1yzTQSxaKaYl25DzAviI4tquisv4m4sdprfru0X7FRb516Rwgo1lBQipOxMdBWJAVmU3pVFbH-FCBQorpYyUMwrsEZeectZ9x8EhnJ_wYs7Sd2a1lnficzNBqKBC3zWOvZLXKnwY?v=1&asi=0&autoPlay=0&ao=0&autostart=true
	//Connection: keep-alive
	//Sec-Fetch-Dest: empty
	//Sec-Fetch-Mode: cors
	//Sec-Fetch-Site: same-origin
	//Priority: u=4
	//Pragma: no-cache
	//Cache-Control: no-cache

	client := &http.Client{Timeout: 10 * time.Second}
	req, err := http.NewRequest("GET", url, nil)
	if err != nil {
		return "", err
	}

	req.Header.Add("Referer", fmt.Sprintf("https://play.echovideo.ru/embed-1/%s?v=1&asi=0&autoPlay=0&ao=0&autostart=true", id))

	resp, err := client.Do(req)
	if err != nil {
		return "", err
	}
	defer resp.Body.Close() // Essential: Close the body
	body, err := io.ReadAll(resp.Body)
	if err != nil {
		return "", err
	}

	sr := PlayEchoVideoSourceResponse{}
	err = json.Unmarshal(body, &sr)
	if err != nil {
		return "", err
	}

	return sr.Sources, nil
}