diff --git a/cmd/configs.go b/cmd/configs.go new file mode 100644 index 0000000..fdf0fbd --- /dev/null +++ b/cmd/configs.go @@ -0,0 +1,8 @@ +package cmd + +const ( + BASE_URL = "http://dspace.amritanet.edu:8080" + COURSE_URL = BASE_URL + "/xmlui/handle/123456789/" + COURSE_LIST_URL = COURSE_URL + "16" +) + diff --git a/cmd/helpers.go b/cmd/helpers.go new file mode 100644 index 0000000..c62e947 --- /dev/null +++ b/cmd/helpers.go @@ -0,0 +1,38 @@ +package cmd + +import ( + "fmt" + "log" + "github.com/anaskhan96/soup" + "os/exec" + "runtime" +) + +// Fetches and parses HTML from the given URL. +func fetchHTML(url string) (string, error) { + doc, err := soup.Get(url) + + if err != nil { + fmt.Println("Error fetching the URL. Make sure you're connected to Amrita WiFi or VPN.") + return "", err + } + + return doc, nil +} + + +// Opens a URL in the default web browser. +func openBrowser(url string) { + var err error + switch runtime.GOOS { + case "linux": + err = exec.Command("xdg-open", url).Start() + case "windows": + err = exec.Command("rundll32", "url.dll,FileProtocolHandler", url).Start() + case "darwin": + err = exec.Command("open", url).Start() + } + if err != nil { + log.Fatal(err) + } +} \ No newline at end of file diff --git a/cmd/logo.go b/cmd/logo.go new file mode 100644 index 0000000..b1eb521 --- /dev/null +++ b/cmd/logo.go @@ -0,0 +1,12 @@ +package cmd + +const LOGO_ASCII string = ` + __ __ _____ _____ _______ _______ ______ + /\ | \/ | __ \|_ _|__ __|/\ | __ \ \ / / __ \ + / \ | \ / | |__) | | | | | / \ | |__) \ \_/ / | | | + / /\ \ | |\/| | _ / | | | | / /\ \ | ___/ \ /| | | | + / ____ \| | | | | \ \ _| |_ | |/ ____ \ | | | | | |__| | + /_/ \_\_| |_|_| \_\_____| |_/_/ \_\ |_| |_| \___\_\ + +` + diff --git a/cmd/model.go b/cmd/model.go new file mode 100644 index 0000000..cd5e095 --- /dev/null +++ b/cmd/model.go @@ -0,0 +1,7 @@ +package cmd + +type resource struct { + name string + path string +} + diff --git a/cmd/requestClient.go b/cmd/requestClient.go new file mode 100644 index 0000000..03cf246 --- /dev/null +++ b/cmd/requestClient.go @@ -0,0 +1,151 @@ +package cmd + +import ( + "errors" + "github.com/anaskhan96/soup" +) + +var htmlFetchErr error = errors.New("failed to fetch the HTML content") + +func getCoursesReq(url string) ([]resource, error) { + + res, err := fetchHTML(url) + + if err != nil { + return nil, htmlFetchErr + } + + doc := soup.HTMLParse(res) + div := doc.Find("div", "id", "aspect_artifactbrowser_CommunityViewer_div_community-view") + + subs := div.FindAll("div","class","artifact-title") + + var subjects []resource + + for _, item := range subs { + sub := item.Find("span") + a := item.Find("a") + path := a.Attrs()["href"] + subject := resource{sub.Text(), path} + subjects = append(subjects, subject) + } + + return subjects, nil +} + + +func semChooseReq(url string) ([]resource ,error) { + + res, err := fetchHTML(url) + + if err != nil { + return nil, htmlFetchErr + } + + doc := soup.HTMLParse(res) + div := doc.Find("div", "id", "aspect_artifactbrowser_CommunityViewer_div_community-view") + + if div.Error != nil { + return nil, errors.New("No assesments found on the page.") + } + + ul := div.FindAll("ul") + li := ul[0].FindAll("li") + + if len(ul)>1 { + li = ul[1].FindAll("li") + } else { + li = ul[0].FindAll("li") + } + + var assesments []resource + + for _, link := range li { + a := link.Find("a") + span := a.Find("span") + path := link.Find("a").Attrs()["href"] + assesment := resource{span.Text(), path} + assesments = append(assesments, assesment) + } + + return assesments, nil +} + +func semTableReq(url string) ([]resource, error) { + + res, err := fetchHTML(url) + + if err != nil { + return nil, htmlFetchErr + } + + doc := soup.HTMLParse(res) + div := doc.Find("div", "id", "aspect_artifactbrowser_CommunityViewer_div_community-view") + + if div.Error != nil { + return nil, errors.New("No semesters found on the page.") + } + + ul := div.Find("ul") + li := ul.FindAll("li") + + if len(li) == 0 { + return nil, errors.New("No semesters found on the page.") + } + + var semesters []resource + + for _, link := range li { + a := link.Find("a") + span := a.Find("span") + path := link.Find("a").Attrs()["href"] + semester := resource{span.Text(), path} + semesters = append(semesters, semester) + } + + return semesters, nil + +} + +func yearReq(url string) ([]resource, error) { + + res, err := fetchHTML(url) + + if err != nil { + return nil, htmlFetchErr + } + + doc := soup.HTMLParse(res) + div := doc.Find("div", "xmlns","http://di.tamu.edu/DRI/1.0/") + + ul := div.Find("ul") + li := ul.Find("li") + hyper := li.Find("a").Attrs()["href"] + + url = BASE_URL + hyper + page,err := fetchHTML(url) + + if err != nil { + return nil, htmlFetchErr + } + + doc = soup.HTMLParse(page) + div = doc.Find("div", "class","file-list") + + subdiv := div.FindAll("div","class","file-wrapper") + + var files []resource + + for _, item := range subdiv { + title := item.FindAll("div") + indiv := title[1].Find("div") + span := indiv.FindAll("span") + fileName := span[1].Attrs()["title"] + path := title[0].Find("a").Attrs()["href"] + file := resource{fileName, path} + files = append(files, file) + } + + return files, nil + +} diff --git a/cmd/root.go b/cmd/root.go new file mode 100644 index 0000000..26e09cd --- /dev/null +++ b/cmd/root.go @@ -0,0 +1,64 @@ +package cmd + +import ( + "fmt" + "os" + + "github.com/spf13/cobra" +) + +var rootCmd = &cobra.Command{ + Use: "ampyq", + Short: "Amrita PYQ CLI", + Long: `A CLI application to access Amrita Repository for previous year question papers.`, + Run: func(cmd *cobra.Command, args []string) { + fmt.Print(LOGO_ASCII) + start() + }, +} + +// Execute adds all child commands to the root command and sets flags appropriately. +func Execute() { + if err := rootCmd.Execute(); err != nil { + fmt.Println(err) + os.Exit(1) + } +} + +// start function - equivalent to start() in Python +func start() { + fmt.Println("Fetching Courses...") + + subjects, err := getCoursesReq(COURSE_LIST_URL) + + if err != nil { + fmt.Errorf(err.Error()) + return + } + + fmt.Println("Available Courses:") + + for i, subject := range subjects { + fmt.Printf("%d.\t%s\n", i+1, subject.name) + } + + // Option to quit. + fmt.Printf("%d.\tQuit\n", len(subjects)+1) + + for { + var ch int + fmt.Printf("\nEnter your choice: ") + fmt.Scanln(&ch) + + if ch > 0 && ch <= len(subjects) { + path := subjects[ch-1].path + url := BASE_URL + path + semTable(url) + } else if ch == len(subjects)+1 { + fmt.Println("Goodbye!") + os.Exit(0) + } else { + fmt.Println("Please enter a valid input!") + } + } +} \ No newline at end of file diff --git a/cmd/semChoose.go b/cmd/semChoose.go new file mode 100644 index 0000000..375f086 --- /dev/null +++ b/cmd/semChoose.go @@ -0,0 +1,46 @@ +package cmd + +import ( + "fmt" +) + +func semChoose(url string) { + fmt.Println("Fetching assesments...") + params_url := url + + assesments, err := semChooseReq(url) + + if err != nil { + fmt.Errorf(err.Error()) + return + } + + // Display the found items + fmt.Printf("No\tSemesters\n") + for i, assesment := range assesments { + fmt.Printf("%d\t%s\n", i+1, assesment.name) // Extract the text from the span element + } + + // Option to add "Back" + fmt.Printf("%d\tBack\n", len(assesments)+1) + + for { + var ch int + fmt.Print("\nEnter your assesment: ") + fmt.Scanln(&ch) + + if ch > 0 && ch <= len(assesments) { + url = BASE_URL + assesments[ch-1].path + break + } else if ch == len(assesments)+1 { + semTable(stack.Pop()) + } else { + fmt.Println("Please enter a valid input!") + } + } + + // append to stack + stack.Push(params_url) + + year(url) +} \ No newline at end of file diff --git a/cmd/semTable.go b/cmd/semTable.go new file mode 100644 index 0000000..e9fd121 --- /dev/null +++ b/cmd/semTable.go @@ -0,0 +1,44 @@ +package cmd + +import ( + "fmt" +) + +func semTable(url string) { + fmt.Println("Fetching semesters...") + + semesters, err := semTableReq(url) + + if err != nil { + fmt.Errorf(err.Error()) + return + } + + fmt.Printf("No\tSemesters\n") + for i, semester := range semesters { + fmt.Printf("%d\t%s\n", i+1, semester.name) + } + + // Option to add "Back". + fmt.Printf("%d\tBack\n", len(semesters)+1) + + stack.Push(url) + + for { + var ch int + fmt.Print("\nEnter your semester: ") + fmt.Scanln(&ch) + + if ch > 0 && ch <= len(semesters) { + url := BASE_URL + semesters[ch-1].path + semChoose(url) + break + } else if ch == len(semesters)+1 { + start() + break + } else { + fmt.Println("Please enter a valid input!") + } + } + +} diff --git a/cmd/stack.go b/cmd/stack.go new file mode 100644 index 0000000..810484f --- /dev/null +++ b/cmd/stack.go @@ -0,0 +1,41 @@ +package cmd + +// Stack implementation using Go slices. +type Stack struct { + items []string +} + +// Push adds an item to the stack. +func (s *Stack) Push(item string) { + s.items = append(s.items, item) +} + +// Pop removes and returns the top item from the stack. +func (s *Stack) Pop() string { + if len(s.items) == 0 { + return "" + } + item := s.items[len(s.items)-1] + s.items = s.items[:len(s.items)-1] + return item +} + +// IsEmpty returns true if the stack is empty. +func (s *Stack) IsEmpty() bool { + return len(s.items) == 0 +} + +// Peek returns the top item without removing it. +func (s *Stack) Peek() string { + if len(s.items) == 0 { + return "" + } + return s.items[len(s.items)-1] +} + +// NewStack creates a new stack. +func NewStack() *Stack { + return &Stack{} +} + +var stack = NewStack() \ No newline at end of file diff --git a/cmd/version.go b/cmd/version.go new file mode 100644 index 0000000..aa1bfe5 --- /dev/null +++ b/cmd/version.go @@ -0,0 +1,19 @@ +package cmd + +import ( + "fmt" + "github.com/spf13/cobra" +) + +func init() { + rootCmd.AddCommand(versionCmd) +} + +var versionCmd = &cobra.Command{ + Use: "version", + Short: "Print the version number of ampyq", + Long: `Displays version of ampyq installed on the system.`, + Run: func(cmd *cobra.Command, args []string) { + fmt.Println("Amrita Previous Year Questions v0.0.1-alpha") + }, +} \ No newline at end of file diff --git a/cmd/year.go b/cmd/year.go new file mode 100644 index 0000000..bea6561 --- /dev/null +++ b/cmd/year.go @@ -0,0 +1,56 @@ +package cmd + +import ( + "fmt" + "os" +) + +func year(url string) { + fmt.Println("Fetching...") + year_url := url + + files, err := yearReq(url) + + if err != nil { + fmt.Errorf(err.Error()) + return + } + + fmt.Printf("No\tFiles\n") + for i, file := range files { + fmt.Printf("%d\t%s\n", i+1, file.name) + } + + // Option to add "Back". + fmt.Printf("%d\tBack\n", len(files)+1) + + for { + var ch int + fmt.Print("\nEnter your choice: ") + fmt.Scanln(&ch) + + if ch > 0 && ch <= len(files) { + link := files[ch-1].path + url = BASE_URL + link + break + } else if ch == len(files)+1 { + semChoose(stack.Pop()) + } else { + fmt.Println("Please enter a valid input!") + } + } + + fmt.Println("Please wait until browser opens !") + openBrowser(url) + + var ch int + fmt.Println("Do you want to continue ? \nPress 1 for Yes and 0 for No : "); + fmt.Scanln(&ch) + + if ch == 0 { + fmt.Println("Exiting...") + os.Exit(0) + } else { + year(year_url) + } +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..17cc7fd --- /dev/null +++ b/go.mod @@ -0,0 +1,19 @@ +module amrita_pyq + +go 1.23 + +toolchain go1.23.1 + +require ( + github.com/anaskhan96/soup v1.2.5 + github.com/spf13/cobra v1.8.1 +) + +require ( + github.com/PuerkitoBio/goquery v1.10.0 // indirect + github.com/andybalholm/cascadia v1.3.2 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/spf13/pflag v1.0.5 // indirect + golang.org/x/net v0.29.0 // indirect + golang.org/x/text v0.18.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..ee993ca --- /dev/null +++ b/go.sum @@ -0,0 +1,14 @@ +github.com/PuerkitoBio/goquery v1.10.0/go.mod h1:TjZZl68Q3eGHNBA8CWaxAN7rOU1EbDz3CWuolcO5Yu4= +github.com/anaskhan96/soup v1.2.5 h1:V/FHiusdTrPrdF4iA1YkVxsOpdNcgvqT1hG+YtcZ5hM= +github.com/anaskhan96/soup v1.2.5/go.mod h1:6YnEp9A2yywlYdM4EgDz9NEHclocMepEtku7wg6Cq3s= +github.com/andybalholm/cascadia v1.3.2/go.mod h1:7gtRlve5FxPPgIgX36uWBX58OdBsSS6lUvCFb+h7KvU= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= +github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo= +golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0= +golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= +golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= diff --git a/main.go b/main.go new file mode 100644 index 0000000..09b7ef0 --- /dev/null +++ b/main.go @@ -0,0 +1,9 @@ +package main + +import ( + "amrita_pyq/cmd" +) + +func main() { + cmd.Execute() +} \ No newline at end of file