Skip to content

Commit

Permalink
Release/v0.2.0 (#3)
Browse files Browse the repository at this point in the history
* Feature/user api (#1)

* Init user api

* Add first method

* Add ClientTokenSource interface as an abstract layer of api token provider

* Add request interface

* Fix readme

* fix token url

* fix oauth

* fix interface

* fix tokensource

* add authen api

* fix test

* fix test

* fix time

* fix test

* fix app

* fix token

* add Type

* add lark supports

* fix readme

* change feishu -> lafi
  • Loading branch information
joyqi authored Sep 21, 2022
1 parent 3691860 commit a6e4794
Show file tree
Hide file tree
Showing 18 changed files with 477 additions and 170 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,6 @@ jobs:
env:
APP_ID: '${{ secrets.FEISHU_APP_ID }}'
APP_SECRET: '${{ secrets.FEISHU_APP_SECRET }}'
ACCESS_TOKEN: '${{ secrets.FEISHU_ACCESS_TOKEN }}'
REFRESH_TOKEN: '${{ secrets.FEISHU_REFRESH_TOKEN }}'
run: go test -v ./...
41 changes: 28 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
# go-feishu
# go-lafi

go-feishu is a Go client library for accessing the Feishu API.
**lafi = lark + feishu**

> As of now I am writing this library, there is no official Feishu SDK for Go.
go-lafi is a Go client library for accessing the Lark/Feishu API.

> As of now I am writing this library, there is no official Lark/Feishu SDK for Go.
> Although Bytedance is the fastest growing tech company in China,
> seems like they don't want to hire a Go developer to write a Go SDK for Feishu. 😂
> seems like they don't want to hire a Go developer to write a Go SDK for Lark/Feishu. 😂
The goals of this library are:

Expand All @@ -15,22 +17,35 @@ The goals of this library are:
## Installation

```bash
go get github.com/joyqi/go-feishu
go get github.com/joyqi/go-lafi
```

## Usage

### OAuth2 Authentication

Initialize Feishu client with OAuth2 authentication:
Initialize Lark client with OAuth2 authentication:

```go
import "github.com/joyqi/go-lafi/oauth2"

var conf = &oauth2.Config{
AppID: "your-client-id",
AppSecret: "your-client-secret",
RedirectURL: "your-redirect-url",
}
```

For Feishu, you can specify the `Type` field to `TypeFeishu`:

```go
import "github.com/joyqi/go-feishu/oauth2"
import "github.com/joyqi/go-lafi/oauth2"

var conf = &oauth2.Config{
AppID: "your-client-id",
AppSecret: "your-client-secret",
RedirectURL: "your-redirect-url",
Type: oauth2.TypeFeishu,
}
```

Expand All @@ -53,7 +68,7 @@ ts := conf.TokenSource(ctx, token)
token, err := ts.Token()
```

### Feishu API
### Lark/Feishu API

Features:

Expand All @@ -66,20 +81,20 @@ Features:
- [ ] CustomAttr
- [ ] Scope

Initialize Feishu API client:
Initialize API client:

```go
import "github.com/joyqi/go-feishu/oauth2"
import "github.com/joyqi/go-lafi/oauth2"

client := conf.TenantTokenSource(ctx).Client()
```

Use the client to access the Feishu API. For example, to list all groups:
Use the client to access the API. For example, to list all groups:

```go
import (
"github.com/joyqi/go-feishu/oauth2"
"github.com/joyqi/go-feishu/api/contact"
"github.com/joyqi/go-lafi/oauth2"
"github.com/joyqi/go-lafi/api/contact"
)

client := conf.TenantTokenSource(ctx).Client()
Expand Down
3 changes: 3 additions & 0 deletions api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ type Api struct {
Client
}

type EmptyData struct {
}

// Client defines the interface of api client
type Client interface {
Request(method string, uri string, body interface{}, data interface{}) error
Expand Down
33 changes: 33 additions & 0 deletions api/auth/app.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package auth

import "github.com/joyqi/go-lafi/api"

const (
AppCommonURL = "/auth/v3/app_access_token"
AppInternalURL = "/auth/v3/app_access_token/internal"
)

// AppCommonBody represents a request to retrieve an app token
type AppCommonBody struct {
AppID string `json:"app_id"`
AppSecret string `json:"app_secret"`
AppTicket string `json:"app_ticket"`
}

// AppInternalBody represents a request to retrieve an app token
type AppInternalBody struct {
AppID string `json:"app_id"`
AppSecret string `json:"app_secret"`
}

type App api.Api

// CommonAccessToken retrieves a common token from the app token endpoint
func (a *App) CommonAccessToken(body *AppCommonBody) (string, int64, error) {
return MakeTokenApi(a, "app_access_token", AppCommonURL, body)
}

// InternalAccessToken retrieves an internal token from the app token endpoint
func (a *App) InternalAccessToken(body *AppInternalBody) (string, int64, error) {
return MakeTokenApi(a, "app_access_token", AppInternalURL, body)
}
51 changes: 51 additions & 0 deletions api/auth/auth.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package auth

import (
"encoding/json"
"errors"
"github.com/joyqi/go-lafi/api"
)

// TokenResponse represents the common token response structure
type TokenResponse struct {
// Code is the response status code
Code int `json:"code"`

// Msg is the response message
Msg string `json:"msg"`

// AccessToken is the access token
AccessToken string

// Expire is the expiration time of the access token
Expire int64 `json:"expire"`
}

// MakeTokenApi creates a new token api
func MakeTokenApi(c api.Client, tokenName string, uri string, body interface{}) (string, int64, error) {
var resp map[string]json.RawMessage
token := TokenResponse{}
err := c.Request("POST", uri, body, &resp)

if err = json.Unmarshal(resp["code"], &token.Code); err != nil {
return "", 0, err
}

if err = json.Unmarshal(resp["msg"], &token.Msg); err != nil {
return "", 0, err
}

if err = json.Unmarshal(resp[tokenName], &token.AccessToken); err != nil {
return "", 0, err
}

if err = json.Unmarshal(resp["expire"], &token.Expire); err != nil {
return "", 0, err
}

if token.Code != 0 {
return "", 0, errors.New(token.Msg)
}

return token.AccessToken, token.Expire, nil
}
32 changes: 32 additions & 0 deletions api/auth/tenant.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package auth

import "github.com/joyqi/go-lafi/api"

const (
TenantCommonURL = "/auth/v3/tenant_access_token"
TenantInternalURL = "/auth/v3/tenant_access_token/internal"
)

// TenantCommonBody represents a request to retrieve a tenant token
type TenantCommonBody struct {
AppAccessToken string `json:"app_access_token"`
TenantKey string `json:"tenant_key"`
}

// TenantInternalBody represents a request to retrieve a tenant token
type TenantInternalBody struct {
AppID string `json:"app_id"`
AppSecret string `json:"app_secret"`
}

type Tenant api.Api

// CommonAccessToken retrieves a common token from the tenant token endpoint
func (t *Tenant) CommonAccessToken(body *TenantCommonBody) (string, int64, error) {
return MakeTokenApi(t, "tenant_access_token", TenantCommonURL, body)
}

// InternalAccessToken retrieves a internal token from the tenant token endpoint
func (t *Tenant) InternalAccessToken(body *TenantInternalBody) (string, int64, error) {
return MakeTokenApi(t, "tenant_access_token", TenantInternalURL, body)
}
50 changes: 50 additions & 0 deletions api/authen/accesstoken.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package authen

import (
"github.com/joyqi/go-lafi/api"
"net/http"
)

const (
AccessTokenURL = "/authen/v1/access_token"
AccessTokenRefreshURL = "/authen/v1/refresh_access_token"
)

// AccessTokenCreateBody represents the request body of creating AccessToken
type AccessTokenCreateBody struct {
GrantType string `json:"grant_type"`
Code string `json:"code"`
}

// AccessTokenData represents the data of creating AccessToken
type AccessTokenData struct {
// OpenId represents the open ID of the user
OpenId string `json:"open_id"`

// AccessToken is the token used to access the application
AccessToken string `json:"access_token"`

// RefreshToken is the token used to refresh the user's access token
RefreshToken string `json:"refresh_token"`

// ExpiresIn is the number of seconds the token will be valid
ExpiresIn int64 `json:"expires_in"`
}

// AccessTokenRefreshBody represents the request body of refreshing AccessToken
type AccessTokenRefreshBody struct {
GrantType string `json:"grant_type"`
RefreshToken string `json:"refresh_token"`
}

type AccessToken api.Api

// Create creates the access token.
func (a *AccessToken) Create(body *AccessTokenCreateBody) (*AccessTokenData, error) {
return api.MakeApi[AccessTokenData](a.Client, http.MethodPost, AccessTokenURL, body)
}

// Refresh refreshes the access token.
func (a *AccessToken) Refresh(body *AccessTokenRefreshBody) (*AccessTokenData, error) {
return api.MakeApi[AccessTokenData](a.Client, http.MethodPost, AccessTokenRefreshURL, body)
}
34 changes: 34 additions & 0 deletions api/authen/userinfo.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package authen

import (
"github.com/joyqi/go-lafi/api"
"net/http"
)

const (
UserInfoURL = "/authen/v1/user_info"
)

// UserInfoData represents the response data of UserInfo
type UserInfoData struct {
Name string `json:"name"`
EnName string `json:"en_name"`
AvatarURL string `json:"avatar_url"`
AvatarThumb string `json:"avatar_thumb"`
AvatarMiddle string `json:"avatar_middle"`
AvatarBig string `json:"avatar_big"`
OpenId string `json:"open_id"`
UnionId string `json:"union_id"`
Email string `json:"email"`
EnterpriseEmail string `json:"enterprise_email"`
UserId string `json:"user_id"`
Mobile string `json:"mobile"`
TenantKey string `json:"tenant_key"`
}

type UserInfo api.Api

// Get fetches the user info through the access token.
func (a *UserInfo) Get() (data *UserInfoData, err error) {
return api.MakeApi[UserInfoData](a.Client, http.MethodGet, UserInfoURL, nil)
}
20 changes: 9 additions & 11 deletions api/contact/group.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,16 @@ package contact

import (
"github.com/creasty/defaults"
"github.com/joyqi/go-feishu/api"
"github.com/joyqi/go-feishu/httptool"
"github.com/joyqi/go-lafi/api"
"github.com/joyqi/go-lafi/httptool"
"net/http"
)

const (
GroupURL = "https://open.feishu.cn/open-apis/contact/v3/group/:group_id"
GroupCreateURL = "https://open.feishu.cn/open-apis/contact/v3/group"
GroupSimpleListURL = "https://open.feishu.cn/open-apis/contact/v3/group/simplelist"
GroupMemberBelongURL = "https://open.feishu.cn/open-apis/contact/v3/group/member_belong"
GroupURL = "/contact/v3/group/:group_id"
GroupCreateURL = "/contact/v3/group"
GroupSimpleListURL = "/contact/v3/group/simplelist"
GroupMemberBelongURL = "/contact/v3/group/member_belong"
)

// GroupType represents the type of group.
Expand Down Expand Up @@ -44,12 +44,10 @@ type GroupPatchBody struct {
}

// GroupPatchData represents the response data of Group.Patch
type GroupPatchData struct {
}
type GroupPatchData = api.EmptyData

// GroupDeleteData represents the response data of Group.Delete
type GroupDeleteData struct {
}
type GroupDeleteData = api.EmptyData

// GroupGetData represents the response data of Group.Get
type GroupGetData struct {
Expand Down Expand Up @@ -94,7 +92,7 @@ type GroupMemberBelongParams struct {

// GroupMemberBelongData represents the response data of Group.MemberBelong
type GroupMemberBelongData struct {
GroupList []string `json:"group_list,flow"`
GroupList []string `json:"group_list"`
HasMore bool `json:"has_more"`
PageToken string `json:"page_token"`
}
Expand Down
9 changes: 4 additions & 5 deletions api/contact/group_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package contact

import (
"context"
"github.com/joyqi/go-feishu/oauth2"
"github.com/joyqi/go-lafi/oauth2"
"os"
"testing"
)
Expand All @@ -11,6 +11,7 @@ var conf = &oauth2.Config{
AppID: os.Getenv("APP_ID"),
AppSecret: os.Getenv("APP_SECRET"),
RedirectURL: "https://example.com",
Type: oauth2.TypeFeishu,
}

var client = conf.TenantTokenSource(context.Background()).Client()
Expand All @@ -25,9 +26,7 @@ func TestGroup_Create(t *testing.T) {

if err != nil {
t.Error(err)
}

if data.GroupId != "test001" {
} else if data.GroupId != "test001" {
t.Fail()
}
}
Expand All @@ -52,7 +51,7 @@ func TestGroup_Patch(t *testing.T) {
})

if err != nil {
t.Error(err)
t.Fatal(err)
}

g, err := a.Get("test001")
Expand Down
Loading

0 comments on commit a6e4794

Please sign in to comment.