From 486c0fb7c4fc31bbc80b68e94041268b5c523d62 Mon Sep 17 00:00:00 2001 From: Anony <73958752+anonyindian@users.noreply.github.com> Date: Sat, 9 Oct 2021 23:10:17 +0530 Subject: [PATCH] spamwatch-go v1 This is the first version of spamwatch-go wrapper. --- README.md | 64 ++++++++++++ constants.go | 11 +++ errors.go | 32 ++++++ examples/banlist/main.go | 41 ++++++++ examples/misc/main.go | 27 ++++++ examples/tokens/main.go | 49 ++++++++++ go.mod | 3 + methods.go | 203 +++++++++++++++++++++++++++++++++++++++ requests.go | 55 +++++++++++ types.go | 72 ++++++++++++++ 10 files changed, 557 insertions(+) create mode 100644 README.md create mode 100644 constants.go create mode 100644 errors.go create mode 100644 examples/banlist/main.go create mode 100644 examples/misc/main.go create mode 100644 examples/tokens/main.go create mode 100644 go.mod create mode 100644 methods.go create mode 100644 requests.go create mode 100644 types.go diff --git a/README.md b/README.md new file mode 100644 index 0000000..7fc70b4 --- /dev/null +++ b/README.md @@ -0,0 +1,64 @@ +# SpamWatch API Go Wrapper + +[![Go Reference](https://pkg.go.dev/badge/github.com/SpamWatch/spamwatch-go.svg)](https://pkg.go.dev/github.com/SpamWatch/spamwatch-go) [![GPLv3 license](https://img.shields.io/badge/License-GPLv3-blue.svg)](http://perso.crans.org/besson/LICENSE.html) + +spamwatch-go is official Go wrapper for [SpamWatch API](https://api.spamwat.ch), which is fast, secure and requires no additional packages to be installed. + +
+ +## Features + +- Can use custom SpamWatch API endpoint with the help of ClientOpts. +- It's in pure go, no need to install any kind of plugin or include any kind of additional files. +- No third party library bloat; only uses standard library. +- Type safe; no weird `interface{}` logic. + +
+ +## Getting started + +You can easily download the library with the standard `go get` command: + +```bash +go get github.com/SpamWatch/spamwatch-go +``` + +Full documentation of this API, can be found [here](https://docs.spamwat.ch/). + +
+ +## Basic Usage + +```go +package main + +import ( + "fmt" + + "github.com/SpamWatch/spamwatch-go" +) + +var client = spamwatch.Client("API_KEY", nil) + +func main() { + ban, err := client.GetBan(777000) + if err != nil { + fmt.Println(err.Error()) + return + } + fmt.Println(ban) +} +``` + +Still need more examples? Take a look at the [examples directory](examples). + +Ask your doubts at the [support group](https://telegram.me/SpamWatchSupport). + +
+ +## License + +[![GNU General Public License v3.0](https://www.gnu.org/graphics/gplv3-127x51.png)](https://www.gnu.org/licenses/gpl-3.0.en.html#header) + +The spamwatch-go project is under the [GPL-3.0](https://opensource.org/licenses/GPL-3.0) license. You can find the license file [here](LICENSE). + diff --git a/constants.go b/constants.go new file mode 100644 index 0000000..4648978 --- /dev/null +++ b/constants.go @@ -0,0 +1,11 @@ +package spamwatch + +const DEFAULT_API_URL = "https://api.spamwat.ch/" + +const SPAMWATCH_ERROR_PREFIX = "SpamWatch-Error" + +const UserPermission = "User" + +const AdminPermission = "Admin" + +const RootPermission = "Root" diff --git a/errors.go b/errors.go new file mode 100644 index 0000000..3695933 --- /dev/null +++ b/errors.go @@ -0,0 +1,32 @@ +package spamwatch + +import ( + "encoding/json" + "fmt" + "time" +) + +func handleError(sw *SpamWatch, s int, b []byte, method string) error { + if s != 200 && s != 201 && s != 204 { + var e = Error{} + err := json.Unmarshal(b, &e) + if err != nil { + return err + } + return &ErrorHandler{Err: &e, Spamwatch: sw, Method: method} + } + return nil +} + +func (e *ErrorHandler) Error() string { + switch e.Err.Code { + case 401: + return fmt.Sprintf("%s: Make sure your API Token is correct", SPAMWATCH_ERROR_PREFIX) + case 403: + return fmt.Sprintf("%s: Your token's permission '%s' is not high enough", SPAMWATCH_ERROR_PREFIX, e.Spamwatch.Token.Permission) + case 429: + return fmt.Sprintf("%s: Too Many Requests for method '%s': Try again in %d seconds", SPAMWATCH_ERROR_PREFIX, e.Method, e.Err.Until-time.Now().Unix()) + default: + return fmt.Sprintf("%s: %s: %d-%s", SPAMWATCH_ERROR_PREFIX, e.Method, e.Err.Code, e.Err.Description) + } +} diff --git a/examples/banlist/main.go b/examples/banlist/main.go new file mode 100644 index 0000000..ac8b35a --- /dev/null +++ b/examples/banlist/main.go @@ -0,0 +1,41 @@ +package main + +import ( + "fmt" + + "github.com/SpamWatch/spamwatch-go" +) + +var client = spamwatch.Client("API_KEY", nil) + +func main() { + // Getting a specific ban + ban, err := client.GetBan(777000) + if err != nil { + fmt.Println(err.Error()) + return + } + fmt.Println(ban) + + // Getting all bans + bans, err := client.GetBans() + if err != nil { + fmt.Println(err.Error()) + return + } + fmt.Println(bans) + + // Getting a list of banned ids + bannedIds, err := client.GetBansMin() + if err != nil { + fmt.Println(err.Error()) + return + } + fmt.Println(bannedIds) + + // Adding a ban + client.AddBan(777000, "reason", "message") + + // Deleting a ban + client.DeleteBan(777000) +} diff --git a/examples/misc/main.go b/examples/misc/main.go new file mode 100644 index 0000000..078f87e --- /dev/null +++ b/examples/misc/main.go @@ -0,0 +1,27 @@ +package main + +import ( + "fmt" + + "github.com/SpamWatch/spamwatch-go" +) + +var client = spamwatch.Client("API_KEY", nil) + +func main() { + // Getting the API version + v, err := client.Version() + if err != nil { + fmt.Println(err.Error()) + return + } + fmt.Println(v) + + // Getting some stats + stats, err := client.Stats() + if err != nil { + fmt.Println(err.Error()) + return + } + fmt.Println(stats.TotalBansCount) +} diff --git a/examples/tokens/main.go b/examples/tokens/main.go new file mode 100644 index 0000000..52ecbd1 --- /dev/null +++ b/examples/tokens/main.go @@ -0,0 +1,49 @@ +package main + +import ( + "fmt" + + "github.com/SpamWatch/spamwatch-go" +) + +var client = spamwatch.Client("API_KEY", nil) + +func main() { + // Getting your own Token + myToken, err := client.GetSelf() + if err != nil { + fmt.Println(err) + return + } + fmt.Println(myToken) + + // Getting all Tokens + tokens, err := client.GetTokens() + if err != nil { + fmt.Println(err) + return + } + fmt.Println(tokens) + + // Getting a specific Token + Token, err := client.GetToken(1) + if err != nil { + fmt.Println(err) + return + } + fmt.Println(Token) + + // Getting a Users tokens + tokens, err = client.GetUserTokens(777000) + if err != nil { + fmt.Println(err) + return + } + fmt.Println(tokens) + + // Creating a Token + client.CreateToken(777000, spamwatch.UserPermission) + + // Retiring a specific Token + client.DeleteToken(1) +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..efb857b --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module github.com/SpamWatch/spamwatch-go + +go 1.17 diff --git a/methods.go b/methods.go new file mode 100644 index 0000000..e7bdcc4 --- /dev/null +++ b/methods.go @@ -0,0 +1,203 @@ +package spamwatch + +import ( + "bytes" + "encoding/json" + "fmt" + "net/http" + "strconv" + "strings" +) + +// This returns the major, minor and patch version of the API. This endpoint doesn't need a Authorization Token. +// https://docs.spamwat.ch/?go#getting-the-api-version +func (s *SpamWatch) Version() (*Version, error) { + b, err := s.MakeRequest(http.MethodGet, "version", nil) + if err != nil { + return nil, err + } + var a = &Version{} + err = json.Unmarshal(b, a) + if err != nil { + return nil, err + } + return a, nil +} + +// This returns general stats about the API. Right now this only returns the total ban count. +// https://docs.spamwat.ch/?go#getting-some-stats +func (s *SpamWatch) Stats() (*Stats, error) { + b, err := s.MakeRequest(http.MethodGet, "stats", nil) + if err != nil { + return nil, err + } + var a = &Stats{} + err = json.Unmarshal(b, a) + if err != nil { + return nil, err + } + return a, nil +} + +// Check the ban status of a specific User. +// https://docs.spamwat.ch/?go#getting-a-specific-ban +func (s *SpamWatch) GetBan(id int64) (*BanList, error) { + b, err := s.MakeRequest(http.MethodGet, fmt.Sprintf("banlist/%d", id), nil) + if err != nil { + return nil, err + } + var a = &BanList{} + err = json.Unmarshal(b, a) + if err != nil { + return nil, err + } + return a, nil +} + +// This returns a list of all Bans. +// https://docs.spamwat.ch/?go#getting-all-bans +func (s *SpamWatch) GetBans() (*[]BanList, error) { + b, err := s.MakeRequest(http.MethodGet, "banlist", nil) + if err != nil { + return nil, err + } + var a = []BanList{} + err = json.Unmarshal(b, &a) + if err != nil { + return nil, err + } + return &a, nil +} + +// This returns a newline seperated list of all Bans. This method currently ignores the Accept header and will always return a newline seperated list. In the future it might return a JSON with the corresponding content type. +// https://docs.spamwat.ch/?go#getting-a-list-of-banned-ids +func (s *SpamWatch) GetBansMin() ([]int64, error) { + b, err := s.MakeRequest(http.MethodGet, "banlist/all", nil) + if err != nil { + return nil, err + } + idstrs := strings.Fields(string(b)) + a := make([]int64, 0, len(idstrs)) + for _, x := range idstrs { + n, err := strconv.ParseInt(x, 10, 0) + if err != nil { + return nil, err + } + a = append(a, n) + } + return a, nil +} + +// This method can be used for adding a ban. +// https://docs.spamwat.ch/?go#adding-a-ban +func (s *SpamWatch) AddBan(id int64, reason string, message string) (bool, error) { + _, err := s.MakeRequest(http.MethodPost, "banlist", bytes.NewBuffer([]byte(fmt.Sprintf(`[{"id":%d,"reason":"%s","message":"%s"}]`, id, reason, message)))) + if err != nil { + return false, err + } + return true, nil +} + +// This method can be used for adding multiple bans at a time. +// https://docs.spamwat.ch/?go#adding-a-ban +func (s *SpamWatch) AddBans(toBan []AddBans) (bool, error) { + jsonBytes, err := json.Marshal(toBan) + if err != nil { + return false, err + } + _, err = s.MakeRequest(http.MethodPost, "banlist", bytes.NewBuffer(jsonBytes)) + if err != nil { + return false, err + } + return true, nil +} + +// Deleting a ban +// https://docs.spamwat.ch/?go#deleting-a-ban +func (s *SpamWatch) DeleteBan(id int64) (bool, error) { + _, err := s.MakeRequest(http.MethodDelete, fmt.Sprintf("banlist/%d", id), nil) + if err != nil { + return false, err + } + return true, nil +} + +// This returns the Token the request was made with. Useful for checking the permission Level of the token. +// https://docs.spamwat.ch/?go#getting-your-own-token +func (s *SpamWatch) GetSelf() (*Tokens, error) { + b, err := s.MakeRequest(http.MethodGet, "tokens/self", nil) + if err != nil { + return nil, err + } + var a = &Tokens{} + err = json.Unmarshal(b, a) + if err != nil { + return nil, err + } + return a, nil +} + +// This returns a list of all Tokens. +// https://docs.spamwat.ch/?go#getting-all-tokens +func (s *SpamWatch) GetTokens() (*[]Tokens, error) { + b, err := s.MakeRequest(http.MethodGet, "tokens", nil) + if err != nil { + return nil, err + } + var a = []Tokens{} + err = json.Unmarshal(b, &a) + if err != nil { + return nil, err + } + return &a, nil +} + +// This returns a specific Tokens. +// https://docs.spamwat.ch/?go#getting-a-specific-token +func (s *SpamWatch) GetToken(id int) (*Tokens, error) { + b, err := s.MakeRequest(http.MethodGet, fmt.Sprintf("tokens/%d", id), nil) + if err != nil { + return nil, err + } + var a = &Tokens{} + err = json.Unmarshal(b, a) + if err != nil { + return nil, err + } + return a, nil +} + +// This returns a list of all tokens associated with the specified user id. +// https://docs.spamwat.ch/?go#getting-a-users-tokens +func (s *SpamWatch) GetUserTokens(id int64) (*[]Tokens, error) { + b, err := s.MakeRequest(http.MethodGet, fmt.Sprintf("tokens/userid/%d", id), nil) + if err != nil { + return nil, err + } + var a = []Tokens{} + err = json.Unmarshal(b, &a) + if err != nil { + return nil, err + } + return &a, nil +} + +// Creating a Token +// https://docs.spamwat.ch/?go#creating-a-token +func (s *SpamWatch) CreateToken(userId int64, permission string) (bool, error) { + _, err := s.MakeRequest(http.MethodPost, "tokens", bytes.NewBuffer([]byte(fmt.Sprintf(`{"id":%d,"permission":"%s"}`, userId, permission)))) + if err != nil { + return false, err + } + return true, nil +} + +// This retires a specific Token. The Token won't be able to make any requests anymore. +// https://docs.spamwat.ch/?go#retiring-a-specific-token +func (s *SpamWatch) DeleteToken(tokenId int) (bool, error) { + _, err := s.MakeRequest(http.MethodDelete, fmt.Sprintf("tokens/%d", tokenId), nil) + if err != nil { + return false, err + } + return true, nil +} diff --git a/requests.go b/requests.go new file mode 100644 index 0000000..a62cc70 --- /dev/null +++ b/requests.go @@ -0,0 +1,55 @@ +package spamwatch + +import ( + "fmt" + "io" + "net/http" +) + +func Client(token string, opts *ClientOpts) *SpamWatch { + if opts == nil { + opts = &ClientOpts{} + } + if opts.ApiUrl == "" { + opts.ApiUrl = DEFAULT_API_URL + } + var sw = &SpamWatch{} + sw.TimeOut = opts.TimeOut + sw.Token.Token = token + sw.ApiUrl = opts.ApiUrl + // Ignore error on GetSelf method as it would already be handled in succeeding calls + t, _ := sw.GetSelf() + if t != nil { + sw.Token = *t + } + return sw +} + +func (s *SpamWatch) MakeRequest(http_method string, method string, body io.Reader) ([]byte, error) { + r, err := http.NewRequest(http_method, s.ApiUrl+method, body) + r.Header.Set("Authorization", fmt.Sprintf("Bearer %s", s.Token.Token)) + if body != nil { + r.Header.Set("Content-Type", "application/json") + } + if err != nil { + return nil, fmt.Errorf("failed to build %s request to %s: %w", http_method, method, err) + } + var client = http.Client{ + Timeout: s.TimeOut, + } + + resp, err := client.Do(r) + if err != nil { + return nil, fmt.Errorf("failed to execute %s request to %s: %w", http_method, method, err) + } + defer resp.Body.Close() + b, err := io.ReadAll(resp.Body) + if err != nil { + return nil, err + } + err = handleError(s, resp.StatusCode, b, method) + if err != nil { + return nil, err + } + return b, nil +} diff --git a/types.go b/types.go new file mode 100644 index 0000000..d0516dd --- /dev/null +++ b/types.go @@ -0,0 +1,72 @@ +package spamwatch + +import "time" + +type SpamWatch struct { + Token Tokens + ApiUrl string + TimeOut time.Duration +} + +// JSON object returned on /version method +// https://docs.spamwat.ch/?go#misc +type Version struct { + Major string `json:"major"` + Minor string `json:"minor"` + Patch string `json:"patch"` + Version string `json:"version"` +} + +// JSON object returned on /stats method +// https://docs.spamwat.ch/?go#misc +type Stats struct { + TotalBansCount int `json:"total_ban_count"` +} + +// JSON object returned on /banlist methods +// https://docs.spamwat.ch/?go#banlist +type BanList struct { + Admin int `json:"admin,omitempty"` + Date int64 `json:"date,omitempty"` + Id int64 `json:"id"` + Reason string `json:"reason"` + Message string `json:"message,omitempty"` +} + +// JSON object used while adding multiple bans +type AddBans struct { + Id int64 `json:"id"` + Reason string `json:"reason"` + Message string `json:"message,omitempty"` +} + +// JSON object returned in /tokens methods +// https://docs.spamwat.ch/?go#tokens +type Tokens struct { + Id int `json:"id"` + Permission string `json:"permission"` + Retired bool `json:"retired"` + Token string `json:"token"` + Userid int64 `json:"userid"` +} + +// JSON object returned when an error occurs +type Error struct { + Code int `json:"code"` + Description string `json:"error"` + Until int64 `json:"until,omitempty"` + Reason string `json:"reason,omitempty"` +} + +// Struct that handles errors +type ErrorHandler struct { + Err *Error + Spamwatch *SpamWatch + Method string +} + +// These are the optional parameters of SpamWatch Client +type ClientOpts struct { + ApiUrl string + TimeOut time.Duration +}