From b176c2099bcfffd870650e5ddd73023063965d23 Mon Sep 17 00:00:00 2001 From: Andreas Bielawski Date: Wed, 15 Jun 2016 01:16:27 +0200 Subject: [PATCH] =?UTF-8?q?-=20Portiere=20Weather-=20und=20Forecast-Plugin?= =?UTF-8?q?s=20-=20convert=5Ftimestamp()=20und=20round()=20in=20utilites?= =?UTF-8?q?=20-=20Bugfixes=20und=20kleinere=20=C3=84nderungen?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- otouto/plugins/creds.lua | 6 +- otouto/plugins/forecast.lua | 222 +++++++++++++++++++++++++++++++++++ otouto/plugins/weather.lua | 175 ++++++++++++++++++++------- otouto/plugins/wikipedia.lua | 4 +- otouto/utilities.lua | 14 +++ 5 files changed, 372 insertions(+), 49 deletions(-) create mode 100644 otouto/plugins/forecast.lua diff --git a/otouto/plugins/creds.lua b/otouto/plugins/creds.lua index 42c6092..ff32f46 100644 --- a/otouto/plugins/creds.lua +++ b/otouto/plugins/creds.lua @@ -60,7 +60,7 @@ end function creds_manager:add_creds(var, key) print('Saving credential for '..var..' to redis hash '..hash) redis:hset(hash, var, key) - reload_creds() + creds_manager:reload_creds() return 'Gespeichert!' end @@ -68,7 +68,7 @@ function creds_manager:del_creds(var) if redis:hexists(hash, var) == true then print('Deleting credential for '..var..' from redis hash '..hash) redis:hdel(hash, var) - reload_creds() + creds_manager:reload_creds() return 'Key von "'..var..'" erfolgreich gelöscht!' else return 'Du hast keine Logininformationen für diese Variable eingespeichert.' @@ -80,7 +80,7 @@ function creds_manager:rename_creds(var, newvar) local key = redis:hget(hash, var) if redis:hsetnx(hash, newvar, key) == true then redis:hdel(hash, var) - reload_creds() + creds_manager:reload_creds() return '"'..var..'" erfolgreich zu "'..newvar..'" umbenannt.' else return "Variable konnte nicht umbenannt werden: Zielvariable existiert bereits." diff --git a/otouto/plugins/forecast.lua b/otouto/plugins/forecast.lua new file mode 100644 index 0000000..47fcc3c --- /dev/null +++ b/otouto/plugins/forecast.lua @@ -0,0 +1,222 @@ +local forecast = {} + +local HTTPS = require('ssl.https') +local URL = require('socket.url') +local JSON = require('dkjson') +local utilities = require('otouto.utilities') +local bindings = require('otouto.bindings') +local redis = (loadfile "./otouto/redis.lua")() + +function forecast:init(config) + if not cred_data.forecastio_apikey then + print('Missing config value: forecastio_apikey.') + print('weather.lua will not be enabled.') + return + elseif not cred_data.google_apikey then + print('Missing config value: google_apikey.') + print('weather.lua will not be enabled.') + return + end + + forecast.triggers = { + "^(/f)$", + "^(/f) (.*)$", + "^(/fh)$", + "^(/fh) (.*)$", + "^(/forecast)$", + "^(/forecast) (.*)$", + "^(/forecasth)$", + "^(/forecasth) (.*)$" + } + forecast.doc = [[* +]]..config.cmd_pat..[[f*: Wettervorhersage für deinen Wohnort _(/location set )_ +*]]..config.cmd_pat..[[f* __: Wettervorhersage für diesen Ort +*]]..config.cmd_pat..[[fh*: 24-Stunden-Wettervorhersage für deine Stadt _(/location set [Ort]_ +*]]..config.cmd_pat..[[fh* __: 24-Stunden-Wettervorhersage für diesen Ort +]] +end + +forecast.command = 'forecast' + +local BASE_URL = "https://api.forecast.io/forecast" +local apikey = cred_data.forecastio_apikey +local google_apikey = cred_data.google_apikey + +function get_city_name(lat, lng) + local city = redis:hget('telegram:cache:weather:pretty_names', lat..','..lng) + if city then return city end + local url = 'https://maps.googleapis.com/maps/api/geocode/json?latlng='..lat..','..lng..'&result_type=political&language=de&key='..google_apikey + local res, code = HTTPS.request(url) + if code ~= 200 then return 'Unbekannte Stadt' end + local data = JSON.decode(res).results[1] + local city = data.formatted_address + print('Setting '..lat..','..lng..' in redis hash telegram:cache:weather:pretty_names to "'..city..'"') + redis:hset('telegram:cache:weather:pretty_names', lat..','..lng, city) + return city +end + +function get_condition_symbol(weather, n) + if weather.data[n].icon == 'clear-day' then + return '☀️' + elseif weather.data[n].icon == 'clear-night' then + return '🌙' + elseif weather.data[n].icon == 'rain' then + return '☔️' + elseif weather.data[n].icon == 'snow' then + return '❄️' + elseif weather.data[n].icon == 'sleet' then + return '🌨' + elseif weather.data[n].icon == 'wind' then + return '💨' + elseif weather.data[n].icon == 'fog' then + return '🌫' + elseif weather.data[n].icon == 'cloudy' then + return '☁️☁️' + elseif weather.data[n].icon == 'partly-cloudy-day' then + return '🌤' + elseif weather.data[n].icon == 'partly-cloudy-night' then + return '🌙☁️' + else + return '' + end +end + +function get_temp(weather, n, hourly) + if hourly then + local temperature = string.gsub(round(weather.data[n].temperature, 1), "%.", ",") + local condition = weather.data[n].summary + return temperature..'°C | '..get_condition_symbol(weather, n)..' '..condition + else + local day = string.gsub(round(weather.data[n].temperatureMax, 1), "%.", ",") + local night = string.gsub(round(weather.data[n].temperatureMin, 1), "%.", ",") + local condition = weather.data[n].summary + return '☀️ '..day..'°C | 🌙 '..night..'°C | '..get_condition_symbol(weather, n)..' '..condition + end +end + +function forecast:get_forecast(lat, lng) + print('Finde Wetter in '..lat..', '..lng) + local text = redis:get('telegram:cache:forecast:'..lat..','..lng) + if text then print('...aus dem Cache..') return text end + + local url = BASE_URL..'/'..apikey..'/'..lat..','..lng..'?lang=de&units=si&exclude=currently,minutely,hourly,alerts,flags' + + local response_body = {} + local request_constructor = { + url = url, + method = "GET", + sink = ltn12.sink.table(response_body) + } + local ok, response_code, response_headers, response_status_line = HTTPS.request(request_constructor) + if not ok then return nil end + local data = JSON.decode(table.concat(response_body)) + local ttl = string.sub(response_headers["cache-control"], 9) + + + local weather = data.daily + local city = get_city_name(lat, lng) + + local header = '*Vorhersage für '..city..':*\n_'..weather.summary..'_\n' + + local text = '*Heute:* '..get_temp(weather, 1) + local text = text..'\n*Morgen:* '..get_temp(weather, 2) + + for day in pairs(weather.data) do + if day > 2 then + text = text..'\n*'..convert_timestamp(weather.data[day].time, '%a, %d.%m')..'*: '..get_temp(weather, day) + end + end + + local text = string.gsub(text, "Mon", "Mo") + local text = string.gsub(text, "Tue", "Di") + local text = string.gsub(text, "Wed", "Mi") + local text = string.gsub(text, "Thu", "Do") + local text = string.gsub(text, "Fri", "Fr") + local text = string.gsub(text, "Sat", "Sa") + local text = string.gsub(text, "Sun", "So") + + cache_data('forecast', lat..','..lng, header..text, tonumber(ttl), 'key') + + return header..text +end + +function forecast:get_forecast_hourly(lat, lng) + print('Finde stündliches Wetter in '..lat..', '..lng) + local text = redis:get('telegram:cache:forecast:'..lat..','..lng..':hourly') + if text then print('...aus dem Cache..') return text end + + local url = BASE_URL..'/'..apikey..'/'..lat..','..lng..'?lang=de&units=si&exclude=currently,minutely,daily,alerts,flags' + + local response_body = {} + local request_constructor = { + url = url, + method = "GET", + sink = ltn12.sink.table(response_body) + } + local ok, response_code, response_headers, response_status_line = HTTPS.request(request_constructor) + if not ok then return nil end + local data = JSON.decode(table.concat(response_body)) + local ttl = string.sub(response_headers["cache-control"], 9) + + + local weather = data.hourly + local city = get_city_name(lat, lng) + + local header = '*24-Stunden-Vorhersage für '..city..':*\n_'..weather.summary..'_' + local text = "" + + for hour in pairs(weather.data) do + if hour < 26 then + text = text..'\n*'..convert_timestamp(weather.data[hour].time, '%H:%M Uhr')..'* | '..get_temp(weather, hour, true) + end + end + + cache_data('forecast', lat..','..lng..':hourly', header..text, tonumber(ttl), 'key') + + return header..text +end + +function forecast:action(msg, config, matches) + local user_id = msg.from.id + local city = get_location(user_id) + + if matches[2] then + city = matches[2] + else + local set_location = get_location(user_id) + if not set_location then + city = 'Berlin, Deutschland' + else + city = set_location + end + end + + local lat = redis:hget('telegram:cache:weather:'..string.lower(city), 'lat') + local lng = redis:hget('telegram:cache:weather:'..string.lower(city), 'lng') + if not lat and not lng then + print('Koordinaten nicht eingespeichert, frage Google...') + coords = utilities.get_coords(city, config) + lat = coords.lat + lng = coords.lon + end + + if not lat and not lng then + utilities.send_reply(self, msg, '*Diesen Ort gibt es nicht!*', true) + return + end + + redis:hset('telegram:cache:weather:'..string.lower(city), 'lat', lat) + redis:hset('telegram:cache:weather:'..string.lower(city), 'lng', lng) + + if matches[1] == '/forecasth' or matches[1] == '/fh' then + text = forecast:get_forecast_hourly(lat, lng) + else + text = forecast:get_forecast(lat, lng) + end + if not text then + text = '*Konnte die Wettervorhersage für diese Stadt nicht bekommen.*' + end + utilities.send_reply(self, msg, text, true) +end + +return forecast diff --git a/otouto/plugins/weather.lua b/otouto/plugins/weather.lua index d437c46..ee7399c 100644 --- a/otouto/plugins/weather.lua +++ b/otouto/plugins/weather.lua @@ -1,63 +1,150 @@ local weather = {} -local HTTP = require('socket.http') +local HTTPS = require('ssl.https') +local URL = require('socket.url') local JSON = require('dkjson') local utilities = require('otouto.utilities') +local bindings = require('otouto.bindings') +local redis = (loadfile "./otouto/redis.lua")() function weather:init(config) - if not config.owm_api_key then - print('Missing config value: owm_api_key.') + if not cred_data.forecastio_apikey then + print('Missing config value: forecastio_apikey.') + print('weather.lua will not be enabled.') + return + elseif not cred_data.google_apikey then + print('Missing config value: google_apikey.') print('weather.lua will not be enabled.') return end - weather.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('weather', true).table - weather.doc = [[``` -]]..config.cmd_pat..[[weather -Returns the current weather conditions for a given location. -```]] + weather.triggers = { + "^/wetter$", + "^/wetter (.*)$", + "^/w$", + "^/w (.*)$" + } + weather.doc = [[* +]]..config.cmd_pat..[[wetter*: Wetter für deinen Wohnort _(/location set [Ort])_ +*]]..config.cmd_pat..[[wetter* __: Wetter für diesen Ort +]] end -weather.command = 'weather ' +weather.command = 'wetter' -function weather:action(msg, config) +local BASE_URL = "https://api.forecast.io/forecast" +local apikey = cred_data.forecastio_apikey +local google_apikey = cred_data.google_apikey - local input = utilities.input(msg.text) - if not input then - if msg.reply_to_message and msg.reply_to_message.text then - input = msg.reply_to_message.text - else - utilities.send_message(self, msg.chat.id, weather.doc, true, msg.message_id, true) - return - end +function get_city_name(lat, lng) + local city = redis:hget('telegram:cache:weather:pretty_names', lat..','..lng) + if city then return city end + local url = 'https://maps.googleapis.com/maps/api/geocode/json?latlng='..lat..','..lng..'&result_type=political&language=de&key='..google_apikey + local res, code = HTTPS.request(url) + if code ~= 200 then return 'Unbekannte Stadt' end + local data = JSON.decode(res).results[1] + local city = data.formatted_address + print('Setting '..lat..','..lng..' in redis hash telegram:cache:weather:pretty_names to "'..city..'"') + redis:hset('telegram:cache:weather:pretty_names', lat..','..lng, city) + return city +end + +function weather:get_weather(lat, lng) + print('Finde Wetter in '..lat..', '..lng) + local text = redis:get('telegram:cache:weather:'..lat..','..lng) + if text then print('...aus dem Cache') return text end + + local url = BASE_URL..'/'..apikey..'/'..lat..','..lng..'?lang=de&units=si&exclude=minutely,hourly,daily,alerts,flags' + + local response_body = {} + local request_constructor = { + url = url, + method = "GET", + sink = ltn12.sink.table(response_body) + } + local ok, response_code, response_headers, response_status_line = HTTPS.request(request_constructor) + if not ok then return nil end + local data = JSON.decode(table.concat(response_body)) + local ttl = string.sub(response_headers["cache-control"], 9) + + + local weather = data.currently + local city = get_city_name(lat, lng) + local temperature = string.gsub(round(weather.temperature, 1), "%.", ",") + local feelslike = string.gsub(round(weather.apparentTemperature, 1), "%.", ",") + local temp = '*Wetter in '..city..':*\n'..temperature..' °C' + local conditions = ' | '..weather.summary + if weather.icon == 'clear-day' then + conditions = conditions..' ☀️' + elseif weather.icon == 'clear-night' then + conditions = conditions..' 🌙' + elseif weather.icon == 'rain' then + conditions = conditions..' ☔️' + elseif weather.icon == 'snow' then + conditions = conditions..' ❄️' + elseif weather.icon == 'sleet' then + conditions = conditions..' 🌨' + elseif weather.icon == 'wind' then + conditions = conditions..' 💨' + elseif weather.icon == 'fog' then + conditions = conditions..' 🌫' + elseif weather.icon == 'cloudy' then + conditions = conditions..' ☁️☁️' + elseif weather.icon == 'partly-cloudy-day' then + conditions = conditions..' 🌤' + elseif weather.icon == 'partly-cloudy-night' then + conditions = conditions..' 🌙☁️' + else + conditions = conditions..'' + end + local windspeed = ' | 💨 '..string.gsub(round(weather.windSpeed, 1), "%.", ",")..' m/s' + + local text = temp..conditions..windspeed + + if temperature ~= feelslike then + text = text..'\n(gefühlt: '..feelslike..' °C)' + end + + cache_data('weather', lat..','..lng, text, tonumber(ttl), 'key') + return text +end + +function weather:action(msg, config, matches) + local user_id = msg.from.id + + if matches[1] ~= '/wetter' and matches[1] ~= '/w' then + city = matches[1] + else + local set_location = get_location(user_id) + if not set_location then + city = 'Berlin, Deutschland' + else + city = set_location end + end + + local lat = redis:hget('telegram:cache:weather:'..string.lower(city), 'lat') + local lng = redis:hget('telegram:cache:weather:'..string.lower(city), 'lng') + if not lat and not lng then + print('Koordinaten nicht eingespeichert, frage Google...') + coords = utilities.get_coords(city, config) + lat = coords.lat + lng = coords.lon + end + + if not lat and not lng then + utilities.send_reply(self, msg, '*Diesen Ort gibt es nicht!*', true) + return + end - local coords = utilities.get_coords(input, config) - if type(coords) == 'string' then - utilities.send_reply(self, msg, coords) - return - end - - local url = 'http://api.openweathermap.org/data/2.5/weather?APPID=' .. config.owm_api_key .. '&lat=' .. coords.lat .. '&lon=' .. coords.lon - - local jstr, res = HTTP.request(url) - if res ~= 200 then - utilities.send_reply(self, msg, config.errors.connection) - return - end - - local jdat = JSON.decode(jstr) - if jdat.cod ~= 200 then - utilities.send_reply(self, msg, 'Error: City not found.') - return - end - - local celsius = string.format('%.2f', jdat.main.temp - 273.15) - local fahrenheit = string.format('%.2f', celsius * (9/5) + 32) - local output = '`' .. celsius .. '°C | ' .. fahrenheit .. '°F, ' .. jdat.weather[1].description .. '.`' - - utilities.send_reply(self, msg, output, true) - + redis:hset('telegram:cache:weather:'..string.lower(city), 'lat', lat) + redis:hset('telegram:cache:weather:'..string.lower(city), 'lng', lng) + + local text = weather:get_weather(lat, lng) + if not text then + text = 'Konnte das Wetter von dieser Stadt nicht bekommen.' + end + utilities.send_reply(self, msg, text, true) end return weather diff --git a/otouto/plugins/wikipedia.lua b/otouto/plugins/wikipedia.lua index 777b87d..b15fdbc 100644 --- a/otouto/plugins/wikipedia.lua +++ b/otouto/plugins/wikipedia.lua @@ -8,10 +8,10 @@ local utilities = require('otouto.utilities') wikipedia.command = 'wiki ' function wikipedia:init(config) - wikipedia.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('wikipedia', true):t('wiki', true):t('w', true).table + wikipedia.triggers = utilities.triggers(self.info.username, config.cmd_pat):t('wikipedia', true):t('wiki', true).table wikipedia.doc = [[* ]]..config.cmd_pat..[[wiki* __: Gibt Wikipedia-Artikel aus -Aliase: ]]..config.cmd_pat..[[w, ]]..config.cmd_pat..[[wikipedia]] +Alias: ]]..config.cmd_pat..[[wikipedia]] end local get_title = function(search) diff --git a/otouto/utilities.lua b/otouto/utilities.lua index 5306af7..cec8851 100644 --- a/otouto/utilities.lua +++ b/otouto/utilities.lua @@ -240,6 +240,12 @@ function run_command(str) return result end +function convert_timestamp(timestamp, format) + local converted_date = run_command('date -d @'..timestamp..' +"'..format..'"') + local converted_date = string.gsub(converted_date, '%\n', '') + return converted_date +end + function string.starts(String, Start) return Start == string.sub(String,1,string.len(Start)) end @@ -659,6 +665,14 @@ function tablelength(T) return count end +function round(num, idp) + if idp and idp>0 then + local mult = 10^idp + return math.floor(num * mult + 0.5) / mult + end + return math.floor(num + 0.5) +end + function comma_value(amount) local formatted = amount while true do