1
0
Fork 0

Revert removing old version changes

This commit is contained in:
Maxim Lebedev 2024-08-05 16:16:24 +05:00
parent 87a95802ea
commit f4bd348774
Signed by: toby3d
GPG key ID: 1F14E25B7C119FC5
45 changed files with 1036 additions and 1262 deletions

View file

@ -1,28 +0,0 @@
package telegraph
// Account represents a Telegraph account.
type Account struct {
// Profile link, opened when users click on the author's name below the
// title. Can be any link, not necessarily to a Telegram profile or
// channel.
AuthorURL URL `json:"author_url"`
// Optional. URL to authorize a browser on [telegra.ph] and connect it to
// a Telegraph account. This URL is valid for only one use and for 5
// minutes only.
//
// [telegra.ph]: https://telegra.ph/
AuthURL *URL `json:"auth_url,omitempty"`
ShortName ShortName `json:"short_name"`
// Default author name used when creating new articles.
AuthorName AuthorName `json:"author_name"`
// Optional. Only returned by the [createAccount] and
// [revokeAccessToken] method. Access token of the Telegraph account.
AccessToken string `json:"access_token,omitempty"`
// Optional. Number of pages belonging to the Telegraph account.
PageCount uint `json:"page_count,omitempty"`
}

View file

@ -1,6 +0,0 @@
package telegraph
type Attributes struct {
Href string `json:"href,omitempty"`
Src string `json:"src,omitempty"`
}

View file

@ -1,67 +0,0 @@
package telegraph
import (
"errors"
"fmt"
"strconv"
"testing"
"source.toby3d.me/toby3d/telegraph/internal/util"
)
// AuthorName represent author name used when creating new articles.
type AuthorName struct {
authorName string // 0-128 characters
}
var ErrAuthorNameLength error = errors.New("unsupported length")
// NewAuthorName parse raw string as AuthorName and validate it's length.
func NewAuthorName(raw string) (*AuthorName, error) {
if err := util.ValidateLength(raw, -1, 128); err != nil {
return nil, fmt.Errorf("AuthorName: unsupported length: %w", err)
}
return &AuthorName{raw}, nil
}
// IsEmpty returns true if current [AuthorName] is empty.
func (an AuthorName) IsEmpty() bool { return an.authorName == "" }
func (an *AuthorName) UnmarshalJSON(v []byte) error {
unquoted, err := strconv.Unquote(string(v))
if err != nil {
return fmt.Errorf("AuthorName: UnmarshalJSON: cannot unquote value '%s': %w", string(v), err)
}
result, err := NewAuthorName(unquoted)
if err != nil {
return fmt.Errorf("AuthorName: UnmarshalJSON: cannot parse value '%s': %w", string(v), err)
}
*an = *result
return nil
}
func (an AuthorName) MarshalJSON() ([]byte, error) {
if an.authorName != "" {
return []byte(strconv.Quote(an.authorName)), nil
}
return nil, nil
}
func (an AuthorName) String() string {
return an.authorName
}
func (an AuthorName) GoString() string {
return "telegraph.AuthorName(" + an.String() + ")"
}
func TestAuthorName(tb testing.TB) *AuthorName {
tb.Helper()
return &AuthorName{"Anonymous"}
}

View file

@ -1,40 +0,0 @@
package telegraph_test
import (
"testing"
"source.toby3d.me/toby3d/telegraph"
)
func TestNewAuthorName(t *testing.T) {
t.Parallel()
f := func(name, input string) {
t.Run(name, func(t *testing.T) {
t.Parallel()
actual, err := telegraph.NewAuthorName(input)
if err != nil {
t.Fatal(err)
}
if actual.String() != input {
t.Errorf("want '%s', got '%s'", input, actual)
}
})
}
f("empty", "")
f("short", "L")
f("medium", telegraph.TestAuthorName(t).String())
f("long", "Pablo Diego José Francisco de Paula Juan Nepomuceno María de los Remedios Cipriano de la Santísima "+
"Trinidad Ruiz y Picasso")
}
func TestAuthorName_IsEmpty(t *testing.T) {
t.Parallel()
if actual, expect := (telegraph.AuthorName{}).IsEmpty(), true; actual != expect {
t.Errorf("want %t, got %t", expect, actual)
}
}

66
content.go Normal file
View file

@ -0,0 +1,66 @@
package telegraph
import (
"bytes"
"io"
"strings"
"golang.org/x/net/html"
)
// ContentFormat transforms data to a DOM-based format to represent the content of the page.
func ContentFormat(data interface{}) (n []Node, err error) {
var dst *html.Node
switch src := data.(type) {
case string:
dst, err = html.Parse(strings.NewReader(src))
case []byte:
dst, err = html.Parse(bytes.NewReader(src))
case io.Reader:
dst, err = html.Parse(src)
default:
return nil, ErrInvalidDataType
}
if err != nil {
return nil, err
}
n = append(n, domToNode(dst.FirstChild))
return n, nil
}
func domToNode(domNode *html.Node) interface{} {
if domNode.Type == html.TextNode {
return domNode.Data
}
if domNode.Type != html.ElementNode {
return nil
}
nodeElement := new(NodeElement)
switch strings.ToLower(domNode.Data) {
case "a", "aside", "b", "blockquote", "br", "code", "em", "figcaption", "figure", "h3", "h4", "hr", "i",
"iframe", "img", "li", "ol", "p", "pre", "s", "strong", "u", "ul", "video":
nodeElement.Tag = domNode.Data
for i := range domNode.Attr {
switch strings.ToLower(domNode.Attr[i].Key) {
case "href", "src":
nodeElement.Attrs = map[string]string{domNode.Attr[i].Key: domNode.Attr[i].Val}
default:
continue
}
}
}
for child := domNode.FirstChild; child != nil; child = child.NextSibling {
nodeElement.Children = append(nodeElement.Children, domToNode(child))
}
return nodeElement
}

