initial commit
commit
6358d0754b
|
@ -0,0 +1,3 @@
|
|||
.idea
|
||||
/.tdlib
|
||||
/.cmd
|
|
@ -0,0 +1,20 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2018-2019 Aleksandr Zelenin
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
@ -0,0 +1,19 @@
|
|||
TAG := v1.7.0
|
||||
|
||||
schema-update:
|
||||
curl https://raw.githubusercontent.com/tdlib/td/${TAG}/td/generate/scheme/td_api.tl 2>/dev/null > ./data/td_api.tl
|
||||
|
||||
generate-json:
|
||||
go run ./cmd/generate-json.go \
|
||||
-version "${TAG}" \
|
||||
-output "./data/td_api.json"
|
||||
|
||||
generate-code:
|
||||
go run ./cmd/generate-code.go \
|
||||
-version "${TAG}" \
|
||||
-outputDir "./client" \
|
||||
-package client \
|
||||
-functionFile function.go \
|
||||
-typeFile type.go \
|
||||
-unmarshalerFile unmarshaler.go
|
||||
go fmt ./...
|
|
@ -0,0 +1,127 @@
|
|||
# go-tdlib
|
||||
|
||||
Go wrapper for [TDLib (Telegram Database Library)](https://github.com/tdlib/td) with full support of TDLib v1.7.0
|
||||
|
||||
## TDLib installation
|
||||
|
||||
Use [TDLib build instructions](https://tdlib.github.io/td/build.html)
|
||||
|
||||
## Usage
|
||||
|
||||
### Client
|
||||
|
||||
[Register an application](https://my.telegram.org/apps) to obtain an api_id and api_hash
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/zelenin/go-tdlib/client"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// client authorizer
|
||||
authorizer := client.ClientAuthorizer()
|
||||
go client.CliInteractor(authorizer)
|
||||
|
||||
// or bot authorizer
|
||||
// botToken := "000000000:gsVCGG5YbikxYHC7bP5vRvmBqJ7Xz6vG6td"
|
||||
// authorizer := client.BotAuthorizer(botToken)
|
||||
|
||||
const (
|
||||
apiId = 00000
|
||||
apiHash = "8pu9yg32qkuukj83ozaqo5zzjwhkxhnk"
|
||||
)
|
||||
|
||||
authorizer.TdlibParameters <- &client.TdlibParameters{
|
||||
UseTestDc: false,
|
||||
DatabaseDirectory: filepath.Join(".tdlib", "database"),
|
||||
FilesDirectory: filepath.Join(".tdlib", "files"),
|
||||
UseFileDatabase: true,
|
||||
UseChatInfoDatabase: true,
|
||||
UseMessageDatabase: true,
|
||||
UseSecretChats: false,
|
||||
ApiId: apiId,
|
||||
ApiHash: apiHash,
|
||||
SystemLanguageCode: "en",
|
||||
DeviceModel: "Server",
|
||||
SystemVersion: "1.0.0",
|
||||
ApplicationVersion: "1.0.0",
|
||||
EnableStorageOptimizer: true,
|
||||
IgnoreFileNames: false,
|
||||
}
|
||||
|
||||
logVerbosity := client.WithLogVerbosity(&client.SetLogVerbosityLevelRequest{
|
||||
NewVerbosityLevel: 0,
|
||||
})
|
||||
|
||||
tdlibClient, err := client.NewClient(authorizer, logVerbosity)
|
||||
if err != nil {
|
||||
log.Fatalf("NewClient error: %s", err)
|
||||
}
|
||||
|
||||
optionValue, err := tdlibClient.GetOption(&client.GetOptionRequest{
|
||||
Name: "version",
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatalf("GetOption error: %s", err)
|
||||
}
|
||||
|
||||
log.Printf("TDLib version: %s", optionValue.(*client.OptionValueString).Value)
|
||||
|
||||
me, err := tdlibClient.GetMe()
|
||||
if err != nil {
|
||||
log.Fatalf("GetMe error: %s", err)
|
||||
}
|
||||
|
||||
log.Printf("Me: %s %s [%s]", me.FirstName, me.LastName, me.Username)
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
### Receive updates
|
||||
|
||||
```go
|
||||
tdlibClient, err := client.NewClient(authorizer)
|
||||
if err != nil {
|
||||
log.Fatalf("NewClient error: %s", err)
|
||||
}
|
||||
|
||||
listener := tdlibClient.GetListener()
|
||||
defer listener.Close()
|
||||
|
||||
for update := range listener.Updates {
|
||||
if update.GetClass() == client.ClassUpdate {
|
||||
log.Printf("%#v", update)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Proxy support
|
||||
|
||||
```go
|
||||
proxy := client.WithProxy(&client.AddProxyRequest{
|
||||
Server: "1.1.1.1",
|
||||
Port: 1080,
|
||||
Enable: true,
|
||||
Type: &client.ProxyTypeSocks5{
|
||||
Username: "username",
|
||||
Password: "password",
|
||||
},
|
||||
})
|
||||
|
||||
tdlibClient, err := client.NewClient(authorizer, proxy)
|
||||
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
* WIP. Library API can be changed in the future
|
||||
* The package includes a .tl-parser and generated [json-schema](https://github.com/zelenin/go-tdlib/tree/master/data) for creating libraries in other languages
|
||||
|
||||
## Author
|
||||
|
||||
[Aleksandr Zelenin](https://github.com/zelenin/), e-mail: [aleksandr@zelenin.me](mailto:aleksandr@zelenin.me)
|
|
@ -0,0 +1,233 @@
|
|||
package client
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"golang.org/x/crypto/ssh/terminal"
|
||||
)
|
||||
|
||||
var ErrNotSupportedAuthorizationState = errors.New("not supported state")
|
||||
|
||||
type AuthorizationStateHandler interface {
|
||||
Handle(client *Client, state AuthorizationState) error
|
||||
Close()
|
||||
}
|
||||
|
||||
func Authorize(client *Client, authorizationStateHandler AuthorizationStateHandler) error {
|
||||
defer authorizationStateHandler.Close()
|
||||
|
||||
var authorizationError error
|
||||
|
||||
for {
|
||||
state, err := client.GetAuthorizationState()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if state.AuthorizationStateType() == TypeAuthorizationStateClosed {
|
||||
return authorizationError
|
||||
}
|
||||
|
||||
if state.AuthorizationStateType() == TypeAuthorizationStateReady {
|
||||
// dirty hack for db flush after authorization
|
||||
time.Sleep(1 * time.Second)
|
||||
return nil
|
||||
}
|
||||
|
||||
err = authorizationStateHandler.Handle(client, state)
|
||||
if err != nil {
|
||||
authorizationError = err
|
||||
client.Close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type clientAuthorizer struct {
|
||||
TdlibParameters chan *TdlibParameters
|
||||
PhoneNumber chan string
|
||||
Code chan string
|
||||
State chan AuthorizationState
|
||||
Password chan string
|
||||
}
|
||||
|
||||
func ClientAuthorizer() *clientAuthorizer {
|
||||
return &clientAuthorizer{
|
||||
TdlibParameters: make(chan *TdlibParameters, 1),
|
||||
PhoneNumber: make(chan string, 1),
|
||||
Code: make(chan string, 1),
|
||||
State: make(chan AuthorizationState, 10),
|
||||
Password: make(chan string, 1),
|
||||
}
|
||||
}
|
||||
|
||||
func (stateHandler *clientAuthorizer) Handle(client *Client, state AuthorizationState) error {
|
||||
stateHandler.State <- state
|
||||
|
||||
switch state.AuthorizationStateType() {
|
||||
case TypeAuthorizationStateWaitTdlibParameters:
|
||||
_, err := client.SetTdlibParameters(&SetTdlibParametersRequest{
|
||||
Parameters: <-stateHandler.TdlibParameters,
|
||||
})
|
||||
return err
|
||||
|
||||
case TypeAuthorizationStateWaitEncryptionKey:
|
||||
_, err := client.CheckDatabaseEncryptionKey(&CheckDatabaseEncryptionKeyRequest{})
|
||||
return err
|
||||
|
||||
case TypeAuthorizationStateWaitPhoneNumber:
|
||||
_, err := client.SetAuthenticationPhoneNumber(&SetAuthenticationPhoneNumberRequest{
|
||||
PhoneNumber: <-stateHandler.PhoneNumber,
|
||||
Settings: &PhoneNumberAuthenticationSettings{
|
||||
AllowFlashCall: false,
|
||||
IsCurrentPhoneNumber: false,
|
||||
AllowSmsRetrieverApi: false,
|
||||
},
|
||||
})
|
||||
return err
|
||||
|
||||
case TypeAuthorizationStateWaitCode:
|
||||
_, err := client.CheckAuthenticationCode(&CheckAuthenticationCodeRequest{
|
||||
Code: <-stateHandler.Code,
|
||||
})
|
||||
return err
|
||||
|
||||
case TypeAuthorizationStateWaitRegistration:
|
||||
return ErrNotSupportedAuthorizationState
|
||||
|
||||
case TypeAuthorizationStateWaitPassword:
|
||||
_, err := client.CheckAuthenticationPassword(&CheckAuthenticationPasswordRequest{
|
||||
Password: <-stateHandler.Password,
|
||||
})
|
||||
return err
|
||||
|
||||
case TypeAuthorizationStateReady:
|
||||
return nil
|
||||
|
||||
case TypeAuthorizationStateLoggingOut:
|
||||
return ErrNotSupportedAuthorizationState
|
||||
|
||||
case TypeAuthorizationStateClosing:
|
||||
return nil
|
||||
|
||||
case TypeAuthorizationStateClosed:
|
||||
return nil
|
||||
}
|
||||
|
||||
return ErrNotSupportedAuthorizationState
|
||||
}
|
||||
|
||||
func (stateHandler *clientAuthorizer) Close() {
|
||||
close(stateHandler.TdlibParameters)
|
||||
close(stateHandler.PhoneNumber)
|
||||
close(stateHandler.Code)
|
||||
close(stateHandler.State)
|
||||
close(stateHandler.Password)
|
||||
}
|
||||
|
||||
func CliInteractor(clientAuthorizer *clientAuthorizer) {
|
||||
for {
|
||||
select {
|
||||
case state, ok := <-clientAuthorizer.State:
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
switch state.AuthorizationStateType() {
|
||||
case TypeAuthorizationStateWaitPhoneNumber:
|
||||
fmt.Println("Enter phone number: ")
|
||||
var phoneNumber string
|
||||
fmt.Scanln(&phoneNumber)
|
||||
|
||||
clientAuthorizer.PhoneNumber <- phoneNumber
|
||||
|
||||
case TypeAuthorizationStateWaitCode:
|
||||
var code string
|
||||
|
||||
fmt.Println("Enter code: ")
|
||||
fmt.Scanln(&code)
|
||||
|
||||
clientAuthorizer.Code <- code
|
||||
|
||||
case TypeAuthorizationStateWaitPassword:
|
||||
fmt.Println("Enter password: ")
|
||||
// var password string
|
||||
// fmt.Scanln(&password)
|
||||
bytePassword, _ := terminal.ReadPassword(0)
|
||||
clientAuthorizer.Password <- string(bytePassword)
|
||||
|
||||
// clientAuthorizer.Password <- password
|
||||
|
||||
case TypeAuthorizationStateReady:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type botAuthorizer struct {
|
||||
TdlibParameters chan *TdlibParameters
|
||||
Token chan string
|
||||
State chan AuthorizationState
|
||||
}
|
||||
|
||||
func BotAuthorizer(token string) *botAuthorizer {
|
||||
botAuthorizer := &botAuthorizer{
|
||||
TdlibParameters: make(chan *TdlibParameters, 1),
|
||||
Token: make(chan string, 1),
|
||||
State: make(chan AuthorizationState, 10),
|
||||
}
|
||||
|
||||
botAuthorizer.Token <- token
|
||||
|
||||
return botAuthorizer
|
||||
}
|
||||
|
||||
func (stateHandler *botAuthorizer) Handle(client *Client, state AuthorizationState) error {
|
||||
stateHandler.State <- state
|
||||
|
||||
switch state.AuthorizationStateType() {
|
||||
case TypeAuthorizationStateWaitTdlibParameters:
|
||||
_, err := client.SetTdlibParameters(&SetTdlibParametersRequest{
|
||||
Parameters: <-stateHandler.TdlibParameters,
|
||||
})
|
||||
return err
|
||||
|
||||
case TypeAuthorizationStateWaitEncryptionKey:
|
||||
_, err := client.CheckDatabaseEncryptionKey(&CheckDatabaseEncryptionKeyRequest{})
|
||||
return err
|
||||
|
||||
case TypeAuthorizationStateWaitPhoneNumber:
|
||||
_, err := client.CheckAuthenticationBotToken(&CheckAuthenticationBotTokenRequest{
|
||||
Token: <-stateHandler.Token,
|
||||
})
|
||||
return err
|
||||
|
||||
case TypeAuthorizationStateWaitCode:
|
||||
return ErrNotSupportedAuthorizationState
|
||||
|
||||
case TypeAuthorizationStateWaitPassword:
|
||||
return ErrNotSupportedAuthorizationState
|
||||
|
||||
case TypeAuthorizationStateReady:
|
||||
return nil
|
||||
|
||||
case TypeAuthorizationStateLoggingOut:
|
||||
return ErrNotSupportedAuthorizationState
|
||||
|
||||
case TypeAuthorizationStateClosing:
|
||||
return ErrNotSupportedAuthorizationState
|
||||
|
||||
case TypeAuthorizationStateClosed:
|
||||
return ErrNotSupportedAuthorizationState
|
||||
}
|
||||
|
||||
return ErrNotSupportedAuthorizationState
|
||||
}
|
||||
|
||||
func (stateHandler *botAuthorizer) Close() {
|
||||
close(stateHandler.TdlibParameters)
|
||||
close(stateHandler.Token)
|
||||
close(stateHandler.State)
|
||||
}
|
|
@ -0,0 +1,154 @@
|
|||
package client
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
jsonClient *JsonClient
|
||||
extraGenerator ExtraGenerator
|
||||
catcher chan *Response
|
||||
listenerStore *listenerStore
|
||||
catchersStore *sync.Map
|
||||
updatesTimeout time.Duration
|
||||
catchTimeout time.Duration
|
||||
}
|
||||
|
||||
type Option func(*Client)
|
||||
|
||||
func WithExtraGenerator(extraGenerator ExtraGenerator) Option {
|
||||
return func(client *Client) {
|
||||
client.extraGenerator = extraGenerator
|
||||
}
|
||||
}
|
||||
|
||||
func WithCatchTimeout(timeout time.Duration) Option {
|
||||
return func(client *Client) {
|
||||
client.catchTimeout = timeout
|
||||
}
|
||||
}
|
||||
|
||||
func WithUpdatesTimeout(timeout time.Duration) Option {
|
||||
return func(client *Client) {
|
||||
client.updatesTimeout = timeout
|
||||
}
|
||||
}
|
||||
|
||||
func WithProxy(req *AddProxyRequest) Option {
|
||||
return func(client *Client) {
|
||||
client.AddProxy(req)
|
||||
}
|
||||
}
|
||||
|
||||
func WithLogVerbosity(req *SetLogVerbosityLevelRequest) Option {
|
||||
return func(client *Client) {
|
||||
client.SetLogVerbosityLevel(req)
|
||||
}
|
||||
}
|
||||
|
||||
func NewClient(authorizationStateHandler AuthorizationStateHandler, options ...Option) (*Client, error) {
|
||||
catchersListener := make(chan *Response, 1000)
|
||||
|
||||
client := &Client{
|
||||
jsonClient: NewJsonClient(),
|
||||
catcher: catchersListener,
|
||||
listenerStore: newListenerStore(),
|
||||
catchersStore: &sync.Map{},
|
||||
}
|
||||
|
||||
client.extraGenerator = UuidV4Generator()
|
||||
client.catchTimeout = 60 * time.Second
|
||||
client.updatesTimeout = 60 * time.Second
|
||||
|
||||
for _, option := range options {
|
||||
option(client)
|
||||
}
|
||||
|
||||
go client.receive()
|
||||
go client.catch(catchersListener)
|
||||
|
||||
err := Authorize(client, authorizationStateHandler)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return client, nil
|
||||
}
|
||||
|
||||
func (client *Client) receive() {
|
||||
for {
|
||||
resp, err := client.jsonClient.Receive(client.updatesTimeout)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
client.catcher <- resp
|
||||
|
||||
typ, err := UnmarshalType(resp.Data)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
needGc := false
|
||||
for _, listener := range client.listenerStore.Listeners() {
|
||||
if listener.IsActive() {
|
||||
listener.Updates <- typ
|
||||
} else {
|
||||
needGc = true
|
||||
}
|
||||
}
|
||||
if needGc {
|
||||
client.listenerStore.gc()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (client *Client) catch(updates chan *Response) {
|
||||
for update := range updates {
|
||||
if update.Extra != "" {
|
||||
value, ok := client.catchersStore.Load(update.Extra)
|
||||
if ok {
|
||||
value.(chan *Response) <- update
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (client *Client) Send(req Request) (*Response, error) {
|
||||
req.Extra = client.extraGenerator()
|
||||
|
||||
catcher := make(chan *Response, 1)
|
||||
|
||||
client.catchersStore.Store(req.Extra, catcher)
|
||||
|
||||
defer func() {
|
||||
close(catcher)
|
||||
client.catchersStore.Delete(req.Extra)
|
||||
}()
|
||||
|
||||
client.jsonClient.Send(req)
|
||||
|
||||
select {
|
||||
case response := <-catcher:
|
||||
return response, nil
|
||||
|
||||
case <-time.After(client.catchTimeout):
|
||||
return nil, errors.New("response catching timeout")
|
||||
}
|
||||
}
|
||||
|
||||
func (client *Client) GetListener() *Listener {
|
||||
listener := &Listener{
|
||||
isActive: true,
|
||||
Updates: make(chan Type, 1000),
|
||||
}
|
||||
client.listenerStore.Add(listener)
|
||||
|
||||
return listener
|
||||
}
|
||||
|
||||
func (client *Client) Stop() {
|
||||
client.Destroy()
|
||||
client.jsonClient.Destroy()
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
package client
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
)
|
||||
|
||||
type ExtraGenerator func() string
|
||||
|
||||
func UuidV4Generator() ExtraGenerator {
|
||||
return func() string {
|
||||
var uuid [16]byte
|
||||
rand.Read(uuid[:])
|
||||
|
||||
uuid[6] = (uuid[6] & 0x0f) | 0x40
|
||||
uuid[8] = (uuid[8] & 0x3f) | 0x80
|
||||
|
||||
return fmt.Sprintf("%08x-%04x-%04x-%04x-%012x", uuid[:4], uuid[4:6], uuid[6:8], uuid[8:10], uuid[10:])
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,66 @@
|
|||
package client
|
||||
|
||||
import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
func newListenerStore() *listenerStore {
|
||||
return &listenerStore{
|
||||
listeners: []*Listener{},
|
||||
}
|
||||
}
|
||||
|
||||
type listenerStore struct {
|
||||
sync.Mutex
|
||||
listeners []*Listener
|
||||
}
|
||||
|
||||
func (store *listenerStore) Add(listener *Listener) {
|
||||
store.Lock()
|
||||
defer store.Unlock()
|
||||
|
||||
store.listeners = append(store.listeners, listener)
|
||||
}
|
||||
|
||||
func (store *listenerStore) Listeners() []*Listener {
|
||||
store.Lock()
|
||||
defer store.Unlock()
|
||||
|
||||
return store.listeners
|
||||
}
|
||||
|
||||
func (store *listenerStore) gc() {
|
||||
store.Lock()
|
||||
defer store.Unlock()
|
||||
|
||||
oldListeners := store.listeners
|
||||
|
||||
store.listeners = []*Listener{}
|
||||
|
||||
for _, listener := range oldListeners {
|
||||
if listener.IsActive() {
|
||||
store.listeners = append(store.listeners, listener)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type Listener struct {
|
||||
mu sync.Mutex
|
||||
isActive bool
|
||||
Updates chan Type
|
||||
}
|
||||
|
||||
func (listener *Listener) Close() {
|
||||
listener.mu.Lock()
|
||||
defer listener.mu.Unlock()
|
||||
|
||||
listener.isActive = false
|
||||
close(listener.Updates)
|
||||
}
|
||||
|
||||
func (listener *Listener) IsActive() bool {
|
||||
listener.mu.Lock()
|
||||
defer listener.mu.Unlock()
|
||||
|
||||
return listener.isActive
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
package puller
|
||||
|
||||
import (
|
||||
"github.com/astravexton/go-tdlib/client"
|
||||
)
|
||||
|
||||
func ChatHistory(tdlibClient *client.Client, chatId int64) (chan *client.Message, chan error) {
|
||||
messageChan := make(chan *client.Message, 10)
|
||||
errChan := make(chan error, 1)
|
||||
|
||||
var fromMessageId int64 = 0
|
||||
var offset int32 = 0
|
||||
var limit int32 = 100
|
||||
|
||||
go chatHistory(tdlibClient, messageChan, errChan, chatId, fromMessageId, offset, limit, false)
|
||||
|
||||
return messageChan, errChan
|
||||
}
|
||||
|
||||
func chatHistory(tdlibClient *client.Client, messageChan chan *client.Message, errChan chan error, chatId int64, fromMessageId int64, offset int32, limit int32, onlyLocal bool) {
|
||||
defer func() {
|
||||
close(messageChan)
|
||||
close(errChan)
|
||||
}()
|
||||
|
||||
for {
|
||||
messages, err := tdlibClient.GetChatHistory(&client.GetChatHistoryRequest{
|
||||
ChatId: chatId,
|
||||
FromMessageId: fromMessageId,
|
||||
Offset: offset,
|
||||
Limit: limit,
|
||||
OnlyLocal: onlyLocal,
|
||||
})
|
||||
if err != nil {
|
||||
errChan <- err
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if len(messages.Messages) == 0 {
|
||||
errChan <- EOP
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
for _, message := range messages.Messages {
|
||||
fromMessageId = message.Id
|
||||
|
||||
messageChan <- message
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
package puller
|
||||
|
||||
import (
|
||||
"math"
|
||||
|
||||
"github.com/astravexton/go-tdlib/client"
|
||||
)
|
||||
|
||||
func Chats(tdlibClient *client.Client) (chan *client.Chat, chan error) {
|
||||
chatChan := make(chan *client.Chat, 10)
|
||||
errChan := make(chan error, 1)
|
||||
|
||||
var offsetOrder client.JsonInt64 = math.MaxInt64
|
||||
var offsetChatId int64 = 0
|
||||
var limit int32 = 100
|
||||
|
||||
go chats(tdlibClient, chatChan, errChan, offsetOrder, offsetChatId, limit)
|
||||
|
||||
return chatChan, errChan
|
||||
}
|
||||
|
||||
func chats(tdlibClient *client.Client, chatChan chan *client.Chat, errChan chan error, offsetOrder client.JsonInt64, offsetChatId int64, limit int32) {
|
||||
defer func() {
|
||||
close(chatChan)
|
||||
close(errChan)
|
||||
}()
|
||||
|
||||
for {
|
||||
chats, err := tdlibClient.GetChats(&client.GetChatsRequest{
|
||||
OffsetOrder: offsetOrder,
|
||||
OffsetChatId: offsetChatId,
|
||||
Limit: limit,
|
||||
})
|
||||
if err != nil {
|
||||
errChan <- err
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if len(chats.ChatIds) == 0 {
|
||||
errChan <- EOP
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
for _, chatId := range chats.ChatIds {
|
||||
chat, err := tdlibClient.GetChat(&client.GetChatRequest{
|
||||
ChatId: chatId,
|
||||
})
|
||||
if err != nil {
|
||||
errChan <- err
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
offsetOrder = chat.Order
|
||||
offsetChatId = chat.Id
|
||||
|
||||
chatChan <- chat
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
package puller
|
||||
|
||||
import (
|
||||
"errors"
|
||||
)
|
||||
|
||||
var EOP = errors.New("end of pull")
|
|
@ -0,0 +1,53 @@
|
|||
package puller
|
||||
|
||||
import (
|
||||
"github.com/astravexton/go-tdlib/client"
|
||||
)
|
||||
|
||||
func SupergroupMembers(tdlibClient *client.Client, supergroupId int32) (chan *client.ChatMember, chan error) {
|
||||
chatMemberChan := make(chan *client.ChatMember, 10)
|
||||
errChan := make(chan error, 1)
|
||||
|
||||
var filter client.SupergroupMembersFilter = nil
|
||||
var offset int32 = 0
|
||||
var limit int32 = 200
|
||||
|
||||
go supergroupMembers(tdlibClient, chatMemberChan, errChan, supergroupId, filter, offset, limit)
|
||||
|
||||
return chatMemberChan, errChan
|
||||
}
|
||||
|
||||
func supergroupMembers(tdlibClient *client.Client, chatMemberChan chan *client.ChatMember, errChan chan error, supergroupId int32, filter client.SupergroupMembersFilter, offset int32, limit int32) {
|
||||
defer func() {
|
||||
close(chatMemberChan)
|
||||
close(errChan)
|
||||
}()
|
||||
|
||||
var page int32 = 0
|
||||
|
||||
for {
|
||||
chatMembers, err := tdlibClient.GetSupergroupMembers(&client.GetSupergroupMembersRequest{
|
||||
SupergroupId: supergroupId,
|
||||
Filter: filter,
|
||||
Offset: page*limit + offset,
|
||||
Limit: limit,
|
||||
})
|
||||
if err != nil {
|
||||
errChan <- err
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if len(chatMembers.Members) == 0 {
|
||||
errChan <- EOP
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
for _, member := range chatMembers.Members {
|
||||
chatMemberChan <- member
|
||||
}
|
||||
|
||||
page++
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
// +build darwin
|
||||
|
||||
package client
|
||||
|
||||
/*
|
||||
#cgo darwin CFLAGS: -I/usr/local/include
|
||||
#cgo darwin LDFLAGS: -L/usr/local/lib -L/usr/local/opt/openssl/lib -ltdjson_static -ltdjson_private -ltdclient -ltdcore -ltdactor -ltddb -ltdsqlite -ltdnet -ltdutils -lstdc++ -lssl -lcrypto -ldl -lz -lm
|
||||
*/
|
||||
import "C"
|
|
@ -0,0 +1,14 @@
|
|||
// +build libtdjson
|
||||
// +build linux darwin windows
|
||||
|
||||
package client
|
||||
|
||||
/*
|
||||
#cgo linux CFLAGS: -I/usr/local/include
|
||||
#cgo linux LDFLAGS: -L/usr/local/lib -ltdjson -lstdc++ -lssl -lcrypto -ldl -lz -lm
|
||||
#cgo darwin CFLAGS: -I/usr/local/include
|
||||
#cgo darwin LDFLAGS: -L/usr/local/lib -ltdjson -lstdc++ -lssl -lcrypto -ldl -lz -lm
|
||||
#cgo windows CFLAGS: -Ic:/td -Ic:/td/example/csharp/build
|
||||
#cgo windows LDFLAGS: -Lc:/td/example/csharp/build/Release -ltdjson
|
||||
*/
|
||||
import "C"
|
|
@ -0,0 +1,12 @@
|
|||
// +build !libtdjson
|
||||
// +build linux darwin
|
||||
|
||||
package client
|
||||
|
||||
/*
|
||||
#cgo linux CFLAGS: -I/usr/local/include
|
||||
#cgo linux LDFLAGS: -L/usr/local/lib -ltdjson_static -ltdjson_private -ltdclient -ltdcore -ltdactor -ltdapi -ltddb -ltdsqlite -ltdnet -ltdutils -lstdc++ -lssl -lcrypto -ldl -lz -lm
|
||||
#cgo darwin CFLAGS: -I/usr/local/include
|
||||
#cgo darwin LDFLAGS: -L/usr/local/lib -ltdjson_static -ltdjson_private -ltdclient -ltdcore -ltdactor -ltdapi -ltddb -ltdsqlite -ltdnet -ltdutils -lstdc++ -lssl -lcrypto -ldl -lz -lm
|
||||
*/
|
||||
import "C"
|
|
@ -0,0 +1,164 @@
|
|||
package client
|
||||
|
||||
/*
|
||||
#include <stdlib.h>
|
||||
#include <td/telegram/td_json_client.h>
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"time"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
type JsonClient struct {
|
||||
jsonClient unsafe.Pointer
|
||||
}
|
||||
|
||||
func NewJsonClient() *JsonClient {
|
||||
return &JsonClient{
|
||||
jsonClient: C.td_json_client_create(),
|
||||
}
|
||||
}
|
||||
|
||||
// Sends request to the TDLib client. May be called from any thread.
|
||||
func (jsonClient *JsonClient) Send(req Request) {
|
||||
data, _ := json.Marshal(req)
|
||||
|
||||
query := C.CString(string(data))
|
||||
defer C.free(unsafe.Pointer(query))
|
||||
|
||||
C.td_json_client_send(jsonClient.jsonClient, query)
|
||||
}
|
||||
|
||||
// Receives incoming updates and request responses from the TDLib client. May be called from any thread, but
|
||||
// shouldn't be called simultaneously from two different threads.
|
||||
// Returned pointer will be deallocated by TDLib during next call to td_json_client_receive or td_json_client_execute
|
||||
// in the same thread, so it can't be used after that.
|
||||
func (jsonClient *JsonClient) Receive(timeout time.Duration) (*Response, error) {
|
||||
result := C.td_json_client_receive(jsonClient.jsonClient, C.double(float64(timeout)/float64(time.Second)))
|
||||
if result == nil {
|
||||
return nil, errors.New("update receiving timeout")
|
||||
}
|
||||
|
||||
data := []byte(C.GoString(result))
|
||||
|
||||
var resp Response
|
||||
|
||||
err := json.Unmarshal(data, &resp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp.Data = data
|
||||
|
||||
return &resp, nil
|
||||
}
|
||||
|
||||
// Synchronously executes TDLib request. May be called from any thread.
|
||||
// Only a few requests can be executed synchronously.
|
||||
// Returned pointer will be deallocated by TDLib during next call to td_json_client_receive or td_json_client_execute
|
||||
// in the same thread, so it can't be used after that.
|
||||
func (jsonClient *JsonClient) Execute(req Request) (*Response, error) {
|
||||
data, _ := json.Marshal(req)
|
||||
|
||||
query := C.CString(string(data))
|
||||
defer C.free(unsafe.Pointer(query))
|
||||
|
||||
result := C.td_json_client_execute(jsonClient.jsonClient, query)
|
||||
if result == nil {
|
||||
return nil, errors.New("request can't be parsed")
|
||||
}
|
||||
|
||||
data = []byte(C.GoString(result))
|
||||
|
||||
var resp Response
|
||||
|
||||
err := json.Unmarshal(data, &resp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp.Data = data
|
||||
|
||||
return &resp, nil
|
||||
}
|
||||
|
||||
// Destroys the TDLib client instance. After this is called the client instance shouldn't be used anymore.
|
||||
func (jsonClient *JsonClient) Destroy() {
|
||||
C.td_json_client_destroy(jsonClient.jsonClient)
|
||||
}
|
||||
|
||||
type meta struct {
|
||||
Type string `json:"@type"`
|
||||
Extra string `json:"@extra"`
|
||||
}
|
||||
|
||||
type Request struct {
|
||||
meta
|
||||
Data map[string]interface{}
|
||||
}
|
||||
|
||||
func (req Request) MarshalJSON() ([]byte, error) {
|
||||
req.Data["@type"] = req.Type
|
||||
req.Data["@extra"] = req.Extra
|
||||
|
||||
return json.Marshal(req.Data)
|
||||
}
|
||||
|
||||
type Response struct {
|
||||
meta
|
||||
Data json.RawMessage
|
||||
}
|
||||
|
||||
type ResponseError struct {
|
||||
Err *Error
|
||||
}
|
||||
|
||||
func (responseError ResponseError) Error() string {
|
||||
return fmt.Sprintf("%d %s", responseError.Err.Code, responseError.Err.Message)
|
||||
}
|
||||
|
||||
func buildResponseError(data json.RawMessage) error {
|
||||
respErr, err := UnmarshalError(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return ResponseError{
|
||||
Err: respErr,
|
||||
}
|
||||
}
|
||||
|
||||
// JsonInt64 alias for int64, in order to deal with json big number problem
|
||||
type JsonInt64 int64
|
||||
|
||||
// MarshalJSON marshals to json
|
||||
func (jsonInt64 JsonInt64) MarshalJSON() ([]byte, error) {
|
||||
return []byte(`"` + strconv.FormatInt(int64(jsonInt64), 10) + `"`), nil
|
||||
}
|
||||
|
||||
// UnmarshalJSON unmarshals from json
|
||||
func (jsonInt64 *JsonInt64) UnmarshalJSON(data []byte) error {
|
||||
if len(data) > 2 && data[0] == '"' && data[len(data)-1] == '"' {
|
||||
data = data[1 : len(data)-1]
|
||||
}
|
||||
|
||||
jsonBigInt, err := strconv.ParseInt(string(data), 10, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*jsonInt64 = JsonInt64(jsonBigInt)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type Type interface {
|
||||
GetType() string
|
||||
GetClass() string
|
||||
}
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,86 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"flag"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/astravexton/go-tdlib/codegen"
|
||||
"github.com/astravexton/go-tdlib/tlparser"
|
||||
)
|
||||
|
||||
type config struct {
|
||||
version string
|
||||
outputDirPath string
|
||||
packageName string
|
||||
functionFileName string
|
||||
typeFileName string
|
||||
unmarshalerFileName string
|
||||
}
|
||||
|
||||
func main() {
|
||||
var config config
|
||||
|
||||
flag.StringVar(&config.version, "version", "", "TDLib version")
|
||||
flag.StringVar(&config.outputDirPath, "outputDir", "./tdlib", "output directory")
|
||||
flag.StringVar(&config.packageName, "package", "tdlib", "package name")
|
||||
flag.StringVar(&config.functionFileName, "functionFile", "function.go", "functions filename")
|
||||
flag.StringVar(&config.typeFileName, "typeFile", "type.go", "types filename")
|
||||
flag.StringVar(&config.unmarshalerFileName, "unmarshalerFile", "unmarshaler.go", "unmarshalers filename")
|
||||
|
||||
flag.Parse()
|
||||
|
||||
resp, err := http.Get("https://raw.githubusercontent.com/tdlib/td/" + config.version + "/td/generate/scheme/td_api.tl")
|
||||
if err != nil {
|
||||
log.Fatalf("http.Get error: %s", err)
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
schema, err := tlparser.Parse(resp.Body)
|
||||
if err != nil {
|
||||
log.Fatalf("schema parse error: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
err = os.MkdirAll(config.outputDirPath, 0755)
|
||||
if err != nil {
|
||||
log.Fatalf("error creating %s: %s", config.outputDirPath, err)
|
||||
}
|
||||
|
||||
functionFilePath := filepath.Join(config.outputDirPath, config.functionFileName)
|
||||
|
||||
os.Remove(functionFilePath)
|
||||
functionFile, err := os.OpenFile(functionFilePath, os.O_CREATE|os.O_RDWR|os.O_TRUNC, os.ModePerm)
|
||||
if err != nil {
|
||||
log.Fatalf("functionFile open error: %s", err)
|
||||
}
|
||||
defer functionFile.Close()
|
||||
|
||||
bufio.NewWriter(functionFile).Write(codegen.GenerateFunctions(schema, config.packageName))
|
||||
|
||||
typeFilePath := filepath.Join(config.outputDirPath, config.typeFileName)
|
||||
|
||||
os.Remove(typeFilePath)
|
||||
typeFile, err := os.OpenFile(typeFilePath, os.O_CREATE|os.O_RDWR|os.O_TRUNC, os.ModePerm)
|
||||
if err != nil {
|
||||
log.Fatalf("typeFile open error: %s", err)
|
||||
}
|
||||
defer typeFile.Close()
|
||||
|
||||
bufio.NewWriter(typeFile).Write(codegen.GenerateTypes(schema, config.packageName))
|
||||
|
||||
unmarshalerFilePath := filepath.Join(config.outputDirPath, config.unmarshalerFileName)
|
||||
|
||||
os.Remove(unmarshalerFilePath)
|
||||
unmarshalerFile, err := os.OpenFile(unmarshalerFilePath, os.O_CREATE|os.O_RDWR|os.O_TRUNC, os.ModePerm)
|
||||
if err != nil {
|
||||
log.Fatalf("unmarshalerFile open error: %s", err)
|
||||
}
|
||||
defer unmarshalerFile.Close()
|
||||
|
||||
bufio.NewWriter(unmarshalerFile).Write(codegen.GenerateUnmarshalers(schema, config.packageName))
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/astravexton/go-tdlib/tlparser"
|
||||
)
|
||||
|
||||
func main() {
|
||||
var version string
|
||||
var outputFilePath string
|
||||
|
||||
flag.StringVar(&version, "version", "", "TDLib version")
|
||||
flag.StringVar(&outputFilePath, "output", "./td_api.json", "json schema file")
|
||||
|
||||
flag.Parse()
|
||||
|
||||
resp, err := http.Get("https://raw.githubusercontent.com/tdlib/td/" + version + "/td/generate/scheme/td_api.tl")
|
||||
if err != nil {
|
||||
log.Fatalf("http.Get error: %s", err)
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
schema, err := tlparser.Parse(resp.Body)
|
||||
if err != nil {
|
||||
log.Fatalf("schema parse error: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
resp, err = http.Get("https://raw.githubusercontent.com/tdlib/td/" + version + "/td/telegram/Td.cpp")
|
||||
if err != nil {
|
||||
log.Fatalf("http.Get error: %s", err)
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
err = tlparser.ParseCode(resp.Body, schema)
|
||||
if err != nil {
|
||||
log.Fatalf("parse code error: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
err = os.MkdirAll(filepath.Dir(outputFilePath), os.ModePerm)
|
||||
if err != nil {
|
||||
log.Fatalf("make dir error: %s", filepath.Dir(outputFilePath))
|
||||
}
|
||||
|
||||
file, err := os.OpenFile(outputFilePath, os.O_CREATE|os.O_RDWR|os.O_TRUNC, os.ModePerm)
|
||||
if err != nil {
|
||||
log.Fatalf("open file error: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
data, err := json.MarshalIndent(schema, "", strings.Repeat(" ", 4))
|
||||
if err != nil {
|
||||
log.Fatalf("json marshal error: %s", err)
|
||||
return
|
||||
}
|
||||
bufio.NewWriter(file).Write(data)
|
||||
}
|
|
@ -0,0 +1,115 @@
|
|||
package codegen
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
|
||||
"github.com/astravexton/go-tdlib/tlparser"
|
||||
)
|
||||
|
||||
func GenerateFunctions(schema *tlparser.Schema, packageName string) []byte {
|
||||
buf := bytes.NewBufferString("")
|
||||
|
||||
buf.WriteString(fmt.Sprintf("%s\n\npackage %s\n\n", header, packageName))
|
||||
|
||||
buf.WriteString(`import (
|
||||
"errors"
|
||||
)`)
|
||||
|
||||
buf.WriteString("\n")
|
||||
|
||||
for _, function := range schema.Functions {
|
||||
tdlibFunction := TdlibFunction(function.Name, schema)
|
||||
tdlibFunctionReturn := TdlibFunctionReturn(function.Class, schema)
|
||||
|
||||
if len(function.Properties) > 0 {
|
||||
buf.WriteString("\n")
|
||||
buf.WriteString(fmt.Sprintf("type %sRequest struct { \n", tdlibFunction.ToGoName()))
|
||||
for _, property := range function.Properties {
|
||||
tdlibTypeProperty := TdlibTypeProperty(property.Name, property.Type, schema)
|
||||
|
||||
buf.WriteString(fmt.Sprintf(" // %s\n", property.Description))
|
||||
buf.WriteString(fmt.Sprintf(" %s %s `json:\"%s\"`\n", tdlibTypeProperty.ToGoName(), tdlibTypeProperty.ToGoType(), property.Name))
|
||||
}
|
||||
buf.WriteString("}\n")
|
||||
}
|
||||
|
||||
buf.WriteString("\n")
|
||||
buf.WriteString("// " + function.Description)
|
||||
buf.WriteString("\n")
|
||||
|
||||
requestArgument := ""
|
||||
if len(function.Properties) > 0 {
|
||||
requestArgument = fmt.Sprintf("req *%sRequest", tdlibFunction.ToGoName())
|
||||
}
|
||||
|
||||
buf.WriteString(fmt.Sprintf("func (client *Client) %s(%s) (%s, error) {\n", tdlibFunction.ToGoName(), requestArgument, tdlibFunctionReturn.ToGoReturn()))
|
||||
|
||||
sendMethod := "Send"
|
||||
if function.IsSynchronous {
|
||||
sendMethod = "jsonClient.Execute"
|
||||
}
|
||||
|
||||
if len(function.Properties) > 0 {
|
||||
buf.WriteString(fmt.Sprintf(` result, err := client.%s(Request{
|
||||
meta: meta{
|
||||
Type: "%s",
|
||||
},
|
||||
Data: map[string]interface{}{
|
||||
`, sendMethod, function.Name))
|
||||
|
||||
for _, property := range function.Properties {
|
||||
tdlibTypeProperty := TdlibTypeProperty(property.Name, property.Type, schema)
|
||||
|
||||
buf.WriteString(fmt.Sprintf(" \"%s\": req.%s,\n", property.Name, tdlibTypeProperty.ToGoName()))
|
||||
}
|
||||
|
||||
buf.WriteString(` },
|
||||
})
|
||||
`)
|
||||
} else {
|
||||
buf.WriteString(fmt.Sprintf(` result, err := client.%s(Request{
|
||||
meta: meta{
|
||||
Type: "%s",
|
||||
},
|
||||
Data: map[string]interface{}{},
|
||||
})
|
||||
`, sendMethod, function.Name))
|
||||
}
|
||||
|
||||
buf.WriteString(` if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if result.Type == "error" {
|
||||
return nil, buildResponseError(result.Data)
|
||||
}
|
||||
|
||||
`)
|
||||
|
||||
if tdlibFunctionReturn.IsClass() {
|
||||
buf.WriteString(" switch result.Type {\n")
|
||||
|
||||
for _, subType := range tdlibFunctionReturn.GetClass().GetSubTypes() {
|
||||
buf.WriteString(fmt.Sprintf(` case %s:
|
||||
return Unmarshal%s(result.Data)
|
||||
|
||||
`, subType.ToTypeConst(), subType.ToGoType()))
|
||||
|
||||
}
|
||||
|
||||
buf.WriteString(` default:
|
||||
return nil, errors.New("invalid type")
|
||||
`)
|
||||
|
||||
buf.WriteString(" }\n")
|
||||
} else {
|
||||
buf.WriteString(fmt.Sprintf(` return Unmarshal%s(result.Data)
|
||||
`, tdlibFunctionReturn.ToGoType()))
|
||||
}
|
||||
|
||||
buf.WriteString("}\n")
|
||||
}
|
||||
|
||||
return buf.Bytes()
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
package codegen
|
||||
|
||||
const header = "// AUTOGENERATED"
|
|
@ -0,0 +1,26 @@
|
|||
package codegen
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
func firstUpper(str string) string {
|
||||
for i, r := range str {
|
||||
return string(unicode.ToUpper(r)) + str[i+1:]
|
||||
}
|
||||
|
||||
return str
|
||||
}
|
||||
|
||||
func firstLower(str string) string {
|
||||
for i, r := range str {
|
||||
return string(unicode.ToLower(r)) + str[i+1:]
|
||||
}
|
||||
|
||||
return str
|
||||
}
|
||||
|
||||
func underscoreToCamelCase(s string) string {
|
||||
return strings.Replace(strings.Title(strings.Replace(strings.ToLower(s), "_", " ", -1)), " ", "", -1)
|
||||
}
|
|
@ -0,0 +1,487 @@
|
|||
package codegen
|
||||
|
||||
import (
|
||||
"log"
|
||||
"strings"
|
||||
|
||||
"github.com/astravexton/go-tdlib/tlparser"
|
||||
)
|
||||
|
||||
type tdlibFunction struct {
|
||||
name string
|
||||
schema *tlparser.Schema
|
||||
}
|
||||
|
||||
func TdlibFunction(name string, schema *tlparser.Schema) *tdlibFunction {
|
||||
return &tdlibFunction{
|
||||
name: name,
|
||||
schema: schema,
|
||||
}
|
||||
}
|
||||
|
||||
func (entity *tdlibFunction) ToGoName() string {
|
||||
return firstUpper(entity.name)
|
||||
}
|
||||
|
||||
type tdlibFunctionReturn struct {
|
||||
name string
|
||||
schema *tlparser.Schema
|
||||
}
|
||||
|
||||
func TdlibFunctionReturn(name string, schema *tlparser.Schema) *tdlibFunctionReturn {
|
||||
return &tdlibFunctionReturn{
|
||||
name: name,
|
||||
schema: schema,
|
||||
}
|
||||
}
|
||||
|
||||
func (entity *tdlibFunctionReturn) IsType() bool {
|
||||
return isType(entity.name, func(entity *tlparser.Type) string {
|
||||
return entity.Class
|
||||
}, entity.schema)
|
||||
}
|
||||
|
||||
func (entity *tdlibFunctionReturn) GetType() *tdlibType {
|
||||
return getType(entity.name, func(entity *tlparser.Type) string {
|
||||
return entity.Class
|
||||
}, entity.schema)
|
||||
}
|
||||
|
||||
func (entity *tdlibFunctionReturn) IsClass() bool {
|
||||
return isClass(entity.name, func(entity *tlparser.Class) string {
|
||||
return entity.Name
|
||||
}, entity.schema)
|
||||
}
|
||||
|
||||
func (entity *tdlibFunctionReturn) GetClass() *tdlibClass {
|
||||
return getClass(entity.name, func(entity *tlparser.Class) string {
|
||||
return entity.Name
|
||||
}, entity.schema)
|
||||
}
|
||||
|
||||
func (entity *tdlibFunctionReturn) ToGoReturn() string {
|
||||
if strings.HasPrefix(entity.name, "vector<") {
|
||||
log.Fatal("vectors are not supported")
|
||||
}
|
||||
|
||||
if entity.IsClass() {
|
||||
return entity.GetClass().ToGoType()
|
||||
}
|
||||
|
||||
if entity.GetType().IsInternal() {
|
||||
return entity.GetType().ToGoType()
|
||||
}
|
||||
|
||||
return "*" + entity.GetType().ToGoType()
|
||||
}
|
||||
|
||||
func (entity *tdlibFunctionReturn) ToGoType() string {
|
||||
if strings.HasPrefix(entity.name, "vector<") {
|
||||
log.Fatal("vectors are not supported")
|
||||
}
|
||||
|
||||
if entity.IsClass() {
|
||||
return entity.GetClass().ToGoType()
|
||||
}
|
||||
|
||||
return entity.GetType().ToGoType()
|
||||
}
|
||||
|
||||
type tdlibFunctionProperty struct {
|
||||
name string
|
||||
propertyType string
|
||||
schema *tlparser.Schema
|
||||
}
|
||||
|
||||
func TdlibFunctionProperty(name string, propertyType string, schema *tlparser.Schema) *tdlibFunctionProperty {
|
||||
return &tdlibFunctionProperty{
|
||||
name: name,
|
||||
propertyType: propertyType,
|
||||
schema: schema,
|
||||
}
|
||||
}
|
||||
|
||||
func (entity *tdlibFunctionProperty) GetPrimitive() string {
|
||||
primitive := entity.propertyType
|
||||
|
||||
for strings.HasPrefix(primitive, "vector<") {
|
||||
primitive = strings.TrimSuffix(strings.TrimPrefix(primitive, "vector<"), ">")
|
||||
}
|
||||
|
||||
return primitive
|
||||
}
|
||||
|
||||
func (entity *tdlibFunctionProperty) IsType() bool {
|
||||
primitive := entity.GetPrimitive()
|
||||
return isType(primitive, func(entity *tlparser.Type) string {
|
||||
return entity.Name
|
||||
}, entity.schema)
|
||||
}
|
||||
|
||||
func (entity *tdlibFunctionProperty) GetType() *tdlibType {
|
||||
primitive := entity.GetPrimitive()
|
||||
return getType(primitive, func(entity *tlparser.Type) string {
|
||||
return entity.Name
|
||||
}, entity.schema)
|
||||
}
|
||||
|
||||
func (entity *tdlibFunctionProperty) IsClass() bool {
|
||||
primitive := entity.GetPrimitive()
|
||||
return isClass(primitive, func(entity *tlparser.Class) string {
|
||||
return entity.Name
|
||||
}, entity.schema)
|
||||
}
|
||||
|
||||
func (entity *tdlibFunctionProperty) GetClass() *tdlibClass {
|
||||
primitive := entity.GetPrimitive()
|
||||
return getClass(primitive, func(entity *tlparser.Class) string {
|
||||
return entity.Name
|
||||
}, entity.schema)
|
||||
}
|
||||
|
||||
func (entity *tdlibFunctionProperty) ToGoName() string {
|
||||
name := firstLower(underscoreToCamelCase(entity.name))
|
||||
if name == "type" {
|
||||
name += "Param"
|
||||
}
|
||||
|
||||
return name
|
||||
}
|
||||
|
||||
func (entity *tdlibFunctionProperty) ToGoType() string {
|
||||
tdlibType := entity.propertyType
|
||||
goType := ""
|
||||
|
||||
for strings.HasPrefix(tdlibType, "vector<") {
|
||||
goType = goType + "[]"
|
||||
tdlibType = strings.TrimSuffix(strings.TrimPrefix(tdlibType, "vector<"), ">")
|
||||
}
|
||||
|
||||
if entity.IsClass() {
|
||||
return goType + entity.GetClass().ToGoType()
|
||||
}
|
||||
|
||||
if entity.GetType().IsInternal() {
|
||||
return goType + entity.GetType().ToGoType()
|
||||
}
|
||||
|
||||
return goType + "*" + entity.GetType().ToGoType()
|
||||
}
|
||||
|
||||
type tdlibType struct {
|
||||
name string
|
||||
schema *tlparser.Schema
|
||||
}
|
||||
|
||||
func TdlibType(name string, schema *tlparser.Schema) *tdlibType {
|
||||
return &tdlibType{
|
||||
name: name,
|
||||
schema: schema,
|
||||
}
|
||||
}
|
||||
|
||||
func (entity *tdlibType) IsInternal() bool {
|
||||
switch entity.name {
|
||||
case "double":
|
||||
return true
|
||||
|
||||
case "string":
|
||||
return true
|
||||
|
||||
case "int32":
|
||||
return true
|
||||
|
||||
case "int53":
|
||||
return true
|
||||
|
||||
case "int64":
|
||||
return true
|
||||
|
||||
case "bytes":
|
||||
return true
|
||||
|
||||
case "boolFalse":
|
||||
return true
|
||||
|
||||
case "boolTrue":
|
||||
return true
|
||||
|
||||
case "vector<t>":
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (entity *tdlibType) GetType() *tlparser.Type {
|
||||
name := normalizeEntityName(entity.name)
|
||||
for _, typ := range entity.schema.Types {
|
||||
if typ.Name == name {
|
||||
return typ
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (entity *tdlibType) ToGoType() string {
|
||||
if strings.HasPrefix(entity.name, "vector<") {
|
||||
log.Fatal("vectors are not supported")
|
||||
}
|
||||
|
||||
switch entity.name {
|
||||
case "double":
|
||||
return "float64"
|
||||
|
||||
case "string":
|
||||
return "string"
|
||||
|
||||
case "int32":
|
||||
return "int32"
|
||||
|
||||
case "int53":
|
||||
return "int64"
|
||||
|
||||
case "int64":
|
||||
return "JsonInt64"
|
||||
|
||||
case "bytes":
|
||||
return "[]byte"
|
||||
|
||||
case "boolFalse":
|
||||
return "bool"
|
||||
|
||||
case "boolTrue":
|
||||
return "bool"
|
||||
}
|
||||
|
||||
return firstUpper(entity.name)
|
||||
}
|
||||
|
||||
func (entity *tdlibType) ToType() string {
|
||||
return entity.ToGoType() + "Type"
|
||||
}
|
||||
|
||||
func (entity *tdlibType) HasClass() bool {
|
||||
className := entity.GetType().Class
|
||||
for _, class := range entity.schema.Classes {
|
||||
if class.Name == className {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (entity *tdlibType) GetClass() *tlparser.Class {
|
||||
className := entity.GetType().Class
|
||||
for _, class := range entity.schema.Classes {
|
||||
if class.Name == className {
|
||||
return class
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (entity *tdlibType) HasClassProperties() bool {
|
||||
for _, prop := range entity.GetType().Properties {
|
||||
tdlibTypeProperty := TdlibTypeProperty(prop.Name, prop.Type, entity.schema)
|
||||
if tdlibTypeProperty.IsClass() {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (entity *tdlibType) IsList() bool {
|
||||
return strings.HasPrefix(entity.name, "vector<")
|
||||
}
|
||||
|
||||
func (entity *tdlibType) ToClassConst() string {
|
||||
if entity.HasClass() {
|
||||
return "Class" + TdlibClass(entity.GetType().Class, entity.schema).ToGoType()
|
||||
}
|
||||
return "Class" + entity.ToGoType()
|
||||
}
|
||||
|
||||
func (entity *tdlibType) ToTypeConst() string {
|
||||
return "Type" + entity.ToGoType()
|
||||
}
|
||||
|
||||
type tdlibClass struct {
|
||||
name string
|
||||
schema *tlparser.Schema
|
||||
}
|
||||
|
||||
func TdlibClass(name string, schema *tlparser.Schema) *tdlibClass {
|
||||
return &tdlibClass{
|
||||
name: name,
|
||||
schema: schema,
|
||||
}
|
||||
}
|
||||
|
||||
func (entity *tdlibClass) ToGoType() string {
|
||||
return firstUpper(entity.name)
|
||||
}
|
||||
|
||||
func (entity *tdlibClass) ToType() string {
|
||||
return entity.ToGoType() + "Type"
|
||||
}
|
||||
|
||||
func (entity *tdlibClass) GetSubTypes() []*tdlibType {
|
||||
types := []*tdlibType{}
|
||||
|
||||
for _, t := range entity.schema.Types {
|
||||
if t.Class == entity.name {
|
||||
types = append(types, TdlibType(t.Name, entity.schema))
|
||||
}
|
||||
}
|
||||
|
||||
return types
|
||||
}
|
||||
|
||||
func (entity *tdlibClass) ToClassConst() string {
|
||||
return "Class" + entity.ToGoType()
|
||||
}
|
||||
|
||||
type tdlibTypeProperty struct {
|
||||
name string
|
||||
propertyType string
|
||||
schema *tlparser.Schema
|
||||
}
|
||||
|
||||
func TdlibTypeProperty(name string, propertyType string, schema *tlparser.Schema) *tdlibTypeProperty {
|
||||
return &tdlibTypeProperty{
|
||||
name: name,
|
||||
propertyType: propertyType,
|
||||
schema: schema,
|
||||
}
|
||||
}
|
||||
|
||||
func (entity *tdlibTypeProperty) IsList() bool {
|
||||
return strings.HasPrefix(entity.propertyType, "vector<")
|
||||
}
|
||||
|
||||
func (entity *tdlibTypeProperty) GetPrimitive() string {
|
||||
primitive := entity.propertyType
|
||||
|
||||
for strings.HasPrefix(primitive, "vector<") {
|
||||
primitive = strings.TrimSuffix(strings.TrimPrefix(primitive, "vector<"), ">")
|
||||
}
|
||||
|
||||
return primitive
|
||||
}
|
||||
|
||||
func (entity *tdlibTypeProperty) IsType() bool {
|
||||
primitive := entity.GetPrimitive()
|
||||
return isType(primitive, func(entity *tlparser.Type) string {
|
||||
return entity.Name
|
||||
}, entity.schema)
|
||||
}
|
||||
|
||||
func (entity *tdlibTypeProperty) GetType() *tdlibType {
|
||||
primitive := entity.GetPrimitive()
|
||||
return getType(primitive, func(entity *tlparser.Type) string {
|
||||
return entity.Name
|
||||
}, entity.schema)
|
||||
}
|
||||
|
||||
func (entity *tdlibTypeProperty) IsClass() bool {
|
||||
primitive := entity.GetPrimitive()
|
||||
return isClass(primitive, func(entity *tlparser.Class) string {
|
||||
return entity.Name
|
||||
}, entity.schema)
|
||||
}
|
||||
|
||||
func (entity *tdlibTypeProperty) GetClass() *tdlibClass {
|
||||
primitive := entity.GetPrimitive()
|
||||
return getClass(primitive, func(entity *tlparser.Class) string {
|
||||
return entity.Name
|
||||
}, entity.schema)
|
||||
}
|
||||
|
||||
func (entity *tdlibTypeProperty) ToGoName() string {
|
||||
return firstUpper(underscoreToCamelCase(entity.name))
|
||||
}
|
||||
|
||||
func (entity *tdlibTypeProperty) ToGoFunctionPropertyName() string {
|
||||
name := firstLower(underscoreToCamelCase(entity.name))
|
||||
if name == "type" {
|
||||
name += "Param"
|
||||
}
|
||||
|
||||
return name
|
||||
}
|
||||
|
||||
func (entity *tdlibTypeProperty) ToGoType() string {
|
||||
tdlibType := entity.propertyType
|
||||
goType := ""
|
||||
|
||||
for strings.HasPrefix(tdlibType, "vector<") {
|
||||
goType = goType + "[]"
|
||||
tdlibType = strings.TrimSuffix(strings.TrimPrefix(tdlibType, "vector<"), ">")
|
||||
}
|
||||
|
||||
if entity.IsClass() {
|
||||
return goType + entity.GetClass().ToGoType()
|
||||
}
|
||||
|
||||
if entity.GetType().IsInternal() {
|
||||
return goType + entity.GetType().ToGoType()
|
||||
}
|
||||
|
||||
return goType + "*" + entity.GetType().ToGoType()
|
||||
}
|
||||
|
||||
func isType(name string, field func(entity *tlparser.Type) string, schema *tlparser.Schema) bool {
|
||||
name = normalizeEntityName(name)
|
||||
for _, entity := range schema.Types {
|
||||
if name == field(entity) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func getType(name string, field func(entity *tlparser.Type) string, schema *tlparser.Schema) *tdlibType {
|
||||
name = normalizeEntityName(name)
|
||||
for _, entity := range schema.Types {
|
||||
if name == field(entity) {
|
||||
return TdlibType(entity.Name, schema)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func isClass(name string, field func(entity *tlparser.Class) string, schema *tlparser.Schema) bool {
|
||||
name = normalizeEntityName(name)
|
||||
for _, entity := range schema.Classes {
|
||||
if name == field(entity) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func getClass(name string, field func(entity *tlparser.Class) string, schema *tlparser.Schema) *tdlibClass {
|
||||
name = normalizeEntityName(name)
|
||||
for _, entity := range schema.Classes {
|
||||
if name == field(entity) {
|
||||
return TdlibClass(entity.Name, schema)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func normalizeEntityName(name string) string {
|
||||
if name == "Bool" {
|
||||
name = "boolFalse"
|
||||
}
|
||||
|
||||
return name
|
||||
}
|
|
@ -0,0 +1,186 @@
|
|||
package codegen
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
|
||||
"github.com/astravexton/go-tdlib/tlparser"
|
||||
)
|
||||
|
||||
func GenerateTypes(schema *tlparser.Schema, packageName string) []byte {
|
||||
buf := bytes.NewBufferString("")
|
||||
|
||||
buf.WriteString(fmt.Sprintf("%s\n\npackage %s\n\n", header, packageName))
|
||||
|
||||
buf.WriteString(`import (
|
||||
"encoding/json"
|
||||
)
|
||||
|
||||
`)
|
||||
|
||||
buf.WriteString("const (\n")
|
||||
for _, entity := range schema.Classes {
|
||||
tdlibClass := TdlibClass(entity.Name, schema)
|
||||
buf.WriteString(fmt.Sprintf(" %s = %q\n", tdlibClass.ToClassConst(), entity.Name))
|
||||
}
|
||||
for _, entity := range schema.Types {
|
||||
tdlibType := TdlibType(entity.Name, schema)
|
||||
if tdlibType.IsInternal() || tdlibType.HasClass() {
|
||||
continue
|
||||
}
|
||||
buf.WriteString(fmt.Sprintf(" %s = %q\n", tdlibType.ToClassConst(), entity.Class))
|
||||
}
|
||||
buf.WriteString(")")
|
||||
|
||||
buf.WriteString("\n\n")
|
||||
|
||||
buf.WriteString("const (\n")
|
||||
for _, entity := range schema.Types {
|
||||
tdlibType := TdlibType(entity.Name, schema)
|
||||
if tdlibType.IsInternal() {
|
||||
continue
|
||||
}
|
||||
buf.WriteString(fmt.Sprintf(" %s = %q\n", tdlibType.ToTypeConst(), entity.Name))
|
||||
}
|
||||
buf.WriteString(")")
|
||||
|
||||
buf.WriteString("\n\n")
|
||||
|
||||
for _, class := range schema.Classes {
|
||||
tdlibClass := TdlibClass(class.Name, schema)
|
||||
|
||||
buf.WriteString(fmt.Sprintf(`// %s
|
||||
type %s interface {
|
||||
%sType() string
|
||||
}
|
||||
|
||||
`, class.Description, tdlibClass.ToGoType(), tdlibClass.ToGoType()))
|
||||
}
|
||||
|
||||
for _, typ := range schema.Types {
|
||||
tdlibType := TdlibType(typ.Name, schema)
|
||||
if tdlibType.IsInternal() {
|
||||
continue
|
||||
}
|
||||
|
||||
buf.WriteString("// " + typ.Description + "\n")
|
||||
|
||||
if len(typ.Properties) > 0 {
|
||||
buf.WriteString(`type ` + tdlibType.ToGoType() + ` struct {
|
||||
meta
|
||||
`)
|
||||
for _, property := range typ.Properties {
|
||||
tdlibTypeProperty := TdlibTypeProperty(property.Name, property.Type, schema)
|
||||
|
||||
buf.WriteString(fmt.Sprintf(" // %s\n", property.Description))
|
||||
buf.WriteString(fmt.Sprintf(" %s %s `json:\"%s\"`\n", tdlibTypeProperty.ToGoName(), tdlibTypeProperty.ToGoType(), property.Name))
|
||||
}
|
||||
|
||||
buf.WriteString("}\n\n")
|
||||
} else {
|
||||
buf.WriteString(`type ` + tdlibType.ToGoType() + ` struct{
|
||||
meta
|
||||
}
|
||||
|
||||
`)
|
||||
}
|
||||
|
||||
buf.WriteString(fmt.Sprintf(`func (entity *%s) MarshalJSON() ([]byte, error) {
|
||||
entity.meta.Type = entity.GetType()
|
||||
|
||||
type stub %s
|
||||
|
||||
return json.Marshal((*stub)(entity))
|
||||
}
|
||||
|
||||
`, tdlibType.ToGoType(), tdlibType.ToGoType()))
|
||||
|
||||
buf.WriteString(fmt.Sprintf(`func (*%s) GetClass() string {
|
||||
return %s
|
||||
}
|
||||
|
||||
func (*%s) GetType() string {
|
||||
return %s
|
||||
}
|
||||
|
||||
`, tdlibType.ToGoType(), tdlibType.ToClassConst(), tdlibType.ToGoType(), tdlibType.ToTypeConst()))
|
||||
|
||||
if tdlibType.HasClass() {
|
||||
tdlibClass := TdlibClass(tdlibType.GetClass().Name, schema)
|
||||
|
||||
buf.WriteString(fmt.Sprintf(`func (*%s) %sType() string {
|
||||
return %s
|
||||
}
|
||||
|
||||
`, tdlibType.ToGoType(), tdlibClass.ToGoType(), tdlibType.ToTypeConst()))
|
||||
}
|
||||
|
||||
if tdlibType.HasClassProperties() {
|
||||
buf.WriteString(fmt.Sprintf(`func (%s *%s) UnmarshalJSON(data []byte) error {
|
||||
var tmp struct {
|
||||
`, typ.Name, tdlibType.ToGoType()))
|
||||
|
||||
var countSimpleProperties int
|
||||
|
||||
for _, property := range typ.Properties {
|
||||
tdlibTypeProperty := TdlibTypeProperty(property.Name, property.Type, schema)
|
||||
|
||||
if !tdlibTypeProperty.IsClass() {
|
||||
buf.WriteString(fmt.Sprintf(" %s %s `json:\"%s\"`\n", tdlibTypeProperty.ToGoName(), tdlibTypeProperty.ToGoType(), property.Name))
|
||||
countSimpleProperties++
|
||||
} else {
|
||||
if tdlibTypeProperty.IsList() {
|
||||
buf.WriteString(fmt.Sprintf(" %s %s `json:\"%s\"`\n", tdlibTypeProperty.ToGoName(), "[]json.RawMessage", property.Name))
|
||||
} else {
|
||||
buf.WriteString(fmt.Sprintf(" %s %s `json:\"%s\"`\n", tdlibTypeProperty.ToGoName(), "json.RawMessage", property.Name))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
buf.WriteString(` }
|
||||
|
||||
err := json.Unmarshal(data, &tmp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
`)
|
||||
|
||||
for _, property := range typ.Properties {
|
||||
tdlibTypeProperty := TdlibTypeProperty(property.Name, property.Type, schema)
|
||||
|
||||
if !tdlibTypeProperty.IsClass() {
|
||||
buf.WriteString(fmt.Sprintf(" %s.%s = tmp.%s\n", typ.Name, tdlibTypeProperty.ToGoName(), tdlibTypeProperty.ToGoName()))
|
||||
}
|
||||
}
|
||||
|
||||
if countSimpleProperties > 0 {
|
||||
buf.WriteString("\n")
|
||||
}
|
||||
|
||||
for _, property := range typ.Properties {
|
||||
tdlibTypeProperty := TdlibTypeProperty(property.Name, property.Type, schema)
|
||||
|
||||
if tdlibTypeProperty.IsClass() && !tdlibTypeProperty.IsList() {
|
||||
buf.WriteString(fmt.Sprintf(` field%s, _ := Unmarshal%s(tmp.%s)
|
||||
%s.%s = field%s
|
||||
|
||||
`, tdlibTypeProperty.ToGoName(), tdlibTypeProperty.ToGoType(), tdlibTypeProperty.ToGoName(), typ.Name, tdlibTypeProperty.ToGoName(), tdlibTypeProperty.ToGoName()))
|
||||
}
|
||||
if tdlibTypeProperty.IsClass() && tdlibTypeProperty.IsList() {
|
||||
buf.WriteString(fmt.Sprintf(` field%s, _ := UnmarshalListOf%s(tmp.%s)
|
||||
%s.%s = field%s
|
||||
|
||||
`, tdlibTypeProperty.ToGoName(), tdlibTypeProperty.GetClass().ToGoType(), tdlibTypeProperty.ToGoName(), typ.Name, tdlibTypeProperty.ToGoName(), tdlibTypeProperty.ToGoName()))
|
||||
}
|
||||
}
|
||||
|
||||
buf.WriteString(` return nil
|
||||
}
|
||||
|
||||
`)
|
||||
}
|
||||
}
|
||||
|
||||
return buf.Bytes()
|
||||
}
|
|
@ -0,0 +1,120 @@
|
|||
package codegen
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
|
||||
"github.com/astravexton/go-tdlib/tlparser"
|
||||
)
|
||||
|
||||
func GenerateUnmarshalers(schema *tlparser.Schema, packageName string) []byte {
|
||||
buf := bytes.NewBufferString("")
|
||||
|
||||
buf.WriteString(fmt.Sprintf("%s\n\npackage %s\n\n", header, packageName))
|
||||
|
||||
buf.WriteString(`import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
`)
|
||||
|
||||
for _, class := range schema.Classes {
|
||||
tdlibClass := TdlibClass(class.Name, schema)
|
||||
|
||||
buf.WriteString(fmt.Sprintf(`func Unmarshal%s(data json.RawMessage) (%s, error) {
|
||||
var meta meta
|
||||
|
||||
err := json.Unmarshal(data, &meta)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
switch meta.Type {
|
||||
`, tdlibClass.ToGoType(), tdlibClass.ToGoType()))
|
||||
|
||||
for _, subType := range tdlibClass.GetSubTypes() {
|
||||
buf.WriteString(fmt.Sprintf(` case %s:
|
||||
return Unmarshal%s(data)
|
||||
|
||||
`, subType.ToTypeConst(), subType.ToGoType()))
|
||||
|
||||
}
|
||||
|
||||
buf.WriteString(` default:
|
||||
return nil, fmt.Errorf("Error unmarshaling. Unknown type: " + meta.Type)
|
||||
}
|
||||
}
|
||||
|
||||
`)
|
||||
|
||||
buf.WriteString(fmt.Sprintf(`func UnmarshalListOf%s(dataList []json.RawMessage) ([]%s, error) {
|
||||
list := []%s{}
|
||||
|
||||
for _, data := range dataList {
|
||||
entity, err := Unmarshal%s(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
list = append(list, entity)
|
||||
}
|
||||
|
||||
return list, nil
|
||||
}
|
||||
|
||||
`, tdlibClass.ToGoType(), tdlibClass.ToGoType(), tdlibClass.ToGoType(), tdlibClass.ToGoType()))
|
||||
|
||||
}
|
||||
|
||||
for _, typ := range schema.Types {
|
||||
tdlibType := TdlibType(typ.Name, schema)
|
||||
|
||||
if tdlibType.IsList() || tdlibType.IsInternal() {
|
||||
continue
|
||||
}
|
||||
|
||||
buf.WriteString(fmt.Sprintf(`func Unmarshal%s(data json.RawMessage) (*%s, error) {
|
||||
var resp %s
|
||||
|
||||
err := json.Unmarshal(data, &resp)
|
||||
|
||||
return &resp, err
|
||||
}
|
||||
|
||||
`, tdlibType.ToGoType(), tdlibType.ToGoType(), tdlibType.ToGoType()))
|
||||
|
||||
}
|
||||
|
||||
buf.WriteString(`func UnmarshalType(data json.RawMessage) (Type, error) {
|
||||
var meta meta
|
||||
|
||||
err := json.Unmarshal(data, &meta)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
switch meta.Type {
|
||||
`)
|
||||
|
||||
for _, typ := range schema.Types {
|
||||
tdlibType := TdlibType(typ.Name, schema)
|
||||
|
||||
if tdlibType.IsList() || tdlibType.IsInternal() {
|
||||
continue
|
||||
}
|
||||
|
||||
buf.WriteString(fmt.Sprintf(` case %s:
|
||||
return Unmarshal%s(data)
|
||||
|
||||
`, tdlibType.ToTypeConst(), tdlibType.ToGoType()))
|
||||
|
||||
}
|
||||
|
||||
buf.WriteString(` default:
|
||||
return nil, fmt.Errorf("Error unmarshaling. Unknown type: " + meta.Type)
|
||||
}
|
||||
}
|
||||
`)
|
||||
|
||||
return buf.Bytes()
|
||||
}
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,5 @@
|
|||
module github.com/astravexton/go-tdlib
|
||||
|
||||
go 1.16
|
||||
|
||||
require golang.org/x/crypto v0.0.0-20210921155107-089bfa567519
|
|
@ -0,0 +1,10 @@
|
|||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 h1:7I4JAnoQBe7ZtJcBaYHi5UtiO8tQHbUSXxL+pnGRANg=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 h1:SrN+KX8Art/Sf4HNj6Zcz06G7VEz+7w9tdXTPOZ7+l4=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
|
@ -0,0 +1,74 @@
|
|||
package tlparser
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func ParseCode(reader io.Reader, schema *Schema) error {
|
||||
var prevLine string
|
||||
var curLine string
|
||||
|
||||
userMethods := map[string]bool{}
|
||||
botMethods := map[string]bool{}
|
||||
|
||||
scanner := bufio.NewScanner(reader)
|
||||
for scanner.Scan() {
|
||||
prevLine = curLine
|
||||
curLine = scanner.Text()
|
||||
|
||||
if strings.Contains(curLine, "CHECK_IS_USER();") {
|
||||
fields := strings.Fields(prevLine)
|
||||
for _, field := range fields {
|
||||
var methodName string
|
||||
n, err := fmt.Sscanf(field, "td_api::%s", &methodName)
|
||||
if err == nil && n > 0 {
|
||||
userMethods[methodName] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if strings.Contains(curLine, "CHECK_IS_BOT();") {
|
||||
fields := strings.Fields(prevLine)
|
||||
for _, field := range fields {
|
||||
var methodName string
|
||||
n, err := fmt.Sscanf(field, "td_api::%s", &methodName)
|
||||
if err == nil && n > 0 {
|
||||
botMethods[methodName] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
err := scanner.Err()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var ok bool
|
||||
|
||||
for index, _ := range schema.Functions {
|
||||
hasType := false
|
||||
_, ok = userMethods[schema.Functions[index].Name]
|
||||
if ok {
|
||||
schema.Functions[index].Type = FUNCTION_TYPE_USER
|
||||
hasType = true
|
||||
}
|
||||
|
||||
_, ok = botMethods[schema.Functions[index].Name]
|
||||
if ok {
|
||||
schema.Functions[index].Type = FUNCTION_TYPE_BOT
|
||||
hasType = true
|
||||
}
|
||||
|
||||
if !hasType {
|
||||
schema.Functions[index].Type = FUNCTION_TYPE_COMMON
|
||||
}
|
||||
|
||||
ok = false
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,174 @@
|
|||
package tlparser
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"io"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func Parse(reader io.Reader) (*Schema, error) {
|
||||
schema := &Schema{
|
||||
Types: []*Type{},
|
||||
Classes: []*Class{},
|
||||
Functions: []*Function{},
|
||||
}
|
||||
|
||||
scanner := bufio.NewScanner(reader)
|
||||
|
||||
hitFunctions := false
|
||||
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
|
||||
switch {
|
||||
case strings.HasPrefix(line, "//@description"):
|
||||
if hitFunctions {
|
||||
schema.Functions = append(schema.Functions, parseFunction(line, scanner))
|
||||
} else {
|
||||
schema.Types = append(schema.Types, parseType(line, scanner))
|
||||
}
|
||||
|
||||
case strings.HasPrefix(line, "//@class"):
|
||||
schema.Classes = append(schema.Classes, parseClass(line, scanner))
|
||||
|
||||
case strings.Contains(line, "---functions---"):
|
||||
hitFunctions = true
|
||||
|
||||
case line == "":
|
||||
|
||||
default:
|
||||
bodyFields := strings.Fields(line)
|
||||
name := bodyFields[0]
|
||||
class := strings.TrimRight(bodyFields[len(bodyFields)-1], ";")
|
||||
if hitFunctions {
|
||||
schema.Functions = append(schema.Functions, &Function{
|
||||
Name: name,
|
||||
Description: "",
|
||||
Class: class,
|
||||
Properties: []*Property{},
|
||||
IsSynchronous: false,
|
||||
Type: FUNCTION_TYPE_UNKNOWN,
|
||||
})
|
||||
} else {
|
||||
if name == "vector" {
|
||||
name = "vector<t>"
|
||||
class = "Vector<T>"
|
||||
}
|
||||
|
||||
schema.Types = append(schema.Types, &Type{
|
||||
Name: name,
|
||||
Description: "",
|
||||
Class: class,
|
||||
Properties: []*Property{},
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return schema, nil
|
||||
}
|
||||
|
||||
func parseType(firstLine string, scanner *bufio.Scanner) *Type {
|
||||
name, description, class, properties, _ := parseEntity(firstLine, scanner)
|
||||
return &Type{
|
||||
Name: name,
|
||||
Description: description,
|
||||
Class: class,
|
||||
Properties: properties,
|
||||
}
|
||||
}
|
||||
|
||||
func parseFunction(firstLine string, scanner *bufio.Scanner) *Function {
|
||||
name, description, class, properties, isSynchronous := parseEntity(firstLine, scanner)
|
||||
return &Function{
|
||||
Name: name,
|
||||
Description: description,
|
||||
Class: class,
|
||||
Properties: properties,
|
||||
IsSynchronous: isSynchronous,
|
||||
Type: FUNCTION_TYPE_UNKNOWN,
|
||||
}
|
||||
}
|
||||
|
||||
func parseClass(firstLine string, scanner *bufio.Scanner) *Class {
|
||||
class := &Class{
|
||||
Name: "",
|
||||
Description: "",
|
||||
}
|
||||
|
||||
classLineParts := strings.Split(firstLine, "@")
|
||||
|
||||
_, class.Name = parseProperty(classLineParts[1])
|
||||
_, class.Description = parseProperty(classLineParts[2])
|
||||
|
||||
return class
|
||||
}
|
||||
|
||||
func parseEntity(firstLine string, scanner *bufio.Scanner) (string, string, string, []*Property, bool) {
|
||||
name := ""
|
||||
description := ""
|
||||
class := ""
|
||||
properties := []*Property{}
|
||||
|
||||
propertiesLine := strings.TrimLeft(firstLine, "//")
|
||||
|
||||
Loop:
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
|
||||
switch {
|
||||
case strings.HasPrefix(line, "//@"):
|
||||
propertiesLine += " " + strings.TrimLeft(line, "//")
|
||||
|
||||
case strings.HasPrefix(line, "//-"):
|
||||
propertiesLine += " " + strings.TrimLeft(line, "//-")
|
||||
|
||||
default:
|
||||
bodyFields := strings.Fields(line)
|
||||
name = bodyFields[0]
|
||||
|
||||
for _, rawProperty := range bodyFields[1 : len(bodyFields)-2] {
|
||||
propertyParts := strings.Split(rawProperty, ":")
|
||||
property := &Property{
|
||||
Name: propertyParts[0],
|
||||
Type: propertyParts[1],
|
||||
}
|
||||
properties = append(properties, property)
|
||||
}
|
||||
class = strings.TrimRight(bodyFields[len(bodyFields)-1], ";")
|
||||
break Loop
|
||||
}
|
||||
}
|
||||
|
||||
rawProperties := strings.Split(propertiesLine, "@")
|
||||
for _, rawProperty := range rawProperties[1:] {
|
||||
name, value := parseProperty(rawProperty)
|
||||
switch {
|
||||
case name == "description":
|
||||
description = value
|
||||
default:
|
||||
name = strings.TrimPrefix(name, "param_")
|
||||
property := getProperty(properties, name)
|
||||
property.Description = value
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return name, description, class, properties, strings.Contains(description, "Can be called synchronously")
|
||||
}
|
||||
|
||||
func parseProperty(str string) (string, string) {
|
||||
strParts := strings.Fields(str)
|
||||
|
||||
return strParts[0], strings.Join(strParts[1:], " ")
|
||||
}
|
||||
|
||||
func getProperty(properties []*Property, name string) *Property {
|
||||
for _, property := range properties {
|
||||
if property.Name == name {
|
||||
return property
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
package tlparser
|
||||
|
||||
type Schema struct {
|
||||
Types []*Type `json:"types"`
|
||||
Classes []*Class `json:"classes"`
|
||||
Functions []*Function `json:"functions"`
|
||||
}
|
||||
|
||||
type Type struct {
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Class string `json:"class"`
|
||||
Properties []*Property `json:"properties"`
|
||||
}
|
||||
|
||||
type Class struct {
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
}
|
||||
|
||||
type FunctionType int
|
||||
|
||||
const (
|
||||
FUNCTION_TYPE_UNKNOWN FunctionType = iota
|
||||
FUNCTION_TYPE_COMMON
|
||||
FUNCTION_TYPE_USER
|
||||
FUNCTION_TYPE_BOT
|
||||
)
|
||||
|
||||
type Function struct {
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Class string `json:"class"`
|
||||
Properties []*Property `json:"properties"`
|
||||
IsSynchronous bool `json:"is_synchronous"`
|
||||
Type FunctionType `json:"type"`
|
||||
}
|
||||
|
||||
type Property struct {
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
Description string `json:"description"`
|
||||
}
|
Loading…
Reference in New Issue