polyhoraire 1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,125 @@
1
+ require 'rubygems'
2
+ require 'google/api_client'
3
+ require 'nokogiri'
4
+ require 'yaml'
5
+ require 'tzinfo'
6
+
7
+ class GoogleExporter
8
+
9
+ def initialize
10
+ auth
11
+ @tz = TZInfo::Timezone.get('America/Montreal')
12
+ @sentEvents = Array.new
13
+ end
14
+
15
+ def send(schedule,toCalID)
16
+ @sentEvents = Array.new
17
+ @sentCalendarID = toCalID
18
+
19
+ schedule.courses.each do |course|
20
+ course.periods.each do |period|
21
+
22
+ courseBeginsOn = schedule.trimester.starts + period.weekDay - 1
23
+ courseBeginsOn += 7 if period.week == 2
24
+ event = {
25
+ 'summary' => course.acronym + '(' + period.group + ') ' + (period.isLab ? '[Lab]' : '') ,
26
+ 'location' => period.location,
27
+ 'timeZone' => 'America/Montreal',
28
+ 'start' => {
29
+ 'dateTime' => dateTime(courseBeginsOn, period.from),
30
+ 'timeZone' => 'America/Montreal'
31
+ },
32
+ 'end' => {
33
+ 'dateTime' => dateTime(courseBeginsOn, period.to),
34
+ 'timeZone' => 'America/Montreal'
35
+ },
36
+ 'recurrence' => [
37
+ 'RDATE;VALUE=DATE:' + rDates(schedule.trimester,period)
38
+ ]
39
+ }
40
+
41
+ result = @client.execute(:api_method => @service.events.insert,
42
+ :parameters => {'calendarId' => toCalID},
43
+ :body_object => event,
44
+ :headers => {'Content-Type' => 'application/json'})
45
+ @sentEvents.push(result)
46
+ end
47
+ end
48
+ end
49
+
50
+ def deleteEvent(eventID,calendarID)
51
+ result = @client.execute(:api_method => @service.events.delete,
52
+ :parameters => {'calendarId' => calendarID, 'eventId' => eventID})
53
+ end
54
+
55
+ def deleteSentEvents
56
+ @sentEvents.each do |event|
57
+ deleteEvent(event.data.id,@sentCalendarID)
58
+ end
59
+ end
60
+
61
+ def selectCalendar(idUrl)
62
+
63
+ end
64
+
65
+ # return : hash[:calendarID => :calendarName]
66
+ def calendarList
67
+ list = Hash.new
68
+
69
+ page_token = nil
70
+ result = client.execute(:api_method => service.calendar_list.list)
71
+ while true
72
+ entries = result.data.items
73
+ entries.each do |e|
74
+ list[e.id] = e.summary
75
+ end
76
+ if !(page_token = result.data.next_page_token)
77
+ break
78
+ end
79
+ result = client.execute(:api_method => service.calendar_list.list,
80
+ :parameters => {'pageToken' => page_token})
81
+ end
82
+ end
83
+
84
+ def auth
85
+ oauth_yaml = YAML.load_file(Poly::userConfDir + '/google-api.yaml')
86
+ client = Google::APIClient.new
87
+ client.authorization.client_id = oauth_yaml["client_id"]
88
+ client.authorization.client_secret = oauth_yaml["client_secret"]
89
+ client.authorization.scope = oauth_yaml["scope"]
90
+ client.authorization.refresh_token = oauth_yaml["refresh_token"]
91
+ client.authorization.access_token = oauth_yaml["access_token"]
92
+
93
+ if client.authorization.refresh_token && client.authorization.expired?
94
+ client.authorization.fetch_access_token!
95
+ end
96
+
97
+ @service = client.discovered_api('calendar', 'v3')
98
+
99
+ @client = client
100
+ end
101
+
102
+ private
103
+
104
+ def dateTime(date, time)
105
+ date = DateTime.parse(date.to_s + ' ' + time)
106
+ date = @tz.local_to_utc(date)
107
+ date.strftime('%FT%TZ')
108
+ end
109
+
110
+ def rDates(trimester,period)
111
+ str = ''
112
+ trimester.getDates(period).each do |date|
113
+ date = DateTime.parse(date.to_s + ' ' + period.from)
114
+ date = @tz.local_to_utc(date)
115
+ str += date.strftime('%Y%m%dT%H%M%SZ,')
116
+ end
117
+ str.chomp(',')
118
+ end
119
+
120
+ def to_calendar(xmlDoc)
121
+ xsl = Nokogiri::XSLT(File.read(Poly::XSLDocs[:exportGoogle]))
122
+ xsl.transform(xmlDoc)
123
+ end
124
+
125
+ end
data/lib/html.xsl ADDED
@@ -0,0 +1,256 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <xsl:stylesheet version="1.0"
3
+ xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:fn="http://www.w3.org/2005/xpath-functions"
4
+ xmlns:php="http://php.net/xsl" xmlns:url="http://whatever/java/java.net.URLEncoder"
5
+ xmlns:atom="http://www.w3.org/2005/Atom" xmlns:batch='http://schemas.google.com/gdata/batch'
6
+ xmlns:openSearch='http://a9.com/-/spec/opensearch/1.1/' xmlns:gCal='http://schemas.google.com/gCal/2005'
7
+ xmlns:gd='http://schemas.google.com/g/2005' gd:etag='W/"CkYFSHg-fyp7ImA9WxRVGUo."'
8
+ exclude-result-prefixes="atom gCal openSearch gd fn url php">
9
+
10
+
11
+ <xsl:output encoding="UTF-8" method="html" indent="yes" />
12
+ <xsl:param name="app" />
13
+
14
+ <xsl:template match="/">
15
+ <xsl:apply-templates select="/root/form" />
16
+ <xsl:apply-templates select="/root/atom:feed" />
17
+ <xsl:apply-templates select="/root/linkButton" />
18
+
19
+
20
+ <xsl:element name="ul">
21
+ <xsl:apply-templates select="/root/addedRecently" />
22
+ </xsl:element>
23
+ </xsl:template>
24
+
25
+ <xsl:template match="addedRecently">
26
+ <xsl:if test="position() = 1">
27
+ <xsl:element name="h1">
28
+ <xsl:text>Modification récentes</xsl:text>
29
+ </xsl:element>
30
+ </xsl:if>
31
+ <xsl:element name="li">
32
+ <xsl:value-of select="."/>
33
+ </xsl:element>
34
+ </xsl:template>
35
+
36
+ <xsl:template
37
+ match="atom:feed[atom:id = 'http://www.google.com/calendar/feeds/default/allcalendars/full']">
38
+ <h3>Choix du calendrier</h3>
39
+ <table>
40
+ <xsl:apply-templates select="atom:entry" />
41
+ </table>
42
+ </xsl:template>
43
+
44
+ <xsl:template match="atom:feed"></xsl:template>
45
+
46
+ <xsl:template
47
+ match="atom:feed[atom:id = 'http://www.google.com/calendar/feeds/default/owncalendars/full']">
48
+ <xsl:element name="h1">
49
+ <xsl:text>Étape 2 : Choisir le calendrier</xsl:text>
50
+ </xsl:element>
51
+ <xsl:element name="ul">
52
+ <xsl:apply-templates select="atom:entry" />
53
+ </xsl:element>
54
+ </xsl:template>
55
+
56
+ <xsl:template match="atom:feed[contains(string(atom:id),'/batch/')]">
57
+ <xsl:if test="not(descendant::batch:status/@code = '201')">
58
+ <xsl:variable name="calendarUrl">
59
+ <xsl:value-of select="substring-before(descendant::atom:id, 'batch')" />
60
+ <xsl:text>batch</xsl:text>
61
+ </xsl:variable>
62
+ <xsl:call-template name="linkButton">
63
+ <xsl:with-param name="title">
64
+ Réessayer
65
+ </xsl:with-param>
66
+ <xsl:with-param name="link">?app=PolyHoraire&amp;action=send
67
+ <xsl:value-of select="atom:title" />
68
+ &amp;calendar=
69
+ <xsl:value-of select="$calendarUrl" />
70
+ </xsl:with-param>
71
+
72
+ </xsl:call-template>
73
+ </xsl:if>
74
+ <xsl:element name="table">
75
+ <xsl:apply-templates select="descendant::atom:entry" />
76
+ </xsl:element>
77
+ </xsl:template>
78
+
79
+ <xsl:template match="atom:entry[namespace::batch]">
80
+ <xsl:element name="tr">
81
+ <xsl:element name="td">
82
+ <xsl:value-of select="descendant::atom:title" />
83
+ </xsl:element>
84
+ <xsl:element name="td">
85
+ <xsl:choose>
86
+ <xsl:when test="descendant::batch:status/@code = '201'">
87
+ <xsl:attribute name="class">
88
+ <xsl:text>succes</xsl:text>
89
+ </xsl:attribute>
90
+ <xsl:element name="strong">
91
+ <xsl:text>Réussis</xsl:text>
92
+ </xsl:element>
93
+ </xsl:when>
94
+ <xsl:otherwise>
95
+ <xsl:attribute name="class">
96
+ <xsl:text>failed</xsl:text>
97
+ </xsl:attribute>
98
+ <xsl:element name="strong">
99
+ <xsl:text>Manqué</xsl:text>
100
+ </xsl:element>
101
+ </xsl:otherwise>
102
+
103
+ </xsl:choose>
104
+ </xsl:element>
105
+ </xsl:element>
106
+
107
+ </xsl:template>
108
+
109
+ <xsl:template match="atom:entry">
110
+ <xsl:element name="li">
111
+ <xsl:attribute name="class">
112
+ <xsl:text>linkButton</xsl:text>
113
+ </xsl:attribute>
114
+ <xsl:element name="span">
115
+ <xsl:attribute name="class">
116
+ <xsl:text>gradientBox</xsl:text>
117
+ </xsl:attribute>
118
+ <xsl:attribute name="style">
119
+ <xsl:text>background-color:</xsl:text>
120
+ <xsl:value-of select="gCal:color/@value" />
121
+ <xsl:text>;</xsl:text>
122
+ </xsl:attribute>
123
+ </xsl:element>
124
+ <xsl:element name="a">
125
+ <xsl:attribute name="href">
126
+ <xsl:text>?app=</xsl:text>
127
+ <xsl:value-of select="$app" />
128
+ <xsl:text>&amp;action=send&amp;calendar=</xsl:text>
129
+ <xsl:text>https://www.google.com/calendar/feeds/</xsl:text>
130
+ <xsl:value-of
131
+ select="substring-after(string(atom:id),'http://www.google.com/calendar/feeds/default/owncalendars/full/')" />
132
+ <xsl:text>/private/full/batch</xsl:text>
133
+ <!--
134
+ <xsl:choose>
135
+ <xsl:when test="function-available(url:encode)">
136
+ <xsl:value-of select="url:encode(atom:id)"/>
137
+ </xsl:when>
138
+ <xsl:when test="function-available(fn:encode-for-uri)">
139
+ <xsl:value-of select="fn:encode-for-uri(atom:id)"></xsl:value-of>
140
+ </xsl:when>
141
+ <xsl:otherwise>
142
+ <xsl:value-of select="php:function('urlencode',string(atom:id))"></xsl:value-of>
143
+ </xsl:otherwise>
144
+ </xsl:choose>
145
+ -->
146
+ </xsl:attribute>
147
+ <xsl:value-of select="atom:title" />
148
+ </xsl:element>
149
+ </xsl:element>
150
+ </xsl:template>
151
+
152
+
153
+ <xsl:template match="section[@name='signIn']">
154
+ <form action="?action=getSchedule" method="post">
155
+ <fieldset>
156
+ <legend>Identifiants du dossier étudiant</legend>
157
+ <div>
158
+ <label for="codeAcces">Code d'accèes</label>
159
+ <input type="text" id="codeAcces" />
160
+ </div>
161
+ <div>
162
+ <label for="motDePasse">Mot de passe</label>
163
+ <input type="password" id="motDePasse" />
164
+ </div>
165
+ <div>
166
+ <label for="dateDeNaissance">Date de naissance (AAMMJJ)</label>
167
+ <input type="text" id="dateDeNaissance" />
168
+ </div>
169
+ <button type="submit">Récupérer l'horaire</button>
170
+ </fieldset>
171
+ </form>
172
+ </xsl:template>
173
+
174
+ <xsl:template match="form">
175
+ <xsl:element name="form">
176
+ <xsl:attribute name="action">
177
+ <xsl:text>?app=</xsl:text>
178
+ <xsl:value-of select="$app" />
179
+ <xsl:text>&amp;action=</xsl:text>
180
+ <xsl:value-of select="@action" />
181
+ </xsl:attribute>
182
+ <xsl:attribute name="method">
183
+ <xsl:text>post</xsl:text>
184
+ </xsl:attribute>
185
+ <xsl:apply-templates select="fieldset" />
186
+ </xsl:element>
187
+ </xsl:template>
188
+
189
+ <xsl:template match="fieldset">
190
+ <xsl:element name="fieldset">
191
+ <xsl:element name="legend">
192
+ <xsl:value-of select="@name" />
193
+ </xsl:element>
194
+ <xsl:apply-templates select="input|button" />
195
+
196
+ </xsl:element>
197
+ </xsl:template>
198
+
199
+
200
+ <xsl:template match="input">
201
+ <xsl:element name="div">
202
+ <xsl:element name="label">
203
+ <xsl:attribute name="for">
204
+ <xsl:value-of select="@id" />
205
+ </xsl:attribute>
206
+ <xsl:value-of select="@label" />
207
+ </xsl:element>
208
+ <xsl:element name="input">
209
+ <xsl:attribute name="type">
210
+ <xsl:value-of select="@type" />
211
+ </xsl:attribute>
212
+ <xsl:attribute name="name">
213
+ <xsl:value-of select="@id" />
214
+ </xsl:attribute>
215
+ </xsl:element>
216
+ </xsl:element>
217
+ </xsl:template>
218
+
219
+ <xsl:template match="button">
220
+ <xsl:element name="div">
221
+ <xsl:element name="button">
222
+ <xsl:attribute name="type">
223
+ <xsl:value-of select="@type" />
224
+ </xsl:attribute>
225
+ <xsl:value-of select="." />
226
+ </xsl:element>
227
+ </xsl:element>
228
+ </xsl:template>
229
+
230
+ <xsl:template match="linkButton">
231
+ <xsl:element name="a">
232
+ <xsl:attribute name="href">
233
+ <xsl:value-of select="@href" />
234
+ </xsl:attribute>
235
+ <xsl:attribute name="class">
236
+ <xsl:text>linkButton</xsl:text>
237
+ </xsl:attribute>
238
+ <xsl:value-of select="." />
239
+ </xsl:element>
240
+ </xsl:template>
241
+
242
+ <xsl:template name="linkButton">
243
+ <xsl:param name="link" />
244
+ <xsl:param name="title" />
245
+ <xsl:element name="a">
246
+ <xsl:attribute name="href">
247
+ <xsl:value-of select="$link" />
248
+ </xsl:attribute>
249
+ <xsl:attribute name="class">
250
+ <xsl:text>linkButton</xsl:text>
251
+ </xsl:attribute>
252
+ <xsl:value-of select="$title" />
253
+ </xsl:element>
254
+ </xsl:template>
255
+
256
+ </xsl:stylesheet>
@@ -0,0 +1,46 @@
1
+ #!/usr/bin/ruby
2
+
3
+ require 'net/http'
4
+ require 'uri'
5
+ require 'nokogiri'
6
+
7
+
8
+ module Poly
9
+
10
+ URL = {
11
+ :connection => "https://www4.polymtl.ca/servlet/ValidationServlet",
12
+ :schedule => "https://www4.polymtl.ca/servlet/PresentationHorairePersServlet"
13
+ }
14
+
15
+ XSLDocs = {
16
+ :poly2XML => File.dirname(__FILE__).to_s + "/polyhoraire/poly2XML.xsl",
17
+ :exportGoogle => File.dirname(__FILE__).to_s + "/google.xml.xsl"
18
+ }
19
+
20
+ @userConfDir = 'conf'
21
+
22
+ def self.userConfDir
23
+ @userConfDir
24
+ end
25
+ def self.userConfDir=(value)
26
+ @userConfDir = value
27
+ end
28
+
29
+ def fetch(uri,params)
30
+ url = URI.parse(uri)
31
+
32
+ req = Net::HTTP::Post.new(url.path)
33
+ req.form_data = params
34
+ con = Net::HTTP.new(url.host, url.port)
35
+ con.use_ssl = true
36
+ response = con.start {|http| http.request(req)}
37
+
38
+ case response
39
+ when Net::HTTPSuccess, Net::HTTPRedirection
40
+ return response
41
+ else
42
+ response.error!
43
+ end
44
+ end
45
+
46
+ end
@@ -0,0 +1,106 @@
1
+ require 'polyhoraire'
2
+
3
+ class Poly::Auth
4
+ include Poly
5
+
6
+ def initialize
7
+ @postParams = Hash.new
8
+ @trimesters = Hash.new
9
+ end
10
+
11
+ def connect(user, password, bday)
12
+ params = {
13
+ "code" => user,
14
+ "nip" => password,
15
+ "naissance" => bday }
16
+ response = fetch(Poly::URL[:connection],params)
17
+ extractInformation(response)
18
+ setStatus(response)
19
+ end
20
+
21
+ def connected?
22
+ return @connected
23
+ end
24
+
25
+
26
+ def credentialsValid?(user,password,bday)
27
+ connect(user,password,bday)
28
+ return connected?
29
+ end
30
+
31
+
32
+ # Getters
33
+ def postParams
34
+ return @postParams
35
+ end
36
+ def trimesters
37
+ return @trimesters
38
+ end
39
+ def error
40
+ return @error
41
+ end
42
+
43
+
44
+
45
+ private
46
+
47
+
48
+
49
+ def extractInformation(response)
50
+ doc = Nokogiri::HTML(response.body.to_s)
51
+ @trimesters = extractTrimesters(doc)
52
+ @postParams = extractPostParams(doc)
53
+ end
54
+
55
+ def extractTrimesters(doc)
56
+ trimesterList = doc.xpath("//select[@name = 'selTrimHorPers']/option")
57
+
58
+ trimesters = Hash.new()
59
+ trimesterList.each do |node|
60
+ trimesters[node.get_attribute(:value)] = node.content
61
+ end
62
+ return trimesters
63
+ end
64
+
65
+ def extractPostParams(doc)
66
+ hiddenFields = doc.xpath("//input[@name != 'trimestre']")
67
+
68
+ postParams = Hash.new()
69
+ hiddenFields.each do |node|
70
+ postParams[node.get_attribute(:name)] = node.get_attribute(:value)
71
+ end
72
+ return postParams
73
+ end
74
+
75
+
76
+
77
+
78
+
79
+
80
+ def setStatus(response)
81
+ doc = Nokogiri::HTML(response.body.to_s)
82
+ nodes = doc.xpath("//center/font[@color = '#FF0000']")
83
+ if nodes.empty?
84
+ @connected = true
85
+ else
86
+ @connected = false
87
+
88
+ # Extract the error number and message
89
+ @error = extractError(nodes.first)
90
+ raise "Not connected"
91
+ end
92
+ end
93
+
94
+ def extractError(node)
95
+ error = node.content
96
+ error.delete! ")"
97
+ splitted = error.split("(")
98
+
99
+ error = Hash.new
100
+ error[:number] = splitted[1]
101
+ error[:message] = splitted[0]
102
+
103
+ return error
104
+ end
105
+ end
106
+