31 Commits b73eb734dd ... 11c1b91764

Author SHA1 Message Date
  Toni Fadjukoff 11c1b91764 Remove PERL scripts 5 years ago
  Toni Fadjukoff f0f332eb97 Do not show empty foods for amica menus 5 years ago
  Toni Fadjukoff f602e72a82 Support Fazer Minerva menu again 5 years ago
  Toni Fadjukoff c000e1ed6a Update Juvenes Frenckell menus 5 years ago
  Toni Fadjukoff 9c0ca34233 Update Alakuppila IDs, remove Cafe Lea (My salad) 5 years ago
  Toni Fadjukoff ba5a7bf9d7 Update Juvenes URLs (Finally short and HTTPS) 5 years ago
  Toni Fadjukoff 76ee24eaa3 Make menu update more resistant to HTTP errors and API changes 5 years ago
  Toni Fadjukoff 1176596fe9 Update Reaktori to be fazerfood branched from amica brand (same API) 5 years ago
  Toni Fadjukoff 12e9911b65 Update Juvenes URLs 5 years ago
  Toni Fadjukoff c180b79650 Fix Amica day matching 5 years ago
  Toni Fadjukoff b627518785 Update Amica JSON urls and parsing 5 years ago
  Toni Fadjukoff ac6d00fbdd Add debug to pnalib to find why something fails 6 years ago
  Toni Fadjukoff 8b168d81c9 Juvenes does not use jsonp anymore for menu 6 years ago
  Toni Fadjukoff ade2f4a039 Update Juvenes Ziberia menus 6 years ago
  Toni Fadjukoff c778908346 Fix sodexo failing when no menu available 6 years ago
  Toni Fadjukoff 99e1e63854 Add reason for juvenes if not open 7 years ago
  Toni Fadjukoff 9ec32b6a23 Do not display old food for juvenes 7 years ago
  www-data 9c24e3248c Merge branch 'feature/python-scripts' of http://git.lamperi.name/lamperi/pna into feature/python-scripts 7 years ago
  Toni Fadjukoff 5452c46cb5 Fix link to Juvenes konehuone (python) 7 years ago
  Toni Fadjukoff 65c6d6cde8 Merge branch 'feature/python-scripts' of https://git.lamperi.name/lamperi/pna into feature/python-scripts 7 years ago
  Toni Fadjukoff cd35451b6d Fix link to Juvenes konehuone 7 years ago
  Toni Fadjukoff 8d21c5da4a Add hint on charset to html template 7 years ago
  www-data 751528f891 Merge branch 'feature/python-scripts' of http://git.lamperi.name/lamperi/pna into feature/python-scripts 7 years ago
  www-data 4f81e1137e on prod use_old = False 7 years ago
  Toni Fadjukoff 72af6297f3 Make food executable 7 years ago
  Toni Fadjukoff 63770c92b3 Remove debug prints 7 years ago
  Toni Fadjukoff 2a47b86f2e Start to use py scripts 7 years ago
  Toni Fadjukoff 0f24186b2f Add campusravita 7 years ago
  Toni Fadjukoff 0a74cd1676 Converted all of code to python except pikante 7 years ago
  Toni Fadjukoff 1730a363c5 Fix encoding issues, minor compatibility fixes 7 years ago
  Toni Fadjukoff 985b22a42b Start creating python version of scripts 7 years ago
14 changed files with 887 additions and 934 deletions
  1. 0 101
      amica.pl
  2. 81 0
      amica.py
  3. 175 0
      campusravita.py
  4. 2 2
      cgi-bin/food.cgi
  5. 0 470
      food.pl
  6. 418 0
      food.py
  7. 0 102
      juvenes.pl
  8. 102 0
      juvenes.py
  9. 27 0
      pikante.py
  10. 0 169
      pky.pl
  11. 39 0
      pnalib.py
  12. 1 1
      ruoka.js
  13. 0 89
      sodexo.pl
  14. 42 0
      sodexo.py

+ 0 - 101
amica.pl View File

@@ -1,101 +0,0 @@
1
-use vars qw(@day_names);
2
-use JSON;
3
-
4
-my @restaurant_info = (
5
-  [ "(TaY) Amica Minerva", "http://www.amica.fi/minerva", "", "middle", "http://www.amica.fi/api/restaurant/menu/week?language=fi&restaurantPageId=7381" ],
6
-  [ "(TaY) Tampereen normaalikoulun ravintola", "http://www.amica.fi/tampereennormaalikoulu", "", "middle", "http://www.amica.fi/api/restaurant/menu/week?language=fi&restaurantPageId=6655" ],
7
-  [ "(TTY) Ravintola Reaktori", "http://www.amica.fi/reaktori", "", "middle", "http://www.amica.fi/api/restaurant/menu/week?language=fi&restaurantPageId=69171" ]
8
-);
9
-
10
-sub utf8_to_8859 {
11
-  $_ = shift;
12
-
13
-  s/ä/ä/g;
14
-  s/ö/ö/g;
15
-  s/Ä/Ä/g;
16
-  s/Ö/Ö/g;
17
-  return $_;
18
-}
19
-
20
-sub parse_amica {
21
-  my ($fname, $info_ref) = @_;
22
-  open(FILE, $fname);
23
-  my $json = do { local $/; <FILE> };
24
-  close(FILE);
25
-  
26
-  my $title = @{$info_ref}[0];
27
-  my $week = `date +%V`;
28
-  my @cur_day_foods = ();
29
-  my @week_foods = ();
30
-
31
-  my $data = from_json($json);
32
-  my $LunchMenus = $data->{'LunchMenus'};
33
-  foreach my $LunchMenu (@$LunchMenus) {
34
-    my $SetMenus = $LunchMenu->{'SetMenus'};
35
-    my $Html = $LunchMenu->{'Html'};
36
-    if (length($Html) > 0) {
37
-      my @Menus = split('<p>', $Html);
38
-      foreach my $SetMenu (@Menus) {
39
-        my @Meals = split('<br />', $SetMenu);
40
-	foreach my $Meal (@Meals) {
41
-	  my @parts = split('\\(', $Meal);
42
-	  $cur_food .= "\n" if $cur_food ne "";
43
-	  $cur_food .= @parts[0];
44
-	  my @Diets = split(",\s*", substr(@parts[1], 0, index(@parts[1], ')')));
45
-	  $cur_food .= ' (' . join(', ', @Diets) . ')' if @Diets;
46
-	}
47
-        push @cur_day_foods, utf8_to_8859($cur_food) if ($cur_food ne "");
48
-        $cur_food = "";
49
-      }
50
-    } else {
51
-      foreach my $SetMenu (@$SetMenus) {
52
-        my $Meals = $SetMenu->{'Meals'};
53
-        foreach my $Meal (@$Meals) {
54
-          $cur_food .= "\n" if $cur_food ne "";
55
-          $cur_food .= $Meal->{'Name'};
56
-          my $Diets = $Meal->{'Diets'};
57
-          $cur_food .= ' (' . join(', ', @$Diets) . ')';
58
-        }
59
-        push @cur_day_foods, utf8_to_8859($cur_food) if ($cur_food ne "");
60
-        $cur_food = "";
61
-      }
62
-    }
63
-
64
-    push @week_foods, [@cur_day_foods];
65
-    @cur_day_foods = ();
66
-  }
67
-
68
-  return [ $title, "", $week, [ @week_foods ], $info_ref ];
69
-}
70
-
71
-sub get_amica_restaurant {
72
-  my $use_old = shift;
73
-  my $count = 0;
74
-  my @restaurants = ();
75
-  my $weekDay = `date +%w`;
76
-  chomp($weekDay);
77
-  my $first_day = $weekDay == 1 
78
-    ? `date --date="today" +%Y-%m-%d`
79
-    : `date --date="last monday" +%Y-%m-%d`;
80
-  chomp($first_day);
81
-  my $last_day = $weekDay == 0
82
-    ? `date --date="today" +%Y-%m-%d`
83
-    : `date --date="next sunday" +%Y-%m-%d`;
84
-  chomp($last_day);
85
-  foreach my $i (@restaurant_info) {
86
-    my @info = @{$i};
87
-    my $temp_fname = "amica$count.temp.html";
88
-    my $url = "${info[4]}&weekDate=$first_day";
89
-    if (!-f $temp_fname || !$use_old) {
90
-      system("wget -q --timeout=10 -O $temp_fname.tmp '$url' && mv $temp_fname.tmp $temp_fname") if ($url ne "");
91
-    }
92
-    if (-f $temp_fname) {
93
-      $info[4] = $url;
94
-      push @restaurants, parse_amica($temp_fname, \@info);
95
-    }
96
-    $count++;
97
-  }
98
-  return @restaurants;
99
-}
100
-
101
-1;

+ 81 - 0
amica.py View File

@@ -0,0 +1,81 @@
1
+import pnalib
2
+import datetime
3
+
4
+restaurant_info = [
5
+  [ "(TaY) Amica Minerva", "https://www.fazerfoodco.fi/ravintolat/Ravintolat-kaupungeittain/tampere/minerva/", "", "middle", "https://www.fazerfoodco.fi/modules/json/json/Index?costNumber=0815&language=fi" ],
6
+  #[ "(TaY) Tampereen normaalikoulun ravintola", "http://www.amica.fi/tampereennormaalikoulu", "", "middle", "https://www.amica.fi/modules/json/json/Index?costNumber=0811&language=fi" ],
7
+  [ "(TTY) Ravintola Reaktori", "https://www.fazerfoodco.fi/ravintolat/Ravintolat-kaupungeittain/tampere/reaktori/", "", "middle", "https://www.fazerfoodco.fi/modules/json/json/Index?costNumber=0812&language=fi" ]
8
+]
9
+
10
+def get_restaurants(use_old, week):
11
+    today = datetime.date.today()
12
+    week_day = today.isocalendar()[2]
13
+    this_monday = today - datetime.timedelta(days=week_day-1)
14
+    date_strings = []
15
+    for i in range(7):
16
+        date = this_monday + datetime.timedelta(days=i)
17
+        date_strings.append(date.strftime("%Y-%m-%d"))
18
+
19
+    restaurants = []
20
+    for count, info in enumerate(restaurant_info):
21
+        title = info[0]
22
+        url = info[4]
23
+        week_foods = handle_one(use_old, date_strings, count, url)
24
+        restaurants.append([title, "", week, week_foods, info])
25
+    return restaurants
26
+
27
+def handle_one(use_old, date_strings, count, url):
28
+    temp_fname = "amica_{count}.temp.json".format(count = count)
29
+
30
+    week_foods = {}
31
+    data = pnalib.get_json_file(url, temp_fname, use_old)
32
+    if not data:
33
+        print("Fazer no data")
34
+        return week_foods
35
+
36
+    lunch_menus = data["MenusForDays"]
37
+    if not lunch_menus:
38
+        print("Fazer no MenusForDays")
39
+        return week_foods
40
+
41
+    for lunch_menu in lunch_menus:
42
+        date = lunch_menu["Date"].split("T")[0]
43
+        try:
44
+            week_day = date_strings.index(date)
45
+        except:
46
+            print("Fazer no date dates=({}), key=({})".format(date_strings, date))
47
+            continue
48
+        current_day_foods = []
49
+        set_menus = lunch_menu["SetMenus"]
50
+        html = lunch_menu.get("Html", "")
51
+        if len(html):
52
+                current_day_foods.append(handle_html(html))
53
+        else:
54
+            for set_menu in set_menus:
55
+                meals = set_menu["Components"]
56
+                food = []
57
+                for meal in meals:
58
+                    food.append(format_meal_allergies(meal))
59
+                if food:
60
+                    current_day_foods.append("\n".join(food))
61
+        week_foods[week_day] = current_day_foods
62
+    return week_foods
63
+
64
+# Onko enää tarpeellinen?
65
+def handle_html(html):
66
+    menus = html.split("<p>")
67
+    for set_menu in menus:
68
+        meals = set_menu.split("<br />")
69
+        food = []
70
+        for meal in meals:
71
+            food.append(format_meal_allergies(meal))
72
+    return "\n".join(food)
73
+
74
+def format_meal_allergies(meal):
75
+    parts = meal.split("(")
76
+    current_food = parts[0]
77
+    diets = [s.strip() for s in parts[1].split(")")[0].split(",")]
78
+    if diets:
79
+        current_food += " ({allergies})".format(allergies=", ".join(diets))
80
+    return current_food
81
+

