cedarstalking with keyboard shortcuts
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

at 2e1496913191a9d1b76e87d1ffe7cc9d36e2cc4b 185 lines 5.6 kB view raw
1const BASE_URL = "https://selfservice.cedarville.edu"; 2 3export interface DirectoryPerson { 4 Id: string; 5 Username: string; 6 FirstName: string; 7 LastName: string; 8 MiddleName: string | null; 9 Nickname: string | null; 10 AddressCity: string | null; 11 AddressState: string | null; 12 AddressCountry: string | null; 13 DepartmentDescription: string | null; 14 Title: string | null; 15 OfficeBuildingCode: string | null; 16 OfficeBuildingName: string | null; 17 OfficeRoom: string | null; 18 OfficePhone: string | null; 19 DormCode: string | null; 20 DormName: string | null; 21 DormRoom: string | null; 22 StudentType: string | null; 23 StudentClass: string | null; 24 studentWorker: boolean | null; 25 empInactive: boolean | null; 26 PhotoUrl: string | null; 27} 28 29export class AuthRequiredError extends Error { 30 constructor(public readonly signInUrl: string) { 31 super("Authentication required"); 32 this.name = "AuthRequiredError"; 33 } 34} 35 36function makeHeaders(cookie?: string): Record<string, string> { 37 const headers: Record<string, string> = { 38 accept: "*/*", 39 "accept-language": "en-US,en;q=0.9", 40 referer: `${BASE_URL}/cedarinfo/directory`, 41 "user-agent": 42 "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36", 43 }; 44 if (cookie) headers["cookie"] = cookie; 45 return headers; 46} 47 48export interface ScheduleItem { 49 title: string; 50 description: string; 51 startTime: string; 52 endTime: string; 53 day: string; 54 type: string; 55} 56 57export interface Term { 58 code: string; 59 desc: string; 60 start: string; 61 end: string; 62} 63 64export interface PersonInfo { 65 faculty: { 66 isFaculty: boolean; 67 facultyDepts: { code: string; description: string; division: string }[]; 68 scheduleItems: ScheduleItem[]; 69 term: { key: string; description: string }; 70 }; 71 student: { 72 isStudent: boolean; 73 scheduleItems: ScheduleItem[]; 74 programs: string[]; 75 majors: { code: string; description: string }[]; 76 minors: { code: string; description: string }[]; 77 concentrations: { code: string; description: string }[]; 78 advisors: { id: string; name: string }[]; 79 term: { key: string; description: string }; 80 }; 81} 82 83export interface Department { 84 code: string; 85 description: string; 86} 87 88export interface Population { 89 code: number; 90 desc: string; 91} 92 93export async function getDepartments(cookie?: string): Promise<Department[]> { 94 const url = `${BASE_URL}/CedarInfo/Directory/DepartmentJson`; 95 const res = await fetch(url, { headers: makeHeaders(cookie) }); 96 if (!res.ok || !res.headers.get("content-type")?.includes("json")) return []; 97 try { 98 const data = await res.json(); 99 return Array.isArray(data) ? data : []; 100 } catch { 101 return []; 102 } 103} 104 105export async function getPopulations(cookie?: string): Promise<Population[]> { 106 const url = `${BASE_URL}/CedarInfo/Directory/PopulationsJson`; 107 const res = await fetch(url, { headers: makeHeaders(cookie) }); 108 if (!res.ok || !res.headers.get("content-type")?.includes("json")) return []; 109 try { 110 const data = await res.json(); 111 return Array.isArray(data) ? data : []; 112 } catch { 113 return []; 114 } 115} 116 117export async function getPersonTerms( 118 id: string, 119 cookie: string, 120): Promise<Term[]> { 121 const url = `${BASE_URL}/CedarInfo/Json/GetTerms?id=${id}&past=5&future=2&summer=true`; 122 const res = await fetch(url, { headers: makeHeaders(cookie) }); 123 if (!res.ok) return []; 124 const data = await res.json(); 125 return Array.isArray(data) ? data : []; 126} 127 128export async function getPersonInfo( 129 id: string, 130 term: string, 131 cookie: string, 132): Promise<PersonInfo | null> { 133 const url = `${BASE_URL}/CedarInfo/Info/Json?id=${id}&term=${term}`; 134 const res = await fetch(url, { headers: makeHeaders(cookie) }); 135 if (!res.ok) return null; 136 const data = await res.json(); 137 return data as PersonInfo; 138} 139 140export async function searchDirectory( 141 firstName: string, 142 lastName: string, 143 cookie?: string, 144 options?: { department?: string; population?: number }, 145): Promise<DirectoryPerson[]> { 146 const params = new URLSearchParams(); 147 if (firstName) params.set("FirstNameSearch", firstName); 148 if (lastName) params.set("LastNameSearch", lastName); 149 if (options?.department) params.set("Department", options.department); 150 if (options?.population != null) 151 params.set("PopulationSearch", String(options.population)); 152 153 const apiUrl = `${BASE_URL}/CedarInfo/Directory/SearchResultsJson?${params.toString()}`; 154 const response = await fetch(apiUrl, { headers: makeHeaders(cookie) }); 155 156 // Unauthenticated: server redirects us to SSO. fetch follows by default, 157 // so we end up at a non-selfservice URL. 158 const landedOutside = !response.url.includes("selfservice.cedarville.edu"); 159 if (landedOutside || response.status === 401 || response.status === 403) { 160 const signInUrl = landedOutside 161 ? response.url 162 : `${BASE_URL}/cedarinfo/directory`; 163 throw new AuthRequiredError(signInUrl); 164 } 165 166 if (!response.ok) { 167 throw new Error( 168 `Request failed: ${response.status} ${response.statusText}`, 169 ); 170 } 171 172 const data = await response.json(); 173 174 // Server can also return JSON with a redirect URL instead of an array 175 if (!Array.isArray(data)) { 176 const signInUrl = 177 (data as Record<string, unknown>)?.signInUrl ?? 178 (data as Record<string, unknown>)?.SignInUrl ?? 179 (data as Record<string, unknown>)?.redirectUrl; 180 if (typeof signInUrl === "string") throw new AuthRequiredError(signInUrl); 181 throw new AuthRequiredError(`${BASE_URL}/cedarinfo/directory`); 182 } 183 184 return data as DirectoryPerson[]; 185}