From 420f0706b0eea2f092ce9441369f5db593ac4566 Mon Sep 17 00:00:00 2001 From: Asim Aslam Date: Mon, 14 Feb 2022 15:05:28 +0000 Subject: [PATCH] add a ping service (#372) * add a ping service * add examples and public api file * add minecraft server ping --- go.mod | 5 +- go.sum | 10 + minecraft/.gitignore | 2 + minecraft/Dockerfile | 3 + minecraft/Makefile | 28 ++ minecraft/README.md | 5 + minecraft/examples.json | 20 + minecraft/generate.go | 3 + minecraft/handler/minecraft.go | 42 ++ minecraft/main.go | 25 ++ minecraft/micro.mu | 1 + minecraft/proto/minecraft.pb.go | 366 ++++++++++++++++++ minecraft/proto/minecraft.pb.micro.go | 93 +++++ minecraft/proto/minecraft.proto | 41 ++ minecraft/publicapi.json | 6 + ping/.gitignore | 2 + ping/Dockerfile | 3 + ping/Makefile | 28 ++ ping/README.md | 7 + ping/examples.json | 36 ++ ping/generate.go | 3 + ping/handler/ping.go | 120 ++++++ ping/main.go | 24 ++ ping/micro.mu | 1 + ping/proto/ping.pb.go | 530 ++++++++++++++++++++++++++ ping/proto/ping.pb.micro.go | 127 ++++++ ping/proto/ping.proto | 54 +++ ping/publicapi.json | 6 + 28 files changed, 1590 insertions(+), 1 deletion(-) create mode 100644 minecraft/.gitignore create mode 100644 minecraft/Dockerfile create mode 100644 minecraft/Makefile create mode 100644 minecraft/README.md create mode 100644 minecraft/examples.json create mode 100644 minecraft/generate.go create mode 100644 minecraft/handler/minecraft.go create mode 100644 minecraft/main.go create mode 100644 minecraft/micro.mu create mode 100644 minecraft/proto/minecraft.pb.go create mode 100644 minecraft/proto/minecraft.pb.micro.go create mode 100644 minecraft/proto/minecraft.proto create mode 100644 minecraft/publicapi.json create mode 100644 ping/.gitignore create mode 100644 ping/Dockerfile create mode 100644 ping/Makefile create mode 100644 ping/README.md create mode 100644 ping/examples.json create mode 100644 ping/generate.go create mode 100644 ping/handler/ping.go create mode 100644 ping/main.go create mode 100644 ping/micro.mu create mode 100644 ping/proto/ping.pb.go create mode 100644 ping/proto/ping.pb.micro.go create mode 100644 ping/proto/ping.proto create mode 100644 ping/publicapi.json diff --git a/go.mod b/go.mod index fbfe184..148e673 100644 --- a/go.mod +++ b/go.mod @@ -16,14 +16,17 @@ require ( github.com/dghubble/go-twitter v0.0.0-20210609183100-2fdbf421508e github.com/disintegration/imaging v1.6.2 github.com/enescakir/emoji v1.0.0 + github.com/go-ping/ping v0.0.0-20211130115550-779d1e919534 github.com/gojuno/go.osrm v0.1.1-0.20200217151037-435fc3e1d3d4 github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e github.com/golang/protobuf v1.5.2 - github.com/google/uuid v1.1.2 + github.com/google/uuid v1.2.0 github.com/hablullah/go-prayer v1.0.0 github.com/hailocab/go-geoindex v0.0.0-20160127134810-64631bfe9711 github.com/hashicorp/golang-lru v0.5.3 + github.com/iverly/go-mcping v1.0.1-0.20200818104507-3d8fc23750ae github.com/jackc/pgx/v4 v4.10.1 + github.com/jmoiron/jsonq v0.0.0-20150511023944-e874b168d07e // indirect github.com/kevinburke/go-types v0.0.0-20201208005256-aee49f568a20 // indirect github.com/kevinburke/go.uuid v1.2.0 // indirect github.com/kevinburke/rest v0.0.0-20210506044642-5611499aa33c // indirect diff --git a/go.sum b/go.sum index 338f1df..ae4e3b3 100644 --- a/go.sum +++ b/go.sum @@ -199,6 +199,8 @@ github.com/go-ini/ini v1.44.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3I github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-ping/ping v0.0.0-20211130115550-779d1e919534 h1:dhy9OQKGBh4zVXbjwbxxHjRxMJtLXj3zfgpBYQaR4Q4= +github.com/go-ping/ping v0.0.0-20211130115550-779d1e919534/go.mod h1:xIFjORFzTxqIV/tDVGO4eDy/bLuSyawEeojSm3GfRGk= github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs= github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= @@ -316,6 +318,8 @@ github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm4 github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs= +github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0= @@ -362,6 +366,10 @@ github.com/iij/doapi v0.0.0-20190504054126-0bbf12d6d7df/go.mod h1:QMZY7/J/KSQEhK github.com/improbable-eng/grpc-web v0.13.0 h1:7XqtaBWaOCH0cVGKHyvhtcuo6fgW32Y10yRKrDHFHOc= github.com/improbable-eng/grpc-web v0.13.0/go.mod h1:6hRR09jOEG81ADP5wCQju1z71g6OL4eEvELdran/3cs= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/iverly/go-mcping v1.0.0 h1:zr/6Ko0D2101YslDoEiUg0N2hQP6iLalGQlnfErBlbY= +github.com/iverly/go-mcping v1.0.0/go.mod h1:OTdb13yxI0zg1VKs7G53T43N2hAduaPeUcY+PbP608g= +github.com/iverly/go-mcping v1.0.1-0.20200818104507-3d8fc23750ae h1:x8jYVK94sexeVwGhw4au/tuOnzzJUa1D5CdU3ZSZfuQ= +github.com/iverly/go-mcping v1.0.1-0.20200818104507-3d8fc23750ae/go.mod h1:OTdb13yxI0zg1VKs7G53T43N2hAduaPeUcY+PbP608g= github.com/jackc/chunkreader v1.0.0 h1:4s39bBR8ByfqH+DKm8rQA3E1LHZWB9XWcrz8fqaZbe0= github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo= github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= @@ -425,6 +433,8 @@ github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9Y github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= +github.com/jmoiron/jsonq v0.0.0-20150511023944-e874b168d07e h1:ZZCvgaRDZg1gC9/1xrsgaJzQUCQgniKtw0xjWywWAOE= +github.com/jmoiron/jsonq v0.0.0-20150511023944-e874b168d07e/go.mod h1:+rHyWac2R9oAZwFe1wGY2HBzFJJy++RHBg1cU23NkD8= github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= diff --git a/minecraft/.gitignore b/minecraft/.gitignore new file mode 100644 index 0000000..99ac51e --- /dev/null +++ b/minecraft/.gitignore @@ -0,0 +1,2 @@ + +minecraft diff --git a/minecraft/Dockerfile b/minecraft/Dockerfile new file mode 100644 index 0000000..c0fa4eb --- /dev/null +++ b/minecraft/Dockerfile @@ -0,0 +1,3 @@ +FROM alpine +ADD minecraft /minecraft +ENTRYPOINT [ "/minecraft" ] diff --git a/minecraft/Makefile b/minecraft/Makefile new file mode 100644 index 0000000..c8cb81e --- /dev/null +++ b/minecraft/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/minecraft.proto + +.PHONY: proto +proto: + protoc --proto_path=. --micro_out=. --go_out=:. proto/minecraft.proto + +.PHONY: build +build: + go build -o minecraft *.go + +.PHONY: test +test: + go test -v ./... -cover + +.PHONY: docker +docker: + docker build . -t minecraft:latest diff --git a/minecraft/README.md b/minecraft/README.md new file mode 100644 index 0000000..eb4ea01 --- /dev/null +++ b/minecraft/README.md @@ -0,0 +1,5 @@ +Minecraft server ping + +# Minecraft Service + +Ping your Minecraft servers for latency, player count, etc. diff --git a/minecraft/examples.json b/minecraft/examples.json new file mode 100644 index 0000000..3ac9700 --- /dev/null +++ b/minecraft/examples.json @@ -0,0 +1,20 @@ +{ + "ping": [{ + "title": "Ping a minecraft server", + "description": "Ping a minecrafter server for stats", + "run_check": false, + "request": { + "funcraft.net" + }, + "response": { + "latency": 84, + "players": 412, + "max_players": 1000, + "protocol": 109, + "favicon": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAMAAACdt4HsAAADAFBMVEX////WRjXWRjXWRjXWRjXWRjXWRjXWRjXWRjXWRjXWRjXWRjXWRjXWRjXWRjXWRjXWRjX///8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADFGtetAAAAEHRSTlMAADNEqrt3ImbM3YjumRFVXOavjQAAAYlJREFUeNrNl39zgyAMhgMirqfd9/+Y29l2igHW9lrbTQmR7K7LX8rB0zc/SCzAq01lF35ZBK3AP97Nckuloj5vMW7BVnUIdXN++qAAqrs97ABGnJfbpy3PaEM61TRZv/XSg2zcEElA3nCUAYYByBh4+rh3I+2xisqkpH9VTvc+W0j7Iaja3t+P8DZedcV4KyRWJXazss9FJf60RBDnOI25kOaygFIAFAJq7uVMAaxUwWy7MsBDwKEMsN4GilzoywAVu45yCrAQEPjp1HTTmwoBDbO7JAFm9fJqvSe2rjeJJ4D13VpSaQBqaM+HLHYYLm7VI2c2WhNTwT9Ni460VPDuTbolM4JoifOsLBjiPAYGwJHfBrKeeKiAmcZH1C+/cNOtXey3AtxUEER79J5/lVaGKwbn73U0hbKurNhDIQFopJPJbMmxppamQgXS4VqRpbtJQSEgSl0Qx6D9MwWuDGA3lcEKAOHlQfxHgEYKMGWAIHbBSwHKCXtib07IByT+sdgLIkaQXnaGfQNp/mgnuZ0pQQAAAABJRU5ErkJggg==", + "motd": " »»» FunCraft ««« Mini-Jeux, PvP et Fun !\n Rush, Hikabrain, FreeCube, SkyWars, Survival, ...", + "version": "§4Use 1.9/1.10/1.11§f - §7412§8/§71000", + "sample": [] + } + }] +} diff --git a/minecraft/generate.go b/minecraft/generate.go new file mode 100644 index 0000000..7d9db91 --- /dev/null +++ b/minecraft/generate.go @@ -0,0 +1,3 @@ +package main + +//go:generate make proto diff --git a/minecraft/handler/minecraft.go b/minecraft/handler/minecraft.go new file mode 100644 index 0000000..b1b310c --- /dev/null +++ b/minecraft/handler/minecraft.go @@ -0,0 +1,42 @@ +package handler + +import ( + "context" + + "github.com/iverly/go-mcping/mcping" + "github.com/micro/micro/v3/service/errors" + pb "github.com/micro/services/minecraft/proto" +) + +type Minecraft struct{} + +func (m *Minecraft) Ping(ctx context.Context, req *pb.PingRequest, rsp *pb.PingResponse) error { + if len(req.Address) == 0 { + return errors.BadRequest("minecraft.ping", "missing address") + } + + pinger := mcping.NewPinger() + resp, err := pinger.Ping(req.Address, 25565) + if err != nil { + return err + } + + var samples []*pb.PlayerSample + for _, sample := range resp.Sample { + samples = append(samples, &pb.PlayerSample{ + Uuid: sample.UUID, + Name: sample.Name, + }) + } + + rsp.Latency = uint32(resp.Latency) + rsp.Players = int32(resp.PlayerCount.Online) + rsp.MaxPlayers = int32(resp.PlayerCount.Max) + rsp.Protocol = int32(resp.Protocol) + rsp.Favicon = resp.Favicon + rsp.Motd = resp.Motd + rsp.Version = resp.Version + rsp.Sample = samples + + return nil +} diff --git a/minecraft/main.go b/minecraft/main.go new file mode 100644 index 0000000..c93ab30 --- /dev/null +++ b/minecraft/main.go @@ -0,0 +1,25 @@ +package main + +import ( + "github.com/micro/services/minecraft/handler" + pb "github.com/micro/services/minecraft/proto" + + "github.com/micro/micro/v3/service" + "github.com/micro/micro/v3/service/logger" +) + +func main() { + // Create service + srv := service.New( + service.Name("minecraft"), + service.Version("latest"), + ) + + // Register handler + pb.RegisterMinecraftHandler(srv.Server(), new(handler.Minecraft)) + + // Run service + if err := srv.Run(); err != nil { + logger.Fatal(err) + } +} diff --git a/minecraft/micro.mu b/minecraft/micro.mu new file mode 100644 index 0000000..8895c4d --- /dev/null +++ b/minecraft/micro.mu @@ -0,0 +1 @@ +service minecraft diff --git a/minecraft/proto/minecraft.pb.go b/minecraft/proto/minecraft.pb.go new file mode 100644 index 0000000..f0598bb --- /dev/null +++ b/minecraft/proto/minecraft.pb.go @@ -0,0 +1,366 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.27.1 +// protoc v3.15.6 +// source: proto/minecraft.proto + +package minecraft + +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 PlayerSample struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // unique id of player + Uuid string `protobuf:"bytes,1,opt,name=uuid,proto3" json:"uuid,omitempty"` + // name of the player + Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` +} + +func (x *PlayerSample) Reset() { + *x = PlayerSample{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_minecraft_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *PlayerSample) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PlayerSample) ProtoMessage() {} + +func (x *PlayerSample) ProtoReflect() protoreflect.Message { + mi := &file_proto_minecraft_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 PlayerSample.ProtoReflect.Descriptor instead. +func (*PlayerSample) Descriptor() ([]byte, []int) { + return file_proto_minecraft_proto_rawDescGZIP(), []int{0} +} + +func (x *PlayerSample) GetUuid() string { + if x != nil { + return x.Uuid + } + return "" +} + +func (x *PlayerSample) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +// Ping a minecraft server +type PingRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // address of the server + Address string `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"` +} + +func (x *PingRequest) Reset() { + *x = PingRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_minecraft_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *PingRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PingRequest) ProtoMessage() {} + +func (x *PingRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_minecraft_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 PingRequest.ProtoReflect.Descriptor instead. +func (*PingRequest) Descriptor() ([]byte, []int) { + return file_proto_minecraft_proto_rawDescGZIP(), []int{1} +} + +func (x *PingRequest) GetAddress() string { + if x != nil { + return x.Address + } + return "" +} + +type PingResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Latency (ms) between us and the server (EU) + Latency uint32 `protobuf:"varint,1,opt,name=latency,proto3" json:"latency,omitempty"` + // Number of players online + Players int32 `protobuf:"varint,2,opt,name=players,proto3" json:"players,omitempty"` + // Max players ever + MaxPlayers int32 `protobuf:"varint,3,opt,name=max_players,json=maxPlayers,proto3" json:"max_players,omitempty"` + // Protocol number of the server + Protocol int32 `protobuf:"varint,4,opt,name=protocol,proto3" json:"protocol,omitempty"` + // Favicon in base64 + Favicon string `protobuf:"bytes,5,opt,name=favicon,proto3" json:"favicon,omitempty"` + // Message of the day + Motd string `protobuf:"bytes,6,opt,name=motd,proto3" json:"motd,omitempty"` + // Version of the server + Version string `protobuf:"bytes,7,opt,name=version,proto3" json:"version,omitempty"` + // List of connected players + Sample []*PlayerSample `protobuf:"bytes,8,rep,name=sample,proto3" json:"sample,omitempty"` +} + +func (x *PingResponse) Reset() { + *x = PingResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_minecraft_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *PingResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PingResponse) ProtoMessage() {} + +func (x *PingResponse) ProtoReflect() protoreflect.Message { + mi := &file_proto_minecraft_proto_msgTypes[2] + 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 PingResponse.ProtoReflect.Descriptor instead. +func (*PingResponse) Descriptor() ([]byte, []int) { + return file_proto_minecraft_proto_rawDescGZIP(), []int{2} +} + +func (x *PingResponse) GetLatency() uint32 { + if x != nil { + return x.Latency + } + return 0 +} + +func (x *PingResponse) GetPlayers() int32 { + if x != nil { + return x.Players + } + return 0 +} + +func (x *PingResponse) GetMaxPlayers() int32 { + if x != nil { + return x.MaxPlayers + } + return 0 +} + +func (x *PingResponse) GetProtocol() int32 { + if x != nil { + return x.Protocol + } + return 0 +} + +func (x *PingResponse) GetFavicon() string { + if x != nil { + return x.Favicon + } + return "" +} + +func (x *PingResponse) GetMotd() string { + if x != nil { + return x.Motd + } + return "" +} + +func (x *PingResponse) GetVersion() string { + if x != nil { + return x.Version + } + return "" +} + +func (x *PingResponse) GetSample() []*PlayerSample { + if x != nil { + return x.Sample + } + return nil +} + +var File_proto_minecraft_proto protoreflect.FileDescriptor + +var file_proto_minecraft_proto_rawDesc = []byte{ + 0x0a, 0x15, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x6d, 0x69, 0x6e, 0x65, 0x63, 0x72, 0x61, 0x66, + 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x09, 0x6d, 0x69, 0x6e, 0x65, 0x63, 0x72, 0x61, + 0x66, 0x74, 0x22, 0x36, 0x0a, 0x0c, 0x50, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x53, 0x61, 0x6d, 0x70, + 0x6c, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x75, 0x75, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x04, 0x75, 0x75, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x27, 0x0a, 0x0b, 0x50, 0x69, + 0x6e, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, + 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, + 0x65, 0x73, 0x73, 0x22, 0xf8, 0x01, 0x0a, 0x0c, 0x50, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x6c, 0x61, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x6c, 0x61, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x12, 0x18, + 0x0a, 0x07, 0x70, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, + 0x07, 0x70, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x73, 0x12, 0x1f, 0x0a, 0x0b, 0x6d, 0x61, 0x78, 0x5f, + 0x70, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0a, 0x6d, + 0x61, 0x78, 0x50, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x12, 0x18, 0x0a, 0x07, 0x66, 0x61, 0x76, 0x69, 0x63, 0x6f, 0x6e, + 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x66, 0x61, 0x76, 0x69, 0x63, 0x6f, 0x6e, 0x12, + 0x12, 0x0a, 0x04, 0x6d, 0x6f, 0x74, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6d, + 0x6f, 0x74, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x07, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x2f, 0x0a, + 0x06, 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x18, 0x08, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x17, 0x2e, + 0x6d, 0x69, 0x6e, 0x65, 0x63, 0x72, 0x61, 0x66, 0x74, 0x2e, 0x50, 0x6c, 0x61, 0x79, 0x65, 0x72, + 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x52, 0x06, 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x32, 0x46, + 0x0a, 0x09, 0x4d, 0x69, 0x6e, 0x65, 0x63, 0x72, 0x61, 0x66, 0x74, 0x12, 0x39, 0x0a, 0x04, 0x50, + 0x69, 0x6e, 0x67, 0x12, 0x16, 0x2e, 0x6d, 0x69, 0x6e, 0x65, 0x63, 0x72, 0x61, 0x66, 0x74, 0x2e, + 0x50, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x6d, 0x69, + 0x6e, 0x65, 0x63, 0x72, 0x61, 0x66, 0x74, 0x2e, 0x50, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x13, 0x5a, 0x11, 0x2e, 0x2f, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x3b, 0x6d, 0x69, 0x6e, 0x65, 0x63, 0x72, 0x61, 0x66, 0x74, 0x62, 0x06, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x33, +} + +var ( + file_proto_minecraft_proto_rawDescOnce sync.Once + file_proto_minecraft_proto_rawDescData = file_proto_minecraft_proto_rawDesc +) + +func file_proto_minecraft_proto_rawDescGZIP() []byte { + file_proto_minecraft_proto_rawDescOnce.Do(func() { + file_proto_minecraft_proto_rawDescData = protoimpl.X.CompressGZIP(file_proto_minecraft_proto_rawDescData) + }) + return file_proto_minecraft_proto_rawDescData +} + +var file_proto_minecraft_proto_msgTypes = make([]protoimpl.MessageInfo, 3) +var file_proto_minecraft_proto_goTypes = []interface{}{ + (*PlayerSample)(nil), // 0: minecraft.PlayerSample + (*PingRequest)(nil), // 1: minecraft.PingRequest + (*PingResponse)(nil), // 2: minecraft.PingResponse +} +var file_proto_minecraft_proto_depIdxs = []int32{ + 0, // 0: minecraft.PingResponse.sample:type_name -> minecraft.PlayerSample + 1, // 1: minecraft.Minecraft.Ping:input_type -> minecraft.PingRequest + 2, // 2: minecraft.Minecraft.Ping:output_type -> minecraft.PingResponse + 2, // [2:3] is the sub-list for method output_type + 1, // [1:2] is the sub-list for method input_type + 1, // [1:1] is the sub-list for extension type_name + 1, // [1:1] is the sub-list for extension extendee + 0, // [0:1] is the sub-list for field type_name +} + +func init() { file_proto_minecraft_proto_init() } +func file_proto_minecraft_proto_init() { + if File_proto_minecraft_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_proto_minecraft_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*PlayerSample); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_minecraft_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*PingRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_minecraft_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*PingResponse); 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_minecraft_proto_rawDesc, + NumEnums: 0, + NumMessages: 3, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_proto_minecraft_proto_goTypes, + DependencyIndexes: file_proto_minecraft_proto_depIdxs, + MessageInfos: file_proto_minecraft_proto_msgTypes, + }.Build() + File_proto_minecraft_proto = out.File + file_proto_minecraft_proto_rawDesc = nil + file_proto_minecraft_proto_goTypes = nil + file_proto_minecraft_proto_depIdxs = nil +} diff --git a/minecraft/proto/minecraft.pb.micro.go b/minecraft/proto/minecraft.pb.micro.go new file mode 100644 index 0000000..18740da --- /dev/null +++ b/minecraft/proto/minecraft.pb.micro.go @@ -0,0 +1,93 @@ +// Code generated by protoc-gen-micro. DO NOT EDIT. +// source: proto/minecraft.proto + +package minecraft + +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 Minecraft service + +func NewMinecraftEndpoints() []*api.Endpoint { + return []*api.Endpoint{} +} + +// Client API for Minecraft service + +type MinecraftService interface { + Ping(ctx context.Context, in *PingRequest, opts ...client.CallOption) (*PingResponse, error) +} + +type minecraftService struct { + c client.Client + name string +} + +func NewMinecraftService(name string, c client.Client) MinecraftService { + return &minecraftService{ + c: c, + name: name, + } +} + +func (c *minecraftService) Ping(ctx context.Context, in *PingRequest, opts ...client.CallOption) (*PingResponse, error) { + req := c.c.NewRequest(c.name, "Minecraft.Ping", in) + out := new(PingResponse) + err := c.c.Call(ctx, req, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// Server API for Minecraft service + +type MinecraftHandler interface { + Ping(context.Context, *PingRequest, *PingResponse) error +} + +func RegisterMinecraftHandler(s server.Server, hdlr MinecraftHandler, opts ...server.HandlerOption) error { + type minecraft interface { + Ping(ctx context.Context, in *PingRequest, out *PingResponse) error + } + type Minecraft struct { + minecraft + } + h := &minecraftHandler{hdlr} + return s.Handle(s.NewHandler(&Minecraft{h}, opts...)) +} + +type minecraftHandler struct { + MinecraftHandler +} + +func (h *minecraftHandler) Ping(ctx context.Context, in *PingRequest, out *PingResponse) error { + return h.MinecraftHandler.Ping(ctx, in, out) +} diff --git a/minecraft/proto/minecraft.proto b/minecraft/proto/minecraft.proto new file mode 100644 index 0000000..59d2b94 --- /dev/null +++ b/minecraft/proto/minecraft.proto @@ -0,0 +1,41 @@ +syntax = "proto3"; + +package minecraft; + +option go_package = "./proto;minecraft"; + +service Minecraft { + rpc Ping(PingRequest) returns (PingResponse) {} +} + +message PlayerSample { + // unique id of player + string uuid = 1; + // name of the player + string name = 2; +} + +// Ping a minecraft server +message PingRequest { + // address of the server + string address = 1; +} + +message PingResponse { + // Latency (ms) between us and the server (EU) + uint32 latency = 1; + // Number of players online + int32 players = 2; + // Max players ever + int32 max_players = 3; + // Protocol number of the server + int32 protocol = 4; + // Favicon in base64 + string favicon = 5; + // Message of the day + string motd = 6; + // Version of the server + string version = 7; + // List of connected players + repeated PlayerSample sample = 8; +} diff --git a/minecraft/publicapi.json b/minecraft/publicapi.json new file mode 100644 index 0000000..029ef6b --- /dev/null +++ b/minecraft/publicapi.json @@ -0,0 +1,6 @@ +{ + "name": "minecraft", + "icon": "⛏💎", + "category": "utility", + "display_name": "Minecraft" +} diff --git a/ping/.gitignore b/ping/.gitignore new file mode 100644 index 0000000..382f213 --- /dev/null +++ b/ping/.gitignore @@ -0,0 +1,2 @@ + +ping diff --git a/ping/Dockerfile b/ping/Dockerfile new file mode 100644 index 0000000..a7c0ead --- /dev/null +++ b/ping/Dockerfile @@ -0,0 +1,3 @@ +FROM alpine +ADD ping /ping +ENTRYPOINT [ "/ping" ] diff --git a/ping/Makefile b/ping/Makefile new file mode 100644 index 0000000..7ad26c3 --- /dev/null +++ b/ping/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/ping.proto + +.PHONY: proto +proto: + protoc --proto_path=. --micro_out=. --go_out=:. proto/ping.proto + +.PHONY: build +build: + go build -o ping *.go + +.PHONY: test +test: + go test -v ./... -cover + +.PHONY: docker +docker: + docker build . -t ping:latest diff --git a/ping/README.md b/ping/README.md new file mode 100644 index 0000000..ab56f99 --- /dev/null +++ b/ping/README.md @@ -0,0 +1,7 @@ +Ping any URL + +# Ping Service + +Use ping to monitor services, check uptime and availability of any HTTP URL or TCP endpoint. + + diff --git a/ping/examples.json b/ping/examples.json new file mode 100644 index 0000000..edac710 --- /dev/null +++ b/ping/examples.json @@ -0,0 +1,36 @@ +{ + "ip": [{ + "title": "Ping an ip", + "description": "Ping an ip address to get the latency", + "run_check": false, + "request": { + "google.com" + }, + "response": { + "status": "OK" + } + }], + "tcp": [{ + "title": "Dial a tcp address", + "description": "Dial a tcp address", + "run_check": false, + "request": { + "google.com:80" + }, + "response": { + "status": "OK" + } + }], + "http": [{ + "title": "Check a url", + "description": "Check a url endpoint", + "run_check": false, + "request": { + "google.com" + }, + "response": { + "status": "200 OK", + "code": 200 + } + }] +} diff --git a/ping/generate.go b/ping/generate.go new file mode 100644 index 0000000..7d9db91 --- /dev/null +++ b/ping/generate.go @@ -0,0 +1,3 @@ +package main + +//go:generate make proto diff --git a/ping/handler/ping.go b/ping/handler/ping.go new file mode 100644 index 0000000..ffd86ec --- /dev/null +++ b/ping/handler/ping.go @@ -0,0 +1,120 @@ +package handler + +import ( + "bufio" + "context" + "fmt" + "io/ioutil" + "net" + "net/http" + "net/url" + + "github.com/go-ping/ping" + "github.com/micro/micro/v3/service/errors" + "github.com/micro/micro/v3/service/logger" + pb "github.com/micro/services/ping/proto" +) + +type Ping struct{} + +func (p *Ping) Ip(ctx context.Context, req *pb.IpRequest, rsp *pb.IpResponse) error { + if len(req.Address) == 0 { + return errors.BadRequest("ping.ip", "missing address") + } + + pinger, err := ping.NewPinger(req.Address) + if err != nil { + rsp.Status = err.Error() + return nil + } + + pinger.Count = 4 + pinger.SetPrivileged(true) + err = pinger.Run() // Blocks until finished. + if err != nil { + rsp.Status = err.Error() + return nil + } + + pinger.Stop() + stats := pinger.Statistics() + + rsp.Status = "OK" + rsp.Latency = fmt.Sprintf("%v", stats.AvgRtt) + + return nil +} + +func (p *Ping) Url(ctx context.Context, req *pb.UrlRequest, rsp *pb.UrlResponse) error { + if len(req.Address) == 0 { + return errors.BadRequest("ping.url", "missing address") + } + + u, err := url.Parse(req.Address) + if err != nil { + return errors.BadRequest("ping.url", "failed to parse url: %v", err) + } + + if u.Scheme == "" { + u.Scheme = "https" + } + + if req.Method == "" { + req.Method = "GET" + } + + logger.Infof("Calling %s %s", req.Method, u.String()) + + hreq, err := http.NewRequest(req.Method, u.String(), nil) + if err != nil { + return errors.InternalServerError("ping.url", "failed to make request: %v", err) + } + + resp, err := http.DefaultClient.Do(hreq) + if err != nil { + return errors.InternalServerError("ping.url", "failed to call %v: %v", req.Address, err) + } + defer resp.Body.Close() + + // read body + ioutil.ReadAll(resp.Body) + // set status + rsp.Status = resp.Status + rsp.Code = int32(resp.StatusCode) + + return nil +} + +func (p *Ping) Tcp(ctx context.Context, req *pb.TcpRequest, rsp *pb.TcpResponse) error { + if len(req.Address) == 0 { + return errors.BadRequest("ping.tcp", "missing address") + } + + c, err := net.Dial("tcp", req.Address) + if err != nil { + rsp.Status = err.Error() + return nil + } + defer c.Close() + + // if no data being sent then just return + if len(req.Data) == 0 { + rsp.Status = "OK" + return nil + } + + // write data to the connection + fmt.Fprint(c, req.Data) + + // wait for a response + data, err := bufio.NewReader(c).ReadString('\n') + if err != nil { + rsp.Status = err.Error() + return nil + } + + rsp.Status = "OK" + rsp.Data = data + + return nil +} diff --git a/ping/main.go b/ping/main.go new file mode 100644 index 0000000..56432dd --- /dev/null +++ b/ping/main.go @@ -0,0 +1,24 @@ +package main + +import ( + "github.com/micro/micro/v3/service" + "github.com/micro/micro/v3/service/logger" + "github.com/micro/services/ping/handler" + pb "github.com/micro/services/ping/proto" +) + +func main() { + // Create service + srv := service.New( + service.Name("ping"), + service.Version("latest"), + ) + + // Register handler + pb.RegisterPingHandler(srv.Server(), new(handler.Ping)) + + // Run service + if err := srv.Run(); err != nil { + logger.Fatal(err) + } +} diff --git a/ping/micro.mu b/ping/micro.mu new file mode 100644 index 0000000..7de5382 --- /dev/null +++ b/ping/micro.mu @@ -0,0 +1 @@ +service ping diff --git a/ping/proto/ping.pb.go b/ping/proto/ping.pb.go new file mode 100644 index 0000000..232e7c7 --- /dev/null +++ b/ping/proto/ping.pb.go @@ -0,0 +1,530 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.27.1 +// protoc v3.15.6 +// source: proto/ping.proto + +package ping + +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) +) + +// Ping an IP address +type IpRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // address to ping + Address string `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"` +} + +func (x *IpRequest) Reset() { + *x = IpRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_ping_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *IpRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*IpRequest) ProtoMessage() {} + +func (x *IpRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_ping_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 IpRequest.ProtoReflect.Descriptor instead. +func (*IpRequest) Descriptor() ([]byte, []int) { + return file_proto_ping_proto_rawDescGZIP(), []int{0} +} + +func (x *IpRequest) GetAddress() string { + if x != nil { + return x.Address + } + return "" +} + +type IpResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // response status + Status string `protobuf:"bytes,1,opt,name=status,proto3" json:"status,omitempty"` + // average latency e.g 10ms + Latency string `protobuf:"bytes,2,opt,name=latency,proto3" json:"latency,omitempty"` +} + +func (x *IpResponse) Reset() { + *x = IpResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_ping_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *IpResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*IpResponse) ProtoMessage() {} + +func (x *IpResponse) ProtoReflect() protoreflect.Message { + mi := &file_proto_ping_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 IpResponse.ProtoReflect.Descriptor instead. +func (*IpResponse) Descriptor() ([]byte, []int) { + return file_proto_ping_proto_rawDescGZIP(), []int{1} +} + +func (x *IpResponse) GetStatus() string { + if x != nil { + return x.Status + } + return "" +} + +func (x *IpResponse) GetLatency() string { + if x != nil { + return x.Latency + } + return "" +} + +// Ping a HTTP URL +type UrlRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // address to use + Address string `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"` + // method of the call + Method string `protobuf:"bytes,2,opt,name=method,proto3" json:"method,omitempty"` +} + +func (x *UrlRequest) Reset() { + *x = UrlRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_ping_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *UrlRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UrlRequest) ProtoMessage() {} + +func (x *UrlRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_ping_proto_msgTypes[2] + 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 UrlRequest.ProtoReflect.Descriptor instead. +func (*UrlRequest) Descriptor() ([]byte, []int) { + return file_proto_ping_proto_rawDescGZIP(), []int{2} +} + +func (x *UrlRequest) GetAddress() string { + if x != nil { + return x.Address + } + return "" +} + +func (x *UrlRequest) GetMethod() string { + if x != nil { + return x.Method + } + return "" +} + +type UrlResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // the response status + Status string `protobuf:"bytes,1,opt,name=status,proto3" json:"status,omitempty"` + // the response code + Code int32 `protobuf:"varint,2,opt,name=code,proto3" json:"code,omitempty"` +} + +func (x *UrlResponse) Reset() { + *x = UrlResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_ping_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *UrlResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UrlResponse) ProtoMessage() {} + +func (x *UrlResponse) ProtoReflect() protoreflect.Message { + mi := &file_proto_ping_proto_msgTypes[3] + 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 UrlResponse.ProtoReflect.Descriptor instead. +func (*UrlResponse) Descriptor() ([]byte, []int) { + return file_proto_ping_proto_rawDescGZIP(), []int{3} +} + +func (x *UrlResponse) GetStatus() string { + if x != nil { + return x.Status + } + return "" +} + +func (x *UrlResponse) GetCode() int32 { + if x != nil { + return x.Code + } + return 0 +} + +// Ping a TCP port is open +type TcpRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // address to dial + Address string `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"` + // optional data to send + Data string `protobuf:"bytes,2,opt,name=data,proto3" json:"data,omitempty"` +} + +func (x *TcpRequest) Reset() { + *x = TcpRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_ping_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *TcpRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TcpRequest) ProtoMessage() {} + +func (x *TcpRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_ping_proto_msgTypes[4] + 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 TcpRequest.ProtoReflect.Descriptor instead. +func (*TcpRequest) Descriptor() ([]byte, []int) { + return file_proto_ping_proto_rawDescGZIP(), []int{4} +} + +func (x *TcpRequest) GetAddress() string { + if x != nil { + return x.Address + } + return "" +} + +func (x *TcpRequest) GetData() string { + if x != nil { + return x.Data + } + return "" +} + +type TcpResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // response status + Status string `protobuf:"bytes,1,opt,name=status,proto3" json:"status,omitempty"` + // response data if any + Data string `protobuf:"bytes,2,opt,name=data,proto3" json:"data,omitempty"` +} + +func (x *TcpResponse) Reset() { + *x = TcpResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_ping_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *TcpResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TcpResponse) ProtoMessage() {} + +func (x *TcpResponse) ProtoReflect() protoreflect.Message { + mi := &file_proto_ping_proto_msgTypes[5] + 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 TcpResponse.ProtoReflect.Descriptor instead. +func (*TcpResponse) Descriptor() ([]byte, []int) { + return file_proto_ping_proto_rawDescGZIP(), []int{5} +} + +func (x *TcpResponse) GetStatus() string { + if x != nil { + return x.Status + } + return "" +} + +func (x *TcpResponse) GetData() string { + if x != nil { + return x.Data + } + return "" +} + +var File_proto_ping_proto protoreflect.FileDescriptor + +var file_proto_ping_proto_rawDesc = []byte{ + 0x0a, 0x10, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x70, 0x69, 0x6e, 0x67, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x12, 0x04, 0x70, 0x69, 0x6e, 0x67, 0x22, 0x25, 0x0a, 0x09, 0x49, 0x70, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x22, + 0x3e, 0x0a, 0x0a, 0x49, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x16, 0x0a, + 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, + 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x6c, 0x61, 0x74, 0x65, 0x6e, 0x63, 0x79, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6c, 0x61, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x22, + 0x3e, 0x0a, 0x0a, 0x55, 0x72, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x18, 0x0a, + 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, + 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f, + 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x22, + 0x39, 0x0a, 0x0b, 0x55, 0x72, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x16, + 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, + 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x22, 0x3a, 0x0a, 0x0a, 0x54, 0x63, + 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, + 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, + 0x73, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x22, 0x39, 0x0a, 0x0b, 0x54, 0x63, 0x70, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x12, 0x0a, + 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x64, 0x61, 0x74, + 0x61, 0x32, 0x8d, 0x01, 0x0a, 0x04, 0x50, 0x69, 0x6e, 0x67, 0x12, 0x29, 0x0a, 0x02, 0x49, 0x70, + 0x12, 0x0f, 0x2e, 0x70, 0x69, 0x6e, 0x67, 0x2e, 0x49, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x10, 0x2e, 0x70, 0x69, 0x6e, 0x67, 0x2e, 0x49, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x2c, 0x0a, 0x03, 0x55, 0x72, 0x6c, 0x12, 0x10, 0x2e, 0x70, + 0x69, 0x6e, 0x67, 0x2e, 0x55, 0x72, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x11, + 0x2e, 0x70, 0x69, 0x6e, 0x67, 0x2e, 0x55, 0x72, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x22, 0x00, 0x12, 0x2c, 0x0a, 0x03, 0x54, 0x63, 0x70, 0x12, 0x10, 0x2e, 0x70, 0x69, 0x6e, + 0x67, 0x2e, 0x54, 0x63, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x11, 0x2e, 0x70, + 0x69, 0x6e, 0x67, 0x2e, 0x54, 0x63, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, + 0x00, 0x42, 0x0e, 0x5a, 0x0c, 0x2e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x3b, 0x70, 0x69, 0x6e, + 0x67, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_proto_ping_proto_rawDescOnce sync.Once + file_proto_ping_proto_rawDescData = file_proto_ping_proto_rawDesc +) + +func file_proto_ping_proto_rawDescGZIP() []byte { + file_proto_ping_proto_rawDescOnce.Do(func() { + file_proto_ping_proto_rawDescData = protoimpl.X.CompressGZIP(file_proto_ping_proto_rawDescData) + }) + return file_proto_ping_proto_rawDescData +} + +var file_proto_ping_proto_msgTypes = make([]protoimpl.MessageInfo, 6) +var file_proto_ping_proto_goTypes = []interface{}{ + (*IpRequest)(nil), // 0: ping.IpRequest + (*IpResponse)(nil), // 1: ping.IpResponse + (*UrlRequest)(nil), // 2: ping.UrlRequest + (*UrlResponse)(nil), // 3: ping.UrlResponse + (*TcpRequest)(nil), // 4: ping.TcpRequest + (*TcpResponse)(nil), // 5: ping.TcpResponse +} +var file_proto_ping_proto_depIdxs = []int32{ + 0, // 0: ping.Ping.Ip:input_type -> ping.IpRequest + 2, // 1: ping.Ping.Url:input_type -> ping.UrlRequest + 4, // 2: ping.Ping.Tcp:input_type -> ping.TcpRequest + 1, // 3: ping.Ping.Ip:output_type -> ping.IpResponse + 3, // 4: ping.Ping.Url:output_type -> ping.UrlResponse + 5, // 5: ping.Ping.Tcp:output_type -> ping.TcpResponse + 3, // [3:6] is the sub-list for method output_type + 0, // [0:3] 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_ping_proto_init() } +func file_proto_ping_proto_init() { + if File_proto_ping_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_proto_ping_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*IpRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_ping_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*IpResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_ping_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*UrlRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_ping_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*UrlResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_ping_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*TcpRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_ping_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*TcpResponse); 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_ping_proto_rawDesc, + NumEnums: 0, + NumMessages: 6, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_proto_ping_proto_goTypes, + DependencyIndexes: file_proto_ping_proto_depIdxs, + MessageInfos: file_proto_ping_proto_msgTypes, + }.Build() + File_proto_ping_proto = out.File + file_proto_ping_proto_rawDesc = nil + file_proto_ping_proto_goTypes = nil + file_proto_ping_proto_depIdxs = nil +} diff --git a/ping/proto/ping.pb.micro.go b/ping/proto/ping.pb.micro.go new file mode 100644 index 0000000..f11bdcd --- /dev/null +++ b/ping/proto/ping.pb.micro.go @@ -0,0 +1,127 @@ +// Code generated by protoc-gen-micro. DO NOT EDIT. +// source: proto/ping.proto + +package ping + +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 Ping service + +func NewPingEndpoints() []*api.Endpoint { + return []*api.Endpoint{} +} + +// Client API for Ping service + +type PingService interface { + Ip(ctx context.Context, in *IpRequest, opts ...client.CallOption) (*IpResponse, error) + Url(ctx context.Context, in *UrlRequest, opts ...client.CallOption) (*UrlResponse, error) + Tcp(ctx context.Context, in *TcpRequest, opts ...client.CallOption) (*TcpResponse, error) +} + +type pingService struct { + c client.Client + name string +} + +func NewPingService(name string, c client.Client) PingService { + return &pingService{ + c: c, + name: name, + } +} + +func (c *pingService) Ip(ctx context.Context, in *IpRequest, opts ...client.CallOption) (*IpResponse, error) { + req := c.c.NewRequest(c.name, "Ping.Ip", in) + out := new(IpResponse) + err := c.c.Call(ctx, req, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *pingService) Url(ctx context.Context, in *UrlRequest, opts ...client.CallOption) (*UrlResponse, error) { + req := c.c.NewRequest(c.name, "Ping.Url", in) + out := new(UrlResponse) + err := c.c.Call(ctx, req, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *pingService) Tcp(ctx context.Context, in *TcpRequest, opts ...client.CallOption) (*TcpResponse, error) { + req := c.c.NewRequest(c.name, "Ping.Tcp", in) + out := new(TcpResponse) + err := c.c.Call(ctx, req, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// Server API for Ping service + +type PingHandler interface { + Ip(context.Context, *IpRequest, *IpResponse) error + Url(context.Context, *UrlRequest, *UrlResponse) error + Tcp(context.Context, *TcpRequest, *TcpResponse) error +} + +func RegisterPingHandler(s server.Server, hdlr PingHandler, opts ...server.HandlerOption) error { + type ping interface { + Ip(ctx context.Context, in *IpRequest, out *IpResponse) error + Url(ctx context.Context, in *UrlRequest, out *UrlResponse) error + Tcp(ctx context.Context, in *TcpRequest, out *TcpResponse) error + } + type Ping struct { + ping + } + h := &pingHandler{hdlr} + return s.Handle(s.NewHandler(&Ping{h}, opts...)) +} + +type pingHandler struct { + PingHandler +} + +func (h *pingHandler) Ip(ctx context.Context, in *IpRequest, out *IpResponse) error { + return h.PingHandler.Ip(ctx, in, out) +} + +func (h *pingHandler) Url(ctx context.Context, in *UrlRequest, out *UrlResponse) error { + return h.PingHandler.Url(ctx, in, out) +} + +func (h *pingHandler) Tcp(ctx context.Context, in *TcpRequest, out *TcpResponse) error { + return h.PingHandler.Tcp(ctx, in, out) +} diff --git a/ping/proto/ping.proto b/ping/proto/ping.proto new file mode 100644 index 0000000..9edaab9 --- /dev/null +++ b/ping/proto/ping.proto @@ -0,0 +1,54 @@ +syntax = "proto3"; + +package ping; + +option go_package = "./proto;ping"; + +service Ping { + rpc Ip(IpRequest) returns (IpResponse) {} + rpc Url(UrlRequest) returns (UrlResponse) {} + rpc Tcp(TcpRequest) returns (TcpResponse) {} +} + +// Ping an IP address +message IpRequest { + // address to ping + string address = 1; +} + +message IpResponse { + // response status + string status = 1; + // average latency e.g 10ms + string latency = 2; +} + +// Ping a HTTP URL +message UrlRequest { + // address to use + string address = 1; + // method of the call + string method = 2; +} + +message UrlResponse { + // the response status + string status = 1; + // the response code + int32 code = 2; +} + +// Ping a TCP port is open +message TcpRequest { + // address to dial + string address = 1; + // optional data to send + string data = 2; +} + +message TcpResponse { + // response status + string status = 1; + // response data if any + string data = 2; +} diff --git a/ping/publicapi.json b/ping/publicapi.json new file mode 100644 index 0000000..dbf295c --- /dev/null +++ b/ping/publicapi.json @@ -0,0 +1,6 @@ +{ + "name": "ping", + "icon": "🏓", + "category": "utility", + "display_name": "Ping" +}