+ 175 - 0
campusravita.py View File

@@ -0,0 +1,175 @@
1
+import pnalib
2
+import html.parser
3
+import re
4
+
5
+url = "http://www.campusravita.fi/ruokalista";
6
+
7
+restaurant_info = [
8
+  [ "(TAMK) Campus Food", url, "", "middle" ],
9
+]
10
+
11
+class Tracker(object):
12
+
13
+    def __init__(self, tag, attr_match=None, on_started=None, on_ended=None, on_data=None):
14
+        self.tag = tag
15
+        self.attr_match = attr_match
16
+        self.on_started = on_started
17
+        self.on_ended = on_ended
18
+        self.on_data = on_data
19
+        self.nesting = 0
20
+
21
+    def handle_starttag(self, tag, attrs):
22
+        if self.tag == tag:
23
+            if self.nesting:
24
+                self.nesting += 1
25
+            else:
26
+                attrs_matched = False
27
+                if self.attr_match is None:
28
+                    attrs_matched = True
29
+                else:
30
+                    for attr in attrs:
31
+                        if attr[0] == self.attr_match[0] and self.attr_match[1].match(attr[1]):
32
+                            attrs_matched = True
33
+                if attrs_matched:
34
+                    self.nesting = 1
35
+                    if self.on_started:
36
+                        self.on_started()
37
+
38
+
39
+    def handle_endtag(self, tag):
40
+        if self.nesting and self.tag == tag:
41
+            self.nesting -= 1
42
+            if self.nesting == 0 and self.on_ended:
43
+                self.on_ended()
44
+
45
+    def handle_data(self, data):
46
+        if self.nesting and self.on_data:
47
+            self.on_data(data)
48
+
49
+    def __bool__(self):
50
+        return self.nesting > 0
51
+
52
+class CampusravitaHTMLParser(html.parser.HTMLParser):
53
+    week_re = re.compile("Ruokalista - Viikko (\d+)")
54
+    lunch_re = re.compile("Lounas|Deli-lounas")
55
+    week = None
56
+
57
+    def __init__(self):
58
+        html.parser.HTMLParser.__init__(self)
59
+        self._trackers = []
60
+        self.in_h3 = self._register_tracker("h3", on_data=self.handle_h3)
61
+        # Everything in inside menu
62
+        self.in_menu = self._register_tracker("section", ("id", "block-system-main"),
63
+                on_started=self.handle_menu_start, on_ended=self.handle_menu_end)
64
+        # Date comes after menu
65
+        self.in_date_display = self._register_tracker("span", ("class", "date-display-single"),
66
+                on_data=self.handle_date_display)
67
+        # Lunch element contains one meal
68
+        self.in_lunch = self._register_tracker("div", ("about", r"/fi/field-collection/field-ruoka-annos/\d+"),
69
+                on_started=self.handle_lunch_start, on_ended=self.handle_lunch_end)
70
+        # Next element contains food name 
71
+        self.in_lunch_food = self._register_tracker("div", ("class", ".*field-name-field-nimi.*"),
72
+                on_data=self.handle_lunch_food)
73
+        # Next element contains food allergies
74
+        self.in_allergy = self._register_tracker("div", ("class", ".*field-name-field-ruokavaliot.*"),
75
+                on_started=self.handle_allergy_start, on_ended=self.handle_allergy_end)
76
+        # Next element contains allergy short name
77
+        self.in_allergy_short = self._register_tracker("div", ("class", ".*field-name-title field-type-ds.*"),
78
+                on_data=self.handle_allergy)
79
+        # Next element contains lunch price
80
+        self.in_lunch_price = self._register_tracker("div", ("class", ".*field-name-field-annoksen-hinta.*"))
81
+        self.lunch_type_match = False
82
+        self.lunch = None
83
+
84
+        self.week_foods = {}
85
+
86
+    def _register_tracker(self, tag, attr_match=None, **kwargs):
87
+        tracker = Tracker(tag, (attr_match[0], re.compile(attr_match[1])) if attr_match else None, **kwargs)
88
+        self._trackers.append(tracker)
89
+        return tracker
90
+
91
+    def handle_date_display(self, data):
92
+        index = -1
93
+        if "Maanantai" in data:
94
+            index = 0
95
+        elif "Tiistai" in data:
96
+            index = 1
97
+        elif "Keskiviikko" in data:
98
+            index = 2
99
+        elif "Torstai" in data:
100
+            index = 3
101
+        elif "Perjantai" in data:
102
+            index = 4
103
+        elif "Lauantai" in data:
104
+            index = 5
105
+        elif "Sunnuntai" in data:
106
+            index = 6
107
+        if index >= 0:
108
+            self.current_day = []
109
+            self.week_foods[index] = self.current_day
110
+
111
+    def handle_h3(self, data):
112
+        if self.in_menu:
113
+            lunch_match = self.lunch_re.match(data)
114
+            self.lunch_type_match = bool(lunch_match)
115
+
116
+    def handle_menu_start(self):
117
+        pass
118
+
119
+    def handle_menu_end(self):
120
+        pass
121
+
122
+    def handle_allergy(self, data):
123
+        data = data.strip()
124
+        if self.in_allergy and self.in_allergy_short and self.lunch and data:
125
+            self.lunch["allergies"].append(data) 
126
+
127
+    def handle_allergy_start(self):
128
+        pass
129
+
130
+    def handle_allergy_end(self):
131
+        pass
132
+
133
+    def handle_lunch_food(self, data):
134
+        data = data.strip()
135
+        if self.lunch and data:
136
+            self.lunch["food"].append(data)
137
+
138
+    def handle_lunch_start(self):
139
+        if self.lunch_type_match:
140
+            self.lunch = {"food": [], "allergies": []}
141
+
142
+    def handle_lunch_end(self):
143
+        if self.lunch:
144
+            #print(repr(self.lunch).encode("cp1252", "ignore"))
145
+            menu = "{menu} ({allergies})".format(menu=self.lunch["food"][0], allergies=", ".join(self.lunch["allergies"]))
146
+            self.current_day.append(menu)
147
+            self.lunch = None
148
+
149
+    def handle_starttag(self, tag, attrs):
150
+        for tracker in self._trackers:
151
+            tracker.handle_starttag(tag, attrs)
152
+
153
+    def handle_endtag(self, tag):
154
+        for tracker in self._trackers:
155
+            tracker.handle_endtag(tag)
156
+
157
+    def handle_data(self, data):
158
+        for tracker in self._trackers:
159
+            tracker.handle_data(data)
160
+        week_match = self.week_re.match(data)
161
+        if week_match:
162
+            self.week = int(week_match.group(1))
163
+
164
+def get_restaurants(use_old, week):
165
+
166
+    data = pnalib.get_file(url, "campusravita.html", use_old)
167
+    parser = CampusravitaHTMLParser()
168
+    parser.feed(data)
169
+
170
+    restaurants = []
171
+    if parser.week is not None:
172
+        restaurants.append([restaurant_info[0][0], "", parser.week, parser.week_foods, restaurant_info[0]])
173
+
174
+    return restaurants
175
+

+ 2 - 2
cgi-bin/food.cgi View File

@@ -13,10 +13,10 @@ print '<?xml version="1.0" encoding="iso-8859-1"?>
13 13
 <body>';
14 14
 
15 15
 my $min_refresh_secs = 60*5;
