From bba608b4051bb79c6f755e6ed2b89dce5e3a67e3 Mon Sep 17 00:00:00 2001 From: zhaoyang Date: Sat, 11 Dec 2021 16:20:48 +0800 Subject: [PATCH] Pr/avatar (#319) * feat: add avatar api close #310 * chore: add description line * fix: typo * chore: reformat the json string to make it more readable and fix few bad indents * fix: minimum go version * feat: upload to Micro's CDN * chore: delete redundant blank line * chore: update README.md --- avatar/.gitignore | 2 + avatar/Dockerfile | 3 + avatar/Makefile | 28 ++++ avatar/README.md | 6 + avatar/examples.json | 34 +++++ avatar/generate.go | 3 + avatar/handler/avatar.go | 98 ++++++++++++ avatar/main.go | 27 ++++ avatar/micro.mu | 1 + avatar/proto/avatar.pb.go | 259 ++++++++++++++++++++++++++++++++ avatar/proto/avatar.pb.micro.go | 93 ++++++++++++ avatar/proto/avatar.proto | 30 ++++ avatar/publicapi.json | 6 + go.mod | 2 +- go.sum | 3 + rss/examples.json | 133 ++++++++-------- 16 files changed, 664 insertions(+), 64 deletions(-) create mode 100644 avatar/.gitignore create mode 100644 avatar/Dockerfile create mode 100644 avatar/Makefile create mode 100644 avatar/README.md create mode 100644 avatar/examples.json create mode 100644 avatar/generate.go create mode 100644 avatar/handler/avatar.go create mode 100644 avatar/main.go create mode 100644 avatar/micro.mu create mode 100644 avatar/proto/avatar.pb.go create mode 100644 avatar/proto/avatar.pb.micro.go create mode 100644 avatar/proto/avatar.proto create mode 100644 avatar/publicapi.json diff --git a/avatar/.gitignore b/avatar/.gitignore new file mode 100644 index 0000000..8c570af --- /dev/null +++ b/avatar/.gitignore @@ -0,0 +1,2 @@ + +avatar diff --git a/avatar/Dockerfile b/avatar/Dockerfile new file mode 100644 index 0000000..3e3a729 --- /dev/null +++ b/avatar/Dockerfile @@ -0,0 +1,3 @@ +FROM alpine +ADD avatar /avatar +ENTRYPOINT [ "/avatar" ] diff --git a/avatar/Makefile b/avatar/Makefile new file mode 100644 index 0000000..0e20f5b --- /dev/null +++ b/avatar/Makefile @@ -0,0 +1,28 @@ + +GOPATH:=$(shell go env GOPATH) +.PHONY: init +init: + go get -u github.com/golang/protobuf/proto + go get -u github.com/golang/protobuf/protoc-gen-go + go get github.com/micro/micro/v3/cmd/protoc-gen-micro + go get github.com/micro/micro/v3/cmd/protoc-gen-openapi + +.PHONY: api +api: + protoc --openapi_out=. --proto_path=. proto/avatar.proto + +.PHONY: proto +proto: + protoc --proto_path=. --micro_out=. --go_out=:. proto/avatar.proto + +.PHONY: build +build: + go build -o avatar *.go + +.PHONY: test +test: + go test -v ./... -cover + +.PHONY: docker +docker: + docker build . -t avatar:latest diff --git a/avatar/README.md b/avatar/README.md new file mode 100644 index 0000000..fdef212 --- /dev/null +++ b/avatar/README.md @@ -0,0 +1,6 @@ +Generate an avatar + +# Avatar Service + +Generate a unique avatar for your user profiles, +NFT crypto punks and much more. \ No newline at end of file diff --git a/avatar/examples.json b/avatar/examples.json new file mode 100644 index 0000000..6a33519 --- /dev/null +++ b/avatar/examples.json @@ -0,0 +1,34 @@ +{ + "generate": [ + { + "title": "Generate avatar and return base64string of the avatar", + "run_check": false, + "description": "generate an avatar and return base64encode string", + "request": { + "gender": "female", + "username": "", + "format": "png", + "upload": true + }, + "response": { + "url": "https://cdn.m3ocontent.com/micro/images/micro/admin/aa332cf4-59f5-11ec-9b7c-acde48001122.png", + "base64": "" + } + }, + { + "title": "Generate an avatar and upload the avatar to Micro's CDN", + "run_check": false, + "description": "generate an avatar and return Micro's CDN url", + "request": { + "gender": "female", + "username": "", + "format": "jpeg", + "upload": false + }, + "response": { + "url": "", + "base64": "data:image/jpeg;base64,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" + } + } + ] +} diff --git a/avatar/generate.go b/avatar/generate.go new file mode 100644 index 0000000..7d9db91 --- /dev/null +++ b/avatar/generate.go @@ -0,0 +1,3 @@ +package main + +//go:generate make proto diff --git a/avatar/handler/avatar.go b/avatar/handler/avatar.go new file mode 100644 index 0000000..ad6af57 --- /dev/null +++ b/avatar/handler/avatar.go @@ -0,0 +1,98 @@ +package handler + +import ( + "bytes" + "context" + "encoding/base64" + "fmt" + "image" + "image/jpeg" + "image/png" + + "github.com/google/uuid" + "github.com/micro/micro/v3/service/errors" + "github.com/o1egl/govatar" + + pb "github.com/micro/services/avatar/proto" + imagePb "github.com/micro/services/image/proto" +) + +type avatar struct { + imageSvc imagePb.ImageService +} + +func NewAvatar(service imagePb.ImageService) *avatar { + return &avatar{ + imageSvc: service, + } +} + +// Generate is used to generate a avatar +func (e *avatar) Generate(ctx context.Context, req *pb.GenerateRequest, rsp *pb.GenerateResponse) error { + var gender govatar.Gender + + // gender, default is `male` + if req.Gender == "male" { + gender = govatar.MALE + } else if req.Gender == "female" { + gender = govatar.FEMALE + } else { + gender = govatar.MALE + } + + // generate avatar + var avatarImg image.Image + var err error + + if req.Username == "" { + avatarImg, err = govatar.Generate(gender) + } else { + avatarImg, err = govatar.GenerateForUsername(gender, req.Username) + } + if err != nil { + return errors.InternalServerError("avatar.generate", "generate avatarImg error: %v", err) + } + + // format avatar image, default is `jpeg` + format := req.Format + if format != "png" && format != "jpeg" { + format = "jpeg" + } + + buf := bytes.NewBuffer(nil) + if format == "png" { + err = png.Encode(buf, avatarImg) + } else { + err = jpeg.Encode(buf, avatarImg, nil) + } + if err != nil { + return errors.InternalServerError("avatar.generate", "encode avatar image error: %v", err) + } + + base64String := fmt.Sprintf("data:image/%s;base64,%s", format, base64.StdEncoding.EncodeToString(buf.Bytes())) + + if !req.Upload { + rsp.Base64 = base64String + return nil + } + + // upload to CDN + name := req.Username + if name == "" { + uid, _ := uuid.NewUUID() + name = uid.String() + } + + uploadResp, err := e.imageSvc.Upload(ctx, &imagePb.UploadRequest{ + Base64: base64String, + Name: fmt.Sprintf("%s.%s", name, format), + }) + + if err != nil { + return errors.InternalServerError("avatar.generate", "upload avatar image error: %v", err) + } + + rsp.Url = uploadResp.Url + + return nil +} diff --git a/avatar/main.go b/avatar/main.go new file mode 100644 index 0000000..7256b59 --- /dev/null +++ b/avatar/main.go @@ -0,0 +1,27 @@ +package main + +import ( + "github.com/micro/services/avatar/handler" + pb "github.com/micro/services/avatar/proto" + imagePb "github.com/micro/services/image/proto" + + "github.com/micro/micro/v3/service" + "github.com/micro/micro/v3/service/logger" +) + +func main() { + // Create service + srv := service.New( + service.Name("avatar"), + service.Version("latest"), + ) + + // Register handler + hdlr := handler.NewAvatar(imagePb.NewImageService("image", srv.Client())) + pb.RegisterAvatarHandler(srv.Server(), hdlr) + + // Run service + if err := srv.Run(); err != nil { + logger.Fatal(err) + } +} diff --git a/avatar/micro.mu b/avatar/micro.mu new file mode 100644 index 0000000..8b4086c --- /dev/null +++ b/avatar/micro.mu @@ -0,0 +1 @@ +service avatar diff --git a/avatar/proto/avatar.pb.go b/avatar/proto/avatar.pb.go new file mode 100644 index 0000000..c5bffa3 --- /dev/null +++ b/avatar/proto/avatar.pb.go @@ -0,0 +1,259 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.27.1 +// protoc v3.19.1 +// source: proto/avatar.proto + +package avatar + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type GenerateRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // avatar's gender, `male` or `female`, default is `male` + Gender string `protobuf:"bytes,1,opt,name=gender,proto3" json:"gender,omitempty"` + // avatar's username, unique username will generates the unique avatar; + // if username == "", will generate a random avatar in every request + // if upload == true, username will be used as CDN filename + Username string `protobuf:"bytes,2,opt,name=username,proto3" json:"username,omitempty"` + // encode format of avatar image, `png` or `jpeg`, default is `jpeg` + Format string `protobuf:"bytes,3,opt,name=format,proto3" json:"format,omitempty"` + // if upload to m3o CDN, default is `false` + // if update = true, then it'll return the CDN url + Upload bool `protobuf:"varint,4,opt,name=upload,proto3" json:"upload,omitempty"` +} + +func (x *GenerateRequest) Reset() { + *x = GenerateRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_avatar_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GenerateRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GenerateRequest) ProtoMessage() {} + +func (x *GenerateRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_avatar_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GenerateRequest.ProtoReflect.Descriptor instead. +func (*GenerateRequest) Descriptor() ([]byte, []int) { + return file_proto_avatar_proto_rawDescGZIP(), []int{0} +} + +func (x *GenerateRequest) GetGender() string { + if x != nil { + return x.Gender + } + return "" +} + +func (x *GenerateRequest) GetUsername() string { + if x != nil { + return x.Username + } + return "" +} + +func (x *GenerateRequest) GetFormat() string { + if x != nil { + return x.Format + } + return "" +} + +func (x *GenerateRequest) GetUpload() bool { + if x != nil { + return x.Upload + } + return false +} + +type GenerateResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Micro's CDN url of the avatar image + Url string `protobuf:"bytes,1,opt,name=url,proto3" json:"url,omitempty"` + // base64encode string of the avatar image + Base64 string `protobuf:"bytes,2,opt,name=base64,proto3" json:"base64,omitempty"` +} + +func (x *GenerateResponse) Reset() { + *x = GenerateResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_avatar_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GenerateResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GenerateResponse) ProtoMessage() {} + +func (x *GenerateResponse) ProtoReflect() protoreflect.Message { + mi := &file_proto_avatar_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GenerateResponse.ProtoReflect.Descriptor instead. +func (*GenerateResponse) Descriptor() ([]byte, []int) { + return file_proto_avatar_proto_rawDescGZIP(), []int{1} +} + +func (x *GenerateResponse) GetUrl() string { + if x != nil { + return x.Url + } + return "" +} + +func (x *GenerateResponse) GetBase64() string { + if x != nil { + return x.Base64 + } + return "" +} + +var File_proto_avatar_proto protoreflect.FileDescriptor + +var file_proto_avatar_proto_rawDesc = []byte{ + 0x0a, 0x12, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x61, 0x76, 0x61, 0x74, 0x61, 0x72, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x06, 0x61, 0x76, 0x61, 0x74, 0x61, 0x72, 0x22, 0x75, 0x0a, 0x0f, + 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, + 0x16, 0x0a, 0x06, 0x67, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x06, 0x67, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x12, 0x1a, 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, + 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, + 0x61, 0x6d, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x06, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x75, + 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x75, 0x70, 0x6c, + 0x6f, 0x61, 0x64, 0x22, 0x3c, 0x0a, 0x10, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x12, 0x16, 0x0a, 0x06, 0x62, 0x61, 0x73, + 0x65, 0x36, 0x34, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x62, 0x61, 0x73, 0x65, 0x36, + 0x34, 0x32, 0x49, 0x0a, 0x06, 0x41, 0x76, 0x61, 0x74, 0x61, 0x72, 0x12, 0x3f, 0x0a, 0x08, 0x47, + 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x12, 0x17, 0x2e, 0x61, 0x76, 0x61, 0x74, 0x61, 0x72, + 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x18, 0x2e, 0x61, 0x76, 0x61, 0x74, 0x61, 0x72, 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, + 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x10, 0x5a, 0x0e, + 0x2e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x3b, 0x61, 0x76, 0x61, 0x74, 0x61, 0x72, 0x62, 0x06, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_proto_avatar_proto_rawDescOnce sync.Once + file_proto_avatar_proto_rawDescData = file_proto_avatar_proto_rawDesc +) + +func file_proto_avatar_proto_rawDescGZIP() []byte { + file_proto_avatar_proto_rawDescOnce.Do(func() { + file_proto_avatar_proto_rawDescData = protoimpl.X.CompressGZIP(file_proto_avatar_proto_rawDescData) + }) + return file_proto_avatar_proto_rawDescData +} + +var file_proto_avatar_proto_msgTypes = make([]protoimpl.MessageInfo, 2) +var file_proto_avatar_proto_goTypes = []interface{}{ + (*GenerateRequest)(nil), // 0: avatar.GenerateRequest + (*GenerateResponse)(nil), // 1: avatar.GenerateResponse +} +var file_proto_avatar_proto_depIdxs = []int32{ + 0, // 0: avatar.Avatar.Generate:input_type -> avatar.GenerateRequest + 1, // 1: avatar.Avatar.Generate:output_type -> avatar.GenerateResponse + 1, // [1:2] is the sub-list for method output_type + 0, // [0:1] is the sub-list for method input_type + 0, // [0:0] is the sub-list for extension type_name + 0, // [0:0] is the sub-list for extension extendee + 0, // [0:0] is the sub-list for field type_name +} + +func init() { file_proto_avatar_proto_init() } +func file_proto_avatar_proto_init() { + if File_proto_avatar_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_proto_avatar_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GenerateRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_avatar_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GenerateResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_proto_avatar_proto_rawDesc, + NumEnums: 0, + NumMessages: 2, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_proto_avatar_proto_goTypes, + DependencyIndexes: file_proto_avatar_proto_depIdxs, + MessageInfos: file_proto_avatar_proto_msgTypes, + }.Build() + File_proto_avatar_proto = out.File + file_proto_avatar_proto_rawDesc = nil + file_proto_avatar_proto_goTypes = nil + file_proto_avatar_proto_depIdxs = nil +} diff --git a/avatar/proto/avatar.pb.micro.go b/avatar/proto/avatar.pb.micro.go new file mode 100644 index 0000000..0d53ab3 --- /dev/null +++ b/avatar/proto/avatar.pb.micro.go @@ -0,0 +1,93 @@ +// Code generated by protoc-gen-micro. DO NOT EDIT. +// source: proto/avatar.proto + +package avatar + +import ( + fmt "fmt" + proto "github.com/golang/protobuf/proto" + math "math" +) + +import ( + context "context" + api "github.com/micro/micro/v3/service/api" + client "github.com/micro/micro/v3/service/client" + server "github.com/micro/micro/v3/service/server" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package + +// Reference imports to suppress errors if they are not otherwise used. +var _ api.Endpoint +var _ context.Context +var _ client.Option +var _ server.Option + +// Api Endpoints for Avatar service + +func NewAvatarEndpoints() []*api.Endpoint { + return []*api.Endpoint{} +} + +// Client API for Avatar service + +type AvatarService interface { + Generate(ctx context.Context, in *GenerateRequest, opts ...client.CallOption) (*GenerateResponse, error) +} + +type avatarService struct { + c client.Client + name string +} + +func NewAvatarService(name string, c client.Client) AvatarService { + return &avatarService{ + c: c, + name: name, + } +} + +func (c *avatarService) Generate(ctx context.Context, in *GenerateRequest, opts ...client.CallOption) (*GenerateResponse, error) { + req := c.c.NewRequest(c.name, "Avatar.Generate", in) + out := new(GenerateResponse) + err := c.c.Call(ctx, req, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// Server API for Avatar service + +type AvatarHandler interface { + Generate(context.Context, *GenerateRequest, *GenerateResponse) error +} + +func RegisterAvatarHandler(s server.Server, hdlr AvatarHandler, opts ...server.HandlerOption) error { + type avatar interface { + Generate(ctx context.Context, in *GenerateRequest, out *GenerateResponse) error + } + type Avatar struct { + avatar + } + h := &avatarHandler{hdlr} + return s.Handle(s.NewHandler(&Avatar{h}, opts...)) +} + +type avatarHandler struct { + AvatarHandler +} + +func (h *avatarHandler) Generate(ctx context.Context, in *GenerateRequest, out *GenerateResponse) error { + return h.AvatarHandler.Generate(ctx, in, out) +} diff --git a/avatar/proto/avatar.proto b/avatar/proto/avatar.proto new file mode 100644 index 0000000..eb11faf --- /dev/null +++ b/avatar/proto/avatar.proto @@ -0,0 +1,30 @@ +syntax = "proto3"; + +package avatar; + +option go_package = "./proto;avatar"; + +service Avatar { + rpc Generate(GenerateRequest) returns (GenerateResponse) {} +} + +message GenerateRequest { + // avatar's gender, `male` or `female`, default is `male` + string gender = 1; + // avatar's username, unique username will generates the unique avatar; + // if username == "", will generate a random avatar in every request + // if upload == true, username will be used as CDN filename rather than a random uuid string + string username = 2; + // encode format of avatar image, `png` or `jpeg`, default is `jpeg` + string format = 3; + // if upload to m3o CDN, default is `false` + // if update = true, then it'll return the CDN url + bool upload = 4; +} + +message GenerateResponse { + // Micro's CDN url of the avatar image + string url = 1; + // base64encode string of the avatar image + string base64 = 2; +} diff --git a/avatar/publicapi.json b/avatar/publicapi.json new file mode 100644 index 0000000..4f4b1fa --- /dev/null +++ b/avatar/publicapi.json @@ -0,0 +1,6 @@ +{ + "name": "avatar", + "icon": "🤳🏻", + "category": "for fun", + "display_name": "Avatar" +} diff --git a/go.mod b/go.mod index 9bffceb..1926448 100644 --- a/go.mod +++ b/go.mod @@ -34,6 +34,7 @@ require ( github.com/micro/micro/v3 v3.8.0 github.com/miekg/dns v1.1.31 // indirect github.com/minio/minio-go/v7 v7.0.16 + github.com/o1egl/govatar v0.3.0 github.com/onsi/gomega v1.10.5 github.com/oschwald/geoip2-golang v1.5.0 github.com/patrickmn/go-cache v2.1.0+incompatible @@ -58,7 +59,6 @@ require ( golang.org/x/oauth2 v0.0.0-20211005180243-6b3c2da341f1 golang.org/x/text v0.3.6 google.golang.org/api v0.59.0 - google.golang.org/genproto v0.0.0-20211008145708-270636b82663 // indirect google.golang.org/grpc/examples v0.0.0-20211103202053-3b94303f3754 // indirect google.golang.org/protobuf v1.27.1 googlemaps.github.io/maps v1.3.1 diff --git a/go.sum b/go.sum index 433a0be..bf00b92 100644 --- a/go.sum +++ b/go.sum @@ -525,6 +525,8 @@ github.com/nrdcg/goinwx v0.6.1/go.mod h1:XPiut7enlbEdntAqalBIqcYcTEVhpv/dKWgDCX2 github.com/nrdcg/namesilo v0.2.1/go.mod h1:lwMvfQTyYq+BbjJd30ylEG4GPSS6PII0Tia4rRpRiyw= github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/o1egl/govatar v0.3.0 h1:hGDsiJJs6qgQ6Ea4JiaukRsUKTY2Ai4dgMEdsYvlUa0= +github.com/o1egl/govatar v0.3.0/go.mod h1:YeDGDII+2Ji1RcBKvb1KqaPhk4PmuZyBq+rPYc6b+cQ= github.com/olekukonko/tablewriter v0.0.1/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= github.com/olekukonko/tablewriter v0.0.3/go.mod h1:YZeBtGzYYEsCHp2LST/u/0NDwGkRoBtmn1cIWCJiS6M= github.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA= @@ -661,6 +663,7 @@ github.com/uber/jaeger-client-go v2.29.1+incompatible h1:R9ec3zO3sGpzs0abd43Y+fB github.com/uber/jaeger-client-go v2.29.1+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk= github.com/uber/jaeger-lib v2.4.1+incompatible h1:td4jdvLcExb4cBISKIpHuGoVXh+dVKhn2Um6rjCsSsg= github.com/uber/jaeger-lib v2.4.1+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U= +github.com/urfave/cli v1.21.0/go.mod h1:lxDj6qX9Q6lWQxIrbrT0nwecwUtRnhVZAJjJZrVUZZQ= github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/urfave/cli v1.22.2 h1:gsqYFH8bb9ekPA12kRo0hfjngWQjkJPlN9R0N78BoUo= github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= diff --git a/rss/examples.json b/rss/examples.json index f9dbbe9..6e66d37 100644 --- a/rss/examples.json +++ b/rss/examples.json @@ -1,67 +1,74 @@ - { - "add": [{ - "title": "Add a new feed", - "description": "Add a new rss feed to the crawler", - "run_check": true, - "request": { - "name": "bbc", - "url": "http://feeds.bbci.co.uk/news/rss.xml", - "category": "news" - }, - "response": {} - }], - "feed": [{ - "title": "Read a feed", - "description": "Read an rss feed by name", - "run_check": true, - "request": { - "name": "bbc" - }, - "response": { - "entries": [ - { - "id": "eae9563e0a017c02ba6fcb38bbb6f5d5", - "feed": "http://feeds.bbci.co.uk/news/rss.xml", - "link": "https://www.bbc.co.uk/sport/cricket/57190558", - "title": "Jofra Archer: England bowler to have surgery on elbow injury", - "summary": "England fast bowler Jofra Archer will have surgery on his troublesome right elbow on Friday - with a timeframe on his recovery yet to be given.", - "date": "2021-05-20T14:00:08Z" - }, - { - "id": "bf774b0274930dd9efcfc5182cc41e48", - "feed": "http://feeds.bbci.co.uk/news/rss.xml", - "link": "https://www.bbc.co.uk/sport/football/57188305", - "title": "Home nations openers to be shown live on BBC - how to follow Euro 2020 ", - "summary": "The BBC will give audiences 24/7 access to all of the action, analysis, insight and entertainment from this summer's European Championship.", - "date": "2021-05-20T13:37:21Z" - } - ] + "add": [ + { + "title": "Add a new feed", + "description": "Add a new rss feed to the crawler", + "run_check": true, + "request": { + "name": "bbc", + "url": "http://feeds.bbci.co.uk/news/rss.xml", + "category": "news" + }, + "response": {} } - }], - "list": [{ - "title": "List rss feeds", - "description": "List the saved rss feeds", - "run_check": true, - "request": {}, - "response": { - "feeds": [ - { - "id": "micro/asim/18882020881032690", - "name": "bbc", - "url": "http://feeds.bbci.co.uk/news/rss.xml", - "category": "news" - } - ] + ], + "feed": [ + { + "title": "Read a feed", + "description": "Read an rss feed by name", + "run_check": true, + "request": { + "name": "bbc" + }, + "response": { + "entries": [ + { + "id": "eae9563e0a017c02ba6fcb38bbb6f5d5", + "feed": "http://feeds.bbci.co.uk/news/rss.xml", + "link": "https://www.bbc.co.uk/sport/cricket/57190558", + "title": "Jofra Archer: England bowler to have surgery on elbow injury", + "summary": "England fast bowler Jofra Archer will have surgery on his troublesome right elbow on Friday - with a timeframe on his recovery yet to be given.", + "date": "2021-05-20T14:00:08Z" + }, + { + "id": "bf774b0274930dd9efcfc5182cc41e48", + "feed": "http://feeds.bbci.co.uk/news/rss.xml", + "link": "https://www.bbc.co.uk/sport/football/57188305", + "title": "Home nations openers to be shown live on BBC - how to follow Euro 2020 ", + "summary": "The BBC will give audiences 24/7 access to all of the action, analysis, insight and entertainment from this summer's European Championship.", + "date": "2021-05-20T13:37:21Z" + } + ] + } } - }], - "remove": [{ - "title": "Remove a feed", - "description": "Remove an rss feed from the crawler", - "run_check": true, - "request": { - "name": "bbc" - }, - "response": {} - }] + ], + "list": [ + { + "title": "List rss feeds", + "description": "List the saved rss feeds", + "run_check": true, + "request": {}, + "response": { + "feeds": [ + { + "id": "micro/asim/18882020881032690", + "name": "bbc", + "url": "http://feeds.bbci.co.uk/news/rss.xml", + "category": "news" + } + ] + } + } + ], + "remove": [ + { + "title": "Remove a feed", + "description": "Remove an rss feed from the crawler", + "run_check": true, + "request": { + "name": "bbc" + }, + "response": {} + } + ] }