Commit 7c0f0ee0 authored by Morgan McMillian's avatar Morgan McMillian

Import from project gitlab.dreamfall.space/thrrgilag/peanuts

parent d13f9e89
MIT License
Copyright (c) 2019 Morgan McMillian
Copyright (c) 2017 yukkuri_sinai
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
......
# woodstock
A golang client library for pnut.io
\ No newline at end of file
A golang client library for pnut.io
### Installation
```sh
go get gitlab.dreamfall.space/thrrgilag/woodstock
```
package peanuts
import (
"net/url"
"strings"
)
type App struct {
Id string `json:"id"`
Link string `json:"link"`
Name string `json:"name"`
}
type Token struct {
App App `json:"app"`
Scopes []string `json:"scopes"`
User User `json:"user"`
ClientId string `json:"client_id"`
}
type AccessTokenResult struct {
AccessToken string `json:"access_token"`
Token Token `json:"token"`
UserId string `json:"user_id"`
Username string `json:"username"`
}
// Get AccessToken from authorization code
// https://pnut.io/docs/authentication/web-flows
func (c *Client) AccessToken(code string, redirectURI string) (result AccessTokenResult, err error) {
v := url.Values{}
v.Set("client_id", c.clientId)
v.Set("client_secret", c.clientSecret)
v.Set("code", code)
v.Set("redirect_uri", redirectURI)
v.Set("grant_type", "authorization_code")
response_ch := make(chan response)
c.queryQueue <- query{url: OAUTH_ACCESS_TOKEN_API, form: v, data: &result, method: "POST", response_ch: response_ch}
return result, (<-response_ch).err
}
// Get AccessToken from password
// https://pnut.io/docs/authentication/password-flow
func (c *Client) AccessTokenFromPassword(username string, password string, scope []string) (result AccessTokenResult, err error) {
v := url.Values{}
v.Set("client_id", c.clientId)
v.Set("password_grant_secret", c.passwordGrantSecret)
v.Set("username", username)
v.Set("password", password)
v.Set("grant_type", "password")
v.Set("scope", strings.Join(scope, ","))
response_ch := make(chan response)
c.queryQueue <- query{url: OAUTH_ACCESS_TOKEN_API, form: v, data: &result, method: "POST", response_ch: response_ch}
return result, (<-response_ch).err
}
package peanuts
import (
"net/http"
)
const (
AUTHENTICATE_URL = "https://pnut.io/oauth/authenticate"
API_BASE_URL = "https://api.pnut.io/v0/"
OAUTH_ACCESS_TOKEN_API = API_BASE_URL + "oauth/access_token"
POST_API = API_BASE_URL + "posts"
STREAM_BASE_URL = POST_API + "/" + "streams"
STREAM_ME_API = STREAM_BASE_URL + "/me"
STREAM_UNIFIED_API = STREAM_BASE_URL + "/unified"
STREAM_GLOBAL_API = STREAM_BASE_URL + "/global"
STREAM_TAG_BASE_URL = POST_API + "/" + "tag"
USER_API = API_BASE_URL + "users"
USER_ME_API = USER_API + "/me"
ME_CHANNELS_BASE_URL = USER_ME_API + "/channels"
UNREAD_PM_NUMBER_API = ME_CHANNELS_BASE_URL + "/num_unread/pm"
SUBSCRIBED_CHANNELS_API = ME_CHANNELS_BASE_URL + "/subscribed"
MUTED_CHANNELS_API = ME_CHANNELS_BASE_URL + "/muted"
ME_MESSAGES_API = USER_ME_API + "/messages"
CHANNEL_API = API_BASE_URL + "channels"
PRESENCE_API = API_BASE_URL + "presence"
CLIENT_API = API_BASE_URL + "clients"
MARKER_API = API_BASE_URL + "markers"
)
type Api struct {
accessToken string
HttpClient *http.Client
}
package peanuts
import (
"encoding/json"
"net/url"
"strings"
)
type ChannelResult struct {
*CommonResponse
Data Channel `json:"data"`
}
type ChannelsResult struct {
*CommonResponse
Data []Channel `json:"data"`
}
type createChannel struct {
Type string `json:"type"`
Acl Acl `json:"acl"`
}
// Get channel
// https://pnut.io/docs/resources/channels/lookup#get-channels-id
func (c *Client) GetChannel(id string) (result ChannelResult, err error) {
response_ch := make(chan response)
c.queryQueue <- query{url: CHANNEL_API + "/" + id, data: &result, method: "GET", response_ch: response_ch}
return result, (<-response_ch).err
}
// Get channels
// https://pnut.io/docs/resources/channels/lookup#get-channels
func (c *Client) GetChannels(ids []string) (result UsersResult, err error) {
v := url.Values{}
v.Set("ids", strings.Join(ids, ","))
response_ch := make(chan response)
c.queryQueue <- query{url: CHANNEL_API + "?" + v.Encode(), data: &result, method: "GET", response_ch: response_ch}
return result, (<-response_ch).err
}
// Create channel
// https://pnut.io/docs/resources/channels/lifecycle#post-channels
func (c *Client) CreateChannel(typeStr string, acl Acl) (result ChannelResult, err error) {
json, err := json.Marshal(&createChannel{Type: typeStr, Acl: acl})
if err != nil {
return
}
response_ch := make(chan response)
c.queryQueue <- query{url: CHANNEL_API, data: &result, method: "POST", response_ch: response_ch, json: string(json)}
return result, (<-response_ch).err
}
// Update channel
// https://pnut.io/docs/resources/channels/lifecycle#put-channels-id
func (c *Client) UpdateChannel(id string, acl Acl) (result ChannelResult, err error) {
json, err := json.Marshal(&createChannel{Acl: acl})
if err != nil {
return
}
response_ch := make(chan response)
c.queryQueue <- query{url: CHANNEL_API + "/" + id, data: &result, method: "PUT", response_ch: response_ch, json: string(json)}
return result, (<-response_ch).err
}
// Delete channel
// https://pnut.io/docs/resources/channels/lifecycle#delete-channels-id
func (c *Client) DeleteChannel(id string) (result ChannelResult, err error) {
response_ch := make(chan response)
c.queryQueue <- query{url: CHANNEL_API + "/" + id, data: &result, method: "DELETE", response_ch: response_ch}
return result, (<-response_ch).err
}
// Get subscribers of channel
// https://pnut.io/docs/resources/channels/subscribing#get-channels-id-subscribers
func (c *Client) GetSubscribersOfChannel(id string) (result UsersResult, err error) {
response_ch := make(chan response)
c.queryQueue <- query{url: CHANNEL_API + "/" + id + "/subscribers", data: &result, method: "GET", response_ch: response_ch}
return result, (<-response_ch).err
}
// Subscribe channel
// https://pnut.io/docs/resources/channels/subscribing#put-channels-id-subscribe
func (c *Client) SubscribeChannel(id string) (result ChannelResult, err error) {
response_ch := make(chan response)
c.queryQueue <- query{url: CHANNEL_API + "/" + id + "/subscribe", data: &result, method: "PUT", response_ch: response_ch}
return result, (<-response_ch).err
}
// Delete subscribe
// https://pnut.io/docs/resources/channels/subscribing#delete-channels-id-subscribe
func (c *Client) UnSubscribeChannel(id string) (result ChannelResult, err error) {
response_ch := make(chan response)
c.queryQueue <- query{url: CHANNEL_API + "/" + id + "/subscribe", data: &result, method: "DELETE", response_ch: response_ch}
return result, (<-response_ch).err
}
// Mute channel
// https://pnut.io/docs/resources/channels/muting#put-channels-id-mute
func (c *Client) MuteChannel(id string) (result ChannelResult, err error) {
response_ch := make(chan response)
c.queryQueue <- query{url: CHANNEL_API + "/" + id + "/mute", data: &result, method: "PUT", response_ch: response_ch}
return result, (<-response_ch).err
}
// Delete mute
// https://pnut.io/docs/resources/channels/muting#delete-channels-id-mute
func (c *Client) UnMuteChannel(id string) (result ChannelResult, err error) {
response_ch := make(chan response)
c.queryQueue <- query{url: CHANNEL_API + "/" + id + "/mute", data: &result, method: "DELETE", response_ch: response_ch}
return result, (<-response_ch).err
}
// Get message
// https://pnut.io/docs/resources/messages/lookup#get-channels-id-messages-id
func (c *Client) GetMessage(channelId string, messageId string) (result PostResult, err error) {
response_ch := make(chan response)
c.queryQueue <- query{url: CHANNEL_API + "/" + channelId + "/messages/" + messageId, data: &result, method: "GET", response_ch: response_ch}
return result, (<-response_ch).err
}
// Get messages in thread
// https://pnut.io/docs/resources/messages/lookup#get-channels-id-messages-id-thread
func (c *Client) GetMessagesInThread(channelId string, messageId string) (result PostsResult, err error) {
response_ch := make(chan response)
c.queryQueue <- query{url: CHANNEL_API + "/" + channelId + "/messages/" + messageId + "/thread", data: &result, method: "GET", response_ch: response_ch}
return result, (<-response_ch).err
}
// Get messages from ids
// https://pnut.io/docs/resources/messages/lookup#get-messages
func (c *Client) GetMessages(ids []string) (result PostsResult, err error) {
v := url.Values{}
v.Set("ids", strings.Join(ids, ","))
response_ch := make(chan response)
c.queryQueue <- query{url: CHANNEL_API + "/messages?" + v.Encode(), data: &result, method: "GET", response_ch: response_ch}
return result, (<-response_ch).err
}
// Get messages of channel
// https://pnut.io/docs/resources/messages/lookup#get-channels-id-messages
func (c *Client) GetMessagesOfChannel(id string) (result PostsResult, err error) {
response_ch := make(chan response)
c.queryQueue <- query{url: CHANNEL_API + "/" + id + "/messages", data: &result, method: "GET", response_ch: response_ch}
return result, (<-response_ch).err
}
// Post message
// https://pnut.io/docs/resources/messages/lifecycle#post-channels-id-messages
func (c *Client) CreateMessage(id string, text string) (result PostResult, err error) {
v := url.Values{}
v.Set("text", text)
response_ch := make(chan response)
c.queryQueue <- query{url: CHANNEL_API + "/" + id + "/messages", form: v, data: &result, method: "POST", response_ch: response_ch}
return result, (<-response_ch).err
}
// Delete message
// https://pnut.io/docs/resources/messages/lifecycle#delete-channels-id-messages-id
func (c *Client) DeleteMessage(channelId string, messageId string) (result PostResult, err error) {
response_ch := make(chan response)
c.queryQueue <- query{url: CHANNEL_API + "/" + channelId + "/messages/" + messageId, data: &result, method: "DELETE", response_ch: response_ch}
return result, (<-response_ch).err
}
// Get sticky messages
// https://pnut.io/docs/resources/messages/sticky#get-channels-id-sticky_messages
func (c *Client) GetStickyMessages(id string) (result PostsResult, err error) {
response_ch := make(chan response)
c.queryQueue <- query{url: CHANNEL_API + "/" + id + "/sticky_messages", data: &result, method: "GET", response_ch: response_ch}
return result, (<-response_ch).err
}
// Sticky message
// https://pnut.io/docs/resources/messages/sticky#put-channels-id-messages-id-sticky
func (c *Client) Sticky(channelId string, messageId string) (result PostResult, err error) {
response_ch := make(chan response)
c.queryQueue <- query{url: CHANNEL_API + "/" + channelId + "/messages/" + messageId + "/sticky", data: &result, method: "PUT", response_ch: response_ch}
return result, (<-response_ch).err
}
// Delete sticky
// https://pnut.io/docs/resources/messages/sticky#delete-channels-id-messages-id-sticky
func (c *Client) UnSticky(channelId string, messageId string) (result PostResult, err error) {
response_ch := make(chan response)
c.queryQueue <- query{url: CHANNEL_API + "/" + channelId + "/messages/" + messageId + "/sticky", data: &result, method: "DELETE", response_ch: response_ch}
return result, (<-response_ch).err
}
package peanuts
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"strconv"
"strings"
)
type Client struct {
clientId string
clientSecret string
passwordGrantSecret string
queryQueue chan query
Api Api
}
func NewClient(clientId string, clientSecret string) *Client {
queue := make(chan query)
client := &Client{clientId: clientId, clientSecret: clientSecret, queryQueue: queue}
client.initialize()
go client.throttledQuery()
return client
}
type query struct {
url string
form url.Values
data interface{}
method string
response_ch chan response
json string
redirect bool
}
type response struct {
data interface{}
err error
}
func (c *Client) initialize() {
c.Api = *&Api{
accessToken: "",
HttpClient: http.DefaultClient,
}
}
// Generate authorization url
// https://pnut.io/docs/authentication/web-flows
func (c *Client) AuthURL(redirectURI string, scope []string, responseType string) string {
return AUTHENTICATE_URL + "?client_id=" + c.clientId + "&redirect_uri=" + redirectURI + "&scope=" + strings.Join(scope, "%20") + "&response_type=" + responseType
}
// Set password grant secret
// https://pnut.io/docs/authentication/password-flow
func (c *Client) SetPasswordGrantSecret(passwordGrantSecret string) {
c.passwordGrantSecret = passwordGrantSecret
}
// Set access token
// https://pnut.io/docs/authentication/web-flows
// https://pnut.io/docs/authentication/password-flow
func (c *Client) SetAccessToken(accessToken string) {
c.Api.accessToken = accessToken
}
type StreamMeta struct {
More bool `json:"more"`
MaxId string `json:"max_id"`
MinId string `json:"min_id"`
}
type Meta struct {
*StreamMeta
Code int `json:"code"`
Error string `json:"error"`
ErrorMessage string `json:"error_message"`
}
type CommonResponse struct {
Meta Meta `json:"meta"`
}
func decodeResponse(res *http.Response, data interface{}) error {
b, err := ioutil.ReadAll(res.Body)
if err != nil {
return err
}
err = json.Unmarshal(b, data)
if err != nil {
return err
}
if res.StatusCode < 200 || res.StatusCode >= 300 {
common := &CommonResponse{}
err = json.Unmarshal(b, common)
if err != nil {
return err
}
return fmt.Errorf(strconv.Itoa(res.StatusCode) + ": " + common.Meta.ErrorMessage)
}
return nil
}
func (c *Client) execQuery(url string, form url.Values, data interface{}, method string, jsonStr string, redirect bool) (err error) {
var req *http.Request
if jsonStr == "" {
req, err = http.NewRequest(
method,
url,
strings.NewReader(form.Encode()),
)
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
} else {
req, err = http.NewRequest(
method,
url,
bytes.NewBuffer([]byte(jsonStr)),
)
req.Header.Set("Content-Type", "application/json")
}
if err != nil {
return err
}
if c.Api.accessToken != "" {
req.Header.Set("Authorization", "Bearer "+c.Api.accessToken)
}
if redirect {
res, err := http.DefaultTransport.RoundTrip(req)
if err != nil {
return err
}
defer res.Body.Close()
if res.StatusCode == 301 || res.StatusCode == 302 || res.StatusCode == 303 || res.StatusCode == 307 {
if len(res.Header["Location"]) > 0 {
// gross
// must fix with reflect
err = json.Unmarshal([]byte("{\"data\":\""+res.Header["Location"][0]+"\"}"), data)
return err
} else {
return fmt.Errorf("location is not found from header")
}
} else {
return fmt.Errorf(strconv.Itoa(res.StatusCode))
}
}
res, err := c.Api.HttpClient.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
return decodeResponse(res, data)
}
func (c *Client) throttledQuery() {
for q := range c.queryQueue {
url := q.url
form := q.form
data := q.data
method := q.method
jsonStr := q.json
redirect := q.redirect
response_ch := q.response_ch
err := c.execQuery(url, form, data, method, jsonStr, redirect)
response_ch <- response{data, err}
}
}
func notSupported() error {
return fmt.Errorf("not supported")
}
package peanuts
type ClientInfoResult struct {
*CommonResponse
Data ClientInfo `json:"data"`
}
// Get clinet
// https://pnut.io/docs/resources/clients#get-clients-id
func (c *Client) GetClient(id string) (result ClientInfosResult, err error) {
response_ch := make(chan response)
c.queryQueue <- query{url: CLIENT_API + "/" + id, data: &result, method: "GET", response_ch: response_ch}
return result, (<-response_ch).err
}
package peanuts
type Links struct {
Link string `json:"link"`
Text string `json:"text"`
Len int `json:"len"`
Pos int `json:"pos"`
Title string `json:"title"`
Description string `json:"description"`
}
type Mentions struct {
Id string `json:"id"`
Len int `json:"len"`
Pos int `json:"pos"`
Text string `json:"text"`
IsLeading bool `json:"is_leading"`
IsCopy bool `json:"is_copy"`
}
type Tags struct {
Len int `json:"len"`
Pos int `json:"pos"`
Text string `json:"text"`
}
type Entities struct {
Links []Links `json:"links"`
Mentions []Mentions `json:"mentions"`
Tags []Tags `json:"tags"`
}
package peanuts
type Image struct {
Link string `json:"link"`
IsDefault bool `json:"is_default"`
Width int `json:"width"`
Height int `json:"height"`
}
type ContentOfUser struct {
Text string `json:"text"`
Html string `json:"html"`
Entities Entities `json:"entities"`
AvatarImage Image `json:"avatar_image"`
CoverImage Image `json:"cover_image"`
}
type CountsOfUser struct {
Bookmarks int `json:"bookmarks"`
Clients int `json:"clients"`
Followers int `json:"followers"`
Following int `json:"following"`
Posts int `json:"posts"`
Users int `json:"users"`
}
type Verified struct {
Domain string `json:"domain"`
Link string `json:"link"`
}
type User struct {
CreatedAt string `json:"created_at"`
Guid string `json:"guid"`
Id string `json:"id"`
Locale string `json:"locale"`
Timezone string `json:"timezon"`
Type string `json:"type"`
Username string `json:"username"`
Name string `json:"name"`
Content ContentOfUser `json:"content"`
Counts CountsOfUser `json:"counts"`
FollowsYou bool `json:"follows_you"`
YouBlocked bool `json:"you_blocked"`
YouFollow bool `json:"you_follow"`
YouMuted bool `json:"you_muted"`
YouCanFollow bool `json:"you_can_follow"`
Verified Verified `json:"verified"`
}
type Source struct {
Name string `json:"name"`
Link string `json:"link"`
Id string `json:"id"`
}
type CountsOfPost struct {
Bookmarks int `json:"bookmarks"`
Replies int `json:"replies"`
Reposts int `json:"reposts"`
Threads int `json:"threads"`
}
type ContentOfPost struct {
Text string `json:"text"`
Html string `json:"html"`
Entities Entities `json:"entities"`
LinksNotParsed bool `json:"links_not_parsed"`
}
type Post struct {
CreatedAt string `json:"created_at"`
Guid string `json:"guid"`
Id string `json:"id"`
IsDeleted bool `json:"is_deleted"`
Source Source `json:"source"`
User User `json:"user"`
ThreadId string `json:"thread_id"`
IsRevised bool `json:"is_revised"`
Revision string `json:"revision"`
ReplyTo string `json:"reply_to"`
RepostOf *Post `json:"repost_of"`
Counts CountsOfPost `json:"counts"`
Content ContentOfPost `json:"content"`
YouBookmarked bool `json:"you_bookmarked"`
YouReposted bool `json:"you_reposted"`
PaginationId string `json:"pagination_id"`
}
type Action struct {
PaginationId string `json:"pagination_id"`
EventDate string `json:"event_date"`
Action string `json:"action"`
Users []User `json:"users"`
Objects []Post `json:"objects"`
}
type Presence struct {
Id string `json:"id"`
LastSeenAt string `json:"last_seen_at"`
Presence string `json:"presence"`
}
type Full struct {
Immutable bool `json:"immutable"`
You bool `json:"you"`
UserIds []string `json:"user_ids"`
}
type Write struct {
*Full
AnyUser bool `json:"any_user"`
}
type Read struct {
*Write
Public bool `json:"publicj"`
}
type Acl struct {
Full Full `json:"full"`
Write Write `json:"write"`
Read Read `json:"read"`
}
type CountsOfChannel struct {
Messages int `json:"messages"`
Subscribers int `json:"subscribers"`
}
type Channel struct {
CreatedAt string `json:"created_at"`
Id string `json:"id"`
Type string `json:"type"`
Owner User `json:"owner"`
Acl Acl `json:"acl"`
Counts CountsOfChannel `json:"counts"`
YouSubscribed bool `json:"you_subscribed"`
YouMuted bool `json:"you_muted"`
HasUnread bool `json:"has_unread"`
PaginationId string `json:"pagination_id"`
}
type CountsOfMessage struct {
Replies int `json:"replies"`
}
type ContentOfMessage struct {
Html string `json:"html"`
Text string `json:"text"`
Entities Entities `json:"entities"`
}
type Message struct {
Id string `json:"id"`
ChannelId string `json:"channel_id"`
CreatedAt string `json:"created_at"`
Source Source `json:"source"`
IsDeleted bool `json:"is_deleted"`
ThreadId string `json:"thread_id"`
User User `json:"user"`
Counts CountsOfMessage `json:"counts"`
Content ContentOfMessage `json:"content"`
PaginationId string `json:"pagination_id"`
}
type ContentOfClient struct {
*ContentOfMessage
}
type ClientInfo struct {
CreatedAt string `json:"created_at"`
CreatedBy User `json:"created_by"`
Id string `json:"id"`
Link string `json:"link"`
Name string `json:"name"`
Posts int `json:"posts"`
Content ContentOfClient `json:"content"`
}
type Marker struct {
Id string `json:"id"`
LastReadId string `json:"last_read_id"`
Percentage int `json:"percentage"`
UpdatedAt string `json:"updated_at"`
Version string `json:"version"`
Name string `json:"name"`
}
package peanuts
import (
"net/url"
"strconv"
"strings"
)
type PostResult struct {
*CommonResponse
Data Post `json:"data"`
}
type PostsResult struct {
*CommonResponse
Data []Post `json:"data"`
}
type ActionsResult struct {
*CommonResponse
Data []Action `json:"data"`
}
// Get post