16
-my @st = stat("/var/www/rna/1.html");
16
+my @st = stat("/var/www/pna/1.html");
17 17
 if ($st[9] > time-$min_refresh_secs) {
18 18
   print "<p>Ruokalistaa on jo päivitetty alle 5min sitten.</p>";
19
-} elsif (system("cd /var/www/rna && ./food.pl") == 0) {
19
+} elsif (system("cd /var/www/pna && ./food.py") == 0) {
20 20
   print "<p>Ruokalista päivitetty.</p>";
21 21
 } else {
22 22
   print "<p>Virhe päivittäessä ruokalistaa.</p>";

+ 0 - 470
food.pl View File

@@ -1,470 +0,0 @@
1
-#!/usr/bin/env perl
2
-
3
-# Ruokalistaparseri v1.5.4
4
-# Copyright (c) 2007-2010 Timo Sirainen
5
-#               2011-2013 Toni Fadjukoff
6
-# This is Public Domain
7
-
8
-use strict;
9
-use POSIX qw(strftime mktime);
10
-use HTML::TokeParser;
11
-use HTML::Entities;
12
-
13
-use vars qw(@day_names);
14
-@day_names = ( "Maanantai", "Tiistai", "Keskiviikko", "Torstai", 
15
-	       "Perjantai", "Lauantai", "Sunnuntai" );
16
-
17
-require 'amica.pl';
18
-require 'sodexo.pl';
19
-require 'juvenes.pl';
20
-require 'pky.pl';
21
-
22
-my @allergies = ( "M", "L", "VL", "G", "K", "Ve" );
23
-my %allergy_descriptions = (
24
-  "M" => "Maidoton",
25
-  "L" => "Laktoositon",
26
-  "VL" => "Vähälaktoosinen",
27
-  "G" => "Gluteiiniton",
28
-  "K" => "Kasvis",
29
-  "Ve" => "Vegaani"
30
-);
31
-
32
-my $global_prefix = "";
33
-my $use_old = 0; # 1 is good for testing, 0 for production system!
34
-my @unordered;
35
-
36
-my @l = localtime;
37
-my $this_week = strftime("%V", @l);
38
-
39
-push @unordered, get_amica_restaurant($use_old);
40
-push @unordered, get_juvenes_restaurants($use_old);
41
-push @unordered, get_sodexo_restaurants($use_old);
42
-push @unordered, get_pky_restaurants($use_old, $l[6] == 0 || $l[6] == 6);
43
-
44
-my $max_week = 0;
45
-foreach my $r (@unordered) {
46
-  my $week = @{$r}[2];
47
-  $max_week = $week if ($week > $max_week || $week == 1);
48
-}
49
-
50
-if ($l[6] != 0 && $this_week != $max_week) {
51
-  # it's not sunday, don't force next week's menu yet
52
-  $max_week = $this_week;
53
-}
54
-
55
-my $stamp = time() - 3600*24*7;
56
-my $max_week_daterange = "";
57
-if ($max_week >= 1 && $max_week <= 52) {
58
-  # figure out the date range
59
-  for (;;) {
60
-    my $stamp_week = strftime("%V", localtime($stamp));
61
-    last if ($stamp_week == $max_week);
62
-    $stamp += 3600*24;
63
-  }
64
-  my @l1 = localtime($stamp);
65
-  my @l2 = localtime($stamp + 3600*24*6);
66
-  if ($l1[4] == $l2[4]) {
67
-    # same month
68
-    $max_week_daterange = $l1[3]."-".$l2[3].".".($l1[4]+1).".";
69
-  } else {
70
-    # different months
71
-    $max_week_daterange = $l1[3].".".($l1[4]+1)."-".$l2[3].".".($l2[4]+1).".";
72
-  }
73
-  $max_week_daterange = " ($max_week_daterange)"
74
-}
75
-
76
-my $file_header = '<?xml version="1.0" encoding="iso-8859-1"?>
77
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
78
-<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="fi" lang="fi">
79
-<head>
80
-  <title>Ruokalistat</title>
81
-  <link rel="stylesheet" type="text/css" href="'.$global_prefix.'/ruoka.css" />
82
-</head>
83
-<body>
84
-
85
-<div id="notice" style="border: 1px solid black; border-radius: 5px; padding: 5px;">
86
-PNA.fi on kolmannen osapuolen tarjoama palvelu. En voi taata ruokalistojen oikeellisuutta.  Virallisen ruokalistan saat näkyviin siirtymällä ravintolan omille sivuille painamalla sen nimestä. Jos huomaat ruokalistassa virheen, nopeiten virhe saadaan pois näkyvistä kun lähetät minulle siitä sähköpostia: <a href="mailto:lamperi+pna@gmail.com">lamperi+pna@gmail.com</a>
87
-</div>
88
-
89
-<form method="get" action="/cgi-bin/food.cgi">
90
-';
91
-my $file_footer = "<div class=\"footer\">Päivitetty ".
92
-    strftime("%d.%m.%Y %H:%M:%S", localtime).
93
-    " <input type=\"submit\" value=\"Päivitä nyt\" />".
94
-    " / Palaute <a href=\"mailto:lamperi+pna\@gmail.com\">lamperi+pna\@gmail.com</a>".
95
-    " / <a href=\"$global_prefix/code.html\">Koodit täältä</a>".
96
-    " / <a href=\"$global_prefix/pna.html\">Mikä on PNA?</a>".
97
-    "</div>\n</form>\n</body></html>\n";
98
-
99
-sub find_last_day_with_foods {
100
-  my $restaurants_ref = shift;
101
-
102
-  my $last_day = 0;
103
-  foreach my $r (@${restaurants_ref}) {
104
-    my ($title, $open_hours, $week, $week_foods_ref) = @{$r};
105
-    my @week_foods = @{$week_foods_ref};
106
-    for (my $day = 0; $day < 7; $day++) {
107
-      if (defined($week_foods[$day])) {
108
-	$last_day = $day if ($day > $last_day);
109
-      }
110
-    }
111
-  }
112
-  return $last_day;
113
-}
114
-
115
-sub write_days_header {
116
-  my ($fout, $day, $last_day) = @_;
117
-  
118
-  print $fout "  <span class=\"days\">";
119
-  for (my $i = 0; $i <= $last_day; $i++) {
120
-    if ($i == $day) {
121
-      print $fout $day_names[$i]." ";
122
-    } else {
123
-      print $fout "<a href=\"".($i+1).".html\">".$day_names[$i]."</a> ";
124
-    }
125
-  }
126
-  if ($day < 0) {
127
-    print $fout "Taulukko";
128
-  } else {
129
-    print $fout "<a href=\"table.html\">Taulukko</a>";
130
-  }
131
-  print $fout "</span>\n";
132
-}
133
-
134
-sub write_prefix_header {
135
-  my ($fout, $prefix, $day) = @_;
136
-  
137
-  $day = "table" if ($day == 0);
138
-  print $fout "<span class=\"location\">";
139
-  if ($prefix eq "") {
140
-    print $fout "Kaikki ";
141
-  } else {
142
-    print $fout "<a href=\"$global_prefix/$day.html\">Kaikki</a> ";
143
-  }
144
-  if ($prefix eq "tay/") {
145
-    print $fout "TaY ";
146
-  } else {
147
-    print $fout "<a href=\"$global_prefix/tay/$day.html\">TaY</a> ";
148
-  }
149
-  if ($prefix eq "tays/") {
150
-    print $fout "TAYS ";
151
-  } else {
152
-    print $fout "<a href=\"$global_prefix/tays/$day.html\">TAYS</a> ";
153
-  }
154
-  if ($prefix eq "tty/") {
155
-    print $fout "TTY ";
156
-  } else {
157
-    print $fout "<a href=\"$global_prefix/tty/$day.html\">TTY</a> ";
158
-  }
159
-  print $fout "</span>\n";
160
-}
161
-
162
-sub write_day {
163
-  my ($day, $header, $outfname, $last_day, $restaurants_ref, $prefix) = @_;
164
-  my @restaurants = @{$restaurants_ref};
165
-
166
-  open(my $fout, ">$outfname") || die ("Can't create file $outfname");
167
-  print $fout "$file_header<h1>$header</h1>\n";
168
-  # print weekday links
169
-  print $fout "<div class=\"title\">\n";
170
-  write_days_header($fout, $day, $last_day);
171
-  print $fout "  <span class=\"allergy\">Näytä: ";
172
-  foreach my $a (@allergies) {
173
-    print $fout "<input type=\"checkbox\" name=\"allergy_$a\" id=\"allergy_$a\" onclick=\"highlight()\" />";
174
-    print $fout "<span title=\"".$allergy_descriptions{$a}."\">$a</span>";
175
-  }
176
-  print $fout "</span>\n";
177
-  write_prefix_header($fout, $prefix, $day+1);
178
-  print $fout "</div>\n";
179
-
180
-  # print foods
181
-  my $foodnum = 0;
182
-  my %eatable_food_numbers;
183
-  my %maybe_eatable_food_numbers;
184
-  my $class = "left";
185
-  print $fout "<div class=\"foods\"><div class=\"$class\">\n";
186
-  foreach my $r (@restaurants) {
187
-    my ($title, $open_hours, $week, $week_foods_ref, $info_ref) = @{$r};
188
-    my ($title2, $url, $lazy_allergies, $info_class) = @{$info_ref};
189
-    my @week_foods = @{$week_foods_ref};
190
-    if (defined($week_foods[$day]) || $day < 5) {
191
-
192
-      if ($info_class ne $class) {
193
-	$class = $info_class;
194
-	print $fout "</div><div class=\"$class\">\n";
195
-      }
196
-      $url =~ s/&/&amp;/g;
197
-      print $fout "<h2><a href=\"$url\">$title</a></h2>\n";
198
-      
199
-      if (!defined($week_foods[$day])) {
200
-	print $fout "<p class=\"missing\">Ruokalistaa ei saatavilla.</p>";
201
-	next;
202
-      }
203
-      if ($week ne "" && $week != $max_week) {
204
-	if ($week > $max_week || ($week == 1 && $max_week == 52)) {
205
-	  # early..
206
-	  print $fout "<p class=\"nextweek\">Viikon $week ruokalista:</p>";
207
-	} else {
208
-	  print $fout "<p class=\"missing\">Saatavilla vain viikon $week ruokalista.</p>";
209
-	  next;
210
-	}
211
-      }
212
-      if (scalar(@{$week_foods[$day]}) == 0) {
213
-	print $fout "<p class=\"missing\">Ei ruokatietoja päivälle.</p>";
214
-	next;
215
-      }
216
-      
217
-      print $fout "<ul class=\"food\">\n";
218
-      foreach my $food (@{$week_foods[$day]}) {
219
-	my $output = "";
220
-	my %total_allergies;
221
-	my %maybe_allergies;
222
-	my $part_count = 0;
223
-	foreach my $part (split("\n", $food)) {
224
-	  next if ($part =~ /^(Peruna|Riisi) /); # who cares?
225
-	  # fries: well, maybe we do care, but we don't care about allergy stuff
226
-	  # and keep it in the same line as the previous food so as not to
227
-	  # waste visible space
228
-	  my $fries = ($part =~ /^(Tikkuperunat|Ranskalaiset perunat)/);
229
-	  $part_count++;
230
-	  
231
-	  # add missing () around allergies
232
-	  $part =~ s/ (([MLGK]|VL|Ve|Veg|Hot)(,([MLGK]|VL|Ve|Veg|Hot|))+)$/ ($1)/;
233
-	  
234
-	  if ($part =~ /^(.*) \(([^\)]+)\)$/) {
235
-	    # fix allergy issues
236
-	    my ($food, $allergy) = ($1, $2);
237
-	    # standardization
238
-	    $allergy =~ s/Kasvis/K/g;
239
-	    $allergy =~ s/([MLGK]|VL)([MLGK]|VL)/$1,$2/g;
240
-	    # spaces to commas
241
-	    $allergy =~ s/saatavana[: ]+(.*)$/eriks: $1/;
242
-	    $allergy =~ s/ +/,/g;
243
-	    # remove double commas
244
-	    $allergy =~ s/,+/,/g;
245
-	    # eriks: standardization
246
-	    $allergy =~ s/,?eriks:,?/, eriks: /g;
247
-	    # remove extra commas/spaces from beginning/end
248
-	    $allergy =~ s/^[, ]+//;
249
-	    $allergy =~ s/[, ]+$//;
250
-	    $part = "$food ($allergy)";
251
-	  }
252
-	  
253
-	  $output .= "<br />\n" if ($output ne "" && !$fries);
254
-	  if ($part =~ /Saatavana myös: (.*)/) {
255
-	    # standardize allergy stuff
256
-	    my $alt = $1;
257
-	    $alt =~ s/^\((.*)\)$/$1/;
258
-	    $alt =~ s/[, ]+/,/g;
259
-	    $alt =~ s/^,+//;
260
-	    $alt =~ s/,+$//;
261
-	    $part =~ s/\)[- ]*Saatavana myös:.*/, eriks: $alt)/;
262
-	    $part =~ s/[- ]*Saatavana myös:.*/ (eriks: $alt)/;
263
-	  }
264
-	  if ($part =~ /^(.*)(\([^\)]+\))$/) {
265
-	    my ($text, $allergy) = ($1, $2);
266
-	    if ($fries) {
267
-	      $output .= ", $text";
268
-	    } else {
269
-	      $output .= "$text <span class=\"allergy\">$allergy</span>";
270
-	    }
271
-	    $allergy =~ s/^\((.*)\)$/$1/;
272
-	    $allergy =~ s/ *eriks: //;
273
-	    my %this_allergies;
274
-	    foreach my $a (split(/[, ]/, $allergy)) {
275
-	      foreach my $al (@allergies) {
276
-		if ($a eq $al) {
277
-		  $this_allergies{$a} = 1;
278
-		  last;
279
-		}
280
-	      }
281
-	    }
282
-	    # is M=L always correct? not at least in all restaurants..
283
-	    #$this_allergies{"L"} = 1 if ($this_allergies{"M"});
284
-	    $this_allergies{"VL"} = 1 if ($this_allergies{"L"});
285
-	    foreach my $a (keys %this_allergies) {
286
-	      $total_allergies{$a}++;
287
-	      $maybe_allergies{$a}++;
288
-	    }
289
-	    if ($lazy_allergies =~ /M/) {
290
-	      # L might mean M
291
-	      if ($this_allergies{"L"} && !$this_allergies{"M"}) {
292
-		$maybe_allergies{"M"}++;
293
-	      }
294
-	    }
295
-	  } else {
296
-	    if ($lazy_allergies eq "all") {
297
-	      # no allergy info, make everything maybe
298
-	      foreach my $a (@allergies) {
299
-		$maybe_allergies{$a}++;
300
-	      }
301
-	    }
302
-	    $output .= $part;
303
-	  }
304
-	}
305
-	my $allergy_output = "";
306
-	foreach my $a (@allergies) {
307
-	  if ($total_allergies{$a} == $part_count) {
308
-	    if (!defined($eatable_food_numbers{$a})) {
309
-	      $eatable_food_numbers{$a} = "";
310
-	    } else {
311
-	      $eatable_food_numbers{$a} .= ",";
312
-	    }
313
-	    $eatable_food_numbers{$a} .= $foodnum;
314
-	  } elsif ($maybe_allergies{$a} == $part_count) {
315
-	    if (!defined($maybe_eatable_food_numbers{$a})) {
316
-	      $maybe_eatable_food_numbers{$a} = "";
317
-	    } else {
318
-	      $maybe_eatable_food_numbers{$a} .= ",";
319
-	    }
320
-	    $maybe_eatable_food_numbers{$a} .= $foodnum;
321
-	  }
322
-	}
323
-	print $fout "  <li id=\"f$foodnum\">$output</li>\n";
324
-	$foodnum++;
325
-      }
326
-      print $fout "</ul>\n";
327
-    }
328
-  }
329
-  # write allergy scripts
330
-  print $fout '<script type="text/javascript" src="'.$global_prefix.'/ruoka.js"></script>';
331
-  print $fout '<script type="text/javascript">';
332
-  print $fout "var eatable_foods = [];";
333
-  print $fout "var maybe_eatable_foods = [];";
334
-  foreach my $a (@allergies) {
335
-    print $fout "eatable_foods[\"$a\"] = [".$eatable_food_numbers{$a}."];\n";
336
-    print $fout "maybe_eatable_foods[\"$a\"] = [".$maybe_eatable_food_numbers{$a}."];\n";
337
-  }
338
-  my @allergy_strings = map('"'.$_.'"', @allergies);
339
-  print $fout "var allergies = [".join(",", @allergy_strings)."];\n";
340
-  print $fout "var food_count = $foodnum\n";
341
-  print $fout "window.onload = function() { set_allergies(); show_warning(); };\n";
342
-  print $fout "</script>\n";
343
-
344
-  print $fout "</div></div>$file_footer";
345
-  close $fout;
346
-}
347
-
348
-sub write_all_days {
349
-  my ($restaurants_ref, $prefix, $title) = @_;
350
-  my $last_day = find_last_day_with_foods($restaurants_ref);
351
-  
352
-  for (my $day = 0; $day < 7; $day++) {
353
-    my $outfname = $prefix.($day+1).".html";
354
-    if ($day > $last_day) {
355
-      unlink($outfname);
356
-      next;
357
-    }
358
-    my $header = $day_names[$day]." - $title vko $max_week$max_week_daterange";
359
-    write_day($day, $header, $outfname, $last_day, $restaurants_ref, $prefix);
360
-  }
361
-}
362
-
363
-sub write_table {
364
-  my ($restaurants_ref, $prefix, $title) = @_;
365
-  my @restaurants = @{$restaurants_ref};
366
-  my $last_day = find_last_day_with_foods($restaurants_ref);
367
-
368
-  my $outfname = $prefix."table.html";
369
-  open(my $fout, ">$outfname") || die ("Can't create file $outfname");
370
-  my $header = "$title vko $max_week$max_week_daterange";
371
-  print $fout "$file_header<h1>$header</h1>\n";
372
-  print $fout "<div class=\"title\">\n";
373
-  write_days_header($fout, -1, $last_day);
374
-  write_prefix_header($fout, $prefix, 0);
375
-  print $fout "</div><table border=\"1\"><tr><th>Päivä</th>";
376
-  foreach my $r (@restaurants) {
377
-    my ($title, $open_hours, $week, $week_foods_ref, $info_ref) = @{$r};
378
-    my ($title2, $url) = @{$info_ref};
379
-    $url =~ s/&/&nbsp;/g;
380
-    print $fout "<th><a href=\"$url\">$title</a></th>";
381
-  }
382
-  print $fout "</tr>\n";
383
-  for (my $day = 0; $day <= $last_day; $day++) {
384
-    print $fout "<tr><td>".$day_names[$day]."</td>\n";
385
-    foreach my $r (@restaurants) {
386
-      my ($title, $open_hours, $week, $week_foods_ref, $info_ref) = @{$r};
387
-      my @week_foods = @{$week_foods_ref};
388
-      if (defined($week_foods[$day]) && ($week eq "" || $week == $max_week)) {
389
-	print $fout "<td><ul>\n";
390
-	foreach my $food (@{$week_foods[$day]}) {
391
-	  print $fout "<li>$food</li>";
392
-	}
393
-	print $fout "</ul></td>\n";
394
-      } else {
395
-	print $fout "<td></td>\n";
396
-      }
397
-    }
398
-    print $fout "</tr>\n";
399
-  }
400
-  print $fout "</table>$file_footer";
401
-  close $fout;
402
-}
403
-
404
-sub get_restaurants_sorted {
405
-  my @restaurants = @_;
406
-  my @out;
407
-  foreach my $r (@restaurants) {
408
-    push @out, $r if (@{@{$r}[4]}[3] eq "left");
409
-  }
410
-  foreach my $r (@restaurants) {
411
-    push @out, $r if (@{@{$r}[4]}[3] eq "right");
412
-  }
413
-  foreach my $r (@restaurants) {
414
-    my @e = @{@{$r}[4]};
415
-    push @out, $r if ($e[3] eq "middle" && $e[1] !~ /TAMK/);
416
-  }
417
-  foreach my $r (@restaurants) {
418
-    my @e = @{@{$r}[4]};
419
-    push @out, $r if ($e[3] eq "middle" && $e[1] =~ /TAMK/);
420
-  }
421
-  return @out;
422
-}
423
-
424
-sub get_restaurants_with_prefix {
425
-  my $prefix = shift;
426
-  my @out;
427
-  foreach my $r (@_) {
428
-    my $name = @{$r}[0];
429
-    if ($name =~ /^\($prefix\)/) {
430
-      push @out, $r;
431
-    }
432
-  }
433
-  return get_restaurants_sorted(@out);
434
-}
435
-
436
-my $tty_title = "TTY:n ruokalistat";
437
-my @tty = get_restaurants_with_prefix("TTY", @unordered);
438
-write_all_days(\@tty, "tty/", $tty_title);
439
-write_table(\@tty, "tty/", $tty_title);
440
-
441
-my $tay_title = "Tampereen yliopiston ruokalistat";
442
-my @tay = get_restaurants_with_prefix("TaY", @unordered);
443
-write_all_days(\@tay, "tay/", $tay_title);
444
-write_table(\@tay, "tay/", $tay_title);
445
-
446
-my $tays_title = "TAYS:n ruokalistat";
447
-my @tays = get_restaurants_with_prefix("TAYS", @unordered);
448
-write_all_days(\@tays, "tays/", $tays_title);
449
-write_table(\@tays, "tays/", $tays_title);
450
-
451
-foreach my $r (@unordered) {
452
-  if (@{$r}[0] =~ /^\(TaY\)/) {
453
-    @{@{$r}[4]}[3] = "left";
454
-  }
455
-  if (@{$r}[0] =~ /^\(TTY\)/) {
456
-    @{@{$r}[4]}[3] = "right";
457
-  }
458
-  if (@{$r}[0] =~ /^\(TAYS\)/) {
459
-    @{@{$r}[4]}[3] = "middle";
460
-  }
461
-}
462
-
463
-my $all_title = "Tampereen yliopistojen ruokalistat";
464
-my @all_restaurants = get_restaurants_sorted(@unordered);
465
-# move fusion kitchen last
466
-my @fusion = splice(@all_restaurants, 1, 1);
467
-splice(@all_restaurants, 4, 0, @fusion);
468
-
469
-write_all_days(\@all_restaurants, "", $all_title);
470
-write_table(\@all_restaurants, "", $all_title);

