forked from wgroeneveld/go-jamming
remove unneeded utcOffset in config: only used to create new time instance when parsing failes. update deps
This commit is contained in:
parent
3f1ece1c39
commit
bf018eafe8
|
@ -21,7 +21,6 @@ Place a `config.json` file in the same directory that looks like this: (below ar
|
||||||
"port": 1337,
|
"port": 1337,
|
||||||
"host": "localhost",
|
"host": "localhost",
|
||||||
"token": "miauwkes",
|
"token": "miauwkes",
|
||||||
"utcOffset": 60,
|
|
||||||
"allowedWebmentionSources": [
|
"allowedWebmentionSources": [
|
||||||
"brainbaking.com",
|
"brainbaking.com",
|
||||||
"jefklakscodex.com"
|
"jefklakscodex.com"
|
||||||
|
@ -38,7 +37,6 @@ Place a `config.json` file in the same directory that looks like this: (below ar
|
||||||
- port, host: http server params
|
- port, host: http server params
|
||||||
- token, allowedWebmentionSources: see below, used for authentication
|
- token, allowedWebmentionSources: see below, used for authentication
|
||||||
- blacklist/whitelist: domains from which we do (NOT) send to or accept mentions from.
|
- blacklist/whitelist: domains from which we do (NOT) send to or accept mentions from.
|
||||||
- utcOffset: offset in minutes for date processing, starting from UTC time.
|
|
||||||
|
|
||||||
If a config file is missing, or required keys are missing, a warning will be generated and default values will be used instead. See `common/config.go`.
|
If a config file is missing, or required keys are missing, a warning will be generated and default values will be used instead. See `common/config.go`.
|
||||||
|
|
||||||
|
|
|
@ -200,6 +200,8 @@ Each mention has to be manually approved. An e-mail to `localhost:25` (a local P
|
||||||
|
|
||||||
Approved mentions will have their domain added to the whitelist. Rejected mentions will have their domain added to the blacklist.
|
Approved mentions will have their domain added to the whitelist. Rejected mentions will have their domain added to the blacklist.
|
||||||
|
|
||||||
|
Read more about how spam moderation works at https://brainbaking.com/post/2022/04/fighting-webmention-and-pingback-spam/
|
||||||
|
|
||||||
### Manually blacklisting partial domains
|
### Manually blacklisting partial domains
|
||||||
|
|
||||||
In that case, simply add the domain to the `blacklist` in `config.json`.
|
In that case, simply add the domain to the `blacklist` in `config.json`.
|
||||||
|
|
|
@ -24,7 +24,7 @@
|
||||||
<h2>Mentions To Approve</h2>
|
<h2>Mentions To Approve</h2>
|
||||||
|
|
||||||
{{ range $domain, $mentions := .Mentions }}
|
{{ range $domain, $mentions := .Mentions }}
|
||||||
<h3> 🌐 Domain <em>{{ $domain }}</em> »</h3>
|
<h3> 🌐 Domain <em><a href="{{ $domain.MentionsURL }}">{{ $domain.Name }}</a></em> »</h3>
|
||||||
|
|
||||||
{{ if $mentions }}
|
{{ if $mentions }}
|
||||||
<table>
|
<table>
|
||||||
|
|
|
@ -28,9 +28,14 @@ type dashboardMention struct {
|
||||||
RejectURL string
|
RejectURL string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type domainMention struct {
|
||||||
|
Name string
|
||||||
|
MentionsURL string
|
||||||
|
}
|
||||||
|
|
||||||
type dashboardData struct {
|
type dashboardData struct {
|
||||||
Config string
|
Config string
|
||||||
Mentions map[string][]dashboardMention
|
Mentions map[domainMention][]dashboardMention
|
||||||
}
|
}
|
||||||
|
|
||||||
type dashboardModerated struct {
|
type dashboardModerated struct {
|
||||||
|
@ -62,10 +67,14 @@ func indiewebDataToDashboardMention(c *common.Config, dbMentions []*mf.IndiewebD
|
||||||
func getDashboardData(c *common.Config, repo db.MentionRepo) *dashboardData {
|
func getDashboardData(c *common.Config, repo db.MentionRepo) *dashboardData {
|
||||||
data := &dashboardData{
|
data := &dashboardData{
|
||||||
Config: c.String(),
|
Config: c.String(),
|
||||||
Mentions: map[string][]dashboardMention{},
|
Mentions: map[domainMention][]dashboardMention{},
|
||||||
}
|
}
|
||||||
for _, domain := range c.AllowedWebmentionSources {
|
for _, domain := range c.AllowedWebmentionSources {
|
||||||
data.Mentions[domain] = indiewebDataToDashboardMention(c, repo.GetAllToModerate(domain).Data)
|
domainKey := domainMention{
|
||||||
|
Name: domain,
|
||||||
|
MentionsURL: fmt.Sprintf("%swebmention/%s/%s", c.BaseURL, domain, c.Token),
|
||||||
|
}
|
||||||
|
data.Mentions[domainKey] = indiewebDataToDashboardMention(c, repo.GetAllToModerate(domain).Data)
|
||||||
}
|
}
|
||||||
|
|
||||||
return data
|
return data
|
||||||
|
|
|
@ -92,8 +92,8 @@ func (id *IndiewebData) IsEmpty() bool {
|
||||||
return id.Url == ""
|
return id.Url == ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func PublishedNow(zone *time.Location) string {
|
func PublishedNow() string {
|
||||||
return common.Now().UTC().In(zone).Format(dateFormatWithTimeZone)
|
return common.Now().UTC().Format(dateFormatWithTimeZone)
|
||||||
}
|
}
|
||||||
|
|
||||||
func shorten(txt string) string {
|
func shorten(txt string) string {
|
||||||
|
@ -174,10 +174,10 @@ func Prop(mf *microformats.Microformat, key string) *microformats.Microformat {
|
||||||
return mfEmpty()
|
return mfEmpty()
|
||||||
}
|
}
|
||||||
|
|
||||||
func Published(hEntry *microformats.Microformat, zone *time.Location) string {
|
func Published(hEntry *microformats.Microformat) string {
|
||||||
publishedDate := Str(hEntry, "published")
|
publishedDate := Str(hEntry, "published")
|
||||||
if publishedDate == "" {
|
if publishedDate == "" {
|
||||||
return PublishedNow(zone)
|
return PublishedNow()
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, format := range supportedFormats {
|
for _, format := range supportedFormats {
|
||||||
|
@ -188,7 +188,7 @@ func Published(hEntry *microformats.Microformat, zone *time.Location) string {
|
||||||
return formatted.Format(dateFormatWithTimeZone)
|
return formatted.Format(dateFormatWithTimeZone)
|
||||||
}
|
}
|
||||||
|
|
||||||
return PublishedNow(zone)
|
return PublishedNow()
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewAuthor(hEntry *microformats.Microformat, hCard *microformats.Microformat) IndiewebAuthor {
|
func NewAuthor(hEntry *microformats.Microformat, hCard *microformats.Microformat) IndiewebAuthor {
|
||||||
|
|
|
@ -52,6 +52,13 @@ func TestResultFailureEmptyEncodesAsEmptyJSONArray(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPublished(t *testing.T) {
|
func TestPublished(t *testing.T) {
|
||||||
|
common.Now = func() time.Time {
|
||||||
|
return time.Date(2020, time.January, 1, 12, 30, 0, 0, time.UTC)
|
||||||
|
}
|
||||||
|
nowString := "2020-01-01T12:30:00+00:00"
|
||||||
|
defer func() {
|
||||||
|
common.Now = time.Now
|
||||||
|
}()
|
||||||
cases := []struct {
|
cases := []struct {
|
||||||
label string
|
label string
|
||||||
raw string
|
raw string
|
||||||
|
@ -83,14 +90,14 @@ func TestPublished(t *testing.T) {
|
||||||
"2021-04-25T00:00:00+00:00",
|
"2021-04-25T00:00:00+00:00",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"Returns current date if property with correct timezone not found",
|
"Returns current UTC date if property with correct timezone not found",
|
||||||
"",
|
"",
|
||||||
"2020-01-01T13:30:00+01:00",
|
nowString,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"Reverts to current date if not in correct ISO8601 datetime format",
|
"Reverts to current UTC date if not in correct ISO8601 datetime format",
|
||||||
"26 April 2021",
|
"26 April 2021",
|
||||||
"2020-01-01T13:30:00+01:00",
|
nowString,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"https://www.ietf.org/rfc/rfc3339.txt example 1",
|
"https://www.ietf.org/rfc/rfc3339.txt example 1",
|
||||||
|
@ -105,12 +112,12 @@ func TestPublished(t *testing.T) {
|
||||||
{
|
{
|
||||||
"https://www.ietf.org/rfc/rfc3339.txt example 3 explicitly not implemented",
|
"https://www.ietf.org/rfc/rfc3339.txt example 3 explicitly not implemented",
|
||||||
"1990-12-31T23:59:60Z",
|
"1990-12-31T23:59:60Z",
|
||||||
"2020-01-01T13:30:00+01:00",
|
nowString,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"https://www.ietf.org/rfc/rfc3339.txt example 4 explicitly not implemented",
|
"https://www.ietf.org/rfc/rfc3339.txt example 4 explicitly not implemented",
|
||||||
"1990-12-31T15:59:60-08:00",
|
"1990-12-31T15:59:60-08:00",
|
||||||
"2020-01-01T13:30:00+01:00",
|
nowString,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"https://www.ietf.org/rfc/rfc3339.txt example 5 with seconds ignored",
|
"https://www.ietf.org/rfc/rfc3339.txt example 5 with seconds ignored",
|
||||||
|
@ -118,13 +125,6 @@ func TestPublished(t *testing.T) {
|
||||||
"1937-01-01T12:00:27+00:20",
|
"1937-01-01T12:00:27+00:20",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
common.Now = func() time.Time {
|
|
||||||
return time.Date(2020, time.January, 1, 12, 30, 0, 0, time.UTC)
|
|
||||||
}
|
|
||||||
utcPlusOne := time.FixedZone("UTC+1", 60*60)
|
|
||||||
defer func() {
|
|
||||||
common.Now = time.Now
|
|
||||||
}()
|
|
||||||
|
|
||||||
for _, tc := range cases {
|
for _, tc := range cases {
|
||||||
t.Run(tc.label, func(t *testing.T) {
|
t.Run(tc.label, func(t *testing.T) {
|
||||||
|
@ -134,7 +134,7 @@ func TestPublished(t *testing.T) {
|
||||||
}
|
}
|
||||||
theTime := Published(µformats.Microformat{
|
theTime := Published(µformats.Microformat{
|
||||||
Properties: props,
|
Properties: props,
|
||||||
}, utcPlusOne)
|
})
|
||||||
|
|
||||||
assert.Equal(t, tc.expectedTime, theTime)
|
assert.Equal(t, tc.expectedTime, theTime)
|
||||||
})
|
})
|
||||||
|
|
|
@ -3,7 +3,7 @@ package app
|
||||||
import (
|
import (
|
||||||
"brainbaking.com/go-jamming/db"
|
"brainbaking.com/go-jamming/db"
|
||||||
"brainbaking.com/go-jamming/rest"
|
"brainbaking.com/go-jamming/rest"
|
||||||
"github.com/MagnusFrater/helmet"
|
"github.com/goddtriffin/helmet"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
|
@ -122,7 +122,7 @@ func (recv *Receiver) parseBodyAsIndiewebSite(hEntry *microformats.Microformat,
|
||||||
Author: mf.NewAuthor(hEntry, hCard),
|
Author: mf.NewAuthor(hEntry, hCard),
|
||||||
Content: mf.Content(hEntry),
|
Content: mf.Content(hEntry),
|
||||||
Url: mf.Url(hEntry, wm.Source),
|
Url: mf.Url(hEntry, wm.Source),
|
||||||
Published: mf.Published(hEntry, recv.Conf.Zone()),
|
Published: mf.Published(hEntry),
|
||||||
Source: wm.Source,
|
Source: wm.Source,
|
||||||
Target: wm.Target,
|
Target: wm.Target,
|
||||||
IndiewebType: mf.Type(hEntry),
|
IndiewebType: mf.Type(hEntry),
|
||||||
|
@ -137,7 +137,7 @@ func (recv *Receiver) parseBodyAsNonIndiewebSite(body string, wm mf.Mention) *mf
|
||||||
},
|
},
|
||||||
Name: title,
|
Name: title,
|
||||||
Content: title,
|
Content: title,
|
||||||
Published: mf.PublishedNow(recv.Conf.Zone()),
|
Published: mf.PublishedNow(),
|
||||||
Url: wm.Source,
|
Url: wm.Source,
|
||||||
IndiewebType: mf.TypeMention,
|
IndiewebType: mf.TypeMention,
|
||||||
Source: wm.Source,
|
Source: wm.Source,
|
||||||
|
|
|
@ -8,7 +8,6 @@ import (
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
|
@ -17,7 +16,6 @@ type Config struct {
|
||||||
AdminEmail string `json:"adminEmail"`
|
AdminEmail string `json:"adminEmail"`
|
||||||
Port int `json:"port"`
|
Port int `json:"port"`
|
||||||
Token string `json:"token"`
|
Token string `json:"token"`
|
||||||
UtcOffset int `json:"utcOffset"`
|
|
||||||
AllowedWebmentionSources []string `json:"allowedWebmentionSources"`
|
AllowedWebmentionSources []string `json:"allowedWebmentionSources"`
|
||||||
Blacklist []string `json:"blacklist"`
|
Blacklist []string `json:"blacklist"`
|
||||||
Whitelist []string `json:"whitelist"`
|
Whitelist []string `json:"whitelist"`
|
||||||
|
@ -39,10 +37,6 @@ func isListedIn(url string, list []string) bool {
|
||||||
return Includes(list, domain)
|
return Includes(list, domain)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Config) Zone() *time.Location {
|
|
||||||
return time.FixedZone("local", c.UtcOffset*60)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Config) missingKeys() []string {
|
func (c *Config) missingKeys() []string {
|
||||||
keys := []string{}
|
keys := []string{}
|
||||||
if c.Port == 0 {
|
if c.Port == 0 {
|
||||||
|
@ -142,7 +136,6 @@ func defaultConfig() *Config {
|
||||||
BaseURL: "https://jam.brainbaking.com/",
|
BaseURL: "https://jam.brainbaking.com/",
|
||||||
Port: 1337,
|
Port: 1337,
|
||||||
Token: "miauwkes",
|
Token: "miauwkes",
|
||||||
UtcOffset: 60,
|
|
||||||
AllowedWebmentionSources: []string{"brainbaking.com", "jefklakscodex.com"},
|
AllowedWebmentionSources: []string{"brainbaking.com", "jefklakscodex.com"},
|
||||||
Blacklist: []string{"youtube.com"},
|
Blacklist: []string{"youtube.com"},
|
||||||
Whitelist: []string{"brainbaking.com"},
|
Whitelist: []string{"brainbaking.com"},
|
||||||
|
|
|
@ -26,7 +26,6 @@ func TestReadFromJsonWithCorrectJsonData(t *testing.T) {
|
||||||
"host": "localhost",
|
"host": "localhost",
|
||||||
"baseURL": "https://jam.brainbaking.com/",
|
"baseURL": "https://jam.brainbaking.com/",
|
||||||
"token": "miauwkes",
|
"token": "miauwkes",
|
||||||
"utcOffset": 60,
|
|
||||||
"allowedWebmentionSources": [
|
"allowedWebmentionSources": [
|
||||||
"snoopy.be"
|
"snoopy.be"
|
||||||
],
|
],
|
||||||
|
|
4
go.mod
4
go.mod
|
@ -3,13 +3,13 @@ module brainbaking.com/go-jamming
|
||||||
go 1.16
|
go 1.16
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/MagnusFrater/helmet v1.0.0
|
github.com/goddtriffin/helmet v1.0.2
|
||||||
github.com/gorilla/mux v1.8.0
|
github.com/gorilla/mux v1.8.0
|
||||||
github.com/hashicorp/go-cleanhttp v0.5.1
|
github.com/hashicorp/go-cleanhttp v0.5.1
|
||||||
github.com/hashicorp/go-retryablehttp v0.6.8
|
github.com/hashicorp/go-retryablehttp v0.6.8
|
||||||
github.com/rs/zerolog v1.21.0
|
github.com/rs/zerolog v1.21.0
|
||||||
github.com/stretchr/testify v1.7.0
|
github.com/stretchr/testify v1.7.0
|
||||||
github.com/tidwall/buntdb v1.2.3
|
github.com/tidwall/buntdb v1.2.3
|
||||||
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba
|
golang.org/x/time v0.0.0-20220411224347-583f2d630306
|
||||||
willnorris.com/go/microformats v1.1.1
|
willnorris.com/go/microformats v1.1.1
|
||||||
)
|
)
|
||||||
|
|
6
go.sum
6
go.sum
|
@ -1,11 +1,11 @@
|
||||||
github.com/MagnusFrater/helmet v1.0.0 h1:xzKXDZIXg4ik05MCVHZN3mTOc+3Skzm552nujJ5PzYI=
|
|
||||||
github.com/MagnusFrater/helmet v1.0.0/go.mod h1:giGWX/jKRWjf+jxEmKOXyBdtz7VmAK3SEbadVV2pdxA=
|
|
||||||
github.com/PuerkitoBio/goquery v1.5.0/go.mod h1:qD2PgZ9lccMbQlc7eEOjaeRlFQON7xY8kdmcsrnKqMg=
|
github.com/PuerkitoBio/goquery v1.5.0/go.mod h1:qD2PgZ9lccMbQlc7eEOjaeRlFQON7xY8kdmcsrnKqMg=
|
||||||
github.com/andybalholm/cascadia v1.0.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y=
|
github.com/andybalholm/cascadia v1.0.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y=
|
||||||
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/goddtriffin/helmet v1.0.2 h1:iKahg/oRPrDNz6yhE12WL1YoWsd2NJjtCH+zolqxToo=
|
||||||
|
github.com/goddtriffin/helmet v1.0.2/go.mod h1:UJAbeAOVaXjrOJPMgVLjoDM5ePko0PJX7C8IUDGsu+k=
|
||||||
github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
|
github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
|
||||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
|
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
|
||||||
|
@ -68,6 +68,8 @@ golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba h1:O8mE0/t419eoIwhTFpKVkHiTs/Igowgfkj25AcZrtiE=
|
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba h1:O8mE0/t419eoIwhTFpKVkHiTs/Igowgfkj25AcZrtiE=
|
||||||
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
|
golang.org/x/time v0.0.0-20220411224347-583f2d630306 h1:+gHMid33q6pen7kv9xvT+JRinntgeXO2AeZVd0AWD3w=
|
||||||
|
golang.org/x/time v0.0.0-20220411224347-583f2d630306/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
|
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
|
||||||
|
|
Loading…
Reference in New Issue