1
- import { HEADERS , PROFESSOR_QUERY } from "~data/config" ;
1
+ import { HEADERS , PROFESSOR_QUERY , RMP_GRAPHQL_URL } from "~data/config" ;
2
+ import type { RMPRatingInterface } from "~data/interfaces" ;
3
+
4
+ function reportError ( context , err ) {
5
+ console . error ( "Error in " + context + ": " + err ) ;
6
+ }
2
7
3
8
function getProfessorUrl ( professorName : string , schoolId : string ) : string {
4
9
return `https://www.ratemyprofessors.com/search/professors/${ schoolId } ?q=${ encodeURIComponent ( professorName ) } }`
@@ -15,16 +20,24 @@ function getProfessorIds(texts: string[], professorNames: string[]): string[] {
15
20
const professorIds = [ ]
16
21
const lowerCaseProfessorNames = professorNames . map ( name => name . toLowerCase ( ) )
17
22
texts . forEach ( text => {
18
- let matched = false ;
19
- const regex = / " l e g a c y I d " : ( \d + ) .* ?" f i r s t N a m e " : " ( .* ?) " , " l a s t N a m e " : " ( .* ?) " / g;
20
- for ( const match of text . matchAll ( regex ) ) {
21
- console . log ( match [ 2 ] . split ( ' ' ) [ 0 ] . toLowerCase ( ) + " " + match [ 3 ] . toLowerCase ( ) )
22
- if ( lowerCaseProfessorNames . includes ( match [ 2 ] . split ( ' ' ) [ 0 ] . toLowerCase ( ) + " " + match [ 3 ] . toLowerCase ( ) ) ) {
23
- professorIds . push ( match [ 1 ] ) ;
24
- matched = true ;
23
+ let pendingMatch = null ;
24
+ const regex = / " l e g a c y I d " : ( \d + ) .* ?" n u m R a t i n g s " : ( \d + ) .* ?" f i r s t N a m e " : " ( .* ?) " , " l a s t N a m e " : " ( .* ?) " / g;
25
+ let allMatches : string [ ] = text . match ( regex ) ;
26
+ let highestNumRatings = 0 ;
27
+
28
+ if ( allMatches ) {
29
+ for ( const fullMatch of allMatches ) {
30
+ for ( const match of fullMatch . matchAll ( regex ) ) {
31
+ console . log ( match [ 3 ] . split ( ' ' ) [ 0 ] . toLowerCase ( ) + " " + match [ 4 ] . toLowerCase ( ) + " " )
32
+ let numRatings = parseInt ( match [ 2 ] ) ;
33
+ if ( lowerCaseProfessorNames . includes ( match [ 3 ] . split ( ' ' ) [ 0 ] . toLowerCase ( ) + " " + match [ 4 ] . toLowerCase ( ) ) && numRatings >= highestNumRatings ) {
34
+ pendingMatch = match [ 1 ] ;
35
+ }
36
+ }
25
37
}
26
38
}
27
- if ( ! matched ) professorIds . push ( null )
39
+
40
+ professorIds . push ( pendingMatch ) ;
28
41
} )
29
42
return professorIds
30
43
}
@@ -46,48 +59,84 @@ function getGraphQlUrlProps(professorIds: string[]) {
46
59
return graphQlUrlProps
47
60
}
48
61
49
- function fetchWithGraphQl ( graphQlUrlProps : any [ ] , resolve ) {
50
- const graphqlUrl = "https://www.ratemyprofessors.com/graphql" ;
62
+ function wait ( delay ) {
63
+ return new Promise ( ( resolve ) => setTimeout ( resolve , delay ) ) ;
64
+ }
51
65
52
- Promise . all ( graphQlUrlProps . map ( u => fetch ( graphqlUrl , u ) ) )
53
- . then ( responses => Promise . all ( responses . map ( res => res . json ( ) ) ) )
54
- . then ( ratings => {
55
- for ( let i = 0 ; i < ratings . length ; i ++ ) {
56
- if ( ratings [ i ] != null && ratings [ i ] . hasOwnProperty ( "data" ) && ratings [ i ] [ "data" ] . hasOwnProperty ( "node" ) ) {
57
- ratings [ i ] = ratings [ i ] [ "data" ] [ "node" ] ;
58
- }
66
+ function fetchRetry ( url : string , delay : number , tries : number , fetchOptions ) {
67
+ function onError ( err ) {
68
+ let triesLeft : number = tries - 1 ;
69
+ if ( ! triesLeft ) {
70
+ throw err ;
71
+ }
72
+ return wait ( delay ) . then ( ( ) => fetchRetry ( url , delay , triesLeft , fetchOptions ) ) ;
73
+ }
74
+ return fetch ( url , fetchOptions ) . catch ( onError ) ;
75
+ }
76
+
77
+ // If using orderedFetchOpts, make sure that it is an array and that the index of the fetch options corresponds to the index of the response in the responses array.
78
+ async function validateResponses ( responses : any [ ] , orderedFetchOpts : any [ ] ) {
79
+ for ( const [ key , value ] of Object . entries ( responses ) ) {
80
+ let notOk = value ?. status !== 200 ;
81
+ if ( notOk && value && value . url ) {
82
+ let details = {
83
+ status : value . status ,
84
+ statusText : value . statusText ,
85
+ redirected : value . redirected ,
86
+ url : value . url
59
87
}
60
- console . log ( ratings )
61
- resolve ( ratings )
62
- } )
88
+ reportError ( "validateResponses" , "Status not OK for fetch request. Details are: " + JSON . stringify ( details ) ) ;
89
+ let fetchOptions = orderedFetchOpts [ key ] || { } ; // If we don't have fetch options, we just use an empty object.
90
+ responses [ key ] = await fetchRetry ( value ?. url , 200 , 3 , fetchOptions ) ;
91
+ }
92
+ } ;
93
+ return responses ;
94
+ }
95
+
96
+ export async function fetchWithGraphQl ( graphQlUrlProps : any [ ] ) {
97
+ try {
98
+ let responses = await validateResponses ( await Promise . all ( graphQlUrlProps . map ( u => fetch ( RMP_GRAPHQL_URL , u ) ) ) , graphQlUrlProps ) ;
99
+ // We now have all the responses. So, we consider all the responses, and collect the ratings.
100
+ let ratings : RMPRatingInterface [ ] = await Promise . all ( responses . map ( res => res . json ( ) ) ) ;
101
+ for ( let i = 0 ; i < ratings . length ; i ++ ) {
102
+ if ( ratings [ i ] != null && ratings [ i ] . hasOwnProperty ( "data" ) && ratings [ i ] [ "data" ] . hasOwnProperty ( "node" ) ) {
103
+ ratings [ i ] = ratings [ i ] [ "data" ] [ "node" ] ;
104
+ }
105
+ }
106
+ return ratings ;
107
+ }
108
+ catch ( err ) {
109
+ reportError ( "fetchWithGraphQl" , err ) ;
110
+ return [ ] ;
111
+ }
63
112
}
64
113
65
114
export interface RmpRequest {
66
115
professorNames : string [ ] ,
67
116
schoolId : string
68
117
}
69
- export function requestProfessorsFromRmp ( request : RmpRequest ) : Promise < RMPInterface [ ] > {
70
- return new Promise ( ( resolve , reject ) => {
71
-
72
- // make a list of urls for promises
73
- const professorUrls = getProfessorUrls ( request . professorNames , request . schoolId )
118
+ export async function requestProfessorsFromRmp ( request : RmpRequest ) : Promise < RMPInterface [ ] > {
74
119
75
- // fetch professor ids from each url
76
- Promise . all ( professorUrls . map ( u => fetch ( u ) ) )
77
- . then ( responses => Promise . all ( responses . map ( res => res . text ( ) ) ) )
78
- . then ( texts => {
79
- const professorIds = getProfessorIds ( texts , request . professorNames )
120
+ // make a list of urls for promises
121
+ const professorUrls = getProfessorUrls ( request . professorNames , request . schoolId )
122
+
123
+ // fetch professor ids from each url
124
+ try {
125
+ let responses = await validateResponses ( await Promise . all ( professorUrls . map ( u => ( fetch ( u ) ) ) ) , [ ] ) ;
126
+
127
+ let texts = await Promise . all ( responses . map ( res => res . text ( ) ) ) ;
128
+ const professorIds = getProfessorIds ( texts , request . professorNames )
80
129
81
- // create fetch objects for each professor id
82
- const graphQlUrlProps = getGraphQlUrlProps ( professorIds )
130
+ // create fetch objects for each professor id
131
+ const graphQlUrlProps = getGraphQlUrlProps ( professorIds )
83
132
84
- // fetch professor info by id with graphQL
85
- fetchWithGraphQl ( graphQlUrlProps , resolve )
86
- }
87
- ) . catch ( error => {
88
- reject ( error ) ;
89
- } ) ;
90
- } )
133
+ // fetch professor info by id with graphQL
134
+ let professors = await fetchWithGraphQl ( graphQlUrlProps ) ;
135
+ return professors ;
136
+ } catch ( error ) {
137
+ reportError ( "requestProfessorsFromRmp" , error ) ;
138
+ return [ ] ;
139
+ } ;
91
140
}
92
141
93
142
interface RMPInterface {
0 commit comments