+ 418 - 0
food.py View File

@@ -0,0 +1,418 @@
1
+#!/usr/bin/env python3
2
+
3
+# Ruokalistaparseri
4
+# Copyright (c) 2016 Toni Fadjukoff
5
+
6
+# Based on food.pl by
7
+# Copyright (c) 2007-2010 Timo Sirainen
8
+#               2011-2016 Toni Fadjukoff
9
+# This is Public Domain
10
+
11
+import sys
12
+
13
+day_names = [ "Maanantai", "Tiistai", "Keskiviikko", "Torstai", 
14
+	       "Perjantai", "Lauantai", "Sunnuntai" ]
15
+
16
+import amica
17
+import sodexo
18
+import juvenes
19
+import campusravita
20
+
21
+allergies = [ "M", "L", "VL", "G", "K", "Ve" ]
22
+allergy_descriptions = {
23
+        "M": "Maidoton",
24
+        "L": "Laktoositon","VL": "Vähälaktoosinen",
25
+        "G": "Gluteiiniton",
26
+        "K": "Kasvis",
27
+        "Ve": "Vegaani"
28
+        }
29
+
30
+import os
31
+import time
32
+import datetime
33
+import re
34
+
35
+global_prefix = "";
36
+use_old = False; # 1 is good for testing, 0 for production system!
37
+unordered = []
38
+
39
+l = time.localtime()
40
+this_week = datetime.datetime.now().isocalendar()[1]
41
+
42
+updateException = None
43
+for restaurant_module in [sodexo, amica, juvenes, campusravita]:
44
+    try:
45
+        unordered += restaurant_module.get_restaurants(use_old, this_week)
46
+    except Exception as e:
47
+        updateException = e
48
+
49
+max_week = 0;
50
+for r in unordered:
51
+  week = r[2]
52
+  max_week = week if week > max_week or week == 1 else max_week
53
+
54
+if l[6] != 0 and this_week != max_week:
55
+  # it's not sunday, don't force next week's menu yet
56
+  max_week = this_week
57
+
58
+stamp = time.time() - 3600*24*7
59
+max_week_daterange = ""
60
+if max_week >= 1 and max_week <= 52:
61
+  # figure out the date range
62
+  while True:
63
+    stamp_week = int(time.strftime("%W", time.localtime(stamp)))
64
+    if stamp_week == max_week:
65
+            break
66
+    stamp += 3600*24
67
+  l1 = time.localtime(stamp)
68
+  l2 = time.localtime(stamp + 3600*24*6)
69
+  if l1[1] == l2[1]:
70
+    # same month
71
+    max_week_daterange = "{monday}-{sunday}.{month}.".format(monday=l1[2], sunday=l2[2], month=l1[1])
72
+  else:
73
+    # different months
74
+    max_week_daterange = "{monday}.{month}.-{sunday}.{next_month}.".format(monday=l1[2], month=l1[1],
75
+            sunday=l2[2], next_month=l2[1])
76
+  max_week_daterange = " (" + str(max_week_daterange) + ")"
77
+
78
+file_header = '''<?xml version="1.0" encoding="utf-8"?>
79
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
80
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="fi" lang="fi">
81
+<head>
82
+  <title>Ruokalistat</title>
83
+  <meta charset="UTF-8"/>
84
+  <link rel="stylesheet" type="text/css" href="{resources_prefix}ruoka.css" />
85
+</head>
86
+<body>
87
+
88
+<div id="notice" style="border: 1px solid black; border-radius: 5px; padding: 5px;">
89
+PNA.fi on kolmannen osapuolen tarjoama palvelu. En voi taata ruokalistojen oikeellisuutta.  Virallisen ruokalistan saat näkyviin siirtymällä ravintolan omille sivuille painamalla sen nimestä. Jos huomaat ruokalistassa virheen, nopeiten virhe saadaan pois näkyvistä kun lähetät minulle siitä sähköpostia: <a href="mailto:lamperi+pna@gmail.com">lamperi+pna@gmail.com</a>
90
+</div>
91
+
92
+<form method="get" action="/cgi-bin/food.cgi">
93
+'''
94
+
95
+file_footer = """
96
+      <div class="footer">Päivitetty {stamp}
97
+        <input type="submit\" value=\"Päivitä nyt\" />
98
+          / Palaute <a href=\"mailto:lamperi+pna@gmail.com\">lamperi+pna@gmail.com</a>
99
+          / <a href="{{resources_prefix}}code.html\">Koodit täältä</a>
100
+         / <a href="{{resources_prefix}}pna.html\">Mikä on PNA?</a>
101
+      </div>
102
+    </form>
103
+  </body>
104
+</html>
105
+""".format(stamp=time.strftime("%d.%m.%Y %H:%M:%S"))
106
+
107
+def find_last_day_with_foods(restaurants):
108
+    last_day = 0
109
+    for r in restaurants:
110
+        for day in range(7):
111
+            if day in r[3]:
112
+                last_day = day if day > last_day else last_day
113
+    return last_day
114
+
115
+def write_days_header(fout, day, last_day):
116
+    fout.write( "  <span class=\"days\">")
117
+    for i in range(last_day):
118
+        if i == day:
119
+            fout.write("{0} ".format(day_names[i]))
120
+        else:
121
+            fout.write("<a href=\"{0}.html\">{1}</a> ".format(i+1, day_names[i]))
122
+  
123
+    if day < 0:
124
+        fout.write("Taulukko")
125
+    else:
126
+        fout.write("<a href=\"table.html\">Taulukko</a>")
127
+    fout.write("</span>\n")
128
+
129
+def write_prefix_header(fout, prefix, day, resources_prefix):
130
+    day = "table" if day == 0 else day
131
+    fout.write("<span class=\"location\">")
132
+    if prefix == "":
133
+        fout.write("Kaikki ")
134
+    else:
135
+        fout.write("<a href=\"{resources_prefix}{day}.html\">Kaikki</a> ".format(resources_prefix=resources_prefix, day=day))
136
+    if prefix == "tay/":
137
+        fout.write("TaY ")
138
+    else:
139
+        fout.write("<a href=\"{resources_prefix}tay/{day}.html\">TaY</a> ".format(resources_prefix=resources_prefix, day=day))
140
+    if prefix == "tays/":
141
+        fout.write("TAYS ")
142
+    else:
143
+        fout.write("<a href=\"{resources_prefix}tays/{day}.html\">TAYS</a> ".format(resources_prefix=resources_prefix, day=day))
144
+    if prefix == "tty/":
145
+        fout.write("TTY ")
146
+    else:
147
+        fout.write("<a href=\"{resources_prefix}tty/{day}.html\">TTY</a> ".format(resources_prefix=resources_prefix, day=day))
148
+    fout.write("</span>\n")
149
+
150
+def write_day(day, header, outfname, last_day, restaurants, prefix, resources_prefix):
151
+    with open(outfname, "w", encoding="utf-8") as fout:
152
+
153
+        fout.write(file_header.format(resources_prefix=resources_prefix))
154
+        fout.write("<h1>{header}</h1>\n".format(header=header))
155
+        # print weekday links
156
+        fout.write("<div class=\"title\">\n")
157
+        write_days_header(fout, day, last_day)
158
+        fout.write(" <span class=\"allergy\">Näytä: ")
159
+        for a in allergies:
160
+            fout.write("<input type=\"checkbox\" name=\"allergy_{a}\" id=\"allergy_{a}\" onclick=\"highlight()\" />".format(a=a))
161
+            fout.write("<span title=\"{allergy_description}\">{a}</span>".format(allergy_description=allergy_descriptions[a], a=a))
162
+        fout.write("</span>\n")
163
+        write_prefix_header(fout, prefix, day+1, resources_prefix);
164
+        fout.write("</div>\n")
165
+
166
+        # print foods
167
+        foodnum = 0
168
+        eatable_food_numbers = {}
169
+        maybe_eatable_food_numbers = {}
170
+        for a in allergies:
171
+            eatable_food_numbers[a] = []
172
+            maybe_eatable_food_numbers[a] = []
173
+        css_class = "left"
174
+        fout.write("<div class=\"foods\"><div class=\"{css_class}\">\n".format(css_class=css_class))
175
+        for r in restaurants:
176
+            title, open_hours, week, week_foods, info = r[:5]
177
+            exception = r[5].replace("\n", "<br>") if len(r) > 5 and r[5] else "Ruokalistaa ei saatavilla."
178
+            title2, url, lazy_allergies, info_class = info[0:4]
179
+            if day in week_foods or day < 5:
180
+                if info_class != css_class:
181
+                    css_class = info_class
182
+                    fout.write("</div><div class=\"{css_class}\">\n".format(css_class=css_class))
183
+                url = url.replace("&", "&amp;")
184
+                fout.write("<h2><a href=\"{url}\">{title}</a></h2>\n".format(url=url, title=title))
185
+                if not day in week_foods:
186
+                    fout.write("<p class=\"missing\">{exception}</p>".format(exception=exception))
187
+                    continue
188
+                if week != "" and week != max_week:
189
+                    if week > max_week or (week == 1 and max_week == 52):
190
+                        # early..
191
+                        fout.write("<p class=\"nextweek\">Viikon {week} ruokalista:</p>".format(week=week))
192
+                    else:
193
+                        fout.write("<p class=\"missing\">Saatavilla vain viikon {week} ruokalista.</p>".format(week=week))
194
+                        continue
195
+                if len(week_foods[day]) == 0:
196
+                    fout.write("<p class=\"missing\">Ei ruokatietoja päivälle.</p>")
197
+                    continue
198
+                fout.write("<ul class=\"food\">\n")
199
+                for food in week_foods[day]:
200
+                    output = []
201
+                    total_allergies = {}
202
+                    maybe_allergies = {}
203
+                    for a in allergies:
204
+                        total_allergies[a] = 0
205
+                        maybe_allergies[a] = 0
206
+                    part_count = 0
207
+                    for part in food.split("\n"):
208
+                        # who cares?
209
+                        if re.match("Peruna|Riisi", part):
210
+                            continue
211
+	                # fries: well, maybe we do care, but we don't care about allergy stuff
212
+                        # and keep it in the same line as the previous food so as not to
213
+	                # waste visible space
214
+                        (part, fries) = re.subn("Tikkuperunat|Ranskalaiset perunat", "", part)
215
+                        fries = fries > 0
216
+                        part_count += 1
217
+
218
+                        # add missing () around allergies
219
+                        part = re.sub(" (([MLGKA]|VL|Ve|VE|Veg|Hot)(, *([MLGKA]|VL|Ve|VE|Veg|Hot|))+)$", " (\\1)", part)
220
+                        match = re.match("^(.*) \\(([^\\)]+)\\)$", part)
221
+                        if match:
222
+                            # fix allergy issues
223
+                            food = match.group(1)
224
+                            allergy = match.group(2)
225
+                            # standardization
226
+                            allergy = re.sub("Kasvis", "K", allergy)
227
+                            allergy = re.sub("([MLGK]|VL)([MLGK]|[VL])", "\\1,\\2", allergy)
228
+                            # spaces to commas
229
+                            allergy = re.sub("saatavana[: ]+(.*)$", "eriks: \\1", allergy)
230
+                            allergy = re.sub(" +", ",", allergy)
231
+                            # remove double commas
232
+                            allergy = re.sub(",+", ",", allergy)
233
+                            # eriks: standardization
234
+                            allergy = re.sub(",?eriks:,?", ", eriks: ", allergy)
235
+                            # remove extra commas/spaces from beginning/end
236
+                            allergy = re.sub("^[, ]+", "", allergy)
237
+                            allergy = re.sub("[, ]+$", "", allergy)
238
+                            part = "{food} ({allergy})".format(food=food, allergy=allergy)
239
+
240
+                        if output and not fries:
241
+                            output.append("<br />\n")
242
+
243
+                        match = re.search("Saatavana myös: (.*)", part)
244
+                        if match:
245
+                            alt = match.group(1)
246
+                            alt = re.sub(r"^\((.*)\)$", r"\1", alt)
247
+                            alt = re.sub("[, ]+", r",", alt)
248
+                            alt = re.sub("^,+", "", alt)
249
+                            alt = re.sub(",+$", "", alt)
250
+                            part = re.sub(r"\)[- ]*Saatavana myös:.*", "eriks: {alt})".format(alt=alt), part)
251
+                            part = re.sub(r"[- ]*Saatavana myös:.*", "(eriks: {alt})".format(alt=alt), part)
252
+
253
+                        match = re.match(r"^(.*)(\([^\)]+\))$", part)
254
+                        if match:
255
+                            text = match.group(1)
256
+                            allergy = match.group(2)
257
+                            if fries:
258
+                                output.append(", {text}".format(text=text))
259
+                            else:
260
+                                output.append("{text} <span class=\"allergy\">{allergy}</span>".format(text=text, allergy=allergy))
261
+                            allergy = re.sub(r"^\((.*)\)$", r"\1", allergy)
262
+                            allergy = re.sub(" *eriks: ", "", allergy)
263
+                            this_allergies = set()
264
+                            for a in re.split("[, ]", allergy):
265
+                                for al in allergies:
266
+                                    if a == al:
267
+                                        this_allergies.add(a)
268
+                                        break
269
+                            if "L" in this_allergies:
270
+                                this_allergies.add("VL")
271
+                            for a in this_allergies:
272
+                                if a in total_allergies:
273
+                                    total_allergies[a] += 1
274
+                                if a in maybe_allergies:
275
+                                    maybe_allergies[a] += 1
276
+                            match = re.search("M", lazy_allergies)
277
+                            if match:
278
+                                if "L" in this_allergies and not "M" in this_allergies:
279
+                                    maybe_allergies["M"] += 1
280
+                        else:
281
+                            if lazy_allergies == "all":
282
+                                for a in allergies:
283
+                                    maybe_allergies[a] += 1
284
+
285
+                            output.append(part)
286
+                    allergy_output = ""
287
+                    for a in allergies:
288
+                        if total_allergies[a] == part_count:
289
+                            eatable_food_numbers[a].append(foodnum)
290
+                        elif maybe_allergies[a]== part_count:
291
+                            maybe_eatable_food_numbers[a].append(foodnum)
292
+                    fout.write("  <li id=\"f{foodnum}\">{output}</li>\n".format(foodnum=foodnum, output="".join(output)))
293
+                    foodnum += 1
294
+                fout.write("</ul>\n")
295
+        # write allergy scripts
296
+        fout.write('<script type="text/javascript" src="{resources_prefix}ruoka.js"></script>'.format(resources_prefix=resources_prefix))
297
+        fout.write('<script type="text/javascript">\n')
298
+        fout.write("var eatable_foods = [];\n")
299
+        fout.write("var maybe_eatable_foods = [];\n")
300
+        for a in allergies:
301
+            fout.write("eatable_foods[\"{a}\"] = [{eatable_food_number}];\n".format(a=a, eatable_food_number=",".join(str(e) for e in eatable_food_numbers[a])))
302
+            fout.write("maybe_eatable_foods[\"{a}\"] = [{maybe_eatable_food_number}];\n".format(a=a, maybe_eatable_food_number=",".join(str(e) for e in maybe_eatable_food_numbers[a])))
303
+        allergy_string = ",".join('"{a}"'.format(a=a) for a in allergies)
304
+        fout.write("var allergies = [{allergies}];\n".format(allergies = allergy_string))
305
+        fout.write("var food_count = {foodnum};\n".format(foodnum = foodnum))
306
+        fout.write("window.onload = function() { set_allergies(); show_warning(); };\n")
307
+        fout.write("</script>\n")
308
+        fout.write("</div></div>{file_footer}".format(file_footer=file_footer.format(resources_prefix=resources_prefix)))
309
+
310
+def write_all_days(restaurants, prefix, title, resources_prefix):
311
+    try:
312
+        os.mkdir(prefix)
313
+    except OSError as err:
314
+        pass # hope it already exists
315
+    last_day = find_last_day_with_foods(restaurants);
316
+    for day in range(7):
317
+        outfname = "{prefix}{day}.html".format(prefix=prefix, day=day+1)
318
+        if day > last_day:
319
+            try:
320
+                os.unlink(outfname)
321
+            except OSError as err:
322
+                pass # probably did not exist
323
+            continue
324
+        header = "{day_name} - {title} vko {max_week}{max_week_daterange}".format(day_name=day_names[day], title=title,
325
+                max_week=max_week, max_week_daterange=max_week_daterange)
326
+        write_day(day, header, outfname, last_day, restaurants, prefix, resources_prefix)
327
+ 
328
+def write_table(restaurants, prefix, title, resources_prefix):
329
+    last_day = find_last_day_with_foods(restaurants);
330
+    outfname = "{prefix}table.html".format(prefix=prefix)
331
+    with open(outfname, "w", encoding="utf-8") as fout:
332
+        header = "{title} vko {max_week}{max_week_daterange}".format(title=title, max_week=max_week, max_week_daterange=max_week_daterange)
333
+        fout.write(file_header.format(resources_prefix=resources_prefix))
334
+        fout.write("<h1>{header}</h1>\n".format(header=header))
335
+        fout.write("<div class=\"title\">\n")
336
+
337
+        write_days_header(fout, -1, last_day)
338
+        write_prefix_header(fout, prefix, 0, resources_prefix)
339
+        fout.write("</div><table border=\"1\"><tr><th>Päivä</th>")
340
+        for r in restaurants:
341
+            (title, open_hours, week, week_foods, info) = r[:5]
342
+            (title2, url) = info[0:2]
343
+            url = re.sub("&", "&nbsp;", url)
344
+            fout.write("<th><a href=\"{url}\">{title}</a></th>".format(url=url, title=title))
345
+        fout.write("</tr>\n")
346
+        for day in range(last_day):
347
+            fout.write("<tr><td>{day_name}</td>\n".format(day_name=day_names[day]))
348
+            for r in restaurants:
349
+                (title, open_hours, week, week_foods, info) = r[:5]
350
+                if day in week_foods and (week == "" or week == max_week):
351
+                    fout.write("<td><ul>\n")
352
+                    for food in week_foods[day]:
353
+                        fout.write("<li>{food}</li>".format(food=food))
354
+                    fout.write("</ul></td>\n")
355
+                else:
356
+                    fout.write("<td></td>\n")
357
+            fout.write("</tr\n")
358
+        fout.write("</table>{file_footer}".format(file_footer=file_footer.format(resources_prefix=resources_prefix)))
359
+
360
+def get_restaurants_sorted(restaurants):
361
+    # consider writing comparator
362
+    out = []
363
+    for r in restaurants:
364
+        if r[4][3] == "left":
365
+            out.append(r)
366
+    for r in restaurants:
367
+        if r[4][3] == "right":
368
+            out.append(r)
369
+    for r in restaurants:
370
+        if r[4][3] == "middle" and not re.search("TAMK", r[4][0]):
371
+            out.append(r)
372
+    for r in restaurants:
373
+        if r[4][3] == "middle" and re.search("TAMK", r[4][0]):
374
+            out.append(r)
375
+    return out
376
+
377
+def get_restaurants_with_prefix(prefix, restaurants):
378
+    out = []
379
+    for r in restaurants:
380
+        if re.search(prefix, r[4][0]):
381
+            out.append(r)
382
+    return get_restaurants_sorted(out)
383
+
384
+tty_title = "TTY:n ruokalistat"
385
+tty = get_restaurants_with_prefix("TTY", unordered)
386
+write_all_days(tty, "tty/", tty_title, "../")
387
+write_table(tty, "tty/", tty_title, "../")
388
+
389
+tay_title = "Tampereen yliopiston ruokalistat"
390
+tay = get_restaurants_with_prefix("TaY", unordered)
391
+write_all_days(tay, "tay/", tay_title, "../")
392
+write_table(tay, "tay/", tay_title, "../")
393
+
394
+tays_title = "TAYS:n ruokalistat"
395
+tays = get_restaurants_with_prefix("TAYS", unordered)
396
+write_all_days(tays, "tays/", tays_title, "../")
397
+write_table(tays, "tays/", tays_title, "../")
398
+
399
+for r in unordered:
400
+    if re.search(r"^\(TaY\)", r[0]):
401
+        r[4][3] = "left"
402
+    if re.search(r"^\(TTY\)", r[0]):
403
+        r[4][3] = "right"
404
+    if re.search(r"^\(TAYS\)", r[0]):
405
+        r[4][3] = "middle"
406
+
407
+all_title = "Tampereen yliopistojen ruokalistat";
408
+all_restaurants = get_restaurants_sorted(unordered);
409
+# move fusion kitchen last
410
+#fusion = splice(@all_restaurants, 1, 1);
411
+#splice(@all_restaurants, 4, 0, @fusion);
412
+
413
+write_all_days(all_restaurants, "", all_title, "");
414
+write_table(all_restaurants, "", all_title, "");
415
+
416
+if updateException is not None:
417
+    print("Exception happened while fetching menus")
418
+    raise updateException

