1 # This file is part of NIT ( http://www.nitlanguage.org ).
3 # Copyright 2014 Alexis Laferrière <alexis.laf@xymus.net>
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at
9 # http://www.apache.org/licenses/LICENSE-2.0
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
17 # Daily program to fetch and parse the Web site, update the database and email subscribers
28 # Return a `String` without any HTML tags (such as `<br />`) from `recv`
29 fun strip_tags
: String
36 var at
= str
.index_of_from
('<', from
)
37 if at
== -1 then break
39 new_str
+= str
.substring
(from
, at-from
)
41 at
= str
.index_of_from
('>', at
)
50 # Return an `Array` of the non-empty lines in `self`
52 # assert ["a", "asdf", "", " ", " ", "b"].join("\n").to_clean_lines == ["a", "asdf", "b"]
53 fun to_clean_lines
: Array[String]
55 var orig_lines
= split_with
("\n")
56 var new_lines
= new Array[String]
58 for line
in orig_lines
do
62 if line
== " " then continue
63 if line
.is_empty
then continue
65 new_lines
.add line
.to_s
72 # Main logic of the program to be executed daily
74 # The street on which is the Benelux
77 # The url of this precise Benelux
78 var url
= "www.brasseriebenelux.com/{street}" is lazy
80 # Path to the database
81 var db_path
= "benitlux_{street}.db" is lazy
83 # Where to save the sample email
84 var sample_email_path
= "benitlux_{street}.email" is lazy
86 # Execute the main program logic
87 fun run
(send_emails
: Bool)
90 var body
= download_html_page
92 if opts
.verbose
.value
> 1 then
97 # Parse the Web page and get the available beers
98 var beers
= parse_beers_from_html
(body
)
100 if opts
.verbose
.value
> 0 then
105 var db
= new BenitluxDB.open
(db_path
)
107 # Update the database with the beers of the day
108 db
.insert_beers_of_the_day beers
110 # Query the beer-related events of today
111 var beer_events
= db
.beer_events_today
113 if beer_events
== null then
114 print_error
"Failed to read beer events from the DB"
119 # Generate the email title and content, store them in attributes
120 generate_email beer_events
122 # Save as sample email to file
123 var f
= new FileWriter.open
(sample_email_path
)
124 f
.write email_title
+ "\n"
125 for line
in email_content
do f
.write line
+ "\n"
128 # Set the email if desired
130 var subs
= db
.subscribers
131 if opts
.verbose
.value
> 0 then
132 print
" # Subscribers"
141 # Fetch the Web page at `url`
142 fun download_html_page
: String
144 var request
= new CurlHTTPRequest(url
)
145 var response
= request
.execute
147 if response
isa CurlResponseSuccess then
148 var body
= response
.body_str
150 else if response
isa CurlResponseFailed then
151 print
"Failed downloading URL '{url}' with: {response.error_msg} ({response.error_code})"
157 # Extract the beers of the day information from the HTML if `body`
158 fun parse_beers_from_html
(body
: String): HashSet[Beer]
160 # Parts of the HTML page expected to encapsulate the interesting section
161 var header
= "<h1>Bières<br /></h1>"
162 var ender
= "</div></div></div>"
164 var match
= body
.search
(header
)
165 assert match
!= null else print body
166 var start
= match
.after
168 match
= body
.search_from
(ender
, start
)
170 var finish
= match
.from
172 var of_interest
= body
.substring
(start
, finish-start
)
173 var lines
= of_interest
.strip_tags
.to_clean_lines
175 if opts
.verbose
.value
> 0 then
180 var beers
= new HashSet[Beer]
182 var parts
= line
.split
("- ")
183 if parts
.length
>= 2 then
184 # Let the DB set the id, use 0 temporary
185 beers
.add
new Beer(0, parts
[0].trim
, parts
[1].trim
)
191 # Content lines of the email
192 var email_content
: Array[String] is noautoinit
195 var email_title
: String is noautoinit
197 # Generate email and fill the attributes `email_content` and `email_title`
198 fun generate_email
(beer_events
: BeerEvents)
200 email_title
= "Benitlux {street.capitalized}{beer_events.to_email_title}"
201 email_content
= beer_events
.to_email_content
204 # Send the email to all the addresses in `subs`
205 fun send_emails_to
(subs
: Array[String])
208 var unsub_link
= "http://benitlux.xymus.net/?unsub=&email={email}"
210 {{{email_content.join("<br />\n")}}}
212 To unsubscribe, go to <a href="{{{unsub_link}}}">{{{unsub_link}}}</a>
215 var mail
= new Mail("Benitlux <benitlux@xymus.net>", email_title
, content
)
217 mail
.header
["Content-Type"] = "text/html; charset=\"UTF-8\
""
218 mail
.header
["List-Unsubscribe"] = unsub_link
219 mail
.header
["Precedence"] = "bulk"
220 mail
.encrypt_with_base64
227 redef class OptionContext
228 # Shall we mail the mailing list?
229 var send_emails
= new OptionBool("Send emails to subscribers", "-e", "--email")
231 # Display more debug messages
232 var verbose
= new OptionCount("Display extra debug messages", "-v")
234 # Print the usage message
235 var help
= new OptionBool("Print this help message", "-h", "--help")
237 redef init do add_option
(send_emails
, verbose
, help
)
241 # Command line options
242 var opts
= new OptionContext
245 # Avoid executing when running tests
246 if "NIT_TESTING".environ
== "true" then exit
0
249 if not opts
.errors
.is_empty
or opts
.help
.value
== true then
250 print opts
.errors
.join
("\n")
251 print
"Usage: benitlux_daily [Options]"
256 var ben
= new Benitlux("sherbrooke")
257 ben
.run
(opts
.send_emails
.value
)
259 # The parsing logic for the wellington location is active (to gather data)
260 # but the web interface do not allow to subscribe to its mailing list.
262 # TODO revamp mailing list Web interface
263 ben
= new Benitlux("wellington")
264 ben
.run
(opts
.send_emails
.value
)