Jacek Kowalski
2012-08-29 2fb07e33bc07ad435731e43cf59a961627570fcd
1. Usunięcie modułu pogoda opartego o Google Weather API,
2. Stworzenie nowego modułu wykorzystującego API GeoNames oraz yr.no,
3. Dodanie nowych ikon pogodowych,
4. Uwzględnienie licencji ikon w pliku LICENCE,
5. Aktualizacja pliku UPGRADE w związku z powyższymi zmianami.
2 files modified
24 files added
2 files deleted
791 ■■■■■ changed files
LICENCE 27 ●●●●● patch | view | raw | blame | history
UPGRADE 22 ●●●● patch | view | raw | blame | history
data/pogoda/.emptydir patch | view | raw | blame | history
data/pogoda/1.png patch | view | raw | blame | history
data/pogoda/10.png patch | view | raw | blame | history
data/pogoda/11.png patch | view | raw | blame | history
data/pogoda/12.png patch | view | raw | blame | history
data/pogoda/13.png patch | view | raw | blame | history
data/pogoda/14.png patch | view | raw | blame | history
data/pogoda/15.png patch | view | raw | blame | history
data/pogoda/2.png patch | view | raw | blame | history
data/pogoda/20.png patch | view | raw | blame | history
data/pogoda/21.png patch | view | raw | blame | history
data/pogoda/22.png patch | view | raw | blame | history
data/pogoda/23.png patch | view | raw | blame | history
data/pogoda/3.png patch | view | raw | blame | history
data/pogoda/4.png patch | view | raw | blame | history
data/pogoda/5.png patch | view | raw | blame | history
data/pogoda/6.png patch | view | raw | blame | history
data/pogoda/7.png patch | view | raw | blame | history
data/pogoda/8.png patch | view | raw | blame | history
data/pogoda/9.png patch | view | raw | blame | history
modules/30_pogoda.php 233 ●●●●● patch | view | raw | blame | history
modules/30_pogoda/api_geonames.php 54 ●●●●● patch | view | raw | blame | history
modules/30_pogoda/api_geonames_config.php 6 ●●●●● patch | view | raw | blame | history
modules/30_pogoda/api_yrno.php 221 ●●●●● patch | view | raw | blame | history
modules/30_pogoda/handler.php 171 ●●●●● patch | view | raw | blame | history
modules/30_pogoda/init.php 57 ●●●●● patch | view | raw | blame | history
LICENCE
@@ -5,9 +5,9 @@
|  |____ |  ||  \____ |  |____ |  |\   ||  \____ |  \__/  ||   ____   |
|_______||__| \______||_______||__| \__| \______| \______/ |__|    |__|
======================
Creative Commons by-nc
======================
========================
 Creative Commons by-nc
========================
Niniejszy program, za wyjątkiem wymienionych poniżej zbiorów, jest rozpowszechniany na licencji:
@@ -17,9 +17,9 @@
http://creativecommons.org/licenses/by-nc/2.5/legalcode.pl
=======
GNU GPL
=======
=========
 GNU GPL
=========
Niektóre zbiory danych wykorzystywane w programie są udostępniane na licencji GNU GPL, której treść znajduje się w pliku GNU-GPL. Bot może działać bez tych plików po usunięciu odpowiedniego modułu z katalogu /modules/
@@ -40,10 +40,19 @@
   praca pochodna od w/w na potrzeby bota, wygenerowana za pomoca: /data/synonimy/parse.php
===========================
 CC BY (uznanie autorstwa)