+ 0 - 102
juvenes.pl View File

@@ -1,102 +0,0 @@
1
-use vars qw(@day_names);
2
-use JSON;
3
-
4
-# last two in order are $kitchen_info_id, $menu_type_id
5
-my @restaurant_info = (
6
-  [ "(TaY) Yliopiston Ravintola", "http://www.juvenes.fi/fi-fi/ravintolatjakahvilat/opiskelijaravintolat/tayp%C3%A4%C3%A4kampus/yliopistonravintola.aspx", "M", "left", 13, 60 ],
7
-  [ "(TaY) Yliopiston Ravintola / VegeBar", "http://www.juvenes.fi/fi-fi/ravintolatjakahvilat/opiskelijaravintolat/tayp%C3%A4%C3%A4kampus/yliopistonravintola.aspx", "", "left", 13, 5 ],
8
-  [ "(TaY) Café Campus", "http://www.juvenes.fi/fi-fi/ravintolatjakahvilat/opiskelijaravintolat/tayp%C3%A4%C3%A4kampus/caf%C3%A9campus.aspx", "", "left", 130019, 23 ],
9
-  [ "(TaY) Café Pinni", "http://www.juvenes.fi/fi-fi/ravintolatjakahvilat/opiskelijaravintolat/tayp%C3%A4%C3%A4kampus/caf%C3%A9lunchpinni.aspx", "M", "middle", 130016, 60 ],
10
-  [ "(TAYS) Arvo", "http://www.juvenes.fi/fi-fi/ravintolatjakahvilat/opiskelijaravintolat/taykaupinkampus/arvo.aspx", "M", "left", 5, 60 ],
11
-  [ "(TAYS) Café Lea (Fusion Kitchen)", "http://www.juvenes.fi/fi-fi/ravintolatjakahvilat/opiskelijaravintolat/taykaupinkampus/cafélea.aspx", "M", "left", 50026, 3 ],
12
-  [ "(TAYS) Café Lea (My Salad)", "http://www.juvenes.fi/fi-fi/ravintolatjakahvilat/opiskelijaravintolat/taykaupinkampus/cafélea.aspx", "M", "left", 50026, 76 ],
13
-  [ "(TTY) Newton", "http://www.juvenes.fi/fi-fi/ravintolatjakahvilat/opiskelijaravintolat/ttykampus/newton.aspx", "", "left", 6, 60],
14
-  [ "(TTY) Café Konehuone / Såås bar", "http://www.juvenes.fi/fi-fi/ravintolatjakahvilat/opiskelijaravintolat/ttykampus/caf%C3%A9konehuone/s%C3%A5%C3%A5sbar.aspx", "", "left", 60038, 77],
15
-  [ "(TTY) Café Konehuone / Fusion Kitchen", "http://www.juvenes.fi/fi-fi/ravintolatjakahvilat/opiskelijaravintolat/ttykampus/caf%C3%A9konehuone/fusionkitchen.aspx", "", "middle", 60038, 3 ],
16
-  [ "(TAMK) Ziberia", "http://www.juvenes.fi/fi-fi/ravintolatjakahvilat/henkil%C3%B6st%C3%B6ravintolat/ziberia.aspx", "", "middle", 11, 60 ],
17
-#  [ "(TAMK) Frenckell", "http://www.juvenes.fi/fi-fi/ravintolatjakahvilat/henkil%C3%B6st%C3%B6ravintolat/frenckell.aspx", "", "middle", 33, 60 ],
18
-  [ "(TAMK) Frenckell / Såås bar", "http://www.juvenes.fi/fi-fi/ravintolatjakahvilat/henkil%C3%B6st%C3%B6ravintolat/frenckell.aspx", "", "middle", 33, 77 ]
19
-);
20
-
21
-my @restaurants;
22
-my ($open_hours, $day_id, $cur_title);
23
-my ($cur_food, @cur_day_foods, @week_foods);
24
-
25
-sub finish_food {
26
-  chomp $cur_food;
27
-  if ($cur_food =~ /Liha paniini.*tai Kasvis paniini/i && $cur_title eq $pinni_title) {
28
-    # you get this every day, ignore
29
-  } else {
30
-    push @cur_day_foods, utf8_to_8859($cur_food) if ($cur_food ne "");
31
-  }
32
-  $cur_food = "";
33
-}
34
-
35
-sub finish_day {
36
-  push @week_foods, [@cur_day_foods];
37
-  @cur_day_foods = ();
38
-  $day_id = $day_id + 1;
39
-}
40
-
41
-sub get_juvenes_restaurants {
42
-  my $use_old = shift;
43
-  my $count = 0;
44
-  # Loops restraurants
45
-  foreach my $i (@restaurant_info) {
46
-    my @info = @{$i};
47
-    my $kitchen = $info[4];
48
-    my $menutype = $info[5];
49
-    $title = $info[0];
50
-    $cur_title = $title;
51
-    $open_hours = "";
52
-    @week_foods = ();
53
-    my $week = `date +%V`;
54
-    chomp($week);
55
-    # Loop weekdays
56
-    for (my $weekday = 1; $weekday < 7; $weekday++) {
57
-      my $temp_fname = "juvenes$count-$weekday.temp.js";
58
-      my $url = "http://www.juvenes.fi/DesktopModules/Talents.LunchMenu/LunchMenuServices.asmx/GetMenuByWeekday?KitchenId=$kitchen&MenuTypeId=$menutype&Week=$week&Weekday=$weekday&lang='fi'&format=json";
59
-      if (!-f $temp_fname || !$use_old) {
60
-        system("rm -f $temp_fname && wget -q --timeout=10 -O $temp_fname.tmp \"$url\" && mv $temp_fname.tmp $temp_fname");
61
-      }
62
-      if (-f $temp_fname) {
63
-        open(FILE, $temp_fname);
64
-        my $jsonp = do { local $/; <FILE> };
65
-        close(FILE);
66
-        # the file is encapsulated in ({"d": json}); so we have to double parse it
67
-        my $data = from_json(substr($jsonp, 1, -2));
68
-        unless ($data->{'d'} eq 'null') {
69
-          $data = from_json($data->{'d'});
70
-          my $mealoptions = $data->{'MealOptions'};
71
-          # loop different meals
72
-          foreach my $meal_info (@$mealoptions) {
73
-            if ($meal_info->{'ForceMajeure'} ne '') {
74
-              $cur_food = $meal_info->{'ForceMajeure'}; 
75
-            } else {
76
-              my $menuitems = $meal_info->{'MenuItems'};
77
-              # loops different foods in a meal
78
-              foreach my $food_info (@$menuitems) {
79
-                $cur_food .= "\n" if $cur_food ne ""; 
80
-                my $name = $food_info->{'Name'};
81
-                $name =~ s/^\*//;
82
-                $cur_food .= $name;
83
-                $cur_food .= " (" . $food_info->{'Diets'} . ")" if $food_info->{'Diets'};
84
-              }
85
-            }
86
-            if ($cur_food ne "-") {
87
-              finish_food();
88
-            } else {
89
-              $cur_food = "";
90
-            }
91
-          }
92
-        }
93
-        finish_day();
94
-      }
95
-    }
96
-    push @restaurants, [ $title, $open_hours, $week, [ @week_foods ], \@info ];
97
-    $count++;
98
-  }
99
-  return @restaurants;
100
-}
101
-
102
-1;

