aplus.go (7027B)
1 package main 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "html" 7 "io" 8 "net/http" 9 "net/url" 10 "regexp" 11 "strconv" 12 "strings" 13 "time" 14 ) 15 16 func submit_code(course_code int, attendance_code string) bool { 17 cur_body := launch_aplus(course_code) 18 if cur_body == "" { 19 fmt.Println("Unknown error") 20 return false 21 } else if cur_body == "Roles not supported" { 22 fmt.Println("Cannot access course", course_code, "(canvas error \"Roles not supported\")") 23 return false 24 } 25 26 if verbose { 27 fmt.Printf("%s", cur_body) 28 } 29 30 if strings.Contains(cur_body, "Student not known") { 31 fmt.Println("Student not known. This course must first be visited by an instructor for students to have access.") 32 return false 33 } 34 35 current_url := fmt.Sprintf("%s/student/", base_aplus_link) 36 // the links to submit the code for a class is under the dayPanel div 37 daypanel_start := strings.Index(cur_body, "<div class=\"dayPanel\"") 38 39 if !strings.Contains(cur_body[daypanel_start:], "<a href=\"./?module") { 40 fmt.Println("No code submitted. Did the professor generate the code or is the class in session?") 41 return false 42 } 43 44 // link_start - link_end is to extract the URL 45 // TODO: make this more elegant to allow multiple of these links 46 // (maybe use an html parser) 47 // or at least use extract_attribute() 48 link_start := daypanel_start 49 link_start += strings.Index(cur_body[link_start:], "<a href=\"./?module") 50 link_start += len("<a href=\"") 51 link_end := link_start 52 link_end += strings.Index(cur_body[link_end:], "\"") 53 54 link_url := current_url + cur_body[link_start:link_end] 55 link_url = html.UnescapeString(link_url) 56 57 if verbose { 58 fmt.Println("!!!") 59 fmt.Printf("%i %i %i\n", daypanel_start, link_start, link_end) 60 fmt.Println(link_url) 61 fmt.Println("!!!") 62 } 63 64 resp, _ := client.Get(link_url) 65 body, _ := io.ReadAll(resp.Body) 66 67 form_str := get_form_from_request_body(body) 68 form_values := parse_form(form_str) 69 form_values["ctl01$sessionCode"][0] = attendance_code 70 71 resp, _ = client.PostForm(link_url, form_values) 72 body, _ = io.ReadAll(resp.Body) 73 74 if strings.Index(string(body), "ctl01_errorMessage") != -1 { 75 errormsg_start := strings.Index(string(body), "ctl01_errorMessage") 76 errormsg_start += strings.Index(string(body)[errormsg_start:], "Text\">") 77 errormsg_start += len("Text\">") 78 errormsg_end := errormsg_start 79 errormsg_end += strings.Index(string(body)[errormsg_start:], "<") 80 fmt.Println(string(body)[errormsg_start:errormsg_end]) 81 } else if strings.Index(string(body), "ctl02_codeSuccessMessage") != -1 { 82 fmt.Println("Code successfully recorded") 83 } 84 85 return true 86 } 87 88 func launch_aplus(course_code int) string { 89 err := init_aplus_toolid(course_code) 90 if err != nil { 91 fmt.Println("Failed to get A+ base URL and tool ID. Try again with a different course code.") 92 fmt.Printf("Error: %s\n", err) 93 return "" 94 } 95 96 aplus_link := fmt.Sprintf("%s/courses/%d/external_tools/sessionless_launch?id=%d&access_token=%s", 97 base_link, course_code, external_tools_code, token) 98 aplus := get_aplus(token, aplus_link, client) 99 100 resp, _ := client.Get(aplus) 101 body, _ := io.ReadAll(resp.Body) 102 103 form_str := get_form_from_request_body(body) 104 form_values := parse_form(form_str) 105 106 resp, _ = client.PostForm(base_aplus_link, form_values) 107 body, _ = io.ReadAll(resp.Body) 108 109 if strings.Index(string(body), "Roles not supported") != -1 { 110 return "Roles not supported" 111 } else { 112 return string(body) 113 } 114 } 115 116 func get_aplus(token string, link string, client http.Client) string { 117 resp, _ := client.Get(link) 118 119 body, _ := io.ReadAll(resp.Body) 120 121 var aplus Aplus 122 json.Unmarshal(body, &aplus) 123 124 return aplus.URL 125 } 126 127 func get_form_from_request_body(req_body []byte) string { 128 body_str := string(req_body) 129 form_start := strings.Index(body_str, "<form") 130 form_end := strings.Index(body_str, "</form>") + 7 131 form_html := req_body[form_start:form_end] 132 133 return string(form_html) 134 } 135 136 // parse_form extracts form fields and values from the given HTML form string. 137 func parse_form(form_html string) url.Values { 138 form_values := make(url.Values) 139 inputs := strings.Split(form_html, "<input") 140 141 for _, input := range inputs { 142 // Extract field name and value 143 name := extract_attribute(input, "name") 144 value := html.UnescapeString(extract_attribute(input, "value")) 145 146 if name != "" { 147 form_values.Add(name, value) 148 } 149 } 150 151 return form_values 152 } 153 154 func extract_attribute(input string, attribute string) string { 155 start := strings.Index(input, attribute+"=\"") 156 if start == -1 { 157 start = strings.Index(input, attribute+"='") 158 } 159 160 if start == -1 { 161 return "" 162 } 163 164 start += len(attribute) + 2 165 166 end := strings.Index(input[start:], "\"") 167 if end == -1 { 168 end = strings.Index(input[start:], "'") 169 } 170 171 if end == -1 { 172 return "" 173 } 174 175 return input[start : start+end] 176 } 177 178 func submit_code_sans_course(attendance_code string) { 179 courses := list_all_courses(true) 180 181 for _, course := range courses { 182 fmt.Println("Checking with course", course.ID%10000) 183 if submit_code(int(course.ID%10000), attendance_code) == true { 184 fmt.Println("Submitted code to course", course.ID%10000) 185 return 186 } 187 } 188 189 fmt.Println("Could not submit code", attendance_code, "to any course") 190 } 191 192 func timetable(course_code int) { 193 cur_body := launch_aplus(course_code) 194 if cur_body == "" { 195 fmt.Println("Unknown error") 196 return 197 } else if cur_body == "Roles not supported" { 198 fmt.Println("Cannot access course", course_code, "(canvas error \"Roles not supported\")") 199 return 200 } 201 202 if verbose { 203 fmt.Printf("%s", cur_body) 204 } 205 206 if strings.Contains(cur_body, "Student not known") { 207 fmt.Println("Student not known. This course must first be visited by an instructor for students to have access.") 208 return 209 } 210 211 current_url := fmt.Sprintf("%s/student/", base_aplus_link) 212 tt_start := strings.Index(cur_body, "<div class=\"stv_tt_container\"") 213 214 link_start := tt_start 215 link_start += strings.Index(cur_body[link_start:], "<a href=\"./?canvasCourse") 216 link_start += len("<a href=\"") 217 link_end := link_start 218 link_end += strings.Index(cur_body[link_end:], "\"") 219 220 link_url := current_url + cur_body[link_start:link_end] 221 link_url = html.UnescapeString(link_url) 222 223 resp, _ := client.Get(link_url) 224 body, _ := io.ReadAll(resp.Body) 225 226 tt_full := string(body)[(strings.Index(string(body), "<div class=\"stv_tt_container\"")):] 227 tt_full = tt_full[:strings.Index(tt_full, "\n")] 228 229 re_date := regexp.MustCompile("Date\\.UTC\\([^\\)]+") 230 re_status := regexp.MustCompile("title=\"\">[^<]*") 231 232 dates, statuses := re_date.FindAllString(tt_full, -1), re_status.FindAllString(tt_full, -1) 233 for i := range dates { 234 jsdate := strings.Split(dates[i][9:], ",") 235 year, _ := strconv.Atoi(jsdate[0]) 236 month, _ := strconv.Atoi(jsdate[1]) 237 day, _ := strconv.Atoi(jsdate[2]) 238 hour, _ := strconv.Atoi(jsdate[3]) 239 min, _ := strconv.Atoi(jsdate[4]) 240 sec, _ := strconv.Atoi(jsdate[5]) 241 date := time.Date(year, time.Month(month+1), day, hour, min, sec, 0, time.UTC).Local() 242 243 fmt.Println(date.Format("Mon Jan 02 15:04:05 MST 2006") + ": " + strings.Replace(statuses[i], "title=\"\">", "", 1)) 244 } 245 }