From 4aa52ec72f33323a1b7456dee4e8a39a945747fb Mon Sep 17 00:00:00 2001 From: Ryan Hollis Date: Wed, 14 Feb 2024 11:37:29 -0500 Subject: [PATCH] Initial implemetation --- .gitignore | 3 + main.go | 232 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 235 insertions(+) create mode 100644 main.go diff --git a/.gitignore b/.gitignore index 3b735ec..0715960 100644 --- a/.gitignore +++ b/.gitignore @@ -19,3 +19,6 @@ # Go workspace file go.work + +# Outputed files from the script +output diff --git a/main.go b/main.go new file mode 100644 index 0000000..da46967 --- /dev/null +++ b/main.go @@ -0,0 +1,232 @@ +package main + +import ( + "encoding/json" + "fmt" + "io" + "net/http" + "os" + "strings" +) + +const ( + // These should be set to control the book that is downloaded. + Language = "Spanish" + BookName = "Complete Spanish Step-by-Step" +) + +const ( + // These are for the program and should not be changed. + GetMenuUrl = "https://mhe-language-lab.azurewebsites.net/api/GetSubMenus?parentID=%d" + GetFlashCardsUrl = "https://mhe-language-lab.azurewebsites.net/api/GetFlashCards?menuID=%d" + FlashCards = "Flashcards" + FlashCardsStudyMode = "Flashcards: Study Mode" + FileStart = `#separator:tab +#html:true +#deck column:3 +` +) + +func main() { + err := DoEverything() + if err != nil { + fmt.Println(err) + } +} +func DoEverything() error { + // Get the list of languages and select the desired language. + languageMenu, err := GetMenu(0) + if err != nil { + return err + } + var languageId int + for _, languageEntry := range languageMenu { + if languageEntry.MenuTitle == Language { + languageId = languageEntry.MenuID + break + } + } + + // Get the list of books for the selected language and select the desired book. + bookMenu, err := GetMenu(languageId) + if err != nil { + return err + } + var bookId int + for _, bookEntry := range bookMenu { + if bookEntry.MenuTitle == BookName { + bookId = bookEntry.MenuID + break + } + } + + // Get the chapters of the book and download the flashcards. + bookOptions, err := GetMenu(bookId) + if err != nil { + return err + } + var flashCardId int + for _, options := range bookOptions { + if options.MenuTitle == FlashCards { + flashCardId = options.MenuID + break + } + } + chapters, err := GetMenu(flashCardId) + if err != nil { + return err + } + err = os.MkdirAll("output", 0755) + if err != nil { + return err + } + + fileOutput := strings.Builder{} + fileOutput.WriteString(FileStart) + for _, chapter := range chapters { + // Make 1-digit numbers start with zero, so that alphabetical sorting works correctly. + if chapter.MenuTitle[1] == '.' { + chapter.MenuTitle = "0" + chapter.MenuTitle + } + // Strip html tags from the chapter title + chapter.MenuTitle = strings.ReplaceAll(chapter.MenuTitle, "", "") + chapter.MenuTitle = strings.ReplaceAll(chapter.MenuTitle, "", "") + + fmt.Printf("%+v\n", chapter.MenuTitle) + // Get all sections in the chapter and get the study mode flashcards in each section. + sections, err := GetMenu(chapter.MenuID) + if err != nil { + return err + } + var chapterCards []*Card + for _, section := range sections { + fmt.Printf(" %+v\n", section.MenuTitle) + modes, err := GetMenu(section.MenuID) + if err != nil { + return err + } + for _, mode := range modes { + if mode.MenuTitle == FlashCardsStudyMode { + cards, err := GetFlashCards(mode.MenuID) + if err != nil { + return err + } + for _, card := range cards { + if strings.Contains(card.SideA, "\r\n") { + // There is a broken card in the Spanish course that totally mangles the output. + // Handle that card correctly. There are several cards combined into one message, this will + // actually put them in the output correctly. + parts := strings.Split(card.SideA, "\r\n") + for _, part := range parts[1:] { + aAndB := strings.Split(part, "\t") + if len(aAndB) == 2 { + chapterCards = append(chapterCards, &Card{ + SideA: aAndB[0], + SideB: aAndB[1], + }) + } + } + continue + } + chapterCards = append(chapterCards, card) + } + } + } + } + if len(chapterCards) == 0 { + continue + } + for _, card := range chapterCards { + title := chapter.MenuTitle + if chapter.MenuTitle[2:4] == ". " { + title = chapter.MenuTitle[0:4] + "(" + Language[0:1] + "2E) " + chapter.MenuTitle[4:] + } + fileOutput.WriteString(fmt.Sprintf("%s\t%s\t%s %s\n", card.SideA, card.SideB, BookName, title)) + } + for _, card := range chapterCards { + title := chapter.MenuTitle + if chapter.MenuTitle[2:4] == ". " { + title = chapter.MenuTitle[0:4] + "(E2" + Language[0:1] + ") " + chapter.MenuTitle[4:] + } + fileOutput.WriteString(fmt.Sprintf("%s\t%s\t%s %s\n", card.SideB, card.SideA, BookName, title)) + } + } + fileName := fmt.Sprintf("output/%s.tsv", BookName) + err = os.WriteFile(fileName, []byte(fileOutput.String()), 0644) + if err != nil { + return err + } + + fmt.Printf("File written to %s\n", fileName) + + return nil +} +func GetMenu(parentID int) ([]*MenuEntry, error) { + resp, err := http.Get(fmt.Sprintf(GetMenuUrl, parentID)) + if err != nil { + return nil, err + } + defer resp.Body.Close() + body, err := io.ReadAll(resp.Body) + if err != nil { + return nil, err + } + var getMenuResponse []*MenuEntry + if err := json.Unmarshal(body, &getMenuResponse); err != nil { + return nil, err + } + return getMenuResponse, nil +} + +func GetFlashCards(menuID int) ([]*Card, error) { + resp, err := http.Get(fmt.Sprintf(GetFlashCardsUrl, menuID)) + if err != nil { + return nil, err + } + defer resp.Body.Close() + body, err := io.ReadAll(resp.Body) + if err != nil { + return nil, err + } + var getFlashCardsResponse []*Card + if err := json.Unmarshal(body, &getFlashCardsResponse); err != nil { + return nil, err + } + return getFlashCardsResponse, nil +} + +type MenuEntry struct { + MenuID int `json:"Menu_ID"` + MenuTitle string `json:"MenuTitle"` + TitleInformation string `json:"TitleInformation"` + Base64Image string `json:"Base64Image"` + MenuFormat string `json:"MenuFormat"` + DeckType string `json:"DeckType"` + SelfScoring bool `json:"SelfScoring"` + DeckTitle string `json:"DeckTitle"` + FlashCardsAndQuiz bool `json:"FlashCardsAndQuiz"` + SideALabel string `json:"SideALabel"` + SideBLabel string `json:"SideBLabel"` + DataDeckID int `json:"DataDeck_ID"` + ForceSideA bool `json:"ForceSideA"` + Unpublished bool `json:"Unpublished"` +} + +type Card struct { + CardID int `json:"Card_ID"` + SideA string `json:"SideA"` + SideB string `json:"SideB"` + StyleA string `json:"StyleA"` + StyleB string `json:"StyleB"` + SideAAudio string `json:"SideAAudio"` + SideBAudio string `json:"SideBAudio"` + SideAImage string `json:"SideAImage"` + SideBImage string `json:"SideBImage"` + SideAVideo string `json:"SideAVideo"` + SideBVideo string `json:"SideBVideo"` + SideALabel string `json:"SideALabel"` + SideBLabel string `json:"SideBLabel"` + TTSAudio bool `json:"TTSAudio"` + TTSSideA string `json:"TTSSideA"` + TTSSideB string `json:"TTSSideB"` +}