+ 102 - 0
juvenes.py View File

@@ -0,0 +1,102 @@
1
+# encoding: UTF-8
2
+import pnalib
3
+import datetime
4
+import re
5
+import json
6
+
7
+# Last three columns are Kitchen Info ID, Menu Type ID and Concept ID. There may be multiple concepts in a single restauranta.
8
+# Juvenes API defines also Restaurant ID but that is not used.
9
+restaurant_info = [
10
+  [ "(TaY) Yliopiston Ravintola", 
11
+    "https://www.juvenes.fi/yoravintola",
12
+    "M", "left", 13, 60, []],
13
+  [ "(TaY) Yliopiston Ravintola / VegeBar",
14
+    "https://www.juvenes.fi/yoravintola",
15
+    "", "left", 13, 5, []],
16
+  [ "(TaY) Café Alakuppila (Paniini & Combosalaatti)",
17
+    "https://www.juvenes.fi/alakuppila",
18
+    "", "left", 130018, 58, []],
19
+  [ "(TaY) Café Pinni",
20
+    "https://www.juvenes.fi/pinni",
21
+    "M", "middle", 130016, 60, []],
22
+  [ "(TAYS) Arvo",
23
+    "https://www.juvenes.fi/arvo",
24
+    "M", "left", 5, 60, []],
25
+  [ "(TAYS) Café Lea (Fusion Kitchen)",
26
+    "https://www.juvenes.fi/lea",
27
+    "M", "left", 50026, 3, []],
28
+#  [ "(TAYS) Café Lea (My Salad)",
29
+#    "https://www.juvenes.fi/lea",
30
+#    "M", "left", 50026, 76, []],
31
+  [ "(TTY) Newton",
32
+    "https://www.juvenes.fi/newton",
33
+    "", "left", 6, 60, [1149]],
34
+  [ "(TTY) Café Konehuone / Såås bar",
35
+    "https://www.juvenes.fi/konehuone",
36
+    "", "left", 60038, 77, [3663]],
37
+  [ "(TTY) Café Konehuone / Fusion Kitchen",
38
+    "https://www.juvenes.fi/konehuone",
39
+    "", "middle", 60038, 3, [1674]],
40
+  [ "(TAMK) Ziberia (Koto my plate)",
41
+    "https://www.juvenes.fi/ziberia",
42
+    "", "middle", 11, 82, []],
43
+  [ "(TAMK) Ziberia (Koto)",
44
+    "https://www.juvenes.fi/ziberia",
45
+    "", "middle", 11, 79, []],
46
+  [ "(TAMK) Ziberia (My Salad)",
47
+    "https://www.juvenes.fi/ziberia",
48
+    "", "middle", 11, 76, []],
49
+  [ "(TAMK) Frenckell / Koto",
50
+    "https://www.juvenes.fi/frenckell",
51
+    "", "middle", 33, 60, []],
52
+  [ "(TAMK) Frenckell / Iltapäivälounas",
53
+    "https://www.juvenes.fi/frenckell",
54
+    "", "middle", 33, 85, []]
55
+]
56
+
57
+
58
+def get_restaurants(use_old, week):
59
+    restaurants = []
60
+    for count, info in enumerate(restaurant_info):
61
+        kitchen = info[4]
62
+        menutype = info[5]
63
+        concepts = info[6]
64
+        title = info[0]
65
+        cur_title = title
66
+        open_hours = ""
67
+        exception = None
68
+        week_foods = {}
69
+        for weekday in range(1,7):
70
+            url = "http://www.juvenes.fi/DesktopModules/Talents.LunchMenu/LunchMenuServices.asmx/GetMenuByWeekday?KitchenId={kitchen}&MenuTypeId={menutype}&Week={week}&Weekday={weekday}&lang='fi'&format=json".format(kitchen=kitchen, menutype=menutype, week=week, weekday=weekday)
71
+            temp_fname = "juvenes_{count}-{weekday}.temp.js".format(count=count, weekday=weekday)
72
+            data = pnalib.get_json_file(url, temp_fname, use_old, allow_old=False)
73
+            if not data:
74
+                # Try to find problem
75
+                for concept in concepts:
76
+                    url = "http://www.juvenes.fi/DesktopModules/Talents.Restaurants/RestaurantsService.svc/GetConcept?menuId={concept}&lang=fi".format(concept=concept)
77
+                    temp_fname = "juvenes_{count}-{weekday}-{concept}.temp.js".format(count=count, weekday=weekday, concept=concept)
78
+                    data = pnalib.get_json_file(url, temp_fname, use_old, allow_old=False)
79
+                    if data and data["d"]:
80
+                        exception = data["d"]["OpenInfo"]["Exeption1InfoText"]
81
+            elif data and data["d"] != "null":
82
+                data = json.loads(data["d"])
83
+                cur_day_foods = []
84
+                mealoptions = data["MealOptions"]
85
+                for meal_info in mealoptions:
86
+                    cur_food = []
87
+                    if "ForceMajoure" in meal_info and meal_info["ForceMajoure"] != "":
88
+                        cur_food = [meal_info["ForceMajoure"]]
89
+                    else:
90
+                        menuitems = meal_info["MenuItems"]
91
+                        for food_info in menuitems:
92
+                            name = food_info["Name"]
93
+                            name = re.sub(r"^\*", "", name)
94
+                            if food_info["Diets"]:
95
+                                cur_food.append("{name} ({diets})".format(name=name, diets=food_info["Diets"]))
96
+                            elif name:
97
+                                cur_food.append(name)
98
+                    if cur_food != ["-"]:
99
+                        cur_day_foods.append("\n".join(cur_food))
100
+                week_foods[weekday-1] = cur_day_foods
101
+        restaurants.append([title, open_hours, week, week_foods, info, exception])
102
+    return restaurants

