Split based on location

This commit is contained in:
Daniel Mason 2022-01-03 10:55:54 +13:00
parent 83b1a3d974
commit aeae0ef8ee
6 changed files with 129 additions and 67 deletions

12
CHANGELOG.md Normal file
View file

@ -0,0 +1,12 @@
# Changelog
## 1.1
- Split messages based on location
- Added systemd service
## 1.0
- Reworked to use API
- Stores lastPoll / lastUpdated in lastUpdated.txt to keep track
- Only posts if PublishedDate > lastUpdated timestamp
- Removed twitter + git code
- Added more code comments

View file

@ -1,5 +1,6 @@
# NZCovidBot
Pulls data from Ministry of Health API and parse into Discord and Slack webhooks.
### About
After the twitterbot @nzcovidlocs shut down, I decided to try a different approach, instead of scraping MoH's website, we originally parsed the raw CSV data.
Since then the NZ Ministry of Health have released an API containing this data now. We are now using this https://api.integration.covid19.health.nz/locations/v1/current-locations-of-interest
@ -16,4 +17,7 @@ Copy .env.example to .env and fill in the webhook URLs
```
go build -o nzcovidbot cmd/nzcovidbot/*.go
./nzcovidbot
sudo cp nzcovidbot.service /etc/systemd/system/nzcovidbot.service
# Update user + location of repo in systemd file
sudo systemctl daemon-reload && systemctl enable --now nzcovidbot.service
```

View file

