Skip to content

Commit ccda992

Browse files
committed
WebReg requires new cookies; scrape available classes
1 parent 8c96710 commit ccda992

File tree

2 files changed

+70
-38
lines changed

2 files changed

+70
-38
lines changed

webreg-scraping/scrape.ts

Lines changed: 66 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// deno run --allow-all scrape.ts <UqZBpD3n> <jlinksessionidx> <quarter>
1+
// deno run --allow-all scrape.ts <quarter> <jlinksessionidx> <itscookie>
22

33
import { join as joinPath } from 'std/path/mod.ts'
44
import { ExamCodes, InstructionCodes } from './meeting-types.ts'
@@ -74,28 +74,24 @@ export interface RawGetClassResult extends CommonRawSectionResult {
7474
}
7575

7676
type ScraperOptions = {
77-
/** The `jlinksessionidx` cookie */
78-
jlinksessionidx?: string
79-
/** The `UqZBpD3n` cookie */
80-
UqZBpD3n?: string
77+
/** The cookie header used to authenticate WebReg requests. */
78+
cookie?: string
8179
/** The path to cache WebReg response data in */
8280
cachePath?: string | null
8381
}
8482

8583
/** Scrapes courses and sections from WebReg. */
8684
export class Scraper {
8785
#term: string
88-
#sessionIndex?: string
89-
#uqz?: string
86+
#cookie?: string
9087
#cachePath: string | null
9188

9289
constructor (
9390
term: string,
94-
{ jlinksessionidx, UqZBpD3n, cachePath = null }: ScraperOptions = {}
91+
{ cookie, cachePath = null }: ScraperOptions = {}
9592
) {
9693
this.#term = term
97-
this.#sessionIndex = jlinksessionidx
98-
this.#uqz = UqZBpD3n
94+
this.#cookie = cookie
9995
this.#cachePath = cachePath
10096
}
10197

@@ -122,11 +118,7 @@ export class Scraper {
122118
`https://act.ucsd.edu/webreg2/svc/wradapter/secure/${path}?${new URLSearchParams(
123119
query
124120
)}`,
125-
{
126-
headers: {
127-
Cookie: `jlinksessionidx=${this.#sessionIndex}; UqZBpD3n=${this.#uqz}`
128-
}
129-
}
121+
{ headers: { Cookie: this.#cookie ?? '' } }
130122
).then(response =>
131123
response.ok
132124
? response.json()
@@ -507,6 +499,11 @@ export class Group extends BaseGroup<RawSearchLoadGroupDataResult> {
507499
* Whether the section can be enrolled directly. This should reflect whether
508500
* the button says "Enroll" (true) or "Waitlist" (false) on WebReg. False if
509501
* `plannable` is false.
502+
*
503+
* NOTE: `waitlist` can be greater than 0 even when `enrollable` is true. This
504+
* can happen if the course capacity was just increased, so students haven't
505+
* been let off the waitlist yet, but there would still be seats available
506+
* after everyone is off the waitlist.
510507
*/
511508
enrollable: boolean
512509

@@ -703,16 +700,17 @@ export class ScheduleSection extends BaseGroup<RawGetClassResult> {
703700
}
704701

705702
if (import.meta.main) {
706-
const [UqZBpD3n, jlinksessionidx, quarter] = Deno.args
703+
// There might be multiple itscookies; try the last one first
704+
const [quarter, jlinksessionidx, itscookie] = Deno.args
707705
const getter = new Scraper(quarter, {
708-
jlinksessionidx,
709-
UqZBpD3n,
706+
cookie: `jlinksessionidx=${jlinksessionidx}; itscookie=${itscookie};`,
710707
cachePath: 'cache-' + quarter.toLowerCase()
711708
})
712709
const courses = []
713710
// const freq: Record<number, number> = {}
714711
const examples: Record<number, string> = {}
715712
// let count = 0
713+
console.log('## Lower division (0xx)\n')
716714
for await (const course of getter.allCourses()) {
717715
courses.push(course)
718716
// if (course.hundred <= 1) {
@@ -727,6 +725,27 @@ if (import.meta.main) {
727725
// }
728726
// }
729727
// }
728+
if (course.hundred !== 0) {
729+
continue
730+
}
731+
const enrollable = course.groups.filter(
732+
group =>
733+
group.enrollable &&
734+
group.capacity !== Infinity &&
735+
group.enrolled + group.waitlist < group.capacity
736+
)
737+
if (enrollable.length > 0) {
738+
console.log(
739+
`- **${course.code}**: ${enrollable
740+
.map(
741+
group =>
742+
`${group.code} (${group.enrolled + group.waitlist}/${
743+
group.capacity === Infinity ? '∞' : group.capacity
744+
})`
745+
)
746+
.join(' · ')}`
747+
)
748+
}
730749
for (const group of course.groups) {
731750
// freq[group.raw.DAY_CODE.length] ??= 0
732751
// freq[group.raw.DAY_CODE.length]++
@@ -735,23 +754,38 @@ if (import.meta.main) {
735754
// }
736755
// Get list of lectures that meet on Friday
737756
if (group.time?.days.includes(5) && !group.isExam()) {
738-
console.log(
739-
`${course.code} ${group.code} ${group.type} on ${group.time.days}`
740-
)
741-
}
742-
if (
743-
!group.isExam() &&
744-
group.plannable &&
745-
group.time?.location?.building === 'RCLAS'
746-
) {
747-
// count++
748757
// console.log(
749-
// `${course.code} ${group.code} ${group.enrolled}/${
750-
// group.capacity
751-
// } WL ${group.waitlist} ${group.enrollable ? '✔️' : '❌'}`
758+
// `${course.code} ${group.code} ${group.type} on ${group.time.days}`
752759
// )
753760
}
761+
if (group.enrollable) {
762+
// count++
763+
}
764+
}
765+
}
766+
// console.log(examples)
767+
console.log('\n## Upper division (1xx)\n')
768+
for (const course of courses) {
769+
if (course.hundred !== 1) {
770+
continue
771+
}
772+
const enrollable = course.groups.filter(
773+
group =>
774+
group.enrollable &&
775+
group.capacity !== Infinity &&
776+
group.enrolled + group.waitlist < group.capacity
777+
)
778+
if (enrollable.length > 0) {
779+
console.log(
780+
`- **${course.code}**: ${enrollable
781+
.map(
782+
group =>
783+
`${group.code} (${group.enrolled + group.waitlist}/${
784+
group.capacity === Infinity ? '∞' : group.capacity
785+
})`
786+
)
787+
.join(' · ')}`
788+
)
754789
}
755790
}
756-
console.log(examples)
757791
}

webreg-scraping/track-classes.ts

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// Create a Git-friendly set of files summarizing WebReg data
2-
// deno run --allow-all track-classes.ts <UqZBpD3n> <jlinksessionidx>
2+
// deno run --allow-all track-classes.ts <cookie>
33

44
import { ensureDir } from 'std/fs/ensure_dir.ts'
55
import { writeAll } from 'std/streams/write_all.ts'
@@ -105,8 +105,7 @@ export async function main (
105105
source:
106106
| {
107107
type: 'fetch'
108-
jlinksessionidx: string
109-
UqZBpD3n: string
108+
cookie: string
110109
}
111110
| {
112111
type: 'cache'
@@ -275,8 +274,7 @@ export async function main (
275274
}
276275

277276
if (import.meta.main) {
278-
// The UqZBpD3n cookie doesn't seem to expire as often, so I put it first
279-
const [UqZBpD3n, jlinksessionidx] = Deno.args
277+
const [cookie] = Deno.args
280278
const today = new Date()
281279
await main(
282280
'FA22',
@@ -285,6 +283,6 @@ if (import.meta.main) {
285283
(today.getMonth() + 1).toString().padStart(2, '0'),
286284
today.getDate().toString().padStart(2, '0')
287285
].join(''),
288-
{ type: 'fetch', jlinksessionidx, UqZBpD3n }
286+
{ type: 'fetch', cookie }
289287
)
290288
}

0 commit comments

Comments
 (0)