+ 27 - 0
pikante.py View File

@@ -0,0 +1,27 @@
1
+import pnalib
2
+import html.parser
3
+# Pikante does not have any student restaurants at the moment
4
+
5
+pikante_url = "http://www.pikante.fi/lounaslistat-pdf";
6
+
7
+restaurant_info = [
8
+  [ "(TAYS) Finnmedin ravintola", "$pky_url", "all", "middle" ],
9
+  [ "(TAYS) Café Olive", "$pky_url", "all", "middle" ],
10
+  [ "(TAYS) Ellipsi", "$pky_url", "all", "middle" ]
11
+]
12
+
13
+class PikanteHTMLParser(html.parser.HTMLParser):
14
+    def handle_starttag(self, tag, attrs):
15
+        pass
16
+    def handle_endtag(self, tag):
17
+        pass
18
+    def handle_data(self, data):
19
+        pass
20
+
21
+def get_restaurants(use_old, week):
22
+
23
+    #data = pnalib.get_file(pikante_url, "pikante.html", use_old)
24
+    #parser = PikanteHTMLParser()
25
+    #parser.feed(data)
26
+
27
+    return []

+ 0 - 169
pky.pl View File

@@ -1,169 +0,0 @@
1
-use vars qw(@day_names);
2
-
3
-my @short_day_names = ( "ma", "ti", "ke", "to", "pe", "la", "su" );
4
-
5
-#my $pky_url = "http://www.pky.fi/lounaslistat";
6
-my $pky_url = "http://www.pikante.fi/lounaslistat-pdf";
7
-
8
-my @restaurant_info = (
9
-  [ "(TAYS) Finn-Medi", "$pky_url", "all", "middle" ],
10
-  [ "(TAYS) Café Olive", "$pky_url", "all", "middle" ],
11
-  [ "(TAYS) Ellipsi", "$pky_url", "all", "middle" ]
12
-);
13
-
14
-my ($parse_func, $day_id, $week);
15
-my (@cur_day_foods, @week_foods);
16
-my ($show_next_week, $content_title);
17
-
18
-sub utf8_to_8859 {
19
-  $_ = shift;
20
-
21
-  s/ //g;
22
-  s/é/é/g;
23
-  s/ä/ä/g;
24
-  s/ö/ö/g;
25
-  s/Ä/Ä/g;
26
-  s/Ö/Ö/g;
27
-  return $_;
28
-}
29
-
30
-sub pky_finish_day {
31
-  push @week_foods, [@cur_day_foods];
32
-  @cur_day_foods = ();
33
-  $day_id = $day_id + 1;
34
-}
35
-
36
-sub pky_parse_more_food {
37
-  my $token = shift;
38
-  
39
-  if ($token->[0] eq 'T') {
40
-    my $text = utf8_to_8859($token->[1]);
41
-    foreach my $text (split("/", $text)) {
42
-      push @cur_day_foods, $text if ($text ne "");
43
-    }
44
-  } elsif ($token->[0] eq 'E') {
45
-    if ($token->[1] eq 'tr') {
46
-      pky_finish_day();
47
-      $parse_func = \&pky_parse_day_td;
48
-    }
49
-  }
50
-}
51
-
52
-sub pky_parse_day_td {
53
-  my $token = shift;
54
-  
55
-  if ($token->[0] eq 'E') {
56
-    if ($token->[1] eq 'td') {
57
-      $parse_func = \&pky_parse_more_food;
58
-    } elsif ($token->[1] eq 'table') {
59
-      $parse_func = \&pky_parse_to_week;
60
-    }
61
-  } elsif ($token->[0] eq 'T') {
62
-    my $text = $token->[1];
63
-    my $i = 0;
64
-    foreach my $day (@short_day_names) {
65
-      if ($text =~ /$day$/i) {
66
-	while ($day_id < $i) {
67
-	  push @week_foods, [ ];
68
-	  $day_id++;
69
-	}
70
-	last;
71
-      }
72
-      $i++;
73
-    }
74
-  }
75
-}
76
-
77
-sub pky_parse_to_monday {
78
-  my $token = shift;
79
-  
80
-  if ($token->[0] eq 'S' && $token->[1] eq 'tr') {
81
-    $parse_func = \&pky_parse_day_td;
82
-  } elsif ($token->[0] eq 'E' && $token->[1] eq 'table') {
83
-    $parse_func = \&pky_parse_to_week;
84
-  }
85
-}
86
-
87
-sub want_second_week {
88
-  my $week = shift;
89
-
90
-  return 1 if $day_id == 0; # week didn't start from beginning
91
-  
92
-  my @l = localtime;
93
-  my $this_week = strftime("%V", @l);
94
-  return $week == $this_week || ($l[6] == 6 && ($week%52)+1 == $this_week);
95
-}
96
-
97
-sub pky_parse_to_eof {
98
-}
99
-
100
-sub pky_parse_to_week {
101
-  my $token = shift;
102
-  
103
-  if ($token->[0] eq 'T' && $token->[1] =~ /Viikko (\d+)/) {
104
-    my $parsed_week = $1;
105
-    # earlier version could have shown two tables for two weeks
106
-    # sometimes. but the new version? dunno yet..
107
-    #if ($week == 0 || $show_next_week) {
108
-    if ($week == 0) {
109
-      $week = $parsed_week;
110
-      $day_id = 0;
111
-      @cur_day_foods = ();
112
-      @week_foods = ();
113
-      $parse_func = \&pky_parse_to_monday;
114
-    } else {
115
-      $parse_func = \&pky_parse_to_eof;
116
-    }
117
-  }
118
-}
119
-
120
-sub pky_parse_to_title {
121
-  my $token = shift;
122
-  
123
-  if ($token->[0] eq 'T') {
124
-    my $text = utf8_to_8859($token->[1]);
125
-    if ($text =~ /$content_title.*lounasaika/) {
126
-      $parse_func = \&pky_parse_to_week;
127
-    }
128
-  }
129
-}
130
-
131
-sub parse_pky {
132
-  my ($fname, $info_ref) = @_;
133
-  my $p = HTML::TokeParser->new($fname) or die("Can't open file $fname");
134
-  
135
-  my $title = @{$info_ref}[0];
136
-  $week = 0;
137
-  
138
-  $content_title = $title;
139
-  $content_title =~ s/^\(TAYS\) //;
140
-
141
-  $parse_func = \&pky_parse_to_title;
142
-  while (my $token = $p->get_token) {
143
-    &$parse_func($token);
144
-  }
145
-  return [ $title, "", $week, [ @week_foods ], $info_ref ];
146
-}
147
-
148
-sub get_pky_restaurants {
149
-  my $use_old;
150
-  ($use_old, $show_next_week) = @_;
151
-
152
-  my $temp_fname = "pky.temp.html";
153
-  if (!-f $temp_fname || !$use_old) {
154
-    system("wget -q --timeout=10 -O $temp_fname.tmp '$pky_url' && mv $temp_fname.tmp $temp_fname");
155
-  }
156
-  
157
-  my @restaurants = ();
158
-  if (-f $temp_fname) {
159
-    my $count = 0;
160
-    foreach my $i (@restaurant_info) {
161
-      my @info = @{$i};
162
-      push @restaurants, parse_pky($temp_fname, \@info);
163
-      $count++;
164
-    }
165
-  }
166
-  return @restaurants;
167
-}
168
-
169
-1;

