aplus/aplus.go

246 lines
6.9 KiB
Go

package main
import (
"encoding/json"
"fmt"
"html"
"io"
"net/http"
"net/url"
"regexp"
"strconv"
"strings"
"time"
)
func submit_code(course_code int, attendance_code string) bool {
cur_body := launch_aplus(course_code)
if cur_body == "" {
fmt.Println("Unknown error")
return false
} else if cur_body == "Roles not supported" {
fmt.Println("Cannot access course", course_code, "(canvas error \"Roles not supported\")")
return false
}
if verbose {
fmt.Printf("%s", cur_body)
}
if strings.Contains(cur_body, "Student not known") {
fmt.Println("Student not known. This course must first be visited by an instructor for students to have access.")
return false
}
current_url := fmt.Sprintf("%s/student/", base_aplus_link)
// the links to submit the code for a class is under the dayPanel div
daypanel_start := strings.Index(cur_body, "<div class=\"dayPanel\"")
if !strings.Contains(cur_body[daypanel_start:], "<a href=\"./?module") {
fmt.Println("No code submitted. Did the professor generate the code or is the class in session?")
return false
}
// link_start - link_end is to extract the URL
// TODO: make this more elegant to allow multiple of these links
// (maybe use an html parser)
// or at least use extract_attribute()
link_start := daypanel_start
link_start += strings.Index(cur_body[link_start:], "<a href=\"./?module")
link_start += len("<a href=\"")
link_end := link_start
link_end += strings.Index(cur_body[link_end:], "\"")
link_url := current_url + cur_body[link_start:link_end]
link_url = html.UnescapeString(link_url)
if verbose {
fmt.Println("!!!")
fmt.Printf("%i %i %i\n", daypanel_start, link_start, link_end)
fmt.Println(link_url)
fmt.Println("!!!")
}
resp, _ := client.Get(link_url)
body, _ := io.ReadAll(resp.Body)
form_str := get_form_from_request_body(body)
form_values := parse_form(form_str)
form_values["ctl01$sessionCode"][0] = attendance_code
resp, _ = client.PostForm(link_url, form_values)
body, _ = io.ReadAll(resp.Body)
if strings.Index(string(body), "ctl01_errorMessage") != -1 {
errormsg_start := strings.Index(string(body), "ctl01_errorMessage")
errormsg_start += strings.Index(string(body)[errormsg_start:], "Text\">")
errormsg_start += len("Text\">")
errormsg_end := errormsg_start
errormsg_end += strings.Index(string(body)[errormsg_start:], "<")
fmt.Println(string(body)[errormsg_start:errormsg_end])
} else if strings.Index(string(body), "ctl02_codeSuccessMessage") != -1 {
fmt.Println("Code successfully recorded")
}
return true
}
func launch_aplus(course_code int) string {
err := init_aplus_toolid(course_code)
if err != nil {
fmt.Println("Failed to get A+ base URL and tool ID. Try again with a different course code.")
fmt.Printf("Error: %s\n", err)
return ""
}
aplus_link := fmt.Sprintf("%s/courses/%d/external_tools/sessionless_launch?id=%d&access_token=%s",
base_link, course_code, external_tools_code, token)
aplus := get_aplus(token, aplus_link, client)
resp, _ := client.Get(aplus)
body, _ := io.ReadAll(resp.Body)
form_str := get_form_from_request_body(body)
form_values := parse_form(form_str)
resp, _ = client.PostForm(base_aplus_link, form_values)
body, _ = io.ReadAll(resp.Body)
if strings.Index(string(body), "Roles not supported") != -1 {
return "Roles not supported"
} else {
return string(body)
}
}
func get_aplus(token string, link string, client http.Client) string {
resp, _ := client.Get(link)
body, _ := io.ReadAll(resp.Body)
var aplus Aplus
json.Unmarshal(body, &aplus)
return aplus.URL
}
func get_form_from_request_body(req_body []byte) string {
body_str := string(req_body)
form_start := strings.Index(body_str, "<form")
form_end := strings.Index(body_str, "</form>") + 7
form_html := req_body[form_start:form_end]
return string(form_html)
}
// parse_form extracts form fields and values from the given HTML form string.
func parse_form(form_html string) url.Values {
form_values := make(url.Values)
inputs := strings.Split(form_html, "<input")
for _, input := range inputs {
// Extract field name and value
name := extract_attribute(input, "name")
value := html.UnescapeString(extract_attribute(input, "value"))
if name != "" {
form_values.Add(name, value)
}
}
return form_values
}
func extract_attribute(input string, attribute string) string {
start := strings.Index(input, attribute+"=\"")
if start == -1 {
start = strings.Index(input, attribute+"='")
}
if start == -1 {
return ""
}
start += len(attribute) + 2
end := strings.Index(input[start:], "\"")
if end == -1 {
end = strings.Index(input[start:], "'")
}
if end == -1 {
return ""
}
return input[start : start+end]
}
func submit_code_sans_course(attendance_code string) {
courses := list_all_courses(true)
for _, course := range courses {
fmt.Println("Checking with course", course.ID%10000)
if submit_code(int(course.ID%10000), attendance_code) == true {
fmt.Println("Submitted code to course", course.ID%10000)
return
}
}
fmt.Println("Could not submit code", attendance_code, "to any course")
}
func timetable(course_code int) {
cur_body := launch_aplus(course_code)
if cur_body == "" {
fmt.Println("Unknown error")
return
} else if cur_body == "Roles not supported" {
fmt.Println("Cannot access course", course_code, "(canvas error \"Roles not supported\")")
return
}
if verbose {
fmt.Printf("%s", cur_body)
}
if strings.Contains(cur_body, "Student not known") {
fmt.Println("Student not known. This course must first be visited by an instructor for students to have access.")
return
}
current_url := fmt.Sprintf("%s/student/", base_aplus_link)
tt_start := strings.Index(cur_body, "<div class=\"stv_tt_container\"")
link_start := tt_start
link_start += strings.Index(cur_body[link_start:], "<a href=\"./?canvasCourse")
link_start += len("<a href=\"")
link_end := link_start
link_end += strings.Index(cur_body[link_end:], "\"")
link_url := current_url + cur_body[link_start:link_end]
link_url = html.UnescapeString(link_url)
resp, _ := client.Get(link_url)
body, _ := io.ReadAll(resp.Body)
tt_full := string(body)[(strings.Index(string(body), "<div class=\"stv_tt_container\"")):]
tt_full = tt_full[:strings.Index(tt_full, "\n")]
re_date := regexp.MustCompile("Date\\.UTC\\([^\\)]+")
re_status := regexp.MustCompile("title=\"\">[^<]*")
dates, statuses := re_date.FindAllString(tt_full, -1), re_status.FindAllString(tt_full, -1)
for i := range dates {
jsdate := strings.Split(dates[i][9:], ",")
year, _ := strconv.Atoi(jsdate[0])
month, _ := strconv.Atoi(jsdate[1])
day, _ := strconv.Atoi(jsdate[2])
hour, _ := strconv.Atoi(jsdate[3])
min, _ := strconv.Atoi(jsdate[4])
sec, _ := strconv.Atoi(jsdate[5])
date := time.Date(year, time.Month(month+1), day, hour, min, sec, 0, time.UTC).Local()
fmt.Println(date.Format("Mon Jan 02 15:04:05 MST 2006") + ": " + strings.Replace(statuses[i], "title=\"\">", "", 1))
}
}