[x/go.dev] cmd/events: add utility for updating event content
Adds a command to update event.yaml for rendering events in our Home and
Learn pages. Times are rendered in the local time for the event.
X-GoDev-Commit: 1e492202bb964c17b8a087c54019da84ff7ed3f2
diff --git a/go.dev/cmd/events/main.go b/go.dev/cmd/events/main.go
new file mode 100644
index 0000000..e2e9711
--- /dev/null
+++ b/go.dev/cmd/events/main.go
@@ -0,0 +1,302 @@
+// Copyright 2019 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+package main
+
+import (
+ "encoding/json"
+ "fmt"
+ "log"
+ "net/http"
+ "os"
+ "path"
+ "strings"
+ "time"
+
+ "github.com/microcosm-cc/bluemonday"
+ "gopkg.in/yaml.v2"
+)
+
+const (
+ // eventLimit is the maximum number of events that will be output.
+ eventLimit = 15
+ // groupsSummaryPath is an API endpoint that returns global Go groups.
+ // Fetching from this API path allows to sort groups by next upcoming event.
+ groupsSummaryPath = "/pro/go/es_groups_summary?location=global&order=next_event&desc=false"
+ // eventsHeader is a header comment for the output content.
+ eventsHeader = `# DO NOT EDIT: Autogenerated from cmd/events.
+# To update, run:
+# go run github.com/godevsite/go.dev/cmd/events > data/events.yaml`
+)
+
+func main() {
+ c := &meetupAPI{
+ baseURL: "https://api.meetup.com",
+ }
+ ue, err := getUpcomingEvents(c)
+ if err != nil {
+ log.Fatal(err)
+ }
+ printYAML(ue)
+}
+
+type client interface {
+ getGroupsSummary() (*GroupsSummary, error)
+ getGroup(urlName string) (*Group, error)
+}
+
+// getUpcomingEvents returns upcoming events globally.
+func getUpcomingEvents(c client) (*UpcomingEvents, error) {
+ summary, err := c.getGroupsSummary()
+ if err != nil {
+ return nil, err
+ }
+ p := bluemonday.NewPolicy()
+ p.AllowStandardURLs()
+ p.AllowAttrs("href").OnElements("a")
+ p.AllowElements("br")
+ // Work around messy newlines in content.
+ r := strings.NewReplacer("\n", "<br/>\n", "<br>", "<br/>\n")
+ var events []EventData
+ for _, chapter := range summary.Chapters {
+ if len(events) >= eventLimit {
+ break
+ }
+ group, err := c.getGroup(chapter.URLName)
+ if err != nil || group.NextEvent == nil {
+ continue
+ }
+ tz, err := time.LoadLocation(group.Timezone)
+ if err != nil {
+ tz = time.UTC
+ }
+ // group.NextEvent.Time is in milliseconds since UTC epoch.
+ nextEventTime := time.Unix(group.NextEvent.Time/1000, 0).In(tz)
+ events = append(events, EventData{
+ City: chapter.City,
+ Country: chapter.Country,
+ Description: r.Replace(p.Sanitize(chapter.Description)), // Event descriptions are often blank, use Group description.
+ ID: group.NextEvent.ID,
+ LocalDate: nextEventTime.Format("Jan 2, 2006"),
+ LocalTime: nextEventTime.Format(time.RFC3339),
+ LocalizedCountry: group.LocalizedCountryName,
+ LocalizedLocation: group.LocalizedLocation,
+ Name: group.NextEvent.Name,
+ State: chapter.State,
+ ThumbnailURL: chapter.GroupPhoto.ThumbLink,
+ URL: "https://www.meetup.com/" + path.Join(chapter.URLName, "events", group.NextEvent.ID),
+ })
+ }
+ return &UpcomingEvents{All: events}, nil
+}
+
+type meetupAPI struct {
+ baseURL string
+}
+
+func (c *meetupAPI) getGroupsSummary() (*GroupsSummary, error) {
+ resp, err := http.Get(c.baseURL + groupsSummaryPath)
+ if err != nil {
+ return nil, fmt.Errorf("failed to get events from %q: %v", groupsSummaryPath, err)
+ }
+ defer resp.Body.Close()
+ if resp.StatusCode != http.StatusOK {
+ return nil, fmt.Errorf("failed to get events from %q: %v", groupsSummaryPath, resp.Status)
+ }
+ var summary *GroupsSummary
+ d := json.NewDecoder(resp.Body)
+ if err := d.Decode(&summary); err != nil {
+ return summary, fmt.Errorf("failed to decode events from %q: %w", groupsSummaryPath, err)
+ }
+ return summary, nil
+}
+
+// getGroup fetches group details, which are useful for getting details of the next upcoming event, and timezones.
+func (c *meetupAPI) getGroup(urlName string) (*Group, error) {
+ u := c.baseURL + "/" + urlName
+ resp, err := http.Get(u)
+ if err != nil {
+ return nil, fmt.Errorf("failed to fetch group details from %q: %w", u, err)
+ }
+ defer resp.Body.Close()
+ if resp.StatusCode != http.StatusOK {
+ return nil, fmt.Errorf("failed to fetch group details from %q: %v", u, resp.Status)
+ }
+
+ var group Group
+ d := json.NewDecoder(resp.Body)
+ if err := d.Decode(&group); err != nil {
+ return nil, fmt.Errorf("failed to decode group from %q: %w", u, err)
+ }
+ return &group, nil
+}
+
+func printYAML(v interface{}) {
+ fmt.Println(eventsHeader)
+ e := yaml.NewEncoder(os.Stdout)
+ defer e.Close()
+ if err := e.Encode(v); err != nil {
+ log.Fatalf("failed to encode event yaml: %v", err)
+ }
+}
+
+// UpcomingEvents is a list of EventData written out to YAML for rendering in Hugo.
+type UpcomingEvents struct {
+ All []EventData
+}
+
+// EventData is the structure written out to YAML for rendering in Hugo.
+type EventData struct {
+ City string
+ Country string
+ Description string
+ ID string
+ LocalDate string `yaml:"local_date"`
+ LocalTime string `yaml:"local_time"`
+ LocalizedCountry string
+ LocalizedLocation string
+ Name string
+ State string
+ ThumbnailURL string
+ URL string
+}
+
+// GroupsSummary is the structure returned from /pro/go/es_groups_summary.
+type GroupsSummary struct {
+ Chapters []*Chapter
+}
+
+type Event struct {
+ Created int `json:"created"`
+ Description string `json:"description"`
+ Duration int `json:"duration"`
+ Fee *Fee `json:"fee"`
+ Group *Group `json:"group"`
+ LocalDate string `json:"local_date"`
+ LocalTime string `json:"local_time"`
+ ID string `json:"id"`
+ Link string `json:"link"`
+ Name string `json:"name"`
+ RSVPLimit int `json:"rsvp_limit"`
+ Status string `json:"status"`
+ Time int64 `json:"time"`
+ UTCOffset int `json:"utc_offset"`
+ Updated int `json:"updated"`
+ Venue *Venue `json:"venue"`
+ WaitlistCount int `json:"waitlist_count"`
+ YesRSVPCount int `json:"yes_rsvp_count"`
+}
+
+type Venue struct {
+ Address1 string `json:"address_1"`
+ Address2 string `json:"address_2"`
+ Address3 string `json:"address_3"`
+ City string `json:"city"`
+ Country string `json:"country"`
+ ID int `json:"id"`
+ Lat float64 `json:"lat"`
+ LocalizedCountryName string `json:"localized_country_name"`
+ Lon float64 `json:"lon"`
+ Name string `json:"name"`
+ Repinned bool `json:"repinned"`
+ State string `json:"state"`
+ Zip string `json:"zip"`
+}
+
+type Group struct {
+ Country string `json:"country"`
+ Created int `json:"created"`
+ Description string `json:"description"`
+ ID int `json:"id"`
+ JoinMode string `json:"join_mode"`
+ Lat float64 `json:"lat"`
+ LocalizedLocation string `json:"localized_location"`
+ LocalizedCountryName string `json:"localized_country_name"`
+ Lon float64 `json:"lon"`
+ Name string `json:"name"`
+ NextEvent *Event `json:"next_event"`
+ Region string `json:"region"`
+ Timezone string `json:"timezone"`
+ URLName string `json:"urlname"`
+ Who string `json:"who"`
+}
+
+type Fee struct {
+ Accepts string `json:"accepts"`
+ Amount float64 `json:"amount"`
+ Currency string `json:"currency"`
+ Description string `json:"description"`
+ Label string `json:"label"`
+ Required bool `json:"required"`
+}
+
+type Chapter struct {
+ AverageAge float64 `json:"average_age"`
+ Category []Category `json:"category"`
+ City string `json:"city"`
+ Country string `json:"country"`
+ Description string `json:"description"`
+ FoundedDate int64 `json:"founded_date"`
+ GenderFemale float64 `json:"gender_female"`
+ GenderMale float64 `json:"gender_male"`
+ GenderOther float64 `json:"gender_other"`
+ GenderUnknown float64 `json:"gender_unknown"`
+ GroupPhoto GroupPhoto `json:"group_photo"`
+ ID int `json:"id"`
+ LastEvent int64 `json:"last_event"`
+ Lat float64 `json:"lat"`
+ Lon float64 `json:"lon"`
+ MemberCount int `json:"member_count"`
+ Name string `json:"name"`
+ NextEvent int64 `json:"next_event"`
+ OrganizerPhoto OrganizerPhoto `json:"organizer_photo"`
+ Organizers []Organizer `json:"organizers"`
+ PastEvents int `json:"past_events"`
+ PastRSVPs int `json:"past_rsvps"`
+ ProJoinDate int64 `json:"pro_join_date"`
+ RSVPsPerEvent float64 `json:"rsvps_per_event"`
+ RepeatRSVPers int `json:"repeat_rsvpers"`
+ State string `json:"state"`
+ Status string `json:"status"`
+ Topics []Topic `json:"topics"`
+ URLName string `json:"urlname"`
+ UpcomingEvents int `json:"upcoming_events"`
+}
+
+type Topic struct {
+ ID int `json:"id"`
+ Name string `json:"name"`
+ URLkey string `json:"urlkey"`
+ Lang string `json:"lang"`
+}
+
+type Category struct {
+ ID int `json:"id"`
+ Name string `json:"name"`
+ Shortname string `json:"shortname"`
+ SortName string `json:"sort_name"`
+}
+
+type Organizer struct {
+ Name string `json:"name"`
+ MemberID int `json:"member_id"`
+ Permission string `json:"permission"`
+}
+
+type OrganizerPhoto struct {
+ BaseURL string `json:"base_url"`
+ HighresLink string `json:"highres_link"`
+ ID int `json:"id"`
+ PhotoLink string `json:"photo_link"`
+ ThumbLink string `json:"thumb_link"`
+ Type string `json:"type"`
+}
+
+type GroupPhoto struct {
+ BaseURL string `json:"base_url"`
+ HighresLink string `json:"highres_link"`
+ ID int `json:"id"`
+ PhotoLink string `json:"photo_link"`
+ ThumbLink string `json:"thumb_link"`
+ Type string `json:"type"`
+}
diff --git a/go.dev/cmd/events/main_test.go b/go.dev/cmd/events/main_test.go
new file mode 100644
index 0000000..4f729b2
--- /dev/null
+++ b/go.dev/cmd/events/main_test.go
@@ -0,0 +1,66 @@
+package main
+
+import (
+ "fmt"
+ "testing"
+
+ "github.com/google/go-cmp/cmp"
+)
+
+var (
+ noEventGroup = &Group{Name: "no event group"}
+ upcomingEventGroup = &Group{
+ Name: "Upcoming Event Group",
+ Timezone: "Europe/Oslo",
+ NextEvent: &Event{
+ ID: "12345",
+ Name: "Upcoming Event",
+ Time: 1262976000000,
+ },
+ }
+ fakeGroups = map[string]*Group{
+ "noEvent": noEventGroup,
+ "ueg": upcomingEventGroup,
+ }
+)
+
+type fakeClient struct{}
+
+func (f fakeClient) getGroupsSummary() (*GroupsSummary, error) {
+ return &GroupsSummary{Chapters: []*Chapter{
+ {URLName: "noEvent"},
+ {
+ URLName: "ueg",
+ Description: "We host our own events\n",
+ },
+ }}, nil
+}
+
+func (f fakeClient) getGroup(urlName string) (*Group, error) {
+ g, ok := fakeGroups[urlName]
+ if !ok {
+ return nil, fmt.Errorf("no group %q", urlName)
+ }
+ return g, nil
+}
+
+func TestGetUpcomingEvents(t *testing.T) {
+ want := &UpcomingEvents{All: []EventData{
+ {
+ Name: "Upcoming Event",
+ ID: "12345",
+ Description: "We host our own events<br/>\n",
+ LocalDate: "Jan 8, 2010",
+ LocalTime: "2010-01-08T19:40:00+01:00",
+ URL: "https://www.meetup.com/ueg/events/12345",
+ },
+ }}
+ f := fakeClient{}
+ got, err := getUpcomingEvents(f)
+ if err != nil {
+ t.Fatalf("getUpcomingEvents(%v) error = %v, wanted no error", f, err)
+ }
+ if diff := cmp.Diff(want, got); diff != "" {
+ t.Errorf("getUpcomingEvents(%v) mismatch (-want +got):\n%s", f, diff)
+ }
+}
diff --git a/go.dev/data/events.yaml b/go.dev/data/events.yaml
index 53b145a..ba9ac3c 100644
--- a/go.dev/data/events.yaml
+++ b/go.dev/data/events.yaml
@@ -1,66 +1,7 @@
# DO NOT EDIT: Autogenerated from cmd/events.
+# To update, run:
+# go run github.com/godevsite/go.dev/cmd/events > data/events.yaml
all:
-- city: Pune
- country: India
- description: |-
- WWG Pune is a chapter of Women Who Go.<br/>
- We built this meetup to provide a better entry point to women who are interested in Go. We also want to provide a safe space for networking, learning and finding mentors. This group shall bring us together so we can continue to grow in Go. <br><br/>
- <br><br/>
- Even though any person can attend this meetup regardless of gender, we wish to empower the less-represented developers of the programming community.<br/>
- <br><br/>
- We would love for you to speak at Women Who Go! DM us at <a href="https://twitter.com/wwg_pune" rel="nofollow">@wwg_pune</a> about a short talk, demo, or whatever you have in mind!
- id: "265180374"
- local_date: Sep 28, 2019
- local_time: "2019-09-28T10:00:00+05:30"
- localizedcountry: India
- localizedlocation: Pune, India
- name: Learning Golang Part 2 - Mastering the basics of Go
- state: ""
- thumbnailurl: ""
- url: https://www.meetup.com/wwg_pune/events/265180374
-- city: Paris
- country: France
- description: |-
- This is the Paris Chapter of Women Who Go: <a href="http://www.meetup.com/Women-Who-Go/" rel="nofollow">http://www.meetup.com/Women-Who-Go/</a> This group is an inclusive space for everyone.<br/>
- This group is for you if any of the following: <br>1. You identify as a woman, publicly or privately. 2. You have some interest in the Go programming language, or programming in general.
- id: "264458143"
- local_date: Sep 28, 2019
- local_time: "2019-09-28T10:00:00+02:00"
- localizedcountry: France
- localizedlocation: Paris, France
- name: 'Workshop - Production ready cloud-native services with Go and Kubernetes '
- state: ""
- thumbnailurl: https://secure.meetupstatic.com/photos/event/2/d/3/b/thumb_480491579.jpeg
- url: https://www.meetup.com/Women-Who-Go-Paris/events/264458143
-- city: Chennai
- country: India
- description: |-
- A group for anyone using (want to use) golang for programming and development of software or engineering applications.<br/>
- We are on slack #chennaigolang
- id: "264717526"
- local_date: Sep 28, 2019
- local_time: "2019-09-28T14:00:00+05:30"
- localizedcountry: India
- localizedlocation: Chennai, India
- name: September Go Meetup
- state: ""
- thumbnailurl: https://secure.meetupstatic.com/photos/event/9/1/b/e/thumb_449917310.jpeg
- url: https://www.meetup.com/Chennai-golang-Meetup/events/264717526
-- city: Delhi
- country: India
- description: |-
- You do not need to know Go, be a professional developer or enjoy talking to strangers in order to come hang out with us! <br/>
- This group is for you if you are interested in Go, and are looking for a safe space to learn. Membership is restricted to women and gender minorities. <br/>
- We would love for you to speak at Women Who Go! Contact delhi@womenwhogo.org about a short talk, demo, or whatever you have in mind!
- id: "265075250"
- local_date: Sep 29, 2019
- local_time: "2019-09-29T09:30:00+05:30"
- localizedcountry: India
- localizedlocation: Delhi, India
- name: 'DevFest '
- state: ""
- thumbnailurl: ""
- url: https://www.meetup.com/New-Delhi-Women-Who-Go/events/265075250
- city: London
country: United Kingdom
description: |-
@@ -217,3 +158,92 @@
state: ""
thumbnailurl: https://secure.meetupstatic.com/photos/event/e/4/7/e/thumb_455218494.jpeg
url: https://www.meetup.com/golang-mcr/events/tcljtqyznbmb
+- city: Seattle
+ country: USA
+ description: |-
+ The Seattle Go User Group is a community for anyone interested in the Go programming language. <br><br/>
+ <br><br/>
+ We provide opportunities to:<br/>
+ • Discuss Go and related topics <br/>
+ • Socialize with people who are interested in Go<br/>
+ • Find or fill Go-related jobs <br/>
+ <br><br/>
+ If you want to chat all things Go, feel free to join us on the Gopher slack. <br/>
+ Invites can be found at <a href="https://invite.slack.golangbridge.org" rel="nofollow">https://invite.slack.golangbridge.org</a><br/>
+ There is a #seattle channel which can be joined by anyone, so come say hi!<br/>
+ <br><br/>
+ Our aim is to be a welcoming environment. As such all attendees, organizers and sponsors are required to follow the <a href="https://golang.org/conduct" rel="nofollow">code of conduct</a>.
+ id: dpshmpyznbmb
+ local_date: Oct 9, 2019
+ local_time: "2019-10-09T13:00:00-07:00"
+ localizedcountry: USA
+ localizedlocation: Seattle, WA
+ name: Eastside Go Coffee
+ state: WA
+ thumbnailurl: https://secure.meetupstatic.com/photos/event/a/4/b/e/thumb_450342174.jpeg
+ url: https://www.meetup.com/golang/events/dpshmpyznbmb
+- city: Hannover
+ country: Germany
+ description: |-
+ The place to meet Go(lang) enthusiasts from the Hannover region. Drop by if you are interested in Golang or back end stuff in general :-)<br/>
+ <br><br/>
+ Find recently announced talks at <a href="http://www.golang.wtf/" rel="nofollow">golang.wtf</a>!
+ id: nhvmtqyznbfb
+ local_date: Oct 10, 2019
+ local_time: "2019-10-10T19:00:00+02:00"
+ localizedcountry: Germany
+ localizedlocation: Hannover, Germany
+ name: Hannover Gophers - Vol. 15
+ state: ""
+ thumbnailurl: https://secure.meetupstatic.com/photos/event/a/2/9/2/thumb_475541618.jpeg
+ url: https://www.meetup.com/Hannover-Gophers/events/nhvmtqyznbfb
+- city: Lisbon
+ country: Portugal
+ description: "PortugalGophers is a Portugal based community for anyone interested
+ in the Go programming language.<br/>\n <br>Our get together provides offline opportunities
+ to: <br>✅Discuss Go and related topics <br>✅ Socialise with friendly people who
+ are interested in Go <br>✅ Find or fill Go-related jobs <br/>\n <br><br/>\nAll
+ attendees, organisers and sponsors are required to follow the Go community code
+ of conduct (<a href=\"https://golang.org/conduct\" rel=\"nofollow\">https://golang.org/conduct</a>).
+ <br> <br>If you would like to make a talk in one of our meetups, click <a href=\"http://bit.ly/2Ig1RiV\"
+ rel=\"nofollow\">here</a>.<br/>\n\U0001F4E2<a href=\"https://twitter.com/GophersPortugal\"
+ rel=\"nofollow\">Twitter</a> <br/>\n\U0001F4F9<a href=\"https://www.youtube.com/channel/UCwxeVe6zJSGfOON5C6Wg3Eg/\"
+ rel=\"nofollow\">YouTube</a> <br/>\n <br>"
+ id: "265144357"
+ local_date: Oct 10, 2019
+ local_time: "2019-10-10T19:00:00+01:00"
+ localizedcountry: Portugal
+ localizedlocation: Lisbon, Portugal
+ name: October Gophers with Kat Zień
+ state: ""
+ thumbnailurl: https://secure.meetupstatic.com/photos/event/9/c/f/2/thumb_485200178.jpeg
+ url: https://www.meetup.com/PortugalGophers/events/265144357
+- city: Orlando
+ country: USA
+ description: Orlando's first meetup group dedicated to the Go Programming Language.
+ All skill levels are welcome - whether you're a beginner or a full-fledged
+ gopher.
+ id: blgfpqyznbnb
+ local_date: Oct 10, 2019
+ local_time: "2019-10-10T19:00:00-04:00"
+ localizedcountry: USA
+ localizedlocation: Orlando, FL
+ name: Orlando Golang Monthly Meetup
+ state: FL
+ thumbnailurl: https://secure.meetupstatic.com/photos/event/9/7/c/4/thumb_441638852.jpeg
+ url: https://www.meetup.com/OrlanGo/events/blgfpqyznbnb
+- city: Graz
+ country: Austria
+ description: |-
+ Die <a href="https://golang.org" rel="nofollow">Programmiersprache Go</a> hat einen Grazer "Stammtisch". <br/>
+ Wir treffen uns jeden 2. Montag im Monat bei TAO Digital am Lendplatz.<br/>
+ Schau einfach vorbei oder besuche uns online unter <a href="https://gograz.org" rel="nofollow">GoGraz</a>! <br>
+ id: lbbhjlyznbtb
+ local_date: Oct 14, 2019
+ local_time: "2019-10-14T19:00:00+02:00"
+ localizedcountry: Austria
+ localizedlocation: Graz, Austria
+ name: Go Language Usergroup Graz
+ state: ""
+ thumbnailurl: https://secure.meetupstatic.com/photos/event/3/3/e/0/thumb_459373280.jpeg
+ url: https://www.meetup.com/Graz-Open-Source-Meetup/events/lbbhjlyznbtb
diff --git a/go.dev/go.mod b/go.dev/go.mod
new file mode 100644
index 0000000..de83898
--- /dev/null
+++ b/go.dev/go.mod
@@ -0,0 +1,9 @@
+module github.com/godevsite/go.dev
+
+go 1.13
+
+require (
+ github.com/google/go-cmp v0.3.1
+ github.com/microcosm-cc/bluemonday v1.0.2
+ gopkg.in/yaml.v2 v2.2.2
+)
diff --git a/go.dev/go.sum b/go.dev/go.sum
new file mode 100644
index 0000000..23d783e
--- /dev/null
+++ b/go.dev/go.sum
@@ -0,0 +1,10 @@
+github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg=
+github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/microcosm-cc/bluemonday v1.0.2 h1:5lPfLTTAvAbtS0VqT+94yOtFnGfUWYyx0+iToC3Os3s=
+github.com/microcosm-cc/bluemonday v1.0.2/go.mod h1:iVP4YcDBq+n/5fb23BhYFvIMq/leAFZyRl6bYmGDlGc=
+golang.org/x/net v0.0.0-20181220203305-927f97764cc3 h1:eH6Eip3UpmR+yM/qI9Ijluzb1bNv/cAU/n+6l8tRSis=
+golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
+gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=