+ 39 - 0
pnalib.py View File

@@ -0,0 +1,39 @@
1
+import os.path
2
+import urllib.request
3
+import urllib.error
4
+import json
5
+
6
+def get_jsonp_file(url, temp_fname, use_old, allow_old=True):
7
+    try:
8
+        return get_file(url, temp_fname, use_old, jsonp_load, allow_old)
9
+    except json.decoder.JSONDecodeError as e:
10
+        print("Failed to parse JSON from {}".format(temp_fname))
11
+        raise
12
+
13
+def get_json_file(url, temp_fname, use_old, allow_old=True):
14
+    try:
15
+        return get_file(url, temp_fname, use_old, json.load, allow_old)
16
+    except json.decoder.JSONDecodeError as e:
17
+        print("Failed to parse JSON from {}".format(temp_fname))
18
+        raise
19
+
20
+def jsonp_load(fp):
21
+    return json.loads(fp.read()[1:-2])
22
+
23
+def read_all(fp):
24
+    return fp.read()
25
+
26
+def get_file(url, temp_fname, use_old, consumer=read_all, allow_old=True):
27
+    if not use_old or not os.path.isfile(temp_fname):
28
+        try:
29
+            urllib.request.urlretrieve(url, temp_fname)
30
+        except urllib.error.HTTPError as e:
31
+            print("Failed to download {url}".format(url=url))
32
+            # Juvenes may fail with error code 500 if food is not available
33
+            if not allow_old:
34
+                return None
35
+    try:
36
+        with open(temp_fname, "r", encoding="utf-8") as fin:
37
+            return consumer(fin)
38
+    except OSError as e:
39
+        pass

+ 1 - 1
ruoka.js View File

@@ -114,7 +114,7 @@ function show_warning() {
114 114
   if (shouldShowWarning()) {
115 115
     var input = document.createElement("input");
116 116
     input.type="submit";
117
-    input.value="Älä näytä tätä enää";
117
+    input.value="Älä näytä tätä enää";
118 118
     input.onclick = function() {
119 119
       notice.parentNode.removeChild(notice); 
120 120
       noMoreWarning();

+ 0 - 89
sodexo.pl View File

@@ -1,89 +0,0 @@
1
-use vars qw(@day_names);
2
-use POSIX qw(strftime);
3
-
4
-
5
-my @restaurant_info = (
6
-  [ "(TaY) Sodexo Linna", "http://www.sodexo.fi/linna", "", "right", 92],
7
-  #[ "(TTY) Sodexo Erkkeri", "http://www.sodexo.fi/erkkeri", "", "left", 100]
8
-  [ "(TTY) Sodexo Hertsi", "http://www.sodexo.fi/tty-tietotalo", "", "right", 12812]
9
-);
10
-
11
-my ($cur_text, $cur_title, $parse_func, $day_id, $week);
12
-my (@cur_day_foods, @week_foods);
13
-
14
-sub sodexo_finish_food {
15
-  chomp $cur_food;
16
-  push @cur_day_foods, utf8_to_8859($cur_food) if ($cur_food ne "");
17
-  $cur_food = "";
18
-}
19
-
20
-sub sodexo_finish_day {
21
-  push @week_foods, [@cur_day_foods];
22
-  @cur_day_foods = ();
23
-  $day_id = $day_id + 1;
24
-}
25
-
26
-
27
-sub utf8_to_8859 {
28
-  $_ = shift;
29
-
30
-  s/ä/ä/g;
31
-  s/ö/ö/g;
32
-  s/Ä/Ä/g;
33
-  s/Ö/Ö/g;
34
-  return $_;
35
-}
36
-
37
-sub get_sodexo_restaurants {
38
-  my $use_old = shift;
39
-  my $count = 0;
40
-  # Loops restraurants
41
-  foreach my $i (@restaurant_info) {
42
-    my @info = @{$i};
43
-    my $kitchen = $info[4];
44
-    $title = $info[0];
45
-    $cur_title = $title;
46
-    $open_hours = "";
47
-    @week_foods = ();
48
-    my $week = `date +%V`;
49
-    chomp($week);
50
-    # Loop weekdays
51
-    for (my $weekday = 1; $weekday < 7; $weekday++) {
52
-
53
-      # Get current unix timestamp and week day
54
-      $s = strftime "%s", localtime;
55
-      $v = strftime "%w", localtime;
56
-      # Calculate current weekday 
57
-      $s = $s + ($weekday - $v)  * 86400;
58
-      $timestr = strftime "%Y/%m/%d", localtime($s);
59
-
60
-      my $url = "http://www.sodexo.fi/ruokalistat/output/daily_json/$kitchen/$timestr/fi";
61
-      my $temp_fname = "sodexo$count-$weekday.temp.js";  
62
-      if (!-f $temp_fname || !$use_old) {
63
-        system("wget -q --timeout=10 -O $temp_fname.tmp \"$url\" && mv $temp_fname.tmp $temp_fname");
64
-      }
65
-      if (-f $temp_fname) {
66
-        open(FILE, $temp_fname);
67
-        my $json = do { local $/; <FILE> };
68
-        close(FILE);
69
-        # the file is encapsulated in ({"d": json}); so we have to double parse it
70
-        my $data = from_json($json);
71
-        my $courses = $data->{'courses'};
72
-        # loop different meals
73
-        foreach my $course_info (@$courses) {
74
-          if ($course_info->{'category'} ne 'Aamupala') {
75
-            $cur_food = $course_info->{'title_fi'};
76
-            $cur_food .= " (" . $course_info->{'properties'} . ")" if $course_info->{'properties'};
77
-          }
78
-          sodexo_finish_food();
79
-        }
80
-        sodexo_finish_day();
81
-      }
82
-    }
83
-    push @restaurants, [ $title, $open_hours, $week, [ @week_foods ], \@info ];
84
-    $count++;
85
-  }
86
-  return @restaurants;
87
-}
88
-
89
-1;

+ 42 - 0
sodexo.py View File

@@ -0,0 +1,42 @@
1
+import pnalib
2
+import datetime
3
+
4
+restaurant_info = [
5
+  [ "(TaY) Sodexo Linna", "http://www.sodexo.fi/linna", "", "right", 92],
6
+  #[ "(TTY) Sodexo Erkkeri", "http://www.sodexo.fi/erkkeri", "", "left", 100]
7
+  [ "(TTY) Sodexo Hertsi", "http://www.sodexo.fi/tty-tietotalo", "", "right", 12812]
8
+]
9
+
10
+def get_restaurants(use_old, week):
11
+    restaurants = []
12
+    for count, info in enumerate(restaurant_info):
13
+        kitchen = info[4]
14
+        title = info[0]
15
+        open_hours = ""
16
+        week_foods = {}
17
+        today = datetime.date.today()
18
+        week_day = today.isocalendar()[2]
19
+        last_sunday = today - datetime.timedelta(days=week_day)
20
+        for weekday in range(1,7):
21
+            date = last_sunday + datetime.timedelta(days=weekday)
22
+            timestr = date.strftime("%Y/%m/%d")
23
+            url = "http://www.sodexo.fi/ruokalistat/output/daily_json/{kitchen}/{timestr}/fi".format(kitchen=kitchen, timestr=timestr)
24
+            temp_fname = "sodexo_{count}-{weekday}.temp.js".format(count=count, weekday=weekday)
25
+            data = pnalib.get_json_file(url, temp_fname, use_old)
26
+            if not data:
27
+                continue
28
+            current_day_foods = []
29
+            courses = data["courses"]
30
+            print(data)
31
+            for course_info in courses:
32
+                if not "category" in course_info or not "title_fi" in course_info or not "properties" in course_info:
33
+                    continue
34
+                if course_info["category"] != "Aamupuuro":
35
+                    food = course_info["title_fi"]
36
+                    if "properties" in course_info:
37
+                        food += " ({allergies})".format(allergies=course_info["properties"])
38
+                    current_day_foods.append(food)
39
+            week_foods[weekday-1] = current_day_foods
40
+        restaurants.append([title, open_hours, week, week_foods, info])
41
+
42
+    return restaurants