@ -13,23 +13,29 @@ import (
const API_ENDPOINT = "https://api.integration.covid19.health.nz/locations/v1/current-locations-of-interest"
var newLocations ApiResponse
var newLocations PostResponse
// Response from MoH API
type ApiResponse struct {
Items []ApiItem `json:"items"`
}
// PostResponse - Above items ordered by location
type PostResponse struct {
Items map[string][]ApiItem `json:"items"`
}
type ApiItem struct {
EventID string `json:"eventId"`
EventName string `json:"eventName"`
StartDateTime time.Time `json:"startDateTime"`
EndDateTime time.Time `json:"endDateTime"`
PublicAdvice string `json:"publicAdvice"`
VisibleInWebform bool `json:"visibleInWebform"`
PublishedAt time.Time `json:"publishedAt"`
// UpdatedAt time.Time `json:"updatedAt"` // Nullable
ExposureType string `json:"exposureType"`
Location struct {
EventID string `json:"eventId"`
EventName string `json:"eventName"`
StartDateTime time.Time `json:"startDateTime"`
EndDateTime time.Time `json:"endDateTime"`
PublicAdvice string `json:"publicAdvice"`
VisibleInWebform bool `json:"visibleInWebform"`
PublishedAt time.Time `json:"publishedAt"`
UpdatedAt interface{} `json:"updatedAt"`
ExposureType string `json:"exposureType"`
Location struct {
Latitude string `json:"latitude"`
Longitude string `json:"longitude"`
Suburb string `json:"suburb"`
@ -85,25 +91,30 @@ func getNewAPILocations() {
}
// Re-init our apiRepsonse so we don't hold onto old locations!
newItems := make([]ApiItem, 0)
newItems := make(map[string][]ApiItem, 0)
// Iterate over the data and only find new locations
for _, item := range locations.Items {
if item.PublishedAt.Unix() > lastUpdated.Unix() {
// Clone the item to put in our own lil slice
copy := item
newItems = append(newItems, copy)
newItems[item.Location.City] = append(newItems[item.Location.City], copy)
}
}
// Make sure to clear out the previous list and append new data
newLocations = ApiResponse{}
newLocations.Items = newItems
// Make sure to clear out the previous list and append new data in a map based on location
newLocations = PostResponse{}
newLocations.Items = make(map[string][]ApiItem, 0)
// Order by StartDate
sort.Slice(newLocations.Items, func(i, j int) bool {
return newLocations.Items[i].StartDateTime.Before(newLocations.Items[j].StartDateTime)
})
for mapKey, mapItems := range newItems {
// Add location to our newLocations map
newLocations.Items[mapKey] = mapItems
// Order by StartDate
sort.Slice(newLocations.Items[mapKey], func(i, j int) bool {
return newLocations.Items[mapKey][i].StartDateTime.Before(newLocations.Items[mapKey][j].StartDateTime)
})
}
// If new items, post it!
if len(newLocations.Items) > 0 {
@ -147,5 +158,5 @@ func (item ApiItem) getDateString() string {
dateFormat := "Mon 2 Jan, 03:04PM"
timeFormat := "03:04PM"
return st.Format(dateFormat) + " - " + et.Format(timeFormat)
return st.Local().Format(dateFormat) + " - " + et.Local().Format(timeFormat)
}

View file

@ -20,54 +20,71 @@ func postToDiscord() {
}
for _, discordWebhook := range DiscordWebhooks {
for _, postableData := range postableDiscordData {
if discordWebhook != "" {
tokenParts := strings.Split(discordWebhook, "/")
len := len(tokenParts)
if discordWebhook != "" {
// Build discord request
tokenParts := strings.Split(discordWebhook, "/")
len := len(tokenParts)
webhook, err := disgohook.NewWebhookClientByToken(nil, nil, tokenParts[len-2]+"/"+tokenParts[len-1])
if err != nil {
log.Print(err)
continue
}
// Build discord request
webhook, err := disgohook.NewWebhookClientByToken(nil, nil, tokenParts[len-2]+"/"+tokenParts[len-1])
if err != nil {
log.Print(err)
return
// Build message and send for each location
for location, postableData := range postableDiscordData {
for _, post := range postableData {
// Send discord message
_, err = webhook.SendEmbeds(api.NewEmbedBuilder().
SetTitle("*" + location + "*").
SetDescription(post).
Build(),
)
if err != nil {
log.Print(err)
}
time.Sleep(500 * time.Millisecond)
}
// Send discord message
_, err = webhook.SendEmbeds(api.NewEmbedBuilder().
SetDescription(postableData).
Build(),
)
if err != nil {
log.Print(err)
}
time.Sleep(500 * time.Millisecond)
}
}
}
}
// getPostableDiscordData - Returns slices containing 20~ locations each
// to send as separate messages
func getPostableDiscordData() []string {
// Create our slice of groups
groups := make([]string, 0)
// to send as separate messages. map[location][]locationsofinterest
func getPostableDiscordData() map[string][]string {
// Create our return map
groups := make(map[string][]string, 0)
// If no locations, lets return empty map
if len(newLocations.Items) == 0 {
return groups
}
rows := make([]string, 0)
for _, item := range newLocations.Items {
rows = append(rows, getDiscordRow(item))
for location, items := range newLocations.Items {
// Create out output buffer per location
rows := make([]string, 0)
if len(rows) > 20 {
groups = append(groups, strings.Join(rows, "\n"))
rows = make([]string, 0)
// Foreach item, create the output text based off the item
for _, item := range items {
rows = append(rows, getDiscordRow(item))
// Make sure to create a new slice if we have >20 to send as a different message
if len(rows) > 20 {
groups[location] = append(groups[location], strings.Join(rows, "\n"))
rows = make([]string, 0)
}
}
// If we have less than 20, append any more before next location
if len(rows) > 0 {
groups[location] = append(groups[location], strings.Join(rows, "\n"))
}
}
return append(groups, strings.Join(rows, "\n"))
return groups
}
// formatCsvDiscordRow Format the string to a tidy string for the interwebs

View file

@ -28,30 +28,35 @@ func postToSlack() {
}
parts := strings.Split(webhook, "!")
payload := slack.Payload{
Text: "New Locations of Interest!",
Username: "NZCovidTracker",
Channel: "#" + parts[1],
IconUrl: "https://www.skids.co.nz/wp-content/uploads/2020/08/download.png",
Attachments: attachmentData,
}
err := slack.Send(parts[0], "", payload)
if len(err) > 0 {
fmt.Printf("Wehbook: %s\nError: %s", webhook, err)
for location, items := range attachmentData {
payload := slack.Payload{
Text: "*" + location + "*",
Username: "NZCovidTracker",
Channel: "#" + parts[1],
IconUrl: "https://www.skids.co.nz/wp-content/uploads/2020/08/download.png",
Attachments: items,
}
err := slack.Send(parts[0], "", payload)
if len(err) > 0 {
fmt.Printf("Wehbook: %s\nError: %s", webhook, err)
}
}
}
}
// Adds new rows to a slice for slack
func getPostableSlackData() []slack.Attachment {
rows := make([]slack.Attachment, 0)
func getPostableSlackData() map[string][]slack.Attachment {
rows := make(map[string][]slack.Attachment, 0)
if len(newLocations.Items) == 0 {
return rows
}
for _, item := range newLocations.Items {
rows = append(rows, getSlackRow(item))
for location, items := range newLocations.Items {
for _, item := range items {
rows[location] = append(rows[location], getSlackRow(item))
}
}
return rows

13
nzcovidbot.service Normal file
View file

@ -0,0 +1,13 @@
[Unit]
Description=NZCovidBot
After=network.target
[Service]
Type=simple
User=root
WorkingDirectory=/root/NZCovidBot
ExecStart=/root/NZCovidBot/nzcovidbot
Restart=on-failure
[Install]
WantedBy=multi-user.target