diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index ac49585..fab6ee6 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -26,4 +26,4 @@ jobs: run: go get -v -t -d ./... - name: Test - run: go test -v ./*/ + run: go test -v ./... diff --git a/Network/checkNetwok_test.go b/Network/checkNetwok_test.go deleted file mode 100644 index 313466d..0000000 --- a/Network/checkNetwok_test.go +++ /dev/null @@ -1,41 +0,0 @@ -package network - -import ( - "strconv" - "testing" - - "github.com/jarcoal/httpmock" -) - -func TestHasNetwork(t *testing.T) { - httpmock.Activate() - defer httpmock.DeactivateAndReset() - - tests := []struct { - name string - mockResp httpmock.Responder - want bool - }{ - {"Test 1: Successful request", httpmock.NewStringResponder(200, ""), true}, - {"Test 2: 404 error", httpmock.NewStringResponder(404, ""), false}, - } - - for i := 3; i <= 20; i++ { - testName := "Test " + strconv.Itoa(i) + ": Random scenario" - tests = append(tests, struct { - name string - mockResp httpmock.Responder - want bool - }{testName, httpmock.NewStringResponder(200, ""), true}) - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - httpmock.RegisterResponder("GET", "https://www.google.com", tt.mockResp) - - if got := HasNetwork(); got != tt.want { - t.Errorf("HasNetwork() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/Network/checkNetwork.go b/Network/checkNetwork.go deleted file mode 100644 index be169d2..0000000 --- a/Network/checkNetwork.go +++ /dev/null @@ -1,25 +0,0 @@ -package network - -import ( - "net/http" - "time" -) - -// HasNetwork checks if the system has network connectivity. -// It returns true if network is available, false otherwise. -func HasNetwork() bool { - const url = "https://www.google.com" - const timeout = 3 * time.Second - - client := &http.Client{ - Timeout: timeout, - } - - resp, err := client.Get(url) - if err != nil || resp.StatusCode != http.StatusOK { - return false - } - defer resp.Body.Close() - - return true -} diff --git a/Network/checkWebsiteConnection.go b/Network/checkWebsiteConnection.go deleted file mode 100644 index 533ffb6..0000000 --- a/Network/checkWebsiteConnection.go +++ /dev/null @@ -1,24 +0,0 @@ -package network - -import ( - "log" - "net/http" - "time" -) - -const Timeout = 3 * time.Second - -func CheckWebsite(url string) bool { - client := http.Client{ - Timeout: Timeout, - } - - resp, err := client.Get(url) - if err != nil { - log.Printf("Failed to fetch the website %s: %v", url, err) - return false - } - defer resp.Body.Close() - - return true -} diff --git a/Network/checkWebsiteConnection_test.go b/Network/checkWebsiteConnection_test.go deleted file mode 100644 index 2ecea3c..0000000 --- a/Network/checkWebsiteConnection_test.go +++ /dev/null @@ -1,32 +0,0 @@ -package network - -import ( - "net/http" - "net/http/httptest" - "testing" -) - -func TestCheckWebsite(t *testing.T) { - server := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { - rw.WriteHeader(http.StatusOK) - })) - - defer server.Close() - - tests := []struct { - name string - url string - want bool - }{ - {"Test 1: Valid URL", server.URL, true}, - {"Test 2: Invalid URL", "invalid_url", false}, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := CheckWebsite(tt.url); got != tt.want { - t.Errorf("CheckWebsite() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/Network/downloadAnime.go b/Network/downloadAnime.go deleted file mode 100644 index 6702154..0000000 --- a/Network/downloadAnime.go +++ /dev/null @@ -1,36 +0,0 @@ -package network - -import ( - message "animatic-v2/Message" - "animatic-v2/Structures" - utils "animatic-v2/Utils" - "path/filepath" -) - -type Downloader struct { - DestPath string - Anime Structures.Anime - EpList []Structures.Episode -} - -func (d *Downloader) DownloadAll() { - videoExtractor := VideoExtractor{} - - for i := range d.EpList { - episode := utils.EpisodeFormatter(i + 1) - newDestPath := filepath.Join(d.DestPath, episode) - - if !CheckWebsite(d.EpList[i].URL) { - message.ErrorMessage("Can't access episode url") - return - } - - videoExtractor.AnimeURL = d.EpList[i].URL - videoUrl := videoExtractor.ExtractVideoURL() - - videoSrc := videoExtractor.ExtractActualVideoLabel(videoUrl) - - videoDownloader := VideoDownloader{DestPath: newDestPath, URL: videoSrc} - videoDownloader.Download() - } -} diff --git a/Network/downloadAnime_test.go b/Network/downloadAnime_test.go deleted file mode 100644 index ae3cdf0..0000000 --- a/Network/downloadAnime_test.go +++ /dev/null @@ -1,42 +0,0 @@ -package network - -import ( - "animatic-v2/Structures" - "testing" -) - -type TestDownloader interface { - CheckWebsite(url string) bool - ExtractVideoUrl(url string) string - ExtractActualVideoLabel(url string) string - DownloadVideo(destPath string, videoSrc string) -} - -type TestMockDownloader struct{} - -func (d MockDownloader) CheckWebsite(url string) bool { - return true -} - -func (d MockDownloader) ExtractVideoUrl(url string) string { - return "http://testvideo.com" -} - -func (d MockDownloader) ExtractActualVideoLabel(url string) string { - return "Test Video" -} - -func (d MockDownloader) DownloadVideo(destPath string, videoSrc string) { -} - -func MockDownloadAll(d MockDownloader, destPath string, anime Structures.Anime, epList []Structures.Episode) { -} - -func TestDownloadAll(t *testing.T) { - d := MockDownloader{} - destPath := "/test/path" - anime := Structures.Anime{Name: "Test Anime"} - epList := []Structures.Episode{{Number: "1", URL: "http://testepisode.com"}} - - MockDownloadAll(d, destPath, anime, epList) -} diff --git a/Network/downloadVideo.go b/Network/downloadVideo.go deleted file mode 100644 index 3017127..0000000 --- a/Network/downloadVideo.go +++ /dev/null @@ -1,40 +0,0 @@ -package network - -import ( - "fmt" - "time" - - "github.com/cavaliergopher/grab/v3" - "gopkg.in/cheggaaa/pb.v1" -) - -type VideoDownloader struct { - DestPath string - URL string -} - -func (vd *VideoDownloader) Download() { - client := grab.NewClient() - req, _ := grab.NewRequest(vd.DestPath+".mp4", vd.URL) - req.HTTPRequest.Header.Set("Connection", "keep-alive") - - resp := client.Do(req) - - maxSizeInMB := int(resp.Size() / (1024 * 1024)) - minSizeInMB := 10 - - if maxSizeInMB < minSizeInMB { - maxSizeInMB = minSizeInMB - } - - bar := pb.StartNew(maxSizeInMB) - - fmt.Printf("Episode URL: %s \n", vd.URL) - for !resp.IsComplete() { - completedInMB := int(resp.Progress() * float64(maxSizeInMB)) - bar.Set(completedInMB) - time.Sleep(time.Millisecond * 500) - } - - bar.Finish() -} diff --git a/Network/downloadVideo_test.go b/Network/downloadVideo_test.go deleted file mode 100644 index b41a481..0000000 --- a/Network/downloadVideo_test.go +++ /dev/null @@ -1,47 +0,0 @@ -package network - -import ( - "net/http" - "testing" - - "github.com/cavaliergopher/grab/v3" -) - -type TDownloader interface { - NewRequest(outputPath string, url string) error - Do(request *grab.Request) *grab.Response -} - -type Messenger interface { - ErrorMessage(msg string) - SuccessMessage(msg string) -} - -type MockDownloader struct{} -type MockMessenger struct{} - -func (d MockDownloader) NewRequest(outputPath string, url string) error { - return nil -} - -func (d MockDownloader) Do(request *grab.Request) *grab.Response { - return &grab.Response{HTTPResponse: &http.Response{StatusCode: 200}} -} - -func (m MockMessenger) ErrorMessage(msg string) { -} - -func (m MockMessenger) SuccessMessage(msg string) { -} - -func tdownloadVideo(d MockDownloader, m Messenger, destPath string, url string) { -} - -func TestDownloadVideo(t *testing.T) { - d := MockDownloader{} - m := MockMessenger{} - destPath := "/test/path" - url := "http://testvideo.com" - - tdownloadVideo(d, m, destPath, url) -} diff --git a/Network/extractVideo.go b/Network/extractVideo.go deleted file mode 100644 index 0a8da05..0000000 --- a/Network/extractVideo.go +++ /dev/null @@ -1,84 +0,0 @@ -package network - -import ( - message "animatic-v2/Message" - "animatic-v2/Structures" - "encoding/json" - "io" - "net/http" - - "github.com/PuerkitoBio/goquery" -) - -type VideoExtractor struct { - AnimeURL string -} - -func (ve *VideoExtractor) ExtractVideoURL() string { - - if CheckWebsite(ve.AnimeURL) == false { - message.ErrorMessage("Can't access video website") - return "" - } - - response, err := http.Get(ve.AnimeURL) - - if err != nil { - message.ErrorMessage(err.Error()) - return "" - } - - doc, _ := goquery.NewDocumentFromReader(response.Body) - - videoElements := doc.Find("video") - - if videoElements.Length() > 0 { - oldDataVideo, _ := videoElements.Attr("data-video-src") - return oldDataVideo - } else { - videoElements = doc.Find("div") - if videoElements.Length() > 0 { - oldDataVideo, _ := videoElements.Attr("data-video-src") - return oldDataVideo - } - } - - message.ErrorMessage("Can't find video element in html") - return "" -} - -func (ve *VideoExtractor) ExtractActualVideoLabel(videoSrc string) string { - var videoResponse Structures.VideoResponse - response, err := http.Get(videoSrc) - - if err != nil { - message.ErrorMessage(err.Error()) - } - - defer response.Body.Close() - - if response.StatusCode != http.StatusOK { - message.ErrorMessage("Status Code is different from StatusOK") - } - - body, err := io.ReadAll(response.Body) - - if err != nil { - message.ErrorMessage(err.Error()) - return "" - } - - err = json.Unmarshal(body, &videoResponse) - - if err != nil { - message.ErrorMessage(err.Error()) - return "" - } - - if len(videoResponse.Data) == 0 { - message.ErrorMessage("No Video Found") - return "" - } - - return videoResponse.Data[len(videoResponse.Data)-1].Src -} diff --git a/Network/extractVideo_test.go b/Network/extractVideo_test.go deleted file mode 100644 index a4a0be9..0000000 --- a/Network/extractVideo_test.go +++ /dev/null @@ -1,47 +0,0 @@ -package network - -import ( - "net/http" - "testing" - - "github.com/cavaliergopher/grab/v3" -) - -type mtDownloader interface { - NewRequest(outputPath string, url string) error - Do(request *grab.Request) *grab.Response -} - -type mtMessenger interface { - ErrorMessage(msg string) - SuccessMessage(msg string) -} - -type mtMockDownloader struct{} -type mtMockMessenger struct{} - -func (d MockDownloader) mtNewRequest(outputPath string, url string) error { - return nil -} - -func (d MockDownloader) mtDo(request *grab.Request) *grab.Response { - return &grab.Response{HTTPResponse: &http.Response{StatusCode: 200}} -} - -func (m MockMessenger) mtErrorMessage(msg string) { -} - -func (m MockMessenger) mtSuccessMessage(msg string) { -} - -func mtdownloadVideo(d Downloader, m Messenger, destPath string, url string) { -} - -func MTestDownloadVideo(t *testing.T) { - d := MockDownloader{} - m := MockMessenger{} - destPath := "/test/path" - url := "http://testvideo.com" - - tdownloadVideo(d, m, destPath, url) -} diff --git a/Network/getAnimeEpisode_test.go b/Network/getAnimeEpisode_test.go deleted file mode 100644 index 179c481..0000000 --- a/Network/getAnimeEpisode_test.go +++ /dev/null @@ -1,53 +0,0 @@ -package network - -import ( - "io" - "net/http" - "strings" - "testing" -) - -type mttHTTPClient interface { - Get(url string) (*http.Response, error) -} - -type mttMessenger interface { - ErrorMessage(msg string) -} - -type mttMockHTTPClient struct{} -type mttMockMessenger struct{} - -func (c mttMockHTTPClient) Get(url string) (*http.Response, error) { - return &http.Response{ - StatusCode: http.StatusOK, - Body: io.NopCloser(strings.NewReader("OK")), - }, nil -} - -func (m MockMessenger) mttErrorMessage(msg string) { -} - -func mtextractVideoUrl(c mttMockHTTPClient, m mttMockMessenger, animeUrl string) string { - return "video extracted" -} - -func mtextractActualVideoLabel(c mttMockHTTPClient, m mttMockMessenger, videoSrc string) string { - return "video label" -} - -func TestExtractVideoUrl(t *testing.T) { - c := mttMockHTTPClient{} - m := mttMockMessenger{} - animeUrl := "http://testanime.com" - - mtextractVideoUrl(c, m, animeUrl) -} - -func TestExtractActualVideoLabel(t *testing.T) { - c := mttMockHTTPClient{} - m := mttMockMessenger{} - videoSrc := "http://testvideo.com" - - mtextractActualVideoLabel(c, m, videoSrc) -} diff --git a/Network/searchAnimeinAnimeFire.go b/Network/searchAnimeinAnimeFire.go deleted file mode 100644 index 2b4a064..0000000 --- a/Network/searchAnimeinAnimeFire.go +++ /dev/null @@ -1,87 +0,0 @@ -package network - -import ( - message "animatic-v2/Message" - structure "animatic-v2/Structures" - tui "animatic-v2/Tui" - utils "animatic-v2/Utils" - - "fmt" - "net/http" - "os" - "sort" - "strings" - - "github.com/PuerkitoBio/goquery" -) - -type AnimeSearcher struct { - AnimeName string -} - -func (as *AnimeSearcher) SearchAnimeInAnimeFire() (string, string) { - animeName := utils.NameAnimeTreating(as.AnimeName) - currentPageUrl := fmt.Sprintf("%s/pesquisar/%s", "https://www.animefire.net", animeName) - - if CheckWebsite(currentPageUrl) == false { - message.ErrorMessage("The site is unavailable") - os.Exit(1) - } - - for { - response, err := http.Get(currentPageUrl) - - if err != nil { - message.ErrorMessage(err.Error()) - os.Exit(1) - } - - defer response.Body.Close() - - doc, err := goquery.NewDocumentFromReader(response.Body) - - if err != nil { - message.ErrorMessage(err.Error()) - os.Exit(1) - } - - animes := make([]structure.Anime, 0) - - doc.Find(".row.ml-1.mr-1 a").Each(func(i int, s *goquery.Selection) { - anime := structure.Anime{ - Name: strings.TrimSpace(s.Text()), - Url: s.AttrOr("href", ""), - } - animeName = strings.TrimSpace(s.Text()) - - animes = append(animes, anime) - }) - - // Sort the animes slice to return dub episodes first and leg episodes last - sort.Slice(animes, func(i, j int) bool { - return strings.Contains(animes[i].Name, "Dublado") - }) - - if len(animes) > 0 { - selectedAnimeName, selectedUrl, err := tui.SelectAnimes(animes) - if err != nil { - message.ErrorMessage(err.Error()) - } - return selectedAnimeName, selectedUrl - } - - nextPage, exists := doc.Find(".pagination .next a").Attr("href") - - if !exists || nextPage == "" { - message.ErrorMessage("No anime found with the given name") - os.Exit(1) - } - - currentPageUrl = currentPageUrl + nextPage - - if CheckWebsite(currentPageUrl) == false { - message.ErrorMessage("Can't search in next page") - os.Exit(1) - } - } -} diff --git a/Network/searchAnimeinAnimeFire_test.go b/Network/searchAnimeinAnimeFire_test.go deleted file mode 100644 index d8afc4b..0000000 --- a/Network/searchAnimeinAnimeFire_test.go +++ /dev/null @@ -1,47 +0,0 @@ -package network - -import ( - "net/http" - "testing" - - "github.com/cavaliergopher/grab/v3" -) - -type mttDownloader interface { - NewRequest(outputPath string, url string) error - Do(request *grab.Request) *grab.Response -} - -type mtttMessenger interface { - ErrorMessage(msg string) - SuccessMessage(msg string) -} - -type mttMockDownloader struct{} -type mtttMockMessenger struct{} - -func (d MockDownloader) mttNewRequest(outputPath string, url string) error { - return nil -} - -func (d MockDownloader) mttDo(request *grab.Request) *grab.Response { - return &grab.Response{HTTPResponse: &http.Response{StatusCode: 200}} -} - -func (m MockMessenger) mtttErrorMessage(msg string) { -} - -func (m MockMessenger) mttSuccessMessage(msg string) { -} - -func mttdownloadVideo(d MockDownloader, m Messenger, destPath string, url string) { -} - -func mtTestDownloadVideo(t *testing.T) { - d := MockDownloader{} - m := MockMessenger{} - destPath := "/test/path" - url := "http://testvideo.com" - - mttdownloadVideo(d, m, destPath, url) -} diff --git a/README.md b/README.md index e763625..2f6df49 100644 --- a/README.md +++ b/README.md @@ -15,60 +15,41 @@ Animatic is a Go-based application designed to search and watch anime episodes f - Downloads all episodes of the selected anime. +- Use --gac to salve the content in same directory from [go-anime](https://github.com/alvarorichard/GoAnime) + - Provides error messages for various failure cases such as lack of internet connection, failure to locate the anime, and inability to access episode URLs. ->[!CAUTION] -> Doesn't make pos-install with this script (don't work) or run this script in root mode +## How Install -## Fast Installation -To fast install this project do you need run the script: "install.sh" with sudo +1. Start compiling the animatic-v2 with ```bash -bash install.sh +go build cmd/animatic-v2/main.go ``` ->[!WARNING] -> Fast install works only linux - -## How to use +2. Move the animatic-v2 from $HOME/.local/bin/ ->[!CAUTION] -> Do you need go 1.21.5 installed in your system - -Create a directory chromeMedia in root: ```bash -sudo mkdir /chromeMedia +mv main $HOME/.local/bin/animatic-v2 ``` -If chromeMedia is a root owned directory, you need change owner to your user -```bash -sudo chown $USER /chromeMedia -``` +3. Put the .local/bin in path (adding this part in your shell dotfile) -Compile the project in your enviroment: ```bash -go build +export PATH="$HOME/.local/bin/:$PATH" ``` -Setting Path to new local: -```bash -export PATH=$PATH:~/.local/bin -``` +> [!TIP] +> The animatic-v2 pass by refactoring and doesn't has a default directory from download the files +> We can use go-anime base to salve content and mpv to reproduce automatically alread downloaded files +> Is recomended adding this alias in your default shell dotfile -Move the project from some directory in your path ```bash -mv animatic-v2 ~/.local/bin/animatic +alias animatic="animatic-v2 --gac --debug" ``` -## Running: - -Run in your shell: - -```bash -animatic -``` - -The code open the prompt-ui label to you write the anime name to be downloaded. +- The flag `--gac` uses the go-anime default directory from save the animes +- The flag `--debug` show the information about the download process ## Contributing @@ -80,5 +61,7 @@ Contributions are what make the open-source community such an amazing place to l 4. Push to the Branch 5. Open a Pull Request ->[!WARNING] -> We have a bug in bubbletea with lipgloss, the terminal cursor is hiding and doesn't appears anymore, if you have some solution and want contributing is appreciate \ No newline at end of file +### TODO + +- [ ] Adding a fallback using another website +- [ ] Make blogger download videos working properly diff --git a/Structures/anime_test.go b/Structures/anime_test.go deleted file mode 100644 index 856680f..0000000 --- a/Structures/anime_test.go +++ /dev/null @@ -1,35 +0,0 @@ -package Structures - -import ( - "testing" -) - -func TestAnime(t *testing.T) { - anime := Anime{Name: "Test Anime", Url: "http://testanime.com"} - - if anime.Name != "Test Anime" { - t.Errorf("Expected anime name to be 'Test Anime', got '%s'", anime.Name) - } - - if anime.Url != "http://testanime.com" { - t.Errorf("Expected anime URL to be 'http://testanime.com', got '%s'", anime.Url) - } - - if anime.TotalEpisodes() != 0 { - t.Errorf("Expected total episodes to be 0, got '%d'", anime.TotalEpisodes()) - } - - anime.AddEpisode(Episode{Number: "1", URL: "http://testanime.com/1"}) - - if anime.TotalEpisodes() != 1 { - t.Errorf("Expected total episodes to be 1 after adding an episode, got '%d'", anime.TotalEpisodes()) - } - - if anime.Episodes[0].Number != "1" { - t.Errorf("Expected first episode number to be '1', got '%s'", anime.Episodes[0].Number) - } - - if anime.Episodes[0].URL != "http://testanime.com/1" { - t.Errorf("Expected first episode URL to be 'http://testanime.com/1', got '%s'", anime.Episodes[0].URL) - } -} diff --git a/Structures/link_test.go b/Structures/link_test.go deleted file mode 100644 index f815c90..0000000 --- a/Structures/link_test.go +++ /dev/null @@ -1,47 +0,0 @@ -package Structures - -import ( - "encoding/json" - "testing" -) - -func TestVideoResponse(t *testing.T) { - jsonStr := `{ - "data": [ - { - "src": "http://testvideo.com/1", - "label": "Test Video 1" - }, - { - "src": "http://testvideo.com/2", - "label": "Test Video 2" - } - ] - }` - - var resp VideoResponse - err := json.Unmarshal([]byte(jsonStr), &resp) - if err != nil { - t.Errorf("Failed to unmarshal JSON: %v", err) - } - - if len(resp.Data) != 2 { - t.Errorf("Expected 2 videos, got %d", len(resp.Data)) - } - - if resp.Data[0].Src != "http://testvideo.com/1" { - t.Errorf("Expected first video src to be 'http://testvideo.com/1', got '%s'", resp.Data[0].Src) - } - - if resp.Data[0].Label != "Test Video 1" { - t.Errorf("Expected first video label to be 'Test Video 1', got '%s'", resp.Data[0].Label) - } - - if resp.Data[1].Src != "http://testvideo.com/2" { - t.Errorf("Expected second video src to be 'http://testvideo.com/2', got '%s'", resp.Data[1].Src) - } - - if resp.Data[1].Label != "Test Video 2" { - t.Errorf("Expected second video label to be 'Test Video 2', got '%s'", resp.Data[1].Label) - } -} diff --git a/Tui/searchLabel_test.go b/Tui/searchLabel_test.go deleted file mode 100644 index da6002b..0000000 --- a/Tui/searchLabel_test.go +++ /dev/null @@ -1,57 +0,0 @@ -package tui - -import ( - "testing" - - tea "github.com/charmbracelet/bubbletea" - "github.com/charmbracelet/lipgloss" -) - -func TestSearchLabelModelUpdate(t *testing.T) { - m := &searchLabelModel{} - msg := tea.KeyMsg{Type: tea.KeyEnter} - model, _ := m.Update(msg) - if model.(*searchLabelModel).done != true { - t.Errorf("Expected done to be true, got %v", model.(*searchLabelModel).done) - } -} - -func TestSearchLabelModelView(t *testing.T) { - m := &searchLabelModel{choice: "Naruto"} - expected := lipgloss.NewStyle(). - Foreground(lipgloss.Color("#00FF00")). - Bold(true). - Border(lipgloss.RoundedBorder()). - Padding(1). - Width(50). - Render("Enter the name of the anime to be downloaded: Naruto") - if m.View() != expected { - t.Errorf("Expected %v, got %v", expected, m.View()) - } -} - -func TestUpdateWithBackspace(t *testing.T) { - m := &searchLabelModel{choice: "Naruto"} - msg := tea.KeyMsg{Type: tea.KeyBackspace} - model, _ := m.Update(msg) - if model.(*searchLabelModel).choice != "Narut" { - t.Errorf("Expected choice to be 'Narut', got %v", model.(*searchLabelModel).choice) - } -} - -func TestUpdateWithCharacter(t *testing.T) { - m := &searchLabelModel{choice: "Naruto"} - msg := tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune("s")} - model, _ := m.Update(msg) - if model.(*searchLabelModel).choice != "Narutos" { - t.Errorf("Expected choice to be 'Narutos', got %v", model.(*searchLabelModel).choice) - } -} - -func TestInit(t *testing.T) { - m := &searchLabelModel{} - cmd := m.Init() - if cmd == nil { - t.Errorf("Expected cmd to not be nil") - } -} diff --git a/Tui/searchlabel.go b/Tui/searchlabel.go deleted file mode 100644 index 24d312a..0000000 --- a/Tui/searchlabel.go +++ /dev/null @@ -1,72 +0,0 @@ -package tui - -import ( - message "animatic-v2/Message" - "time" - - tea "github.com/charmbracelet/bubbletea" - "github.com/charmbracelet/lipgloss" -) - -type searchLabelModel struct { - choice string - err error - done bool -} - -func (m *searchLabelModel) Init() tea.Cmd { - return tea.Batch(tea.ClearScreen) -} - -func (m *searchLabelModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { - switch msg := msg.(type) { - case tea.KeyMsg: - switch msg.Type { - case tea.KeyEnter: - m.done = true - return m, tea.Quit - case tea.KeyBackspace: - if len(m.choice) > 0 { - m.choice = m.choice[:len(m.choice)-1] - } - default: - if msg.String() != "" { - m.choice += msg.String() - } - } - } - return m, nil -} - -func (m *searchLabelModel) View() string { - promptStyle := lipgloss.NewStyle(). - Foreground(lipgloss.Color("#00FF00")). - Bold(true). - Border(lipgloss.RoundedBorder()). - Padding(1). - Width(50) - return promptStyle.Render("Enter the name of the anime to be downloaded: " + m.choice) -} - -func GetAnimeName() string { - m := &searchLabelModel{} - p := tea.NewProgram(m) - errChan := make(chan error) - go func() { - errChan <- p.Start() - }() - select { - case err := <-errChan: - if err != nil { - message.ErrorMessage(err.Error()) - return "" - } - case <-time.After(time.Second * 10): - p.Send(tea.KeyMsg{Type: tea.KeyCtrlC}) - } - if m.done { - return m.choice - } - message.ErrorMessage("Could not assert model to searchLabelModel") - return "" -} diff --git a/Tui/selectAnime.go b/Tui/selectAnime.go deleted file mode 100644 index a217746..0000000 --- a/Tui/selectAnime.go +++ /dev/null @@ -1,96 +0,0 @@ -package tui - -import ( - "animatic-v2/Structures" - "errors" - - "github.com/charmbracelet/bubbles/table" - tea "github.com/charmbracelet/bubbletea" - "github.com/charmbracelet/lipgloss" -) - -const ( - AnimeNameColumnTitle = "Anime Name" - AnimeURLColumnTitle = "Anime URL" -) - -type SelectAnimeModel struct { - table table.Model - selectedURL string - selectedAnimeName string -} - -func (m *SelectAnimeModel) Init() tea.Cmd { - return nil -} - -func (m *SelectAnimeModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { - var cmd tea.Cmd - - switch msg := msg.(type) { - case tea.KeyMsg: - switch msg.String() { - case "esc": - tea.Quit() - case "q", "ctrl+c": - return m, tea.Quit - case "enter": - m.selectedAnimeName = m.table.SelectedRow()[0] - m.selectedURL = m.table.SelectedRow()[1] - return m, tea.Quit - } - } - m.table, cmd = m.table.Update(msg) - return m, cmd -} - -func (m *SelectAnimeModel) View() string { - return m.table.View() -} - -func SelectAnimes(animes []Structures.Anime) (string, string, error) { - if len(animes) == 0 { - return "", "", errors.New("Anime List is empty") - } - - columns := []table.Column{ - {Title: AnimeNameColumnTitle, Width: 50}, - {Title: AnimeURLColumnTitle, Width: 180}, - } - - rows := make([]table.Row, 0, len(animes)) - - for _, anime := range animes { - rows = append(rows, table.Row{anime.Name, anime.Url}) - } - - t := table.New( - table.WithColumns(columns), - table.WithRows(rows), - table.WithFocused(true), - table.WithHeight(len(animes)), - ) - - s := table.DefaultStyles() - - s.Header = s.Header. - BorderStyle(lipgloss.NormalBorder()). - BorderForeground(lipgloss.Color("240")). - BorderBottom(true). - Bold(false) - - s.Selected = s.Selected. - Foreground(lipgloss.Color("229")). - Background(lipgloss.Color("57")). - Bold(false) - - t.SetStyles(s) - - m := &SelectAnimeModel{table: t} - p := tea.NewProgram(m) - if err := p.Start(); err != nil { - return "", "", err - } - - return m.selectedAnimeName, m.selectedURL, nil -} diff --git a/Tui/selectAnime_test.go b/Tui/selectAnime_test.go deleted file mode 100644 index f0dc48a..0000000 --- a/Tui/selectAnime_test.go +++ /dev/null @@ -1,52 +0,0 @@ -package tui - -import ( - structure "animatic-v2/Structures" - "testing" -) - -// MockSelectAnimes is a mock function for SelectAnimes -func MockSelectAnimes(animes []structure.Anime) int { - // Mock behavior here based on input - // For simplicity, let's just return the length of the animes slice - return len(animes) -} - -func TestSelectAnimes(t *testing.T) { - animes := []structure.Anime{ - {Name: "Anime 1"}, - {Name: "Anime 2"}, - {Name: "Anime 3"}, - {Name: "Anime 4"}, - {Name: "Anime 5"}, - {Name: "Anime 6"}, - {Name: "Anime 7"}, - {Name: "Anime 8"}, - {Name: "Anime 9"}, - {Name: "Anime 10"}, - } - - tests := []struct { - name string - want int - }{ - {"Test 1: Select first anime", 1}, - {"Test 2: Select second anime", 2}, - {"Test 3: Select third anime", 3}, - {"Test 4: Select fourth anime", 4}, - {"Test 5: Select fifth anime", 5}, - {"Test 6: Select sixth anime", 6}, - {"Test 7: Select seventh anime", 7}, - {"Test 8: Select eighth anime", 8}, - {"Test 9: Select ninth anime", 9}, - {"Test 10: Select tenth anime", 10}, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := MockSelectAnimes(animes[:tt.want]); got != tt.want { - t.Errorf("MockSelectAnimes() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/Utils/NameTreating.go b/Utils/NameTreating.go deleted file mode 100644 index 84d5c44..0000000 --- a/Utils/NameTreating.go +++ /dev/null @@ -1,9 +0,0 @@ -package utils - -import "strings" - -func NameAnimeTreating(animeName string) string { - loweredName := strings.ToLower(animeName) - spacelessName := strings.ReplaceAll(loweredName, " ", "-") - return spacelessName -} diff --git a/Utils/NameTreating_test.go b/Utils/NameTreating_test.go deleted file mode 100644 index 3813100..0000000 --- a/Utils/NameTreating_test.go +++ /dev/null @@ -1,43 +0,0 @@ -package utils - -import ( - "testing" -) - -func TestNameAnimeTreating(t *testing.T) { - tests := []struct { - name string - input string - expected string - }{ - {"Test 1", "Attack on Titan", "attack-on-titan"}, - {"Test 2", "Death Note", "death-note"}, - {"Test 3", "One Punch Man", "one-punch-man"}, - {"Test 4", "My Hero Academia", "my-hero-academia"}, - {"Test 5", "Naruto", "naruto"}, - {"Test 6", "Dragon Ball Z", "dragon-ball-z"}, - {"Test 7", "Fullmetal Alchemist", "fullmetal-alchemist"}, - {"Test 8", "Hunter x Hunter", "hunter-x-hunter"}, - {"Test 9", "Fairy Tail", "fairy-tail"}, - {"Test 10", "One Piece", "one-piece"}, - {"Test 11", "Tokyo Ghoul", "tokyo-ghoul"}, - {"Test 12", "Bleach", "bleach"}, - {"Test 13", "Cowboy Bebop", "cowboy-bebop"}, - {"Test 14", "Demon Slayer", "demon-slayer"}, - {"Test 15", "Sword Art Online", "sword-art-online"}, - {"Test 16", "Steins Gate", "steins-gate"}, - {"Test 17", "Code Geass", "code-geass"}, - {"Test 18", "Gintama", "gintama"}, - {"Test 19", "JoJo's Bizarre Adventure", "jojo's-bizarre-adventure"}, - {"Test 20", "Neon Genesis Evangelion", "neon-genesis-evangelion"}, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - result := NameAnimeTreating(tt.input) - if result != tt.expected { - t.Errorf("Expected %s but got %s", tt.expected, result) - } - }) - } -} diff --git a/Utils/episodeFormatter.go b/Utils/episodeFormatter.go deleted file mode 100644 index 4256d3e..0000000 --- a/Utils/episodeFormatter.go +++ /dev/null @@ -1,7 +0,0 @@ -package utils - -import "fmt" - -func EpisodeFormatter(ep int) string { - return fmt.Sprintf("S01E%02d", ep) -} diff --git a/Utils/episodeFormatter_test.go b/Utils/episodeFormatter_test.go deleted file mode 100644 index de72743..0000000 --- a/Utils/episodeFormatter_test.go +++ /dev/null @@ -1,32 +0,0 @@ -package utils - -import ( - "fmt" - "math/rand" - "testing" - "time" -) - -func TestEpisodeFormatter(t *testing.T) { - rand.Seed(time.Now().UnixNano()) - tests := make([]struct { - name string - input int - expected string - }, 20) - - for i := range tests { - tests[i].name = fmt.Sprintf("Test %d", i+1) - tests[i].input = rand.Intn(41) - 20 // Generates a random number between -20 and 20 - tests[i].expected = fmt.Sprintf("S01E%02d", tests[i].input) - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - result := EpisodeFormatter(tt.input) - if result != tt.expected { - t.Errorf("Expected %s but got %s", tt.expected, result) - } - }) - } -} diff --git a/Utils/splitAnimeName.go b/Utils/splitAnimeName.go deleted file mode 100644 index 398efe8..0000000 --- a/Utils/splitAnimeName.go +++ /dev/null @@ -1,10 +0,0 @@ -package utils - -import ( - "strings" -) - -func SplitAnimeName(AnimeName string) string { - parts := strings.Split(AnimeName, " ") - return parts[0] -} diff --git a/Utils/splitAnimeName_test.go b/Utils/splitAnimeName_test.go deleted file mode 100644 index abc855b..0000000 --- a/Utils/splitAnimeName_test.go +++ /dev/null @@ -1,43 +0,0 @@ -package utils - -import ( - "testing" -) - -func TestSplitAnimeName(t *testing.T) { - tests := []struct { - name string - input string - expected string - }{ - {"Test 1", "Attack on Titan Season 1", "Attack on Titan"}, - {"Test 2", "Death Note Season 1", "Death Note"}, - {"Test 3", "One Punch Man Season 2", "One Punch Man"}, - {"Test 4", "My Hero Academia Season 3", "My Hero Academia"}, - {"Test 5", "Naruto Season 4", "Naruto"}, - {"Test 6", "Dragon Ball Z Season 5", "Dragon Ball Z"}, - {"Test 7", "Fullmetal Alchemist Season 1", "Fullmetal Alchemist"}, - {"Test 8", "Hunter x Hunter Season 2", "Hunter x Hunter"}, - {"Test 9", "Fairy Tail Season 3", "Fairy Tail"}, - {"Test 10", "One Piece Season 4", "One Piece"}, - {"Test 11", "Tokyo Ghoul Season 1", "Tokyo Ghoul"}, - {"Test 12", "Bleach Season 2", "Bleach"}, - {"Test 13", "Cowboy Bebop Season 1", "Cowboy Bebop"}, - {"Test 14", "Demon Slayer Season 2", "Demon Slayer"}, - {"Test 15", "Sword Art Online Season 3", "Sword Art Online"}, - {"Test 16", "Steins Gate Season 1", "Steins Gate"}, - {"Test 17", "Code Geass Season 2", "Code Geass"}, - {"Test 18", "Gintama Season 3", "Gintama"}, - {"Test 19", "JoJo's Bizarre Adventure Season 4", "JoJo's Bizarre Adventure"}, - {"Test 20", "Neon Genesis Evangelion Season 1", "Neon Genesis Evangelion"}, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - result := SplitAnimeName(tt.input) - if result != tt.expected { - t.Errorf("Expected %s but got %s", tt.expected, result) - } - }) - } -} diff --git a/cmd/animatic-v2/main.go b/cmd/animatic-v2/main.go new file mode 100644 index 0000000..f53ca6b --- /dev/null +++ b/cmd/animatic-v2/main.go @@ -0,0 +1,18 @@ +package main + +import ( + "flag" + "log" + + "animatic-v2/internal/Argparse" + orchester "animatic-v2/internal/Orchester" +) + +func main() { + args, err := argparse.Parse(flag.CommandLine) + if err != nil { + log.Fatalf("Can't parse the arguments properly: %s\n", err.Error()) + } + + orchester.Start(args) +} diff --git a/go.mod b/go.mod index 774dfd2..5deb440 100644 --- a/go.mod +++ b/go.mod @@ -3,33 +3,19 @@ module animatic-v2 go 1.21.5 require ( - github.com/PuerkitoBio/goquery v1.8.1 + github.com/PuerkitoBio/goquery v1.9.2 github.com/cavaliergopher/grab/v3 v3.0.1 - github.com/charmbracelet/bubbles v0.17.1 - github.com/charmbracelet/bubbletea v0.25.0 - github.com/charmbracelet/lipgloss v0.9.1 - github.com/jarcoal/httpmock v1.3.1 + github.com/manifoldco/promptui v0.9.0 gopkg.in/cheggaaa/pb.v1 v1.0.28 ) require ( - github.com/andybalholm/cascadia v1.3.1 // indirect - github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect - github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 // indirect - github.com/fatih/color v1.16.0 // indirect - github.com/lucasb-eyer/go-colorful v1.2.0 // indirect + github.com/andybalholm/cascadia v1.3.2 // indirect + github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e // indirect + github.com/fatih/color v1.17.0 // indirect github.com/mattn/go-colorable v0.1.13 // indirect - github.com/mattn/go-isatty v0.0.20 // indirect - github.com/mattn/go-localereader v0.0.1 // indirect - github.com/mattn/go-runewidth v0.0.15 // indirect - github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b // indirect - github.com/muesli/cancelreader v0.2.2 // indirect - github.com/muesli/reflow v0.3.0 // indirect - github.com/muesli/termenv v0.15.2 // indirect - github.com/rivo/uniseg v0.4.3 // indirect - golang.org/x/net v0.7.0 // indirect - golang.org/x/sync v0.1.0 // indirect - golang.org/x/sys v0.14.0 // indirect - golang.org/x/term v0.6.0 // indirect - golang.org/x/text v0.7.0 // indirect + github.com/mattn/go-runewidth v0.0.16 // indirect + github.com/rivo/uniseg v0.2.0 // indirect + golang.org/x/net v0.24.0 // indirect + golang.org/x/sys v0.19.0 // indirect ) diff --git a/go.sum b/go.sum index ec4e5f4..000ebed 100644 --- a/go.sum +++ b/go.sum @@ -1,91 +1,67 @@ -github.com/PuerkitoBio/goquery v1.8.1 h1:uQxhNlArOIdbrH1tr0UXwdVFgDcZDrZVdcpygAcwmWM= -github.com/PuerkitoBio/goquery v1.8.1/go.mod h1:Q8ICL1kNUJ2sXGoAhPGUdYDJvgQgHzJsnnd3H7Ho5jQ= -github.com/andybalholm/cascadia v1.3.1 h1:nhxRkql1kdYCc8Snf7D5/D3spOX+dBgjA6u8x004T2c= -github.com/andybalholm/cascadia v1.3.1/go.mod h1:R4bJ1UQfqADjvDa4P6HZHLh/3OxWWEqc0Sk8XGwHqvA= -github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= -github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= +github.com/PuerkitoBio/goquery v1.9.2 h1:4/wZksC3KgkQw7SQgkKotmKljk0M6V8TUvA8Wb4yPeE= +github.com/PuerkitoBio/goquery v1.9.2/go.mod h1:GHPCaP0ODyyxqcNoFGYlAprUFH81NuRPd0GX3Zu2Mvk= +github.com/andybalholm/cascadia v1.3.2 h1:3Xi6Dw5lHF15JtdcmAHD3i1+T8plmv7BQ/nsViSLyss= +github.com/andybalholm/cascadia v1.3.2/go.mod h1:7gtRlve5FxPPgIgX36uWBX58OdBsSS6lUvCFb+h7KvU= github.com/cavaliergopher/grab/v3 v3.0.1 h1:4z7TkBfmPjmLAAmkkAZNX/6QJ1nNFdv3SdIHXju0Fr4= github.com/cavaliergopher/grab/v3 v3.0.1/go.mod h1:1U/KNnD+Ft6JJiYoYBAimKH2XrYptb8Kl3DFGmsjpq4= -github.com/charmbracelet/bubbles v0.17.1 h1:0SIyjOnkrsfDo88YvPgAWvZMwXe26TP6drRvmkjyUu4= -github.com/charmbracelet/bubbles v0.17.1/go.mod h1:9HxZWlkCqz2PRwsCbYl7a3KXvGzFaDHpYbSYMJ+nE3o= -github.com/charmbracelet/bubbletea v0.25.0 h1:bAfwk7jRz7FKFl9RzlIULPkStffg5k6pNt5dywy4TcM= -github.com/charmbracelet/bubbletea v0.25.0/go.mod h1:EN3QDR1T5ZdWmdfDzYcqOCAps45+QIJbLOBxmVNWNNg= -github.com/charmbracelet/lipgloss v0.9.1 h1:PNyd3jvaJbg4jRHKWXnCj1akQm4rh8dbEzN1p/u1KWg= -github.com/charmbracelet/lipgloss v0.9.1/go.mod h1:1mPmG4cxScwUQALAAnacHaigiiHB9Pmr+v1VEawJl6I= -github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 h1:q2hJAaP1k2wIvVRd/hEHD7lacgqrCPS+k8g1MndzfWY= -github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= -github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= -github.com/jarcoal/httpmock v1.3.1 h1:iUx3whfZWVf3jT01hQTO/Eo5sAYtB2/rqaUuOtpInww= -github.com/jarcoal/httpmock v1.3.1/go.mod h1:3yb8rc4BI7TCBhFY8ng0gjuLKJNquuDNiPaZjnENuYg= -github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= -github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= +github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 h1:q763qf9huN11kDQavWsoZXJNW3xEE4JJyHa5Q25/sd8= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4= +github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI= +github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYthEiA= +github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4= -github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88= -github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= -github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= -github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= -github.com/maxatome/go-testdeep v1.12.0 h1:Ql7Go8Tg0C1D/uMMX59LAoYK7LffeJQ6X2T04nTH68g= -github.com/maxatome/go-testdeep v1.12.0/go.mod h1:lPZc/HAcJMP92l7yI6TRz1aZN5URwUBUAfUNvrclaNM= -github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b h1:1XF24mVaiu7u+CFywTdcDo2ie1pzzhwjt6RHqzpMU34= -github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b/go.mod h1:fQuZ0gauxyBcmsdE3ZT4NasjaRdxmbCS0jRHsrWu3Ho= -github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA= -github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo= -github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= -github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8= -github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo= -github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8= -github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= +github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= -github.com/rivo/uniseg v0.4.3 h1:utMvzDsuh3suAEnhH0RdHmoPbU648o6CvXxTx4SBMOw= -github.com/rivo/uniseg v0.4.3/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= -golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= +golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w= +golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q= -golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= +golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= -golang.org/x/term v0.6.0 h1:clScbb1cHjoCkyRbWwBEUZ5H/tIFu5TAXIqaZD0Gcjw= -golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= +golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/cheggaaa/pb.v1 v1.0.28 h1:n1tBJnnK2r7g9OW2btFH91V92STTUevLXYFb8gy9EMk= gopkg.in/cheggaaa/pb.v1 v1.0.28/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= diff --git a/install.sh b/install.sh deleted file mode 100644 index 6a59ea8..0000000 --- a/install.sh +++ /dev/null @@ -1,75 +0,0 @@ -check_dir_owner() { - dir=$1 - echo $(ls -ld $dir | awk '{print $3}') -} - -print_message() { - local color_code="$1" - local text="$2" - echo -e "\033[0;${color_code}m${text}\033[0m" -} - -print_green() { - print_message "32" "$1" -} - -print_red() { - print_message "31" "$1" -} - -check_directory() { - dir=$1 - if [ ! -d "$dir" ]; then - print_red "$dir was not found, creating it" - sudo mkdir -p "$dir" - else - print_green "[OK]: $dir was found" - fi -} - -check_owner() { - dir=$1 - if [ ! "$USER_DEFAULT" == $(check_dir_owner "$dir") ]; then - print_red "Fixing permissions" - sudo chown "$USER_DEFAULT" "$dir"/* - else - print_green "[OK]: The permissions of $dir are correct" - fi -} - -BASE_DIR="/chromeMedia" -USER_DEFAULT=$USER -LOCAL_BIN="$HOME/.local/bin" - -if [ ! command -v go &> /dev/null ]; then - print_red "Go is not installed" - exit 1 -else - print_green "[OK]: Go is installed" -fi - -check_directory "$LOCAL_BIN" - -if echo $PATH | grep -qv "$LOCAL_BIN"; then - print_red "$LOCAL_BIN isn't found in PATH" - echo "export PATH=$PATH:$LOCAL_BIN" >> $HOME/.profile -else - print_green "[OK]: $LOCAL_BIN was found in PATH" -fi - -GO_PATH=$(which go) -GO_VERSION=$($GO_PATH version | awk '{print $3}' | tr -d "go") - -if [ "$GO_VERSION" != "1.21.5" ]; then - print_red "Go 1.21.5 is not installed, we only have version $GO_VERSION" - exit 1 -else - print_green "[OK]: Go 1.21.5 is installed" -fi - -$($GO_PATH build) - -mv "animatic-v2" "$LOCAL_BIN/Animatic" - -check_directory "$BASE_DIR" -check_owner "$BASE_DIR" diff --git a/internal/Argparse/parser.go b/internal/Argparse/parser.go new file mode 100644 index 0000000..a0fd47f --- /dev/null +++ b/internal/Argparse/parser.go @@ -0,0 +1,21 @@ +package argparse + +import ( + "flag" + "os" +) + +// Parse the user arguments given a `flag.FlagSet`, normally `flag.CommandLine` +// to have access to the shell's command line arguments. That's because it +// becames easier to test without the need to execute system commands, only +// updating the OS'es flags in a custom `flag.FlagSet` for unit testing. +func Parse(fs *flag.FlagSet) (Args, error) { + argv := Args{} + + fs.BoolVar(&argv.Debug, "debug", false, "this flag is used to enable debug mode") + fs.BoolVar(&argv.Gac, "gac", false, "this flag is used to enable goanime compatibility") + + err := fs.Parse(os.Args[1:]) + + return argv, err +} diff --git a/internal/Argparse/parser_test.go b/internal/Argparse/parser_test.go new file mode 100644 index 0000000..df0dd61 --- /dev/null +++ b/internal/Argparse/parser_test.go @@ -0,0 +1,74 @@ +package argparse_test + +import ( + "flag" + "testing" + + "animatic-v2/internal/Argparse" +) + +// MockParse é usado para criar uma interface testável a partir dos argumentos da CLI +func MockParse(fs *flag.FlagSet, args []string) (argparse.Args, error) { + argv := argparse.Args{} + fs.BoolVar(&argv.Debug, "debug", false, "use this flag to enable debug mode") + fs.BoolVar(&argv.Gac, "gac", false, "use this flag to enable goanime compatibility") + + err := fs.Parse(args) + return argv, err +} + +func TestParse(t *testing.T) { + tests := []struct { + name string + args []string + wantDebug bool + wantGac bool + wantErr bool + }{ + { + name: "Test with debug flag set to true", + args: []string{"-debug"}, + wantDebug: true, + wantGac: false, + wantErr: false, + }, + { + name: "Test with debug flag set to false", + args: []string{}, + wantDebug: false, + wantGac: false, + wantErr: false, + }, + { + name: "Test with gac flag set to true", + args: []string{"-gac"}, + wantDebug: false, + wantGac: true, + wantErr: false, + }, + { + name: "Test with gac flag set to false", + args: []string{}, + wantDebug: false, + wantGac: false, + wantErr: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + fs := flag.NewFlagSet("test", flag.ContinueOnError) + got, err := MockParse(fs, tt.args) + if (err != nil) != tt.wantErr { + t.Errorf("Parse() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got.Debug != tt.wantDebug { + t.Errorf("Parse() got.Debug = %v, want %v", got.Debug, tt.wantDebug) + } + if got.Gac != tt.wantGac { + t.Errorf("Parse() got.Gac = %v, want %v", got.Gac, tt.wantGac) + } + }) + } +} diff --git a/internal/Argparse/types.go b/internal/Argparse/types.go new file mode 100644 index 0000000..5e5cee3 --- /dev/null +++ b/internal/Argparse/types.go @@ -0,0 +1,6 @@ +package argparse + +type Args struct { + Debug bool + Gac bool +} diff --git a/internal/Message/colors.go b/internal/Message/colors.go new file mode 100644 index 0000000..0c3f741 --- /dev/null +++ b/internal/Message/colors.go @@ -0,0 +1,7 @@ +package message + +const ( + redColor = "\033[1;31m" + resetColor = "\033[0m" + warningSign = "\u26A0" +) diff --git a/Message/errorMessage.go b/internal/Message/error_message.go similarity index 75% rename from Message/errorMessage.go rename to internal/Message/error_message.go index 2af7bcd..03335a2 100644 --- a/Message/errorMessage.go +++ b/internal/Message/error_message.go @@ -9,9 +9,5 @@ func ErrorMessage(message string) { return } - const redColor = "\033[1;31m" - const resetColor = "\033[0m" - const warningSign = "\u26A0" - fmt.Printf("%s %s %s %s\n", redColor, warningSign, message, resetColor) } diff --git a/Message/sucessMessage.go b/internal/Message/sucess_message.go similarity index 100% rename from Message/sucessMessage.go rename to internal/Message/sucess_message.go diff --git a/internal/Network/Downloader.go b/internal/Network/Downloader.go new file mode 100644 index 0000000..bbf9799 --- /dev/null +++ b/internal/Network/Downloader.go @@ -0,0 +1,186 @@ +package network + +import ( + "encoding/json" + "fmt" + "io" + "net/http" + "os" + "os/exec" + "path/filepath" + "regexp" + "strings" + "time" + + "github.com/PuerkitoBio/goquery" + "github.com/cavaliergopher/grab/v3" + "gopkg.in/cheggaaa/pb.v1" + + "animatic-v2/internal/Message" + "animatic-v2/internal/Structures" +) + +func downloadFolderFormatter(str string) string { + regex := regexp.MustCompile(`https://animefire\.plus/video/([^/?]+)`) + match := regex.FindStringSubmatch(str) + if len(match) > 1 { + finalStep := match[1] + return finalStep + } + return "" +} + +func dirExists(path string) bool { + if _, err := os.Stat(path); os.IsNotExist(err) { + os.MkdirAll(path, os.ModePerm) + return false + } + + return true +} + +func isBloggerURL(videoURL string) bool { + return strings.Contains(videoURL, "blogger.com") +} + +func isYTDLPAvailable() bool { + cmd := exec.Command("which", "yt-dlp") // Use "where" em vez de "which" no Windows + if err := cmd.Run(); err != nil { + return false + } + return true +} + +func downloadBloggerVideo(videoURL, episodePath string) { + if isYTDLPAvailable() { + fmt.Println("Using yt-dlp to download Blogger video...") + cmd := exec.Command("yt-dlp", "-o", episodePath, videoURL) + if err := cmd.Run(); err != nil { + message.ErrorMessage(fmt.Sprintf("Failed to download video using yt-dlp: %s\n", err.Error())) + } + } +} + +func ExtractActualVideoLabel(videoSrc string) string { + var videoResponse Structures.VideoResponse + response, err := http.Get(videoSrc) + if err != nil { + message.ErrorMessage(err.Error()) + } + + defer response.Body.Close() + + if response.StatusCode != http.StatusOK { + message.ErrorMessage("Status Code is different from StatusOK") + } + + body, err := io.ReadAll(response.Body) + if err != nil { + message.ErrorMessage(err.Error()) + return "" + } + + err = json.Unmarshal(body, &videoResponse) + if err != nil { + message.ErrorMessage(err.Error()) + return "" + } + + if len(videoResponse.Data) == 0 { + message.ErrorMessage("No Video Found") + return "" + } + + return videoResponse.Data[len(videoResponse.Data)-1].Src +} + +func ExtractVideoURL(url string) string { + value, err := checkUrlStatus(url) + + if !value || err != nil { + message.ErrorMessage("Can't access video website") + } + + response, err := http.Get(url) + if err != nil { + message.ErrorMessage(err.Error()) + return "" + } + + doc, _ := goquery.NewDocumentFromReader(response.Body) + + videoElements := doc.Find("video") + + if videoElements.Length() > 0 { + oldDataVideo, _ := videoElements.Attr("data-video-src") + return oldDataVideo + } else { + videoElements = doc.Find("div") + if videoElements.Length() > 0 { + oldDataVideo, _ := videoElements.Attr("data-video-src") + return oldDataVideo + } + } + + message.ErrorMessage("Can't find video element in html") + return "" +} + +func downloadStandardVideo(URL, episodePath string) { + fmt.Println("Using standard download method...") + + startUrl := ExtractVideoURL(URL) + videoUrl := ExtractActualVideoLabel(startUrl) + + client := grab.NewClient() + req, _ := grab.NewRequest(episodePath, videoUrl) + req.HTTPRequest.Header.Set("Connection", "keep-alive") + + resp := client.Do(req) + + maxSizeInMB := int(resp.Size() / (1024 * 1024)) + minSizeInMB := 10 + + if maxSizeInMB < minSizeInMB { + maxSizeInMB = minSizeInMB + } + + bar := pb.StartNew(maxSizeInMB) + + fmt.Printf("Episode URL: %s \n", videoUrl) + for !resp.IsComplete() { + completedInMB := int(resp.Progress() * float64(maxSizeInMB)) + bar.Set(completedInMB) + time.Sleep(time.Millisecond * 500) + } + + bar.Finish() +} + +func downloadEp(path string, ep Structures.Episode) { + episodePath := filepath.Join(path, ep.Number+".mp4") + + if _, err := os.Stat(episodePath); os.IsNotExist(err) { + fmt.Println("Downloading the video...") + if isBloggerURL(ep.URL) { + downloadBloggerVideo(ep.URL, episodePath) + } else { + downloadStandardVideo(ep.URL, episodePath) + } + fmt.Println("Video downloaded successfully!") + } else { + fmt.Println("Video already downloaded.") + } +} + +func Download(anime Structures.Anime, eplist []Structures.Episode, Debug bool, Gac bool) { + var path string + if Gac { + path = filepath.Join(os.Getenv("HOME"), ".local", "goanime", "downloads", "anime", downloadFolderFormatter(anime.Url)) + dirExists(path) + } + + for _, ep := range eplist { + downloadEp(path, ep) + } +} diff --git a/internal/Network/build_url.go b/internal/Network/build_url.go new file mode 100644 index 0000000..b9e5ae4 --- /dev/null +++ b/internal/Network/build_url.go @@ -0,0 +1,15 @@ +package network + +import "fmt" + +func animeFireMount(website, anime string) string { + return fmt.Sprintf("%s/pesquisar/%s", website, anime) +} + +func BuildUrl(website string, anime string) string { + if website == "https://animefire.plus" { + return animeFireMount(website, anime) + } + + return "" +} diff --git a/internal/Network/check_links.go b/internal/Network/check_links.go new file mode 100644 index 0000000..82f466c --- /dev/null +++ b/internal/Network/check_links.go @@ -0,0 +1,114 @@ +package network + +import ( + "fmt" + "io" + "net/http" + "time" + + message "animatic-v2/internal/Message" + "animatic-v2/internal/Structures" +) + +func errorHandling(debug bool, site string, action string, err error) { + if debug { + if err != nil { + message.ErrorMessage(fmt.Sprintf("A error founded in %s: %s because %s\n", site, action, err.Error())) + } else { + message.SucessMessage(fmt.Sprintf("No one error founded in %s: %s\n", site, action)) + } + } +} + +func checkUrlStatus(url string) (bool, error) { + client := http.Client{ + Timeout: 5 * time.Second, + } + + resp, err := client.Head(url) + if err != nil { + return false, err + } + + defer resp.Body.Close() + + if resp.StatusCode >= 200 && resp.StatusCode < 300 { + return true, nil + } + + return false, nil +} + +func downloadSpeed(url string) (float64, error) { + resp, err := http.Get(url) + if err != nil { + return 0, err + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return 0, fmt.Errorf("erro ao acessar o URL: status code %d", resp.StatusCode) + } + + buf := make([]byte, 0) + start := time.Now() + + for { + chunk := make([]byte, 1024) + n, err := resp.Body.Read(chunk) + if n > 0 { + buf = append(buf, chunk[:n]...) + } + if err == io.EOF { + break + } + if err != nil { + return 0, err + } + } + + elapsed := time.Since(start) + fileSize := float64(len(buf)) // Tamanho do arquivo em bytes + speed := (fileSize / elapsed.Seconds()) / (1024 * 1024) // Velocidade em MB/s + + return speed, nil +} + +func ChooseWebsite(mainSite string, fallbackSite string, debug bool) Structures.WebsiteChoose { + var MainSiteSpeed float64 + var fallbackSiteSpeed float64 + + mainSiteEnabled, err := checkUrlStatus(mainSite) + + errorHandling(debug, mainSite, "Check Url Status", err) + + fallbackSiteEnabled, err := checkUrlStatus(fallbackSite) + + errorHandling(debug, fallbackSite, "Check Url Status", err) + + if mainSiteEnabled { + MainSiteSpeed, err = downloadSpeed(mainSite) + + errorHandling(debug, mainSite, "Check Download Speed", err) + } + + if fallbackSiteEnabled { + fallbackSiteSpeed, err = downloadSpeed(fallbackSite) + + errorHandling(debug, fallbackSite, "Check Download Speed", err) + } + + if debug { + message.SucessMessage("Structure from Website choose is builded with success") + } + return Structures.WebsiteChoose{ + MainSite: mainSite, + FallbackSite: fallbackSite, + + MainSiteEnabled: mainSiteEnabled, + FallbackSiteEnable: fallbackSiteEnabled, + + MainSitePing: MainSiteSpeed, + FallbackSitePing: fallbackSiteSpeed, + } +} diff --git a/Network/getAnimeEpisode.go b/internal/Network/get_anime_episodes.go similarity index 54% rename from Network/getAnimeEpisode.go rename to internal/Network/get_anime_episodes.go index abb30d3..f775a52 100644 --- a/Network/getAnimeEpisode.go +++ b/internal/Network/get_anime_episodes.go @@ -1,38 +1,37 @@ package network import ( - message "animatic-v2/Message" - "animatic-v2/Structures" "net/http" - "os" + + "animatic-v2/internal/Message" + "animatic-v2/internal/Structures" "github.com/PuerkitoBio/goquery" ) -type Anime struct { - URL string -} +func GetEpisodes(a Structures.Anime, debug bool) []Structures.Episode { + value, err := checkUrlStatus(a.Url) -func (a *Anime) GetEpisodes() []Structures.Episode { - if CheckWebsite(a.URL) == false { - message.ErrorMessage("Can't connect with website") - return nil + if debug { + if !value { + message.ErrorMessage("Can't connect with website") + } } - resp, err := http.Get(a.URL) - - if err != nil { - message.ErrorMessage("Failed to get anime details") - os.Exit(1) + resp, err := http.Get(a.Url) + if debug { + if err != nil { + message.ErrorMessage("Failed to get anime details") + } } defer resp.Body.Close() doc, err := goquery.NewDocumentFromReader(resp.Body) - - if err != nil { - message.ErrorMessage("Failed to parse anime details") - os.Exit(1) + if debug { + if err != nil { + message.ErrorMessage("Failed to parse anime details") + } } episodeContainer := doc.Find("a.lEp.epT.divNumEp.smallbox.px-2.mx-1.text-left.d-flex") diff --git a/internal/Network/search_anime.go b/internal/Network/search_anime.go new file mode 100644 index 0000000..0b87321 --- /dev/null +++ b/internal/Network/search_anime.go @@ -0,0 +1,77 @@ +package network + +import ( + "net/http" + "sort" + "strings" + + "github.com/PuerkitoBio/goquery" + + "animatic-v2/internal/Message" + "animatic-v2/internal/Structures" +) + +func GetAllAnimes(urlBuilded string, debug bool) []Structures.Anime { + var allAnimes []Structures.Anime + currentPageUrl := urlBuilded + + for { + response, err := http.Get(currentPageUrl) + if debug { + if err != nil { + message.ErrorMessage(err.Error()) + } + } + defer response.Body.Close() + + doc, err := goquery.NewDocumentFromReader(response.Body) + if debug { + if err != nil { + message.ErrorMessage(err.Error()) + } + } + + animes := make([]Structures.Anime, 0) + + doc.Find(".row.ml-1.mr-1 a").Each(func(i int, s *goquery.Selection) { + anime := Structures.Anime{ + Name: strings.TrimSpace(s.Text()), + Url: s.AttrOr("href", ""), + } + + animes = append(animes, anime) + }) + + allAnimes = append(allAnimes, animes...) + + nextPage, exists := doc.Find(".pagination .next a").Attr("href") + if !exists || nextPage == "" { + break + } + + currentPageUrl = nextPage + + if !strings.HasPrefix(currentPageUrl, "http") { + currentPageUrl = urlBuilded + nextPage + } + + value, err := checkUrlStatus(currentPageUrl) + if debug { + if !value || err != nil { + message.ErrorMessage("Can't search in next page") + } + } + } + + sort.Slice(allAnimes, func(i, j int) bool { + if strings.Contains(allAnimes[i].Name, "Dublado") && !strings.Contains(allAnimes[j].Name, "Dublado") { + return true + } + if !strings.Contains(allAnimes[i].Name, "Dublado") && strings.Contains(allAnimes[j].Name, "Dublado") { + return false + } + return strings.Compare(allAnimes[i].Name, allAnimes[j].Name) < 0 + }) + + return allAnimes +} diff --git a/internal/Orchester/start.go b/internal/Orchester/start.go new file mode 100644 index 0000000..a012934 --- /dev/null +++ b/internal/Orchester/start.go @@ -0,0 +1,26 @@ +package orchester + +import ( + argparse "animatic-v2/internal/Argparse" + network "animatic-v2/internal/Network" + tui "animatic-v2/internal/Tui" +) + +const ( + MainSite string = "https://animefire.plus" + FallbackSite string = "https://vizer.in" +) + +func Start(args argparse.Args) { + animeChoosed := tui.SearchAnime(args.Debug) + + url := network.BuildUrl(MainSite, animeChoosed) + + animeList := network.GetAllAnimes(url, args.Debug) + + animeGenerated := tui.SelectAnimes(animeList) + + epList := network.GetEpisodes(animeGenerated, args.Debug) + + network.Download(animeGenerated, epList, args.Debug, args.Gac) +} diff --git a/internal/Orchester/start.go~tmp b/internal/Orchester/start.go~tmp new file mode 100644 index 0000000..a012934 --- /dev/null +++ b/internal/Orchester/start.go~tmp @@ -0,0 +1,26 @@ +package orchester + +import ( + argparse "animatic-v2/internal/Argparse" + network "animatic-v2/internal/Network" + tui "animatic-v2/internal/Tui" +) + +const ( + MainSite string = "https://animefire.plus" + FallbackSite string = "https://vizer.in" +) + +func Start(args argparse.Args) { + animeChoosed := tui.SearchAnime(args.Debug) + + url := network.BuildUrl(MainSite, animeChoosed) + + animeList := network.GetAllAnimes(url, args.Debug) + + animeGenerated := tui.SelectAnimes(animeList) + + epList := network.GetEpisodes(animeGenerated, args.Debug) + + network.Download(animeGenerated, epList, args.Debug, args.Gac) +} diff --git a/Structures/anime.go b/internal/Structures/anime.go similarity index 100% rename from Structures/anime.go rename to internal/Structures/anime.go diff --git a/internal/Structures/choose.go b/internal/Structures/choose.go new file mode 100644 index 0000000..e32d8ac --- /dev/null +++ b/internal/Structures/choose.go @@ -0,0 +1,32 @@ +package Structures + +type WebsiteChoose struct { + MainSite string + FallbackSite string + + MainSiteEnabled bool + FallbackSiteEnable bool + + MainSitePing float64 + FallbackSitePing float64 +} + +func (w *WebsiteChoose) ChooseBetweenWebsites() string { + if w.MainSiteEnabled && !w.FallbackSiteEnable { + return w.MainSite + } + + if !w.MainSiteEnabled && w.FallbackSiteEnable { + return w.FallbackSite + } + + if w.MainSiteEnabled && w.FallbackSiteEnable { + if w.MainSitePing > w.FallbackSitePing { + return w.MainSite + } else { + return w.FallbackSite + } + } + + return w.MainSite +} diff --git a/Structures/link.go b/internal/Structures/link.go similarity index 100% rename from Structures/link.go rename to internal/Structures/link.go diff --git a/internal/Tui/search_anime.go b/internal/Tui/search_anime.go new file mode 100644 index 0000000..032b73d --- /dev/null +++ b/internal/Tui/search_anime.go @@ -0,0 +1,32 @@ +package tui + +import ( + "fmt" + "strings" + + message "animatic-v2/internal/Message" + + "github.com/manifoldco/promptui" +) + +func nameformatter(animeName string) string { + loweredName := strings.ToLower(animeName) + spacelessName := strings.ReplaceAll(loweredName, " ", "-") + return spacelessName +} + +func SearchAnime(Debug bool) string { + prompt := promptui.Prompt{ + Label: "Write the anime name from be searched", + } + + animeName, err := prompt.Run() + + if Debug { + if err != nil { + message.ErrorMessage(fmt.Sprintf("Occured an unknown error in %s\n", err.Error())) + } + } + + return nameformatter(animeName) +} diff --git a/internal/Tui/select_anime.go b/internal/Tui/select_anime.go new file mode 100644 index 0000000..b0e80fb --- /dev/null +++ b/internal/Tui/select_anime.go @@ -0,0 +1,27 @@ +package tui + +import ( + "animatic-v2/internal/Structures" + + "github.com/manifoldco/promptui" +) + +func GetAnimeListName(animeList []Structures.Anime) []string { + var animeName []string + for _, r := range animeList { + animeName = append(animeName, r.Name) + } + + return animeName +} + +func SelectAnimes(animeList []Structures.Anime) Structures.Anime { + prompt := promptui.Select{ + Label: "Select the anime from be downloaded", + Items: GetAnimeListName(animeList), + } + + selected, _, _ := prompt.Run() + + return animeList[selected] +} diff --git a/internal/Utils/featureFlag.go b/internal/Utils/featureFlag.go new file mode 100644 index 0000000..fd5416f --- /dev/null +++ b/internal/Utils/featureFlag.go @@ -0,0 +1,11 @@ +package utils + +func IsFunctionEnabled(enabled bool, f func()) { + if !enabled { + return + } + + if f != nil { + f() + } +} diff --git a/main.go b/main.go deleted file mode 100644 index b79ecc3..0000000 --- a/main.go +++ /dev/null @@ -1,59 +0,0 @@ -package main - -import ( - message "animatic-v2/Message" - network "animatic-v2/Network" - "animatic-v2/Structures" - tui "animatic-v2/Tui" - utils "animatic-v2/Utils" - "os" - "os/signal" - "path/filepath" - "runtime" - "syscall" -) - -func main() { - sigs := make(chan os.Signal, 1) - signal.Notify(sigs, syscall.SIGTERM, syscall.SIGKILL) - - go func() { - sig := <-sigs - switch sig { - case syscall.SIGTERM: - os.Exit(0) - case syscall.SIGKILL: - os.Exit(1) - } - }() - - // If hasn't internet connection, break the program - if network.HasNetwork() == false { - message.ErrorMessage("Hasn't internet connection") - return - } - - var destPath string - animeName := tui.GetAnimeName() - - if animeName == "" { - message.ErrorMessage("We didn't receive the name of the anime") - return - } - - animeSearcher := network.AnimeSearcher{AnimeName: animeName} - animeSelectedName, animeURL := animeSearcher.SearchAnimeInAnimeFire() - - if runtime.GOOS != "windows" { - destPath = filepath.Join("/chromeMedia/Series/", utils.SplitAnimeName(animeSelectedName)) - } else { - userProfile := os.Getenv("USERPROFILE") - destPath = filepath.Join(userProfile, "chromeMedia\\Series\\", utils.SplitAnimeName(animeSelectedName)) - } - - anime := network.Anime{URL: animeURL} - epList := anime.GetEpisodes() - - downloader := network.Downloader{DestPath: destPath, Anime: Structures.Anime{Name: animeSelectedName, Url: animeURL}, EpList: epList} - downloader.DownloadAll() -}