===========================
=============
Inna/nieznana
=============
Niektóre zbiory danych wykorzystywane w programie są udostępniane na licencji CC BY 3.0, której treść znajduje się pod adresem http://creativecommons.org/licenses/by/3.0/
/data/pogoda/*.png
   http://api.yr.no/license_data.html
===============
 Inna/nieznana
===============
Poniższe zbiory zostały znalezione w Internecie, a autor nie zaznaczył licencji:
UPGRADE
@@ -6,10 +6,13 @@
  z katalogu ./modules
* nadpisz wszystkie pliki w katalogach ./class i ./modules,
  za wyjątkiem ./class/config.php
* usuń wszystkie pliki z katalogu ./data/pogoda
* wgraj nowe pliki do folderu ./data/pogoda
* usuń wszystkie pliki z katalogu ./cache
===========================
 AKTUALIZACJA Z WERSJI 2.2
===========================
======================================
 AKTUALIZACJA Z WERSJI 2.2/2.1/2.0 RC
======================================
* usuń wszystkie pliki i katalogi (za wyjątkiem własnych modułów)
  z katalogu ./modules
@@ -17,7 +20,10 @@
  za wyjątkiem ./class/config.php
* zastąp plik ./data/lotto/pobierz.php
* zastąp plik ./data/tv/pobierz.php
* usuń wszystkie pliki z katalogu ./data/pogoda
* wgraj nowe pliki do folderu ./data/pogoda
* usuń wszystkie pliki z katalogów ./cache oraz ./data/tv/cache
* dokonaj aktualizacji danych (cd ./data ; ./update_all.sh)
* pobierz Account Key ze strony https://datamarket.azure.com/
  (wymaga identyfikatora Windows Live ID) i uzyskaj subskrybcję Microsoft Translator:
  https://datamarket.azure.com/dataset/1899a118-d202-492c-aa16-ba21c33c06cb
@@ -31,13 +37,6 @@
class msapi_config {
        protected $accountKey = 'A123BC9238...ADD';
==================================
 AKTUALIZACJA Z WERSJI 2.0 RC/2.1
==================================
* wykonaj kroki opisane w sekcji "AKTUALIZACJA Z WERSJI 2.2"
* dokonaj aktualizacji danych (cd ./data ; ./update_all.sh)
================================
 AKTUALIZACJA Z WERSJI 2.0 Beta
================================
@@ -45,8 +44,7 @@
* zastąp wszystkie pliki (bez podfolderów!) w ./data,
  a następnie popraw ścieżkę do bota w ./data/update.sh
* zastąp plik aliases w ./data/tv
* wykonaj polecenia z działu "AKTUALIZACJA Z WERSJI 2.2"
* dokonaj aktualizacji danych (cd ./data ; ./update_all.sh)
* wykonaj polecenia z działu "AKTUALIZACJA Z WERSJI 2.2/2.1/2.0 RC"
-------
 UWAGA
data/pogoda/.emptydir
data/pogoda/1.png
data/pogoda/10.png
data/pogoda/11.png
data/pogoda/12.png
data/pogoda/13.png
data/pogoda/14.png
data/pogoda/15.png
data/pogoda/2.png
data/pogoda/20.png
data/pogoda/21.png
data/pogoda/22.png
data/pogoda/23.png
data/pogoda/3.png
data/pogoda/4.png
data/pogoda/5.png
data/pogoda/6.png
data/pogoda/7.png
data/pogoda/8.png
data/pogoda/9.png
modules/30_pogoda.php
File was deleted
modules/30_pogoda/api_geonames.php
New file
@@ -0,0 +1,54 @@
<?php
require_once(dirname(__FILE__).'/api_geonames_config.php');
class api_geonames extends api_geonames_config {
    function search($name) {
        $url = 'http://'.$this->host.'/search?name='.urlencode($name).'&lang=pl&maxrows=3'.($this->username !== NULL ? '&username='.urlencode($this->username) : '');
        try {
            $download = new DownloadHelper($url);
            $data = $download->exec();
            if(!$data) {
                $download->cacheFor(600);
                return FALSE;
            }
            libxml_use_internal_errors();
            $data = simplexml_load_string();
            libxml_clear_errors();
            if(!$data) {
                $download->cacheFor(600);
                return FALSE;
            }
            // Trzymaj w cache przez około 278 dni
            $download->cacheFor(1000000);
            if($data->geoname[0]->getName() != 'geoname'
                || $data->geoname[0]->name->getName() != 'name'
                || $data->geoname[0]->countryName->getName() != 'countryName'
                || $data->geoname[0]->lat->getName() != 'lat'
                || $data->geoname[0]->lng->getName() != 'lng') {
                return NULL;
            }
            $data = (array)$this->geoname[0];
            foreach($data as &$value) {
                $value = trim($value);
            }
            unset($value);
            if(isset($data['countryName']) && $data['countryName'] == 'Rzeczpospolita Polska') {
                $data['countryName'] = 'Polska';
            }
            return $data;
        }
        catch(Exception $e) {
            return FALSE;
        }
    }
}
?>
modules/30_pogoda/api_geonames_config.php
New file
@@ -0,0 +1,6 @@
<?php
class api_geonames_config {
    protected $host = 'ws.geonames.org';
    protected $username = NULL;
}
?>
modules/30_pogoda/api_yrno.php
New file
@@ -0,0 +1,221 @@
<?php
class api_yrno_parse {
    protected $xml;
    protected $dane;
    var $symbols = array(
        1 => 'Słonecznie',
        2 => 'Lekkie zachmurzenie',
        3 => 'Częściowe zachmurzenie',
        4 => 'Zachmurzenie',
        5 => 'Lekki deszcz z przejaśnieniami',
        6 => 'Lekki deszcz i burze',
        7 => 'Lekki deszcz ze śniegiem',
        8 => 'Śnieg',
        9 => 'Lekki deszcz',
        10 => 'Deszcz',
        11 => 'Burze z deszczem',
        12 => 'Deszcz ze śniegiem',
        13 => 'Śnieg',
        14 => 'Burze ze śniegiem',
        15 => 'Mgły',
        16 => 1,
        17 => 2,
        18 => 5,
        19 => 8,
        20 => 'Deszcz ze śniegiem, burze, możliwe przejaśnienia',
        21 => 'Burze ze śniegiem, możliwe przejaśnienia',
        22 => 'Lekki deszcz i burze',
        23 => 'Deszcz ze śniegiem, burze'
    );
    var $wind = array(
        'N' => 'północny',
        'NW' => 'północno-zachodni',
        'W' => 'zachodni',
        'SW' => 'południowo-zachodni',
        'S' => 'południowy',
        'SE' => 'południowo-wschodni',
        'E' => 'wschodni',
        'NE' => 'północno-wschodni',
    );
    function __construct($xml) {
        libxml_use_internal_errors();
        $this->xml = simplexml_load_string($xml);
        libxml_clear_errors();
        if(!$this->xml) {
            throw new Exception('Niepoprawny format danych meteorologicznych!');
        }
    }
    function mktime($time) {
        return strtotime(substr($time, 0, -1));
    }
    function wind($dir) {
        if(isset($this->wind[$dir])) {
            return $this->wind[$dir];
        }
        else
        {
            return '';
        }
    }
    function parseForecast() {
        $this->dane = array(
            '0h' => array(),
            '3h' => array(),
            '6h' => array(),
        );
        foreach($this->xml->product->time as $time) {
            $to = $this->mktime((string)$time->attributes()->to);
            $from = $this->mktime((string)$time->attributes()->from);
            $time = $time->location;
            if($to == $from) {
                $this->dane['0h'][$to] = array(
                    'temp' => (string)$time->temperature->attributes()->value,
                    'wind_speed' => (string)$time->windSpeed->attributes()->mps,
                    'wind_dir' => (string)$time->windDirection->attributes()->name,
                    'humidity' => (string)$time->humidity->attributes()->value,
                    'pressure' => (string)$time->pressure->attributes()->value,
                );
            }
            elseif($to-$from > 0) {
                if($to-$from > 14400) {
                    $put = '6h';
                }
                else
                {
                    $put = '3h';
                }
                $icon = (int)$time->symbol->attributes()->number;
                if(is_int($this->symbols[$icon])) {
                    $icon = $this->symbols[$icon];
                }
                $this->dane[$put][$to] = array(
                    'from' => $from,
                    'to' => $to,
                    'icon' => $icon
                );
            }
        }
    }
    function getCurrentIcon() {
        $now = time();
        foreach($this->dane['3h'] as $value) {
            if($value['from'] <= $now && $now < $value['to']) {
                return $value['icon'];
            }
        }
        return NULL;
    }
    function getCurrentWeather() {
        $dist = PHP_INT_MAX;
        $current = NULL;
        foreach($this->dane['0h'] as $time => $value) {
            if(abs($time - time()) < $dist) {
                $dist = abs($time - time());
                $current = $value;
            }
            else
            {
                break;
            }
        }
        return $current;
    }
    function getDaypartWeather($timestamp) {
        $start = strtotime('6:00', $timestamp);
        $dayend = strtotime('19:30', $timestamp);
        $end = $start + 22*3600;
        $wind = $temp = array(
            'day' => array(),
            'night' => array(),
        );
        foreach($this->dane['0h'] as $time => $value) {
            $part = NULL;
            if($start <= $time && $time < $dayend) {
                $part = 'day';
            }
            elseif($dayend < $time && $time <= $end) {
                $part = 'night';
            }
            elseif($end <= $time) {
                break;
            }
            if($part !== NULL) {
                if(!isset($temp[$part]['from']) || $value['temp'] < $temp[$part]['from']) {
                    $temp[$part]['from'] = $value['temp'];
                }
                if(!isset($temp[$part]['to']) || $value['temp'] > $temp[$part]['to']) {
                    $temp[$part]['to'] = $value['temp'];
                }
                if(!isset($wind[$part]['from']) || $value['wind_speed'] < $wind[$part]['from']) {
                    $wind[$part]['from'] = $value['wind_speed'];
                }
                if(!isset($wind[$part]['to']) || $value['wind_speed'] > $wind[$part]['to']) {
                    $wind[$part]['to'] = $value['wind_speed'];
                }
            }
        }
        if($temp['day'] == array() || $wind['day'] == array()) {
            unset($temp['day']);
            unset($wind['day']);
        }
        return array($temp, $wind);
    }
    function getDaypartIcon($timestamp) {
        $start = strtotime('6:00', $timestamp);
        $end = strtotime('24:00', $timestamp);
        $return = array();
        foreach($this->dane['3h'] as $time => $value) {
            if($start <= $value['from'] && $value['to'] <= $end) {
                $return[] = $value['icon'];
            }
            elseif($end <= $value['from']) {
                break;
            }
        }
        return $return;
    }
}
function yrno_weather($lat, $lon) {
    $down = new DownloadHelper('http://api.yr.no/weatherapi/locationforecastlts/1.1/?lat='.urlencode($lat).';lon='.urlencode($lon));
    $down->setopt(CURLOPT_USERAGENT, 'BotGG/'.main::VERSION_NUM.' WeatherModule/1.0 (http://bot.jacekk.net/weather.html)');
    try {
        $data = $down->exec();
        $data = new api_yrno_parse($data);
        $data->parseForecast();
    }
    catch(Exception $e) {
        $down->cacheFor(600);
        return FALSE;
    }
    $down->cacheFor(7200);
    return $data;
}
?>
modules/30_pogoda/handler.php
New file
@@ -0,0 +1,171 @@
<?php
require_once(dirname(__FILE__).'/api_geonames.php');
require_once(dirname(__FILE__).'/api_yrno.php');
class bot_pogoda_module extends BotModule {
    function pogoda($msg, $params) {
        $arg = trim($msg->args);
        $out = new BotMsg();
        if(empty($arg)) {
            $msg->session->setClass('pogoda');
            if(isset($msg->session->miasto) && !isset($msg->session->geo)) {
                if(strlen($msg->session->miasto) > 0) {
                    $out->a('<p>Wymagane przekonwertowanie danych... Wywoływanie komendy <i>miasto '.htmlspecialchars($msg->session->miasto).'</i>...</p>');
                    $out->a($this->miasto($msg, $msg->session->miasto));
                    $out->a('<p></p>');
                }
                else
                {
                    unset($msg->session->miasto);
                }
            }
            if(!isset($msg->session->miasto)) {
                try {
                    $api = new BotAPIGG();
                    $data = $api->getPublicData($msg->user);
                    if(is_array($data) && isset($data['city'])) {
                        $arg = trim($data['city']);
                    }
                    unset($data, $api);
                }
                catch(Exception $e) {
                }
                if(empty($arg)) {
                    $arg = 'Warszawa';
                    $forced = TRUE;
                }
                $out->a('<p>Nie ustawiono miasta (pomoc - wpisz: help miasto) - '.(!$forced ? 'na podstawie danych z katalogu publicznego ' : '').'wybieram miasto '.$arg.'</p>'."\n\n");
                $loc = new api_geonames();
                $loc = $loc->search($arg);
                if($loc === FALSE) {
                    return new BotMsg('Nie udało się pobrać danych o podanym miejscu - spróbuj ponownie za około 10 minut.');
                }
                elseif($loc === NULL) {
                    return new BotMsg('Dla podanego miejsca nie udało się uzyskać współrzędnych geograficznych - spróbuj wpisać inną nazwę.');
                }
            }
            else
            {
                $loc = array(
                    'name' => $msg->session->miasto,
                    'countryName' => $msg->session->kraj,
                    'coutryCode' => $msg->session->cc,
                    'lat' => $msg->session->geo['lat'],
                    'lng' => $msg->session->geo['lon']
                );
            }
        }
        $api = yrno_weather($loc['lat'], $loc['lng']);
        if($api == FALSE) {
            return new BotMsg('Nie udało się pobrać danych o pogodzie - spróbuj ponownie za około 10 minut.');
        }
        $out->a('<p>Pogoda dla '.htmlspecialchars($loc->name).', '.htmlspecialchars($loc->countryName).'.</p>'."\n\n");
        $icon = $api->symbols[$api->getCurrentIcon()];
        $weather = $api->getCurrentWeather();
        $out->a('<p><b>Teraz</b><br />'."\n"
            . $icon.'<br />'."\n"
            . 'Temp.: '.htmlspecialchars($weather['temp']).'°C<br />'."\n"
            . 'Wiatr: '.htmlspecialchars($weather['wind']).' km/h, '.$api->wind($weather['wind']).'<br />'."\n"
            . 'Ciśnienie: '.htmlspecialchars($weather['pressure']).' hPa</p>'."\n\n");
        $when = time();
        if($when < strtotime('19:00')) {
            $out->a($this->getHTMLforWeather('Dziś', $api->getDaypartIcon($when), $api->getDaypartWeather($when)));
        }
        $when = strtotime('+1 day', $when);
        $out->a($this->getHTMLforWeather('Jutro', $api->getDaypartIcon($when), $api->getDaypartWeather($when)));
        $when = strtotime('+1 day', $when);
        $out->a($this->getHTMLforWeather('Pojutrze', $api->getDaypartIcon($when), $api->getDaypartWeather($when)));
        $out->a('<p>Dane lokalizacyjne pochodzą z serwisu GeoNames.<br />'."\n"
            . 'Dane pogodowe pochodzą z Norweskiego Instytutu Meteorologicznego.</p>');
        return $out;
    }
    function getHTMLforWeather($name, $icons, $weather) {
        $html = '<p><b>'.$name.'</b><br />'."\n";
        foreach($icons as $icon) {
            if(is_file('./data/pogoda/'.htmlspecialchars($icon).'.png')) {
                $html .= '<img src="./data/pogoda/'.htmlspecialchars($icon).'.png" alt="" /> ';
            }
        }
        $html .= '<br />'."\n"
            . 'Temp.: '.$weather['temp']['from'].($weather['temp']['from'] != $weather['temp']['to'] ? '-'.$weather['temp']['to'] : '').'°C<br />'."\n"
            . 'Wiatr: '.$weather['wind']['from'].($weather['wind']['from'] != $weather['wind']['to'] ? '-'.$weather['wind']['to'] : '').' km/h</p>'."\n\n";
        return $html;
    }
    function miasto($msg, $params) {
        $msg->session->setClass('pogoda');
        if(strlen($params) > 0) {
            $arg = trim($params);
        }
        else
        {
            $arg = trim($msg->args);
        }
        if(empty($arg)) {
            if(isset($this->session->miasto)) {
                return new BotMsg('Aktualnie ustawione miejsce to: '.htmlspecialchars($this->session->miasto).', '.htmlspecialchars($this->session->countryName));
            }
            try {
                $api = new BotAPIGG();
                $dane = $api->getPublicData($msg->user);
                if(!isset($arg['city']) || empty($arg['city'])) {
                    throw new Exception('Brak miasta w danych w katalogu publicznym.');
                }
                $arg = trim($arg['city']);
            }
            catch(Exception $e) {
                return new BotMsg('Nie podano wymaganego argumentu <i>miasto</i>.');
            }
            $out->a('<p>Na podstawie danych z katalogu publicznego wybieram miasto: '.htmlspecialchars($arg).'</p>'."\n\n");
        }
        else
        {
            $out = new BotMsg();
        }
        $api = new api_geonames();
        $dane = $api->search($arg);
        if($dane === FALSE) {
            return new BotMsg('Wystąpił błąd przy wyszukiwaniu miasta. Spróbuj ponownie później.');
        }
        elseif($dane === NULL) {
            return new BotMsg('Nie udało się zlokalizować podanego miejsca. Spróbuj wpisać inną nazwę.');
        }
        $msg->session->miasto = $dane['name'];
        $msg->session->kraj = $dane['countryName'];
        $msg->session->cc = $dane['countryCode'];
        $msg->session->geo = array('lat' => $dane['lat'], 'lon' => $dane['lng']);
        $out->a('<p>Ustawiono miejsce: '.htmlspecialchars($this->session->miasto).', '.htmlspecialchars($this->session->countryName).'</p>');
        return $out;
    }
}
?>
modules/30_pogoda/init.php
New file
@@ -0,0 +1,57 @@
<?php
class bot_pogoda_init implements BotModuleInit {
    function register() {
        $handler_pogoda = array(
            array(
                'file' => 'handler.php',
                'class' => 'bot_pogoda_module',
                'method' => 'pogoda',
            )
        );
        $handler_miasto = array(
            array(
                'file' => 'handler.php',
                'class' => 'bot_pogoda_module',
                'method' => 'miasto',
            )
        );
        return array(
            'pogoda' => $handler_pogoda,
            'p' => $handler_pogoda,
            'weather' => $handler_pogoda,
            'temperatura' => $handler_pogoda,
            'temp' => $handler_pogoda,
            'miasto' => $handler_miasto,
            'm' => $handler_miasto,
        );
    }
    function help($params = NULL) {
        if($params === NULL) {
            return new BotMsg('<b>pogoda</b> <i>[miasto]</i><br />'."\n"
                . '   Pogoda dla miasta.<br />'."\n"
                . '<b>miasto</b> <i>miasto</i><br />'."\n"
                . '   Ustawia domyślne miasto dla funkcji pogoda.<br />'."\n"
                . '<br />'."\n");
        }
        elseif(substr($params, 0, 1) == 'm') {
            return new BotMsg('<b>miasto</b> <i>miasto</i> (alias: <b>m</b>)<br />'."\n"
                . '   Ustawia domyślne miasto dla komendy <b>pogoda</b>. Dane o lokalizacji są również wykorzystywane do wyliczania godziny wschodu i zachodu słońca w funkcji <b>data</b>.<br />'."\n"
                . '<br />'."\n"
                . '<u>Przykład:</u><br />'."\n"
                . 'miasto Warszawa');
        }
        else
        {
            return new BotMsg('<b>pogoda</b> <i>[miasto]</i> (aliasy: <b>p, weather, temp</b>)<br />'."\n"
                . '   Podaje pogodę dla danego miasta na najbliższe dni. Domyślne miasto można ustawić komendą <b>miasto</p>.<br />'."\n"
                . '<br />'."\n"
                . '<u>Przykład:</u><br />'."\n"
                . 'pogoda Warszawa');
        }
    }
}
return 'bot_data_init';
?>