diff --git a/.github/workflows/node.js.yml b/.github/workflows/node.js.yml index fb525670..3030b6ab 100644 --- a/.github/workflows/node.js.yml +++ b/.github/workflows/node.js.yml @@ -38,7 +38,7 @@ jobs: - name: Install Dependencies run: | npm install - npm run install:lint:protoc + npm run install:protoc - name: Generate gRPC Stubs run: npm run generate diff --git a/.github/workflows/npm.yml b/.github/workflows/npm.yml index 7d633923..e1e2add3 100644 --- a/.github/workflows/npm.yml +++ b/.github/workflows/npm.yml @@ -24,7 +24,7 @@ jobs: - name: Install Dependencies run: | npm install - npm run install:lint:protoc + npm run install:protoc - name: Generate gRPC Stubs run: npm run generate diff --git a/.gitignore b/.gitignore index 9e66626c..8a76d452 100644 --- a/.gitignore +++ b/.gitignore @@ -94,3 +94,4 @@ csharp/Wechaty.Grpc/bin/ csharp/Wechaty.Grpc/nupkg/package/ csharp/Wechaty.Grpc/Properties +third-party/ diff --git a/README.md b/README.md index 97e952bc..07d03ae4 100644 --- a/README.md +++ b/README.md @@ -149,6 +149,12 @@ protoc \ > +## OpenAPI + +![gRPC Gateway](docs/images/grpc-gateway-architecture.svg) + +> Image credit: [grpc-gateway](https://grpc-ecosystem.github.io/grpc-gateway/) + ## RESOURCES ### Documentation diff --git a/docs/images/grpc-gateway-architecture.svg b/docs/images/grpc-gateway-architecture.svg new file mode 100644 index 00000000..3c9347e3 --- /dev/null +++ b/docs/images/grpc-gateway-architecture.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/examples/client.ts b/examples/client.ts index b45357cf..254214bc 100644 --- a/examples/client.ts +++ b/examples/client.ts @@ -13,7 +13,7 @@ import { EventResponse, ContactAliasRequest, // EventType, -} from '../src/' +} from '../src/mod' import { StringValue } from 'google-protobuf/google/protobuf/wrappers_pb' /** diff --git a/examples/server.ts b/examples/server.ts index 40fbc3de..fedd34b4 100644 --- a/examples/server.ts +++ b/examples/server.ts @@ -9,7 +9,7 @@ import { EventType, DingResponse, EventRequest, -} from '../src/index' +} from '../src/mod' // import { StringValue } from 'google-protobuf/google/protobuf/wrappers_pb' diff --git a/openapi/.gitignore b/openapi/.gitignore index 251622b0..70df8366 100644 --- a/openapi/.gitignore +++ b/openapi/.gitignore @@ -1,3 +1,2 @@ -gen/ gateway go.sum diff --git a/openapi/Makefile b/openapi/Makefile index f4f3786c..1eb74df0 100644 --- a/openapi/Makefile +++ b/openapi/Makefile @@ -1,18 +1,28 @@ -PROTOC=protoc \ +GENERATED_DIR:="${CURDIR}/../generated/" +THIRD_PARTY_DIR:="${CURDIR}/../third-party/" + +PROTOC:=protoc \ -I ../proto/ \ -I ../proto/wechaty/ \ + -I ${THIRD_PARTY_DIR} \ ../proto/wechaty/puppet.proto \ .PHONY: all -all: clean generate reverse_proxy_server +all: generate + +.PHONY: generate +generate: + ${PROTOC} \ + --openapiv2_out ${GENERATED_DIR} \ + --openapiv2_opt logtostderr=true \ + --openapiv2_opt generate_unbound_methods=true \ .PHONY: clean clean: - rm -fr gen/* + rm -fr ${GENERATED_DIR}/* .PHONY: install install: - [ -d gen ] || mkdir gen go install \ github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-grpc-gateway \ github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2 \ @@ -20,27 +30,16 @@ install: google.golang.org/grpc/cmd/protoc-gen-go-grpc go mod tidy -.PHONY: generate -generate: gen_protobuf gen_gateway gen_openapi - .PHONY: gen_protobuf gen_protobuf: ${PROTOC} \ - --go_out ./gen/ --go_opt paths=source_relative \ - --go-grpc_out ./gen/ --go-grpc_opt paths=source_relative \ - ../proto/wechaty/puppet.proto + --go_out ${GENERATED_DIR} --go_opt paths=source_relative \ + --go-grpc_out ${GENERATED_DIR} --go-grpc_opt paths=source_relative \ .PHONY: gen_gateway gen_gateway: ${PROTOC} \ - --grpc-gateway_out ./gen/ \ + --grpc-gateway_out ${GENERATED_DIR} \ --grpc-gateway_opt logtostderr=true \ --grpc-gateway_opt paths=source_relative \ --grpc-gateway_opt generate_unbound_methods=true \ - -.PHONY: gen_openapi -gen_openapi: - ${PROTOC} \ - --openapiv2_out ./gen/ \ - --openapiv2_opt logtostderr=true \ - diff --git a/openapi/README.md b/openapi/README.md index 2c6aa340..c57c985b 100644 --- a/openapi/README.md +++ b/openapi/README.md @@ -1,2 +1,4 @@ +# GO gRPC Gateway -gRPC Gateway: +- gRPC Gateway: +- Template: diff --git a/openapi/go.mod b/openapi/go.mod index 7a61cfda..3155e2f4 100644 --- a/openapi/go.mod +++ b/openapi/go.mod @@ -4,9 +4,10 @@ go 1.16 require ( github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b - github.com/golang/protobuf v1.4.3 + github.com/golang/protobuf v1.4.3 // indirect + github.com/google/protobuf v3.15.0+incompatible // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.1.0 - github.com/wechaty/go-grpc v0.18.12 + github.com/wechaty/go-grpc v0.18.12 // indirect google.golang.org/grpc v1.34.0 google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0 google.golang.org/protobuf v1.25.0 diff --git a/package.json b/package.json index 819db40f..fd2aa25d 100644 --- a/package.json +++ b/package.json @@ -1,26 +1,26 @@ { - "name": "@chatie/grpc", - "version": "0.18.12", - "description": "gRPC for Chatie", - "main": "dist/src/index.js", - "typings": "dist/src/index.d.js", + "name": "wechaty-grpc", + "version": "0.19.1", + "description": "gRPC for Wechaty", + "main": "dist/src/mod.js", + "typings": "dist/src/mod.d.js", "directories": { "example": "examples", "test": "tests" }, "scripts": { "clean": "shx rm -fr dist/* generated/*", - "dist": "npm run clean && npm run generate && tsc && shx mv generated/ dist/", + "dist": "npm-run-all clean generate && tsc && shx mv generated/ dist/", "dist:py": "python3 setup.py sdist bdist_wheel", "publish:py": "twine upload dist/*", "example:server": "nodemon --exec ts-node examples/server.ts", "example:client": "ts-node examples/client.ts", "generate": "bash -x scripts/generate-stub.sh", - "lint": "npm run lint:es && npm run lint:ts && npm run lint:proto", + "lint": "npm-run-all lint:es lint:ts lint:proto", "lint:es": "eslint --ignore-pattern fixtures/ \"src/**/*.ts\" \"tests/**/*.ts\" \"examples/**/*.ts\"", "lint:ts": "tsc --noEmit", - "lint:proto": "bash -c 'protoc -I proto/wechaty --lint_out=. $(find proto/ -type f -name *.proto)'", - "install:lint:protoc": "bash -x scripts/install-protoc-gen-lint.sh", + "lint:proto": "bash -c 'protoc -I third-party -I proto/wechaty --lint_out=. $(find proto/ -type f -name *.proto)'", + "install:protoc": "bash -x scripts/install-protoc.sh", "test": "npm run lint && npm run test:unit", "test:pack": "bash -x scripts/npm-pack-testing.sh", "test:unit": "blue-tape -r ts-node/register \"src/**/*.spec.ts\" \"tests/**/*.spec.ts\"", @@ -28,7 +28,7 @@ }, "repository": { "type": "git", - "url": "git+https://github.com/Chatie/grpc.git" + "url": "git+https://github.com/wechaty/grpc.git" }, "keywords": [ "grpc", @@ -41,27 +41,28 @@ "author": "Huan LI ", "license": "Apache-2.0", "bugs": { - "url": "https://github.com/Chatie/grpc/issues" + "url": "https://github.com/wechaty/grpc/issues" }, - "homepage": "https://github.com/Chatie/grpc#readme", + "homepage": "https://github.com/wechaty/grpc#readme", "dependencies": { - "grpc": "^1.24.2", - "google-protobuf": "^3.11.3" + "grpc": "^1.24.5", + "google-protobuf": "^3.15.1" }, "devDependencies": { - "@chatie/eslint-config": "^0.12.1", + "@chatie/eslint-config": "^0.12.3", "@chatie/git-scripts": "^0.6.2", "@chatie/semver": "^0.4.7", - "@chatie/tsconfig": "^0.10.1", - "@types/google-protobuf": "^3.7.2", + "@chatie/tsconfig": "^0.14.1", + "@types/google-protobuf": "^3.7.4", "@types/protobufjs": "^6.0.0", - "grpc-tools": "^1.8.0", - "grpc_tools_node_protoc_ts": "^5.0.0", + "grpc-tools": "^1.10.0", + "grpc_tools_node_protoc_ts": "^5.1.1", "grpcc": "^1.1.3", - "nodemon": "^2.0.0", + "nodemon": "^2.0.7", + "npm-run-all": "^4.1.5", "request": "^2.88.2", - "shx": "^0.3.2", - "ts-protoc-gen": "^0.13.0", + "shx": "^0.3.3", + "ts-protoc-gen": "^0.14.0", "tstest": "^0.4.10" }, "engines": { diff --git a/proto/wechaty/puppet.proto b/proto/wechaty/puppet.proto index 0a7d8142..069e5914 100644 --- a/proto/wechaty/puppet.proto +++ b/proto/wechaty/puppet.proto @@ -1,7 +1,7 @@ /** * * Wechaty Puppet gRPC Protocol Buffers - * https://github.com/chatie/grpc/ + * https://github.com/wechaty/grpc/ * Huan LI * Apr 2018 * License: Apache-2.0 @@ -13,9 +13,6 @@ syntax = "proto3"; package wechaty; -option java_package="io.github.wechaty.grpc"; -option go_package="github.com/wechaty/go-grpc/wechaty"; - import public "puppet/base.proto"; import public "puppet/contact.proto"; import public "puppet/event.proto"; @@ -27,64 +24,219 @@ import public "puppet/room_invitation.proto"; import public "puppet/room_member.proto"; import public "puppet/tag.proto"; +import "google/api/annotations.proto"; +import "protoc-gen-openapiv2/options/annotations.proto"; + +option java_package="io.github.wechaty.grpc"; +option go_package="github.com/wechaty/go-grpc/wechaty"; + +option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_swagger) = { + info: { + title: "Wechaty Puppet Service OpenAPI Specification" + description: "Wechaty is a Conversational RPA SDK for chatbot makers. With only 6 lines of code, you can create a bot on the most popular IMs like WeChat, Whatsapp, WeCom, Gitter, etc.\n\nWechaty Puppet Service is the RESTful API for Wechaty API, which is build on top of the Wechaty Puppet Abstraction and the gRPC proto definition." + license: { + name: "Apache-2.0" + url: "http://www.apache.org/licenses/LICENSE-2.0.html" + } + contact: { + name: "Wechaty" + url: "https://github.com/wechaty/openapi" + email: "wechaty@chatie.io" + } + version: "0.0.0" // MUST be 0.0.0 at here: will be synced with the Git Repo when generating. + }; + schemes: [HTTP,HTTPS,WS,WSS] + consumes: "application/json" + produces: "application/json" + responses: { + key: "default" + value: { + description: "" + schema: { + json_schema: { + ref: ".google.rpc.Status" + }; + }; + }; + }; +}; + service Puppet { /** * * Base * */ - rpc Start (puppet.StartRequest) returns (puppet.StartResponse) {} - rpc Stop (puppet.StopRequest) returns (puppet.StopResponse) {} + rpc Start (puppet.StartRequest) returns (puppet.StartResponse) { + option (google.api.http) = { + put: "/start" + }; + } + rpc Stop (puppet.StopRequest) returns (puppet.StopResponse) { + option (google.api.http) = { + put: "/stop" + }; + } - rpc Logout (puppet.LogoutRequest) returns (puppet.LogoutResponse) {} - rpc Ding (puppet.DingRequest) returns (puppet.DingResponse) {} - rpc Version (puppet.VersionRequest) returns (puppet.VersionResponse) {} + rpc Logout (puppet.LogoutRequest) returns (puppet.LogoutResponse) { + option (google.api.http) = { + put: "/logout" + }; + } + rpc Ding (puppet.DingRequest) returns (puppet.DingResponse) { + option (google.api.http) = { + post: "/ding" + body: "*" + }; + } + rpc Version (puppet.VersionRequest) returns (puppet.VersionResponse) { + option (google.api.http) = { + get: "/version" + }; + } /** * * Event - Server Stream * */ - rpc Event (puppet.EventRequest) returns (stream puppet.EventResponse) {} + rpc Event (puppet.EventRequest) returns (stream puppet.EventResponse) { + option (google.api.http) = { + get: "/events" + }; + } - rpc DirtyPayload (puppet.DirtyPayloadRequest) returns (puppet.DirtyPayloadResponse) {} + rpc DirtyPayload (puppet.DirtyPayloadRequest) returns (puppet.DirtyPayloadResponse) { + option (google.api.http) = { + additional_bindings { + put: "/message/{id}/dirty/{type=PAYLOAD_TYPE_MESSAGE}" + } + additional_bindings { + put: "/contacts/{id}/dirty/{type=PAYLOAD_TYPE_CONTACT}" + } + additional_bindings { + put: "/rooms/{id}/dirty/{type=PAYLOAD_TYPE_ROOM}" + } + additional_bindings { + put: "/rooms/{id}/dirty/members/{type=PAYLOAD_TYPE_ROOM_MEMBER}" + } + additional_bindings { + put: "/friendships/{id}/dirty/{type=PAYLOAD_TYPE_FRIENDSHIP}" + } + }; + } /** * * Contact Self * */ - rpc ContactSelfQRCode (puppet.ContactSelfQRCodeRequest) returns (puppet.ContactSelfQRCodeResponse) {} - rpc ContactSelfName (puppet.ContactSelfNameRequest) returns (puppet.ContactSelfNameResponse) {} - rpc ContactSelfSignature (puppet.ContactSelfSignatureRequest) returns (puppet.ContactSelfSignatureResponse) {} + rpc ContactSelfQRCode (puppet.ContactSelfQRCodeRequest) returns (puppet.ContactSelfQRCodeResponse) { + option (google.api.http) = { + get: "/contacts/self/qrcode" + }; + + } + rpc ContactSelfName (puppet.ContactSelfNameRequest) returns (puppet.ContactSelfNameResponse) { + option (google.api.http) = { + put: "/contacts/self/name/{name}" + }; + } + rpc ContactSelfSignature (puppet.ContactSelfSignatureRequest) returns (puppet.ContactSelfSignatureResponse) { + option (google.api.http) = { + put: "/contacts/self/signature" + body: "*" + }; + } /** * * Contact * */ - rpc ContactPayload (puppet.ContactPayloadRequest) returns (puppet.ContactPayloadResponse) {} + rpc ContactPayload (puppet.ContactPayloadRequest) returns (puppet.ContactPayloadResponse) { + option (google.api.http) = { + get: "/contacts/{id}" + }; + } - rpc ContactAlias (puppet.ContactAliasRequest) returns (puppet.ContactAliasResponse) {} - rpc ContactAvatar (puppet.ContactAvatarRequest) returns (puppet.ContactAvatarResponse) {} - rpc ContactPhone (puppet.ContactPhoneRequest) returns (puppet.ContactPhoneResponse) {} - rpc ContactCorporationRemark (puppet.ContactCorporationRemarkRequest) returns (puppet.ContactCorporationRemarkResponse) {} - rpc ContactDescription (puppet.ContactDescriptionRequest) returns (puppet.ContactDescriptionResponse) {} + rpc ContactAlias (puppet.ContactAliasRequest) returns (puppet.ContactAliasResponse) { + option (google.api.http) = { + put: "/contacts/{id}/alias" + body: "*" + }; + } + rpc ContactAvatar (puppet.ContactAvatarRequest) returns (puppet.ContactAvatarResponse) { + option (google.api.http) = { + get: "/contacts/{id}/avatar" // TODO: Huan(202002) disable the avatar query parameters + // additional_bindings { + // put: "/contacts/{id}/avatar" + // body: "*" + // } + }; + } + rpc ContactPhone (puppet.ContactPhoneRequest) returns (puppet.ContactPhoneResponse) { + option (google.api.http) = { + put: "/contacts/{contact_id}/phone" + body: "*" + }; - rpc ContactList (puppet.ContactListRequest) returns (puppet.ContactListResponse) {} + } + rpc ContactCorporationRemark (puppet.ContactCorporationRemarkRequest) returns (puppet.ContactCorporationRemarkResponse) { + option (google.api.http) = { + put: "/contacts/{contact_id}/corporation-remark" + body: "*" + }; + } + rpc ContactDescription (puppet.ContactDescriptionRequest) returns (puppet.ContactDescriptionResponse) { + option (google.api.http) = { + put: "/contacts/{contact_id}/description" + body: "*" + }; + } + + /** + * Huan(202002): consider changing response to a stream in the future for better performance + */ + rpc ContactList (puppet.ContactListRequest) returns (puppet.ContactListResponse) { + option (google.api.http) = { + get: "/contacts" + }; + } /** * * Friendship * */ - rpc FriendshipPayload (puppet.FriendshipPayloadRequest) returns (puppet.FriendshipPayloadResponse) {} + rpc FriendshipPayload (puppet.FriendshipPayloadRequest) returns (puppet.FriendshipPayloadResponse) { + option (google.api.http) = { + get: "/friendship/{id}" + }; + } - rpc FriendshipSearchPhone (puppet.FriendshipSearchPhoneRequest) returns (puppet.FriendshipSearchPhoneResponse) {} - rpc FriendshipSearchWeixin (puppet.FriendshipSearchWeixinRequest) returns (puppet.FriendshipSearchWeixinResponse) {} + rpc FriendshipSearchPhone (puppet.FriendshipSearchPhoneRequest) returns (puppet.FriendshipSearchPhoneResponse) { + option (google.api.http) = { + get: "/friendship/search/phone/{phone}" + }; + } + rpc FriendshipSearchWeixin (puppet.FriendshipSearchWeixinRequest) returns (puppet.FriendshipSearchWeixinResponse) { + option (google.api.http) = { + get: "/friendship/search/weixin/{weixin}" + }; + } - rpc FriendshipAdd (puppet.FriendshipAddRequest) returns (puppet.FriendshipAddResponse) {} - rpc FriendshipAccept (puppet.FriendshipAcceptRequest) returns (puppet.FriendshipAcceptResponse) {} + rpc FriendshipAdd (puppet.FriendshipAddRequest) returns (puppet.FriendshipAddResponse) { + option (google.api.http) = { + put: "/friendship/add/{contact_id}" + body: "*" + }; + } + rpc FriendshipAccept (puppet.FriendshipAcceptRequest) returns (puppet.FriendshipAcceptResponse) { + option (google.api.http) = { + put: "/friendship/accept/{id}" + }; + } /** * @@ -96,78 +248,226 @@ service Puppet { * @deprecated: using MessageFileStream to transfer files * Huan(202010): will be removed (replaced by MessageFileStream) after Dec 31, 2021 */ - rpc MessageFile (puppet.MessageFileRequest) returns (puppet.MessageFileResponse) {} + rpc MessageFile (puppet.MessageFileRequest) returns (puppet.MessageFileResponse) { + option deprecated = true; + } + /** * @deprecated: using MessageImageStream to transfer images * Huan(202010): will be removed (replaced by MessageImageStream) after Dec 31, 2021 */ - rpc MessageImage (puppet.MessageImageRequest) returns (puppet.MessageImageResponse) {} + rpc MessageImage (puppet.MessageImageRequest) returns (puppet.MessageImageResponse) { + option deprecated = true; + } + /** * @deprecated: using MesageSendFileStream to transfer file message to server * Huan(202010): will be removed (replaced by MessageSendFileStream) after Dec 31, 2021 */ - rpc MessageSendFile (puppet.MessageSendFileRequest) returns (puppet.MessageSendFileResponse) {} - - rpc MessagePayload (puppet.MessagePayloadRequest) returns (puppet.MessagePayloadResponse) {} + rpc MessageSendFile (puppet.MessageSendFileRequest) returns (puppet.MessageSendFileResponse) { + option deprecated = true; + } - rpc MessageContact (puppet.MessageContactRequest) returns (puppet.MessageContactResponse) {} - rpc MessageFileStream (puppet.MessageFileStreamRequest) returns (stream puppet.MessageFileStreamResponse) {} - rpc MessageImageStream (puppet.MessageImageStreamRequest) returns (stream puppet.MessageImageStreamResponse) {} - rpc MessageMiniProgram (puppet.MessageMiniProgramRequest) returns (puppet.MessageMiniProgramResponse) {} - rpc MessageUrl (puppet.MessageUrlRequest) returns (puppet.MessageUrlResponse) {} + rpc MessagePayload (puppet.MessagePayloadRequest) returns (puppet.MessagePayloadResponse) { + option (google.api.http) = { + get: "/message/{id}" + }; + } - rpc MessageSendContact (puppet.MessageSendContactRequest) returns (puppet.MessageSendContactResponse) {} - rpc MessageSendFileStream (stream puppet.MessageSendFileStreamRequest) returns (puppet.MessageSendFileStreamResponse) {} - rpc MessageSendText (puppet.MessageSendTextRequest) returns (puppet.MessageSendTextResponse) {} - rpc MessageSendMiniProgram (puppet.MessageSendMiniProgramRequest) returns (puppet.MessageSendMiniProgramResponse) {} - rpc MessageSendUrl (puppet.MessageSendUrlRequest) returns (puppet.MessageSendUrlResponse) {} + rpc MessageContact (puppet.MessageContactRequest) returns (puppet.MessageContactResponse) { + option (google.api.http) = { + get: "/message/{id}/contact" + }; + } + rpc MessageFileStream (puppet.MessageFileStreamRequest) returns (stream puppet.MessageFileStreamResponse) { + option (google.api.http) = { + get: "/message/{id}/filebox" + }; + } + rpc MessageImageStream (puppet.MessageImageStreamRequest) returns (stream puppet.MessageImageStreamResponse) { + option (google.api.http) = { + get: "/message/{id}/image/{type}" + }; + } + rpc MessageMiniProgram (puppet.MessageMiniProgramRequest) returns (puppet.MessageMiniProgramResponse) { + option (google.api.http) = { + get: "/message/{id}/mini-program" + }; + } + rpc MessageUrl (puppet.MessageUrlRequest) returns (puppet.MessageUrlResponse) { + option (google.api.http) = { + get: "/message/{id}/url-link" + }; + } + rpc MessageRecall (puppet.MessageRecallRequest) returns (puppet.MessageRecallResponse) { + option (google.api.http) = { + post: "/message/{id}/recall" + body: "*" + }; + } - rpc MessageRecall (puppet.MessageRecallRequest) returns (puppet.MessageRecallResponse) {} + rpc MessageSendContact (puppet.MessageSendContactRequest) returns (puppet.MessageSendContactResponse) { + option (google.api.http) = { + post: "/conversations/{conversation_id}/contact" + body: "*" + }; + } + rpc MessageSendFileStream (stream puppet.MessageSendFileStreamRequest) returns (puppet.MessageSendFileStreamResponse) { + /** + * TODO: Add support for client upload files + * https://github.com/wechaty/openapi/issues/1 + */ + option (google.api.http) = { + post: "/conversations/-/filebox" + body: "*" + }; + } + rpc MessageSendText (puppet.MessageSendTextRequest) returns (puppet.MessageSendTextResponse) { + option (google.api.http) = { + post: "/conversations/{conversation_id}/text" + body: "*" + }; + } + rpc MessageSendMiniProgram (puppet.MessageSendMiniProgramRequest) returns (puppet.MessageSendMiniProgramResponse) { + option (google.api.http) = { + post: "/conversations/{conversation_id}/mini-program" + body: "*" + }; + } + rpc MessageSendUrl (puppet.MessageSendUrlRequest) returns (puppet.MessageSendUrlResponse) { + option (google.api.http) = { + post: "/conversations/{conversation_id}/url-link" + body: "*" + }; + } /** * * Room * */ - rpc RoomPayload (puppet.RoomPayloadRequest) returns (puppet.RoomPayloadResponse) {} + rpc RoomPayload (puppet.RoomPayloadRequest) returns (puppet.RoomPayloadResponse) { + option (google.api.http) = { + get: "/rooms/{id}" + }; + } - rpc RoomList (puppet.RoomListRequest) returns (puppet.RoomListResponse) {} + rpc RoomList (puppet.RoomListRequest) returns (puppet.RoomListResponse) { + option (google.api.http) = { + get: "/rooms" + }; + } - rpc RoomAdd (puppet.RoomAddRequest) returns (puppet.RoomAddResponse) {} - rpc RoomAvatar (puppet.RoomAvatarRequest) returns (puppet.RoomAvatarResponse) {} - rpc RoomCreate (puppet.RoomCreateRequest) returns (puppet.RoomCreateResponse) {} - rpc RoomDel (puppet.RoomDelRequest) returns (puppet.RoomDelResponse) {} - rpc RoomQuit (puppet.RoomQuitRequest) returns (puppet.RoomQuitResponse) {} + rpc RoomAdd (puppet.RoomAddRequest) returns (puppet.RoomAddResponse) { + option (google.api.http) = { + put: "/rooms/{id}/add/{contact_id}" + }; + } + rpc RoomAvatar (puppet.RoomAvatarRequest) returns (puppet.RoomAvatarResponse) { + option (google.api.http) = { + get: "/rooms/{id}/avatar" + }; + } + rpc RoomCreate (puppet.RoomCreateRequest) returns (puppet.RoomCreateResponse) { + option (google.api.http) = { + post: "/rooms" + body: "*" + }; + } + rpc RoomDel (puppet.RoomDelRequest) returns (puppet.RoomDelResponse) { + option (google.api.http) = { + delete: "/rooms/{id}/member/{contact_id}" + }; + } + rpc RoomQuit (puppet.RoomQuitRequest) returns (puppet.RoomQuitResponse) { + option (google.api.http) = { + delete: "/rooms/{id}" + }; + } - rpc RoomTopic (puppet.RoomTopicRequest) returns (puppet.RoomTopicResponse) {} - rpc RoomQRCode (puppet.RoomQRCodeRequest) returns (puppet.RoomQRCodeResponse) {} - rpc RoomAnnounce (puppet.RoomAnnounceRequest) returns (puppet.RoomAnnounceResponse) {} + rpc RoomTopic (puppet.RoomTopicRequest) returns (puppet.RoomTopicResponse) { + option (google.api.http) = { + put: "/rooms/{id}/topic/{topic}" + }; + } + rpc RoomQRCode (puppet.RoomQRCodeRequest) returns (puppet.RoomQRCodeResponse) { + option (google.api.http) = { + get: "/rooms/{id}/qrcode" + }; + } + rpc RoomAnnounce (puppet.RoomAnnounceRequest) returns (puppet.RoomAnnounceResponse) { + option (google.api.http) = { + get: "/rooms/{id}/announcement" // TODO: disable other get parameters + additional_bindings { + put: "/rooms/{id}/announcement" + body: "*" + } + }; + } /** * * Room Member * */ - rpc RoomMemberPayload (puppet.RoomMemberPayloadRequest) returns (puppet.RoomMemberPayloadResponse) {} + rpc RoomMemberPayload (puppet.RoomMemberPayloadRequest) returns (puppet.RoomMemberPayloadResponse) { + option (google.api.http) = { + get: "/rooms/{id}/members/{member_id}" + }; + } - rpc RoomMemberList (puppet.RoomMemberListRequest) returns (puppet.RoomMemberListResponse) {} + rpc RoomMemberList (puppet.RoomMemberListRequest) returns (puppet.RoomMemberListResponse) { + option (google.api.http) = { + get: "/rooms/{id}/members" + }; + } /** * * Room Invitation * */ - rpc RoomInvitationPayload (puppet.RoomInvitationPayloadRequest) returns (puppet.RoomInvitationPayloadResponse) {} - rpc RoomInvitationAccept (puppet.RoomInvitationAcceptRequest) returns (puppet.RoomInvitationAcceptResponse) {} + rpc RoomInvitationPayload (puppet.RoomInvitationPayloadRequest) returns (puppet.RoomInvitationPayloadResponse) { + option (google.api.http) = { + get: "/room-invitations/{id}" + }; + } + rpc RoomInvitationAccept (puppet.RoomInvitationAcceptRequest) returns (puppet.RoomInvitationAcceptResponse) { + option (google.api.http) = { + put: "/room-invitations/{id}/accept" + }; + } /** * * Tag * */ - rpc TagContactAdd (puppet.TagContactAddRequest) returns (puppet.TagContactAddResponse) {} - rpc TagContactRemove (puppet.TagContactRemoveRequest) returns (puppet.TagContactRemoveResponse) {} - rpc TagContactDelete (puppet.TagContactDeleteRequest) returns (puppet.TagContactDeleteResponse) {} - rpc TagContactList (puppet.TagContactListRequest) returns (puppet.TagContactListResponse) {} + rpc TagContactAdd (puppet.TagContactAddRequest) returns (puppet.TagContactAddResponse) { + option (google.api.http) = { + put: "/contacts/contact_id/tags/id" + }; + } + rpc TagContactRemove (puppet.TagContactRemoveRequest) returns (puppet.TagContactRemoveResponse) { + option (google.api.http) = { + delete: "/contacts/contact_id/tags/id" + }; + } + + /** + * Operate Sub-Collections + * https://cloud.google.com/apis/design/design_patterns#list_sub-collections + */ + rpc TagContactDelete (puppet.TagContactDeleteRequest) returns (puppet.TagContactDeleteResponse) { + option (google.api.http) = { + delete: "/contacts/-/tags/id" + }; + } + + rpc TagContactList (puppet.TagContactListRequest) returns (puppet.TagContactListResponse) { + option (google.api.http) = { + get: "/contacts/contact_id/tags" + }; + } } diff --git a/proto/wechaty/puppet/room_invitation.proto b/proto/wechaty/puppet/room_invitation.proto index db7f807c..c4f438c0 100644 --- a/proto/wechaty/puppet/room_invitation.proto +++ b/proto/wechaty/puppet/room_invitation.proto @@ -9,6 +9,11 @@ import "google/protobuf/wrappers.proto"; message RoomInvitationPayloadRequest { string id = 1; + /** + * Huan(202002): `payload` should be removed. + * The puppet server should take the responsibilities + * for storing the unaccepted friend-request payload. + */ google.protobuf.StringValue payload = 2; } message RoomInvitationPayloadResponse { diff --git a/scripts/generate-stub.sh b/scripts/generate-stub.sh index 1053dcbb..e17da86c 100755 --- a/scripts/generate-stub.sh +++ b/scripts/generate-stub.sh @@ -1,7 +1,8 @@ #!/usr/bin/env bash set -e -PROTO_DIR="./proto" +PROTO_DIR="./proto/" +THIRD_PARTY_DIR="./third-party/" # Directory to write generated code to (.js and .d.ts files) OUT_DIR="./generated/wechaty" @@ -9,10 +10,13 @@ OUT_DIR="./generated/wechaty" mkdir -p ${OUT_DIR} } -PROTO_FILE_LIST=$(find $PROTO_DIR -type f -name *.proto) +PROTO_FILE_LIST=$(find $PROTO_DIR $THIRD_PARTY_DIR -type f -name *.proto) # --proto_path=/usr/local/include/ -PROTOC_CMD="protoc --proto_path=${PROTO_DIR}/wechaty $PROTO_FILE_LIST" +PROTOC_CMD="protoc \ + -I ${THIRD_PARTY_DIR} \ + --proto_path=${PROTO_DIR}/wechaty \ + $PROTO_FILE_LIST" # # 1. JS for Protocol Buffer @@ -28,7 +32,7 @@ $PROTOC_CMD \ # # Generate: wechaty-puppet_grpc_pb.js $PROTOC_CMD \ - --plugin="protoc-gen-grpc=`command -v grpc_tools_node_protoc_plugin`" \ + --plugin="protoc-gen-grpc=node_modules/.bin/grpc_tools_node_protoc_plugin" \ --grpc_out="${OUT_DIR}" # @@ -51,4 +55,4 @@ $PROTOC_CMD \ # wechaty-puppet_pb_service.js $PROTOC_CMD \ --plugin="protoc-gen-ts=node_modules/ts-protoc-gen/bin/protoc-gen-ts" \ - --ts_out="service=true:${OUT_DIR}" + --ts_out="service=grpc-web:${OUT_DIR}" diff --git a/scripts/install-protoc-gen-lint.sh b/scripts/install-protoc-gen-lint.sh deleted file mode 100755 index d9b5d825..00000000 --- a/scripts/install-protoc-gen-lint.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/usr/bin/env bash - -set -eo pipefail - -go get -u github.com/ckaznocha/protoc-gen-lint diff --git a/scripts/install-protoc.sh b/scripts/install-protoc.sh index e67be593..8022ed7a 100755 --- a/scripts/install-protoc.sh +++ b/scripts/install-protoc.sh @@ -1,44 +1,62 @@ #!/usr/bin/env bash set -e +set -o pipefail -# https://superuser.com/questions/603068/unzipping-file-whilst-getting-correct-permissions -umask 644 -SUDO=sudo - -# http://google.github.io/proto-lens/installing-protoc.html - -if [ $(uname) = 'Linux' ]; then - PROTOC_PLATFORM=linux - PROTOC_GEN_LINT_PLATFORM=linux -elif [ $(uname) = 'Darwin' ]; then - PROTOC_PLATFORM=osx - PROTOC_GEN_LINT_PLATFORM=darwin -elif [[ $(uname) =~ ^MINGW64 ]]; then # GitHub Actions in Windows - PROTOC_PLATFORM=win64 - PROTOC_GEN_LINT_PLATFORM=windows - # no sudo in win32 bash - SUDO= -else - echo UNKNOWN PLATFORM -fi - -PROTOC_VERSION='3.11.3' -PROTOC_ZIP="protoc-$PROTOC_VERSION-$PROTOC_PLATFORM-x86_64.zip" - -curl -OL "https://github.com/google/protobuf/releases/download/v$PROTOC_VERSION/$PROTOC_ZIP" -# See: https://github.com/grpc-ecosystem/grpc-gateway/issues/194 -$SUDO unzip -o $PROTOC_ZIP -d /usr/local bin/* include/* -$SUDO chmod -R 755 /usr/local/include/google/ -$SUDO chmod +x /usr/local/bin/protoc -rm -f $PROTOC_ZIP - -# -# https://github.com/ckaznocha/protoc-gen-lint -# -PROTOC_GEN_LINT_VERSION='0.2.1' -PROTOC_GEN_LINT_ZIP="protoc-gen-lint_${PROTOC_GEN_LINT_PLATFORM}_amd64.zip" - -curl -OL "https://github.com/ckaznocha/protoc-gen-lint/releases/download/v$PROTOC_GEN_LINT_VERSION/$PROTOC_GEN_LINT_ZIP" -$SUDO unzip -o "$PROTOC_GEN_LINT_ZIP" protoc-gen-lint -d /usr/local/bin protoc-gen-lint -$SUDO chmod +x /usr/local/bin/protoc-gen-lint -rm -f "$PROTOC_GEN_LINT_ZIP" +# https://stackoverflow.com/a/4774063/1123955 +SCRIPTPATH="$( cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 ; pwd -P )" + +THIRD_PARTY_DIR="${SCRIPTPATH}/../third-party/" + +function install_protoc () { + if command -v protoc > /dev/null; then + echo "install skipped: protoc exists" + return + fi + + # https://grpc.io/docs/protoc-installation/ + if [ $(uname) = 'Linux' ]; then + sudo apt install -y protobuf-compiler + elif [ $(uname) = 'Darwin' ]; then + brew install protobuf + else + echo "UNKNOWN PLATFORM: $(uname)" + exit 1 + fi + + protoc --version +} + +function install_protoc_gen_lint () { + go get -u github.com/ckaznocha/protoc-gen-lint +} + +function install_google_api () { + if [ -d ${THIRD_PARTY_DIR}/google/api ]; then + echo "install skipped: ${THIRD_PARTY_DIR}/google/api exists" + return + fi + + mkdir -p ${THIRD_PARTY_DIR}/google/api + curl https://raw.githubusercontent.com/googleapis/googleapis/master/google/api/annotations.proto > ${THIRD_PARTY_DIR}/google/api/annotations.proto + curl https://raw.githubusercontent.com/googleapis/googleapis/master/google/api/http.proto > ${THIRD_PARTY_DIR}/google/api/http.proto +} + +function install_protoc_gen_openapiv2 () { + if [ -d ${THIRD_PARTY_DIR}/protoc-gen-openapiv2/options ]; then + echo "install skipped: ${THIRD_PARTY_DIR}/protoc-gen-openapiv2/options exists" + return + fi + + mkdir -p ${THIRD_PARTY_DIR}/protoc-gen-openapiv2/options + curl https://raw.githubusercontent.com/grpc-ecosystem/grpc-gateway/master/protoc-gen-openapiv2/options/annotations.proto > ${THIRD_PARTY_DIR}/protoc-gen-openapiv2/options/annotations.proto + curl https://raw.githubusercontent.com/grpc-ecosystem/grpc-gateway/master/protoc-gen-openapiv2/options/openapiv2.proto > ${THIRD_PARTY_DIR}/protoc-gen-openapiv2/options/openapiv2.proto +} + +function main () { + install_protoc + install_google_api + install_protoc_gen_lint + install_protoc_gen_openapiv2 +} + +main diff --git a/src/index.ts b/src/mod.ts similarity index 100% rename from src/index.ts rename to src/mod.ts diff --git a/tests/integration.spec.ts b/tests/integration.spec.ts index cc1f3c96..d88c0ccf 100644 --- a/tests/integration.spec.ts +++ b/tests/integration.spec.ts @@ -15,7 +15,7 @@ import { PuppetClient, DingRequest, EventRequest, -} from '../src/index' +} from '../src/mod' import { puppetServerImpl, @@ -53,7 +53,7 @@ test('integration testing', async (t) => { */ const eventStream = client.event(new EventRequest()) - const future = new Promise((resolve, reject) => { + const future = new Promise((resolve, reject) => { eventStream .on('data', (chunk: EventResponse) => { const payload = chunk.getPayload() @@ -90,7 +90,7 @@ test('integration testing', async (t) => { */ eventStream.cancel() - await new Promise(resolve => server.tryShutdown(resolve)) + await new Promise(resolve => server.tryShutdown(resolve)) // server.forceShutdown() }) diff --git a/tests/nullable.spec.ts b/tests/nullable.spec.ts index e7d9b0bb..a413ef01 100644 --- a/tests/nullable.spec.ts +++ b/tests/nullable.spec.ts @@ -11,7 +11,7 @@ import { ContactAliasResponse, PuppetService, PuppetClient, -} from '../src/' +} from '../src/mod' import { puppetServerImpl } from './puppet-server-impl' diff --git a/tests/puppet-server-impl.ts b/tests/puppet-server-impl.ts index 6ab566b3..b509ff0b 100644 --- a/tests/puppet-server-impl.ts +++ b/tests/puppet-server-impl.ts @@ -1,6 +1,6 @@ import { IPuppetServer, -} from '../src/' +} from '../src/mod' /** * Implements the SayHello RPC method.