1
0
mirror of https://github.com/taigrr/wtf synced 2025-01-18 04:03:14 -08:00

Add krisinformation module

A module to parse the feed from: https://www.krisinformation.se/en

 Krisinformation.se is a web site run by the Swedish Civil Contingencies Agency
that compiles and convey warnings, alerts, and emergency information from
Swedish authorities to the public.

Features:

 - Filter on country (all messages)
 - Filter on county (only show county specific messages)
 - Distance (only show message within a certain distance)
 - Set a max age for displaying messages
 - Set a maximum number of messages displayed
This commit is contained in:
Fredrik Steen 2020-11-30 16:26:51 +01:00
parent 68906edf49
commit 90d672b2f2
4 changed files with 331 additions and 0 deletions

View File

@ -41,6 +41,7 @@ import (
"github.com/wtfutil/wtf/modules/ipaddresses/ipinfo"
"github.com/wtfutil/wtf/modules/jenkins"
"github.com/wtfutil/wtf/modules/jira"
"github.com/wtfutil/wtf/modules/krisinformation"
"github.com/wtfutil/wtf/modules/kubernetes"
"github.com/wtfutil/wtf/modules/logger"
"github.com/wtfutil/wtf/modules/mercurial"
@ -214,6 +215,9 @@ func MakeWidget(
case "kubernetes":
settings := kubernetes.NewSettingsFromYAML(moduleName, moduleConfig, config)
widget = kubernetes.NewWidget(app, settings)
case "krisinformation":
settings := krisinformation.NewSettingsFromYAML(moduleName, moduleConfig, config)
widget = krisinformation.NewWidget(app, settings)
case "logger":
settings := logger.NewSettingsFromYAML(moduleName, moduleConfig, config)
widget = logger.NewWidget(app, settings)

View File

@ -0,0 +1,193 @@
package krisinformation
import (
"fmt"
"math"
"net/http"
"strconv"
"strings"
"time"
"github.com/wtfutil/wtf/logger"
"github.com/wtfutil/wtf/utils"
)
const (
krisinformationAPI = "https://api.krisinformation.se/v2/feed?format=json"
)
type Krisinformation []struct {
Identifier string `json:"Identifier"`
PushMessage string `json:"PushMessage"`
Updated time.Time `json:"Updated"`
Published time.Time `json:"Published"`
Headline string `json:"Headline"`
Preamble string `json:"Preamble"`
BodyText string `json:"BodyText"`
Area []struct {
Type string `json:"Type"`
Description string `json:"Description"`
Coordinate string `json:"Coordinate"`
GeometryInformation interface{} `json:"GeometryInformation"`
} `json:"Area"`
Web string `json:"Web"`
Language string `json:"Language"`
Event string `json:"Event"`
SenderName string `json:"SenderName"`
Push bool `json:"Push"`
BodyLinks []interface{} `json:"BodyLinks"`
SourceID int `json:"SourceID"`
IsVma bool `json:"IsVma"`
IsTestVma bool `json:"IsTestVma"`
}
// Client holds or configuration
type Client struct {
latitude float64
longitude float64
radius int
county string
country bool
}
// Item holds the interesting parts
type Item struct {
PushMessage string
HeadLine string
SenderName string
Country bool
County bool
Distance float64
Updated time.Time
}
//NewClient returns a new Client
func NewClient(latitude, longitude float64, radius int, county string, country bool) *Client {
return &Client{
latitude: latitude,
longitude: longitude,
radius: radius,
county: county,
country: country,
}
}
// getKrisinformation - return items that match either country, county or a radius
// Priority:
// - Country
// - County
// - Region
func (c *Client) getKrisinformation() (items []Item, err error) {
resp, err := http.Get(krisinformationAPI)
if err != nil {
return nil, err
}
defer func() { _ = resp.Body.Close() }()
var data Krisinformation
err = utils.ParseJSON(&data, resp.Body)
if err != nil {
return nil, err
}
for i := range data {
for a := range data[i].Area {
// Country wide events
if c.country && data[i].Area[a].Type == "Country" {
item := Item{
PushMessage: data[i].PushMessage,
HeadLine: data[i].Headline,
SenderName: data[i].SenderName,
Country: true,
Updated: data[i].Updated,
}
items = append(items, item)
continue
}
// County specific events
if c.county != "" && data[i].Area[a].Type == "County" {
// We look for county in description
if strings.Contains(
strings.ToLower(data[i].Area[a].Description),
strings.ToLower(c.county),
) {
item := Item{
PushMessage: data[i].PushMessage,
HeadLine: data[i].Headline,
SenderName: data[i].SenderName,
County: true,
Updated: data[i].Updated,
}
items = append(items, item)
continue
}
}
if c.radius != -1 {
coords := data[i].Area[a].Coordinate
if coords == "" {
continue
}
buf := strings.Split(coords, " ")
latlon := strings.Split(buf[0], ",")
kris_latitude, err := strconv.ParseFloat(latlon[0], 32)
if err != nil {
return nil, err
}
kris_longitude, err := strconv.ParseFloat(latlon[1], 32)
if err != nil {
return nil, err
}
distance := DistanceInMeters(kris_latitude, kris_longitude, c.latitude, c.longitude)
logger.Log(fmt.Sprintf("Distance: %f", distance/1000)) // KM
if distance < float64(c.radius) {
item := Item{
PushMessage: data[i].PushMessage,
HeadLine: data[i].Headline,
SenderName: data[i].SenderName,
Distance: distance,
Updated: data[i].Updated,
}
items = append(items, item)
}
}
}
}
return items, nil
}
// haversin(θ) function
func hsin(theta float64) float64 {
return math.Pow(math.Sin(theta/2), 2)
}
// Distance function returns the distance (in meters) between two points of
// a given longitude and latitude relatively accurately (using a spherical
// approximation of the Earth) through the Haversin Distance Formula for
// great arc distance on a sphere with accuracy for small distances
//
// point coordinates are supplied in degrees and converted into rad. in the func
//
// http://en.wikipedia.org/wiki/Haversine_formula
func DistanceInMeters(lat1, lon1, lat2, lon2 float64) float64 {
// convert to radians
// must cast radius as float to multiply later
var la1, lo1, la2, lo2, r float64
la1 = lat1 * math.Pi / 180
lo1 = lon1 * math.Pi / 180
la2 = lat2 * math.Pi / 180
lo2 = lon2 * math.Pi / 180
r = 6378100 // Earth radius in METERS
// calculate
h := hsin(la2-la1) + math.Cos(la1)*math.Cos(la2)*hsin(lo2-lo1)
return 2 * r * math.Asin(math.Sqrt(h))
}

View File

@ -0,0 +1,44 @@
package krisinformation
import (
"github.com/olebedev/config"
"github.com/wtfutil/wtf/cfg"
)
const (
defaultFocusable = false
defaultTitle = "Krisinformation"
defaultRadius = -1
defaultCountry = true
defaultCounty = ""
defaultMaxItems = -1
defaultMaxAge = 720
)
// Settings defines the configuration properties for this module
type Settings struct {
common *cfg.Common
latitude float64 `help:"The latitude of the position from which the widget should look for messages." optional:"true"`
longitude float64 `help:"The longitude of the position from which the widget should look for messages." optional:"true"`
radius int `help:"The radius in km from your position that the widget should look for messages. need latitude/longitude setting,Default 10" optional:"true"`
county string `help:"The county from where to display messages" optional:"true"`
country bool `help:"Only display country wide messages" optional:"true"`
maxitems int `help:"Only display X number of latest messages" optional:"true"`
maxage int `help:"Only show messages younger than maxage" optional:"true"`
}
// NewSettingsFromYAML creates a new settings instance from a YAML config block
func NewSettingsFromYAML(name string, ymlConfig *config.Config, globalConfig *config.Config) *Settings {
settings := Settings{
common: cfg.NewCommonSettingsFromModule(name, defaultTitle, defaultFocusable, ymlConfig, globalConfig),
latitude: ymlConfig.UFloat64("latitude", -1),
longitude: ymlConfig.UFloat64("longitude", -1),
radius: ymlConfig.UInt("radius", defaultRadius),
country: ymlConfig.UBool("country", defaultCountry),
county: ymlConfig.UString("county", defaultCounty),
maxitems: ymlConfig.UInt("items", defaultMaxItems),
maxage: ymlConfig.UInt("maxages", defaultMaxAge),
}
return &settings
}

View File

@ -0,0 +1,90 @@
package krisinformation
import (
"fmt"
"time"
"github.com/rivo/tview"
"github.com/wtfutil/wtf/view"
)
// Widget is the container for your module's data
type Widget struct {
view.TextWidget
app *tview.Application
settings *Settings
err error
client *Client
}
// NewWidget creates and returns an instance of Widget
func NewWidget(app *tview.Application, settings *Settings) *Widget {
widget := Widget{
TextWidget: view.NewTextWidget(app, nil, settings.common),
app: app,
settings: settings,
client: NewClient(
settings.latitude,
settings.longitude,
settings.radius,
settings.county,
settings.country),
}
return &widget
}
/* -------------------- Exported Functions -------------------- */
// Refresh updates the onscreen contents of the widget
func (widget *Widget) Refresh() {
if widget.Disabled() {
return
}
// The last call should always be to the display function
widget.display()
}
/* -------------------- Unexported Functions -------------------- */
func (widget *Widget) content() (string, string, bool) {
var title = defaultTitle
if widget.CommonSettings().Title != "" {
title = widget.CommonSettings().Title
}
now := time.Now()
kriser, err := widget.client.getKrisinformation()
if err != nil {
handleError(widget, err)
}
var str string
i := 0
for k := range kriser {
diff := now.Sub(kriser[k].Updated)
if widget.settings.maxage != -1 {
// Skip if message is too old
if int(diff.Hours()) > widget.settings.maxage {
//logger.Log(fmt.Sprintf("Article to old: (%s) Days: %d", kriser[k].HeadLine, int(diff.Hours())))
continue
}
}
i++
if i > widget.settings.maxitems && widget.settings.maxitems != -1 {
break
}
str += fmt.Sprintf("- %s\n", kriser[k].HeadLine)
}
return title, str, true
}
func (widget *Widget) display() {
widget.Redraw(func() (string, string, bool) {
return widget.content()
})
}
func handleError(widget *Widget, err error) {
widget.err = err
}