28
content_test.go Normal file
View file

@ -0,0 +1,28 @@
package telegraph_test
import (
"testing"
"github.com/stretchr/testify/assert"
"source.toby3d.me/toby3d/telegraph"
)
func TestContentFormat(t *testing.T) {
t.Run("invalid", func(t *testing.T) {
_, err := telegraph.ContentFormat(42)
assert.EqualError(t, telegraph.ErrInvalidDataType, err.Error())
})
t.Run("valid", func(t *testing.T) {
t.Run("string", func(t *testing.T) {
validContentDOM, err := telegraph.ContentFormat(`<p>Hello, World!</p>`)
assert.NoError(t, err)
assert.NotEmpty(t, validContentDOM)
})
t.Run("bytes", func(t *testing.T) {
validContentDOM, err := telegraph.ContentFormat([]byte(`<p>Hello, World!</p>`))
assert.NoError(t, err)
assert.NotEmpty(t, validContentDOM)
})
})
}

View file

@ -1,36 +1,35 @@
package telegraph
import (
"bytes"
"context"
"encoding/json"
"fmt"
"net/http"
)
// CreateAccount create a new Telegraph account. Most users only need one
// account, but this can be useful for channel administrators who would like to
// keep individual author names and profile links for each of their channels. On
// success, returns an [Account] object with the regular fields and an
// additional access_token field.
type CreateAccount struct {
// Default profile link, opened when users click on the author's name
// below the title. Can be any link, not necessarily to a Telegram
// profile or channel.
AuthorURL *URL `json:"author_url,omitempty"` // 0-512 characters
type createAccount struct {
// Account name, helps users with several accounts remember which they are currently using. Displayed to the
// user above the "Edit/Publish" button on Telegra.ph, other users don't see this name.
ShortName string `json:"short_name"`
// Default author name used when creating new articles.
AuthorName *AuthorName `json:"author_name,omitempty"` // 0-128 characters
AuthorName string `json:"author_name,omitempty"`
// Required.
ShortName ShortName `json:"short_name"` // 1-32 characters
// Default profile link, opened when users click on the author's name below the title. Can be any link, not
// necessarily to a Telegram profile or channel.
AuthorURL string `json:"author_url,omitempty"`
}
func (params CreateAccount) Do(ctx context.Context, client *http.Client) (*Account, error) {
data, err := json.Marshal(params)
// CreateAccount create a new Telegraph account. Most users only need one account, but this can be useful for channel
// administrators who would like to keep individual author names and profile links for each of their channels. On
// success, returns an Account object with the regular fields and an additional access_token field.
func CreateAccount(account Account) (*Account, error) {
data, err := makeRequest("createAccount", createAccount{
ShortName: account.ShortName,
AuthorName: account.AuthorName,
AuthorURL: account.AuthorURL,
})
if err != nil {
return nil, fmt.Errorf("createAccount: cannot marshal request parameters: %w", err)
return nil, err
}
return post[*Account](ctx, client, bytes.NewReader(data), "createAccount")
}
result := new(Account)
if err = parser.Unmarshal(data, result); err != nil {
return nil, err
}
return result, nil
}

32
create_account_test.go Normal file
View file

@ -0,0 +1,32 @@
package telegraph_test
import (
"testing"
"github.com/stretchr/testify/assert"
"source.toby3d.me/toby3d/telegraph"
)
func TestCreateAccount(t *testing.T) {
t.Run("invalid", func(t *testing.T) {
t.Run("nil", func(t *testing.T) {
_, err := telegraph.CreateAccount(telegraph.Account{})
assert.Error(t, err)
})
t.Run("without shortname", func(t *testing.T) {
_, err := telegraph.CreateAccount(telegraph.Account{
ShortName: "",
AuthorName: "Anonymous",
})
assert.Error(t, err)
})
})
t.Run("valid", func(t *testing.T) {
account, err := telegraph.CreateAccount(telegraph.Account{
ShortName: "Sandbox",
AuthorName: "Anonymous",
})
assert.NoError(t, err)
assert.NotNil(t, account)
})
}

View file

@ -1,41 +1,44 @@
package telegraph
import (
"bytes"
"context"
"encoding/json"
"fmt"
"net/http"
)
// CreatePage create a new Telegraph page. On success, returns a [Page] object.
type CreatePage struct {
// Profile link, opened when users click on the author's name below the
// title. Can be any link, not necessarily to a Telegram profile or
// channel.
AuthorURL *URL `json:"author_url,omitempty"` // 0-512 characters
// Required. Access token of the Telegraph account.
type createPage struct {
// Access token of the Telegraph account.
AccessToken string `json:"access_token"`
// Required. Page title.
Title Title `json:"title"` // 1-256 characters
// Page title.
Title string `json:"title"`
// Author name, displayed below the article's title.
AuthorName *AuthorName `json:"author_name,omitempty"` // 0-128 characters
AuthorName string `json:"author_name,omitempty"`
// Required. Content of the page.
Content []Node `json:"content"` // up to 64 KB
// Profile link, opened when users click on the author's name below the title. Can be any link, not
// necessarily to a Telegram profile or channel.
AuthorURL string `json:"author_url,omitempty"`
// Content of the page.
Content []Node `json:"content"`
// If true, a content field will be returned in the Page object.
ReturnContent bool `json:"return_content,omitempty"` // false
ReturnContent bool `json:"return_content,omitempty"`
}
func (params CreatePage) Do(ctx context.Context, client *http.Client) (*Page, error) {
data, err := json.Marshal(params)
// CreatePage create a new Telegraph page. On success, returns a Page object.
func (a *Account) CreatePage(page Page, returnContent bool) (*Page, error) {
data, err := makeRequest("createPage", createPage{
AccessToken: a.AccessToken,
Title: page.Title,
AuthorName: page.AuthorName,
AuthorURL: page.AuthorURL,
Content: page.Content,
ReturnContent: returnContent,
})
if err != nil {
return nil, fmt.Errorf("createPage: cannot marshal request parameters: %w", err)
return nil, err
}
return post[*Page](ctx, client, bytes.NewReader(data), "createPage")
}
result := new(Page)
if err = parser.Unmarshal(data, &result); err != nil {
return nil, err
}
return result, nil
}

40
create_page_test.go Normal file
View file

@ -0,0 +1,40 @@
package telegraph_test
import (
"testing"
"github.com/stretchr/testify/assert"
"source.toby3d.me/toby3d/telegraph"
)
func TestCreatePage(t *testing.T) {
content, err := telegraph.ContentFormat(`<p>Hello, world!</p>`)
assert.NoError(t, err)
t.Run("invalid", func(t *testing.T) {
var a telegraph.Account
_, err := a.CreatePage(telegraph.Page{
Title: "Sample Page",
AuthorName: "Anonymous",
Content: content,
}, true)
assert.Error(t, err)
})
t.Run("valid", func(t *testing.T) {
a := telegraph.Account{
AccessToken: "b968da509bb76866c35425099bc0989a5ec3b32997d55286c657e6994bbb",
ShortName: "Sandbox",
AuthorName: "Anonymous",
}
page, err := a.CreatePage(telegraph.Page{
Title: "Sample Page",
AuthorName: "Anonymous",
Content: content,
}, true)
if !assert.NoError(t, err) {
t.FailNow()
}
assert.NotEmpty(t, page.URL)
})
}

33
doc.go
View file

@ -1,21 +1,14 @@
// The telegraph package contains the base bindings for working with the
// Telegraph API.
//
// Telegra.ph is a minimalist publishing tool that allows you to create richly
// formatted posts and push them to the Web in just a click. Telegraph posts
// also get beautiful [Instant View] pages on Telegram.
//
// To maintain the purity of the basic interface, we launched the [@Telegraph]
// bot for those who require advanced features. This bot can help you manage
// your articles across any number of devices and get page view statistics for
// any Telegraph page.
//
// Anyone can enjoy the simplicity of Telegraph publishing, not just [Telegram]
// users. For this reason, all developers are welcome to use this Telegraph API
// to create bots like [@Telegraph] for any other platform, or even standalone
// interfaces.
//
// [Instant View]: https://telegram.org/blog/instant-view
// [@Telegraph]: https://telegram.me/telegraph
// [Telegram]: https://telegram.org/
/*
Package telegraph has functions and types used for interacting with the Telegraph API.
Telegra.ph is a minimalist publishing tool that allows you to create richly formatted posts and push them to the Web
in just a click. Telegraph posts also get beautiful Instant View pages on Telegram.
To maintain the purity of the basic interface, we launched the @Telegraph bot for those who require advanced features.
This bot can help you manage your articles across any number of devices and get page view statistics for any Telegraph
page.
Anyone can enjoy the simplicity of Telegraph publishing, not just Telegram users. For this reason, all developers are
welcome to use this Telegraph API to create bots like @Telegraph for any other platform, or even standalone interfaces.
*/
package telegraph // import "source.toby3d.me/toby3d/telegraph"

View file

@ -1,38 +1,37 @@
package telegraph
import (
"bytes"
"context"
"encoding/json"
"fmt"
"net/http"
"net/url"
)
// EditAccountInfo update information about a Telegraph account. Pass only the
// parameters that you want to edit. On success, returns an [Account] object
// with the default fields.
type EditAccountInfo struct {
// New default profile link, opened when users click on the author's
// name below the title. Can be any link, not necessarily to a Telegram
// profile or channel.
AuthorURL *url.URL `json:"author_url,omitempty"` // 0-512 characters
type editAccountInfo struct {
// Access token of the Telegraph account.
AccessToken string `json:"access_token"`
// New account name.
ShortName *ShortName `json:"short_name,omitempty"` // 1-32 characters
ShortName string `json:"short_name,omitempty"`
// New default author name used when creating new articles.
AuthorName *AuthorName `json:"author_name,omitempty"` // 0-128 characters
AuthorName string `json:"author_name,omitempty"`
// Required. Access token of the Telegraph account.
AccessToken string `json:"access_token"`
// New default profile link, opened when users click on the author's name below the title. Can be any link,
// not necessarily to a Telegram profile or channel.
AuthorURL string `json:"author_url,omitempty"`
}
func (params EditAccountInfo) Do(ctx context.Context, client *http.Client) (*Account, error) {
data, err := json.Marshal(params)
// EditAccountInfo update information about a Telegraph account. Pass only the parameters that you want to edit. On
// success, returns an Account object with the default fields.
func (a *Account) EditAccountInfo(update Account) (*Account, error) {
data, err := makeRequest("editAccountInfo", editAccountInfo{
AccessToken: a.AccessToken,
ShortName: update.ShortName,
AuthorName: update.AuthorName,
AuthorURL: update.AuthorURL,
})
if err != nil {
return nil, fmt.Errorf("editAccountInfo: cannot marshal request parameters: %w", err)
return nil, err
}
return post[*Account](ctx, client, bytes.NewReader(data), "editAccountInfo")
}
result := new(Account)
if err = parser.Unmarshal(data, result); err != nil {
return nil, err
}
return result, nil
}

29
edit_account_info_test.go Normal file
View file

@ -0,0 +1,29 @@
package telegraph_test
import (
"testing"
"github.com/stretchr/testify/assert"
"source.toby3d.me/toby3d/telegraph"
)
func TestEditAccountInfo(t *testing.T) {
t.Run("invalid", func(t *testing.T) {
var a telegraph.Account
_, err := a.EditAccountInfo(telegraph.Account{})
assert.Error(t, err)
})
t.Run("valid", func(t *testing.T) {
a := telegraph.Account{
AccessToken: "b968da509bb76866c35425099bc0989a5ec3b32997d55286c657e6994bbb",
ShortName: "Sandbox",
AuthorName: "Anonymous",
}
_, err := a.EditAccountInfo(telegraph.Account{
ShortName: "Sandbox",
AuthorName: "Anonymous",
})
assert.NoError(t, err)
})
}

View file

@ -1,45 +1,52 @@
package telegraph
import (
"bytes"
"context"
"encoding/json"
"fmt"
"net/http"
"path"
)
// EditPage edit an existing Telegraph page. On success, returns a [Page]
// object.
type EditPage struct {
// Profile link, opened when users click on the author's name below the
// title. Can be any link, not necessarily to a Telegram profile or
// channel.
AuthorURL *URL `json:"author_url,omitempty"` // 0-512 characters
// Required. Access token of the Telegraph account.
type editPage struct {
// Access token of the Telegraph account.
AccessToken string `json:"access_token"`
// Required. Path to the page.
// Path to the page.
Path string `json:"path"`
// Required. Page title.
Title Title `json:"title"` // 1-256 characters
// Page title.
Title string `json:"title"`
// Content of the page.
Content []Node `json:"content"`
// Author name, displayed below the article's title.
AuthorName *AuthorName `json:"author_name,omitempty"` // 0-128 characters
AuthorName string `json:"author_name,omitempty"`
// Required. [Content] of the page.
Content []Node `json:"content"` // up to 64 KB
// Profile link, opened when users click on the author's name below the title. Can be any link, not
// necessarily to a Telegram profile or channel.
AuthorURL string `json:"author_url,omitempty"`
// If true, a content field will be returned in the [Page] object.
ReturnContent bool `json:"return_content,omitempty"` // false
// If true, a content field will be returned in the Page object.
ReturnContent bool `json:"return_content,omitempty"`
}
func (params EditPage) EditPage(ctx context.Context, client *http.Client) (*Page, error) {
data, err := json.Marshal(params)
// EditPage edit an existing Telegraph page. On success, returns a Page object.
func (a *Account) EditPage(update Page, returnContent bool) (*Page, error) {
data, err := makeRequest(path.Join("editPage", update.Path), editPage{
AccessToken: a.AccessToken,
Path: update.Path,
Title: update.Title,
Content: update.Content,
AuthorName: update.AuthorName,
AuthorURL: update.AuthorURL,
ReturnContent: returnContent,
})
if err != nil {
return nil, fmt.Errorf("editPage: cannot marshal request parameters: %w", err)
return nil, err
}
return post[*Page](ctx, client, bytes.NewReader(data), "editPage", params.Path)
}
result := new(Page)
if err = parser.Unmarshal(data, result); err != nil {
return nil, err
}
return result, nil
}

39
edit_page_test.go Normal file
View file

@ -0,0 +1,39 @@
package telegraph_test
import (
"testing"
"github.com/stretchr/testify/assert"
"source.toby3d.me/toby3d/telegraph"
)
func TestEditPage(t *testing.T) {
content, err := telegraph.ContentFormat("<p>Hello, world!</p>")
assert.NoError(t, err)
t.Run("invalid", func(t *testing.T) {
var a telegraph.Account
_, err := a.EditPage(telegraph.Page{
Title: "Sample Page",
AuthorName: "Anonymous",
Content: content,
}, true)
assert.Error(t, err)
})
t.Run("valid", func(t *testing.T) {
a := telegraph.Account{
AccessToken: "b968da509bb76866c35425099bc0989a5ec3b32997d55286c657e6994bbb",
ShortName: "Sandbox",
AuthorName: "Anonymous",
}
page, err := a.EditPage(telegraph.Page{
Path: "Sample-Page-12-15",
Title: "Sample Page",
AuthorName: "Anonymous",
Content: content,
}, true)
assert.NoError(t, err)
assert.NotEmpty(t, page.Content)
})
}

193
example_test.go Normal file
View file

@ -0,0 +1,193 @@
package telegraph_test
import (
"log"
"time"
"source.toby3d.me/toby3d/telegraph"
)
// Content in a string format (for this example).
// Be sure to wrap every media in a <figure> tag, okay? Be easy.
const data = `
<figure>
<img src="/file/6a5b15e7eb4d7329ca7af.jpg"/>
</figure>
<p><i>Hello</i>, my name is <b>Page</b>, <u>look at me</u>!</p>
<figure>
<iframe src="https://youtu.be/fzQ6gRAEoy0"></iframe>
<figcaption>
Yes, you can embed youtube, vimeo and twitter widgets too!
</figcaption>
</figure>
`
var (
account *telegraph.Account //nolint:gochecknoglobals
page *telegraph.Page //nolint:gochecknoglobals
content []telegraph.Node //nolint:gochecknoglobals
)
func errCheck(err error) {
if err != nil {
log.Fatalln(err.Error())
}
}
func Example_fastStart() {
var err error
// Create new Telegraph account.
requisites := telegraph.Account{
ShortName: "toby3d", // required
// Author name/link can be epmty. So secure. Much anonymously. Wow.
AuthorName: "Maxim Lebedev", // optional
AuthorURL: "https://t.me/toby3d", // optional
}
account, err = telegraph.CreateAccount(requisites)
errCheck(err)
// Make sure that you have saved acc.AuthToken for create new pages or make
// other actions by this account in next time!
// Format content to []telegraph.Node array. Input data can be string, []byte
// or io.Reader.
content, err = telegraph.ContentFormat(data)
errCheck(err)
// Boom!.. And your text will be understandable for Telegraph. MAGIC.
// Create new Telegraph page
pageData := telegraph.Page{
Title: "My super-awesome page", // required
Content: content, // required
// Not necessarily, but, hey, it's just an example.
AuthorName: account.AuthorName, // optional
AuthorURL: account.AuthorURL, // optional
}
page, err = account.CreatePage(pageData, false)
errCheck(err)
// Show link from response on created page.
log.Println("Kaboom! Page created, look what happened:", page.URL)
}
func ExampleCreateAccount() {
var err error
account, err = telegraph.CreateAccount(telegraph.Account{
ShortName: "Sandbox",
AuthorName: "Anonymous",
})
errCheck(err)
log.Println("AccessToken:", account.AccessToken)
log.Println("AuthURL:", account.AuthorURL)
log.Println("ShortName:", account.ShortName)
log.Println("AuthorName:", account.AuthorName)
}
func ExampleAccount_EditAccountInfo() {
var err error
account, err = account.EditAccountInfo(telegraph.Account{
ShortName: "Sandbox",
AuthorName: "Anonymous",
})
errCheck(err)
log.Println("AuthURL:", account.AuthorURL)
log.Println("ShortName:", account.ShortName)
log.Println("AuthorName:", account.AuthorName)
}
func ExampleAccount_GetAccountInfo() {
info, err := account.GetAccountInfo(
telegraph.FieldShortName,
telegraph.FieldPageCount,
)
errCheck(err)
log.Println("ShortName:", info.ShortName)
log.Println("PageCount:", info.PageCount, "pages")
}
func ExampleAccount_RevokeAccessToken() {
var err error
// You must rewrite current variable with account structure for further usage.
account, err = account.RevokeAccessToken()
errCheck(err)
log.Println("AccessToken:", account.AccessToken)
}
func ExampleAccount_CreatePage() {
var err error
page, err = account.CreatePage(telegraph.Page{
Title: "Sample Page",
AuthorName: account.AuthorName,
Content: content,
}, true)
errCheck(err)
log.Println(page.Title, "by", page.AuthorName, "has been created!")
log.Println("PageURL:", page.URL)
}
func ExampleAccount_EditPage() {
var err error
page, err = account.EditPage(telegraph.Page{
Title: "Sample Page",
AuthorName: account.AuthorName,
Content: content,
}, true)
errCheck(err)
log.Println("Page on", page.Path, "path has been updated!")
log.Println("PageURL:", page.URL)
}
func ExampleGetPage() {
info, err := telegraph.GetPage("Sample-Page-12-15", true)
errCheck(err)
log.Println("Getted info about", info.Path, "page:")
log.Println("Author:", info.AuthorName)
log.Println("Views:", info.Views)
log.Println("CanEdit:", info.CanEdit)
}
func ExampleAccount_GetPageList() {
list, err := account.GetPageList(0, 3)
errCheck(err)
log.Println("Getted", list.TotalCount, "pages")
for i := range list.Pages {
p := list.Pages[i]
log.Printf("%s: %s\n~ %s\n\n", p.Title, p.URL, p.Description)
}
}
func ExampleGetViews() {
pagePath := "Sample-Page-12-15"
dateTime := time.Date(2016, time.December, 0, 0, 0, 0, 0, time.UTC)
views, err := telegraph.GetViews(pagePath, dateTime)
errCheck(err)
log.Println(pagePath, "has been viewed", views.Views, "times")
}
func ExampleContentFormat() {
const data = `<figure>
<img src="http://telegra.ph/file/6a5b15e7eb4d7329ca7af.jpg" /></figure>
<p><i>Hello</i>, my name is <b>Page</b>, <u>look at me</u>!</p>
<figure><iframe src="https://youtu.be/fzQ6gRAEoy0"></iframe>
<figcaption>Yes, you can embed youtube, vimeo and twitter widgets too!</figcaption>
</figure>`
var err error
content, err = telegraph.ContentFormat(data)
errCheck(err)
log.Printf("Content: %#v", content)
}

View file

@ -1,58 +1,27 @@
package telegraph
import (
"context"
"net/http"
"net/url"
"strconv"
"strings"
)
type getAccountInfo struct {
// Access token of the Telegraph account.
AccessToken string `json:"access_token"`
type (
// GetAccountInfo get information about a Telegraph account. Returns an
// [Account] object on success.
GetAccountInfo struct {
// Required. Access token of the Telegraph account.
AccessToken string `json:"access_token"`
// List of account fields to return.
Fields []AccountField `json:"fields,omitempty"` // ["short_name","author_name","author_url"]
}
AccountField struct{ accountField string }
)
var (
AuthorNameField AccountField = AccountField{"author_name"}
AuthorURLField AccountField = AccountField{"author_url"}
AuthURLField AccountField = AccountField{"auth_url"}
PageCountField AccountField = AccountField{"page_count"}
ShortNameField AccountField = AccountField{"short_name"}
)
func (params GetAccountInfo) Do(ctx context.Context, client *http.Client) (*Account, error) {
data := make(url.Values)
params.populate(data)
return get[*Account](ctx, client, data, "getAccountInfo")
// List of account fields to return.
Fields []string `json:"fields,omitempty"`
}
func (p GetAccountInfo) populate(dst url.Values) {
dst.Set("access_token", p.AccessToken)
if len(p.Fields) == 0 {
return
// GetAccountInfo get information about a Telegraph account. Returns an Account object on success.
func (a *Account) GetAccountInfo(fields ...string) (*Account, error) {
data, err := makeRequest("getAccountInfo", getAccountInfo{
AccessToken: a.AccessToken,
Fields: fields,
})
if err != nil {
return nil, err
}
values := make([]string, 0, len(p.Fields))
for i := range p.Fields {
if p.Fields[i].accountField == "" {
continue
}
values = append(values, strconv.Quote(p.Fields[i].accountField))
result := new(Account)
if err = parser.Unmarshal(data, result); err != nil {
return nil, err
}
dst.Set("fields", "["+strings.Join(values, ",")+"]")
}
return result, nil
}

28
get_account_info_test.go Normal file
View file

@ -0,0 +1,28 @@
package telegraph_test
import (
"testing"
"github.com/stretchr/testify/assert"
"source.toby3d.me/toby3d/telegraph"
)
func TestGetAccountInfo(t *testing.T) {
t.Run("invalid", func(t *testing.T) {
var a telegraph.Account
_, err := a.GetAccountInfo()
assert.Error(t, err)
})
t.Run("valid", func(t *testing.T) {
a := telegraph.Account{
AccessToken: "b968da509bb76866c35425099bc0989a5ec3b32997d55286c657e6994bbb",
ShortName: "Sandbox",
AuthorName: "Anonymous",
}
info, err := a.GetAccountInfo(telegraph.FieldShortName, telegraph.FieldPageCount)
assert.NoError(t, err)
assert.Equal(t, a.ShortName, info.ShortName)
assert.NotZero(t, info.PageCount)
})
}

View file

@ -1,27 +1,31 @@
package telegraph
import (
"context"
"net/http"
"net/url"
gopath "path"
)
// GetPage get a Telegraph page. Returns a [Page] object on success.
type GetPage struct {
// Required. Path to the Telegraph page (in the format Title-12-31, i.e.
// everything that comes after http://telegra.ph/).
type getPage struct {
// Path to the Telegraph page (in the format Title-12-31, i.e. everything that comes after http://telegra.ph/).
Path string `json:"path"`
// If true, content field will be returned in [Page] object.
// If true, content field will be returned in Page object.
ReturnContent bool `json:"return_content,omitempty"`
}
func (params GetPage) Do(ctx context.Context, client *http.Client) (*Page, error) {
data := make(url.Values)
if params.ReturnContent {
data.Set("return_content", "true")
// GetPage get a Telegraph page. Returns a Page object on success.
func GetPage(path string, returnContent bool) (*Page, error) {
data, err := makeRequest(gopath.Join("getPage", path), getPage{
Path: path,
ReturnContent: returnContent,
})
if err != nil {
return nil, err
}
return get[*Page](ctx, client, data, "getPage", params.Path)
}
result := new(Page)
if err = parser.Unmarshal(data, result); err != nil {
return nil, err
}
return result, nil
}

View file

@ -1,33 +1,32 @@
package telegraph
import (
"context"
"net/http"
"net/url"
"strconv"
)
// GetPageList get a list of pages belonging to a Telegraph account. Returns a
// [PageList] object, sorted by most recently created pages first.
type GetPageList struct {
// Required. Access token of the Telegraph account.
type getPageList struct {
// Access token of the Telegraph account.
AccessToken string `json:"access_token"`
// Sequential number of the first page to be returned.
Offset uint `json:"offset,omitempty"` // 0
Offset int `json:"offset,omitempty"`
// Limits the number of pages to be retrieved.
Limit uint16 `json:"limit,omitempty"` // 50 (0-200)
Limit int `json:"limit,omitempty"`
}
func (params GetPageList) Do(ctx context.Context, client *http.Client) (*PageList, error) {
data := make(url.Values)
data.Set("access_token", params.AccessToken)
data.Set("offset", strconv.FormatUint(uint64(params.Offset), 10))
if params.Limit <= 200 {
data.Set("limit", strconv.FormatUint(uint64(params.Limit), 10))
// GetPageList get a list of pages belonging to a Telegraph account. Returns a PageList object, sorted by most
// recently created pages first.
func (a *Account) GetPageList(offset, limit int) (*PageList, error) {
data, err := makeRequest("getPageList", getPageList{
AccessToken: a.AccessToken,
Offset: offset,
Limit: limit,
})
if err != nil {
return nil, err
}
return get[*PageList](ctx, client, data, "getPageList")
}
result := new(PageList)
if err = parser.Unmarshal(data, result); err != nil {
return nil, err
}
return result, nil
}

27
get_page_list_test.go Normal file
View file

@ -0,0 +1,27 @@
package telegraph_test
import (
"testing"
"github.com/stretchr/testify/assert"
"source.toby3d.me/toby3d/telegraph"
)
func TestGetPageList(t *testing.T) {
t.Run("invalid", func(t *testing.T) {
var a telegraph.Account
_, err := a.GetPageList(0, 0)
assert.Error(t, err)
})
t.Run("valid", func(t *testing.T) {
a := telegraph.Account{
AccessToken: "b968da509bb76866c35425099bc0989a5ec3b32997d55286c657e6994bbb",
ShortName: "Sandbox",
AuthorName: "Anonymous",
}
list, err := a.GetPageList(1, 1)
assert.NoError(t, err)
assert.NotNil(t, list)
})
}

20
get_page_test.go Normal file
View file

@ -0,0 +1,20 @@
package telegraph_test
import (
"testing"
"github.com/stretchr/testify/assert"
"source.toby3d.me/toby3d/telegraph"
)
func TestGetPage(t *testing.T) {
t.Run("invalid", func(t *testing.T) {
_, err := telegraph.GetPage("wtf", true)
assert.Error(t, err)
})
t.Run("valid", func(t *testing.T) {
page, err := telegraph.GetPage("Sample-Page-12-15", true)
assert.NoError(t, err)
assert.NotNil(t, page)
})
}

View file

@ -1,75 +1,50 @@
package telegraph
import (
"context"
"net/http"
"net/url"
"strconv"
gopath "path"
"time"
)
// GetViews get the number of views for a Telegraph article. Returns a
// [PageViews] object on success. By default, the total number of page views
// will be returned.
type GetViews struct {
// Required. Path to the Telegraph page (in the format Title-12-31,
// where 12 is the month and 31 the day the article was first
// published).
Path string `json:"-"`
type getViews struct {
// Path to the Telegraph page (in the format Title-12-31, where 12 is the month and 31 the day the article was
// first published).
Path string `json:"path"`
// Required if month is passed. If passed, the number of page views for
// the requested year will be returned.
Year uint16 `json:"year,omitempty"` // 2000-2100
// Required if month is passed. If passed, the number of page views for the requested year will be returned.
Year int `json:"year,omitempty"`
// Required if day is passed. If passed, the number of page views for
// the requested month will be returned.
Month uint8 `json:"month,omitempty"` // 1-12
// Required if day is passed. If passed, the number of page views for the requested month will be returned.
Month int `json:"month,omitempty"`
// Required if hour is passed. If passed, the number of page views for
// the requested day will be returned.
Day uint8 `json:"day,omitempty"` // 1-31
// Required if hour is passed. If passed, the number of page views for the requested day will be returned.
Day int `json:"day,omitempty"`
// If passed, the number of page views for the requested hour will be
// returned.
Hour uint8 `json:"hour,omitempty"` // 0-24
// If passed, the number of page views for the requested hour will be returned.
Hour int `json:"hour,omitempty"`
}
func (params GetViews) Do(ctx context.Context, client *http.Client) (*PageViews, error) {
data := make(url.Values)
// GetViews get the number of views for a Telegraph article. By default, the total number of page views will be
// returned. Returns a PageViews object on success.
func GetViews(path string, date time.Time) (*PageViews, error) {
p := new(getViews)
p.Path = path
switch {
case 0 < params.Year:
if params.Year < 2000 {
params.Year = 2000
} else if 2100 < params.Year {
params.Year = 2100
}
data.Set("year", strconv.FormatUint(uint64(params.Year), 10))
fallthrough
case 0 < params.Month:
if 12 < params.Month {
params.Month = 12
}
data.Set("month", strconv.FormatUint(uint64(params.Month), 10))
fallthrough
case 0 < params.Day:
if 31 < params.Day {
params.Day = 31
}
data.Set("day", strconv.FormatUint(uint64(params.Day), 10))
fallthrough
case 0 < params.Hour:
if 24 < params.Hour {
params.Hour = 24
}
data.Set("hour", strconv.FormatUint(uint64(params.Hour), 10))
if !date.IsZero() {
p.Year = date.Year()
p.Month = int(date.Month())
p.Day = date.Day()
p.Hour = date.Hour()
}
return get[*PageViews](ctx, client, data, "getViews", params.Path)
}
data, err := makeRequest(gopath.Join("getViews", path), p)
if err != nil {
return nil, err
}
result := new(PageViews)
if err = parser.Unmarshal(data, &result); err != nil {
return nil, err
}
return result, nil
}

51
get_views_test.go Normal file
View file

@ -0,0 +1,51 @@
package telegraph_test
import (
"testing"
"time"
"github.com/stretchr/testify/assert"
"source.toby3d.me/toby3d/telegraph"
)
func TestGetViews(t *testing.T) {
t.Run("invalid", func(t *testing.T) {
t.Run("path", func(t *testing.T) {
_, err := telegraph.GetViews("wtf", time.Time{})
assert.Error(t, err)
})
t.Run("year", func(t *testing.T) {
dt := time.Date(1980, 0, 0, 0, 0, 0, 0, time.UTC)
_, err := telegraph.GetViews("Sample-Page-12-15", dt)
assert.Error(t, err)
})
t.Run("month", func(t *testing.T) {
dt := time.Date(2000, 22, 0, 0, 0, 0, 0, time.UTC)
result, err := telegraph.GetViews("Sample-Page-12-15", dt)
assert.NoError(t, err)
assert.NotNil(t, result)
})
t.Run("day", func(t *testing.T) {
dt := time.Date(2000, time.February, 42, 0, 0, 0, 0, time.UTC)
result, err := telegraph.GetViews("Sample-Page-12-15", dt)
assert.NoError(t, err)
assert.NotNil(t, result)
})
t.Run("hour", func(t *testing.T) {
dt := time.Date(2000, time.February, 12, 65, 0, 0, 0, time.UTC)
result, err := telegraph.GetViews("Sample-Page-12-15", dt)
assert.NoError(t, err)
assert.NotNil(t, result)
})
})
t.Run("valid", func(t *testing.T) {
dt := time.Date(2016, time.December, 31, 0, 0, 0, 0, time.UTC)
stats, err := telegraph.GetViews("Sample-Page-12-15", dt)
assert.NoError(t, err)
if !assert.NotNil(t, stats) {
t.FailNow()
}
assert.NotZero(t, stats.Views)
})
}

11
go.mod
View file

@ -1,8 +1,13 @@
module source.toby3d.me/toby3d/telegraph
require (
github.com/google/go-cmp v0.6.0
golang.org/x/net v0.27.0
github.com/json-iterator/go v1.1.10
github.com/klauspost/compress v1.10.9 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.1 // indirect
github.com/stretchr/testify v1.3.0
github.com/valyala/fasthttp v1.14.0
golang.org/x/net v0.0.0-20200602114024-627f9648deb9
)
go 1.22.0
go 1.13

38
go.sum
View file

@ -1,4 +1,34 @@
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys=
golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE=
github.com/andybalholm/brotli v1.0.0 h1:7UCwP93aiSfvWpapti8g88vVVGp2qqtGyePsSuDafo4=
github.com/andybalholm/brotli v1.0.0/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/json-iterator/go v1.1.10 h1:Kz6Cvnvv2wGdaG/V8yMvfkmNiXq9Ya2KUv4rouJJr68=
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/klauspost/compress v1.10.4/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
github.com/klauspost/compress v1.10.9 h1:pPRt1Z78crspaHISkpSSHjDlx+Tt9suHe519dsI0vF4=
github.com/klauspost/compress v1.10.9/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasthttp v1.14.0 h1:67bfuW9azCMwW/Jlq/C+VeihNpAuJMWkYPBig1gdi3A=
github.com/valyala/fasthttp v1.14.0/go.mod h1:ol1PCaL0dX20wC0htZ7sYCsvCYmrouYra0zHzaclZhE=
github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200602114024-627f9648deb9 h1:pNX+40auqi2JqRfOP1akLGtYcn15TUbkhwuCO3foqqM=
golang.org/x/net v0.0.0-20200602114024-627f9648deb9/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=

70
node.go
View file

@ -1,70 +0,0 @@
package telegraph
import (
"encoding/json"
"fmt"
"strconv"
)
// Node represent abstract object represents a DOM Node. It can be a String
// which represents a DOM text node or a [NodeElement] object.
type Node struct {
Element *NodeElement `json:"-"`
Text string `json:"-"`
}
func (n *Node) UnmarshalJSON(v []byte) error {
switch v[0] {
case '{':
nodeElement := new(NodeElement)
if err := json.Unmarshal(v, nodeElement); err != nil {
return fmt.Errorf("Node: UnmarshalJSON: cannot unmarshal NodeElement: %w", err)
}
n.Element = nodeElement
case '"':
unquoted, err := strconv.Unquote(string(v))
if err != nil {
return fmt.Errorf("Node: UnmarshalJSON: cannot unquote string: %w", err)
}
n.Text = unquoted
}
return nil
}
func (n Node) MarshalJSON() ([]byte, error) {
switch {
default:
return nil, nil
case n.Text != "":
return []byte(strconv.Quote(n.Text)), nil
case n.Element != nil:
result, err := json.Marshal(n.Element)
if err != nil {
return nil, fmt.Errorf("Node: MarshalJSON: cannot encode as Element: %w", err)
}
return result, nil
}
}
func (n Node) String() string {
switch {
default:
return ""
case n.Text != "":