├── .gitignore ├── README.md ├── btsync.go ├── cli └── cli.go ├── client.go └── model.go /.gitignore: -------------------------------------------------------------------------------- 1 | /.idea 2 | /*.iml 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | btsync-cli 2 | ============ 3 | 4 | Console Application for BitTorrent SyncApp API written in Go 5 | Beta version. Tested against 1.2.82. 6 | 7 | Features 8 | ------ 9 | * add folder to sync folders (with empty or predefined secret) 10 | * remove folder from sync folders 11 | * list all sync folders with read_only secrets 12 | * generate secret (with readonly secret) 13 | 14 | Example Usage 15 | ------ 16 | 17 | Usage of ./cli: 18 | -a="": absolute path to add for index (-r for relative path support) 19 | -d="": delete folder by secret 20 | -g=false: get new secret 21 | -host="127.0.0.1": btsync hostname 22 | -l=false: list folders (secret, read-only secret, type, path) 23 | -p="123456": password 24 | -port="8888": btsync port 25 | -r=false: resolve relative path (for -a) 26 | -s="": secret, if empty will be autogenerated 27 | -u="admin": username 28 | -v=false: verbose mode on 29 | 30 | 31 | Known Issues 32 | ------ 33 | * Btsync api is not fully implemented 34 | * Bad Russian support 35 | * You need special API Key http://www.bittorrent.com/intl/ru/sync/developers 36 | 37 | 38 | How-to build on Synology DS210j 39 | ------ 40 | cd ~btsync-cli 41 | export GOPATH=`pwd` 42 | export GOARM=5 43 | /opt/go/bin/go build -o btsync-cli btsync/cli 44 | ./btsync-cli -h 45 | 46 | Changelog 47 | ------ 48 | v0.3 49 | * support official API instead of webui 50 | * delete folder by secret without path 51 | * generate readonly secret for folder 52 | 53 | v0.2 54 | * -r flag for relative path on server 55 | * tests 56 | -------------------------------------------------------------------------------- /btsync.go: -------------------------------------------------------------------------------- 1 | // See http://www.bittorrent.com/intl/ru/sync/developers/api 2 | // Inspired by https://github.com/vole/btsync-api 3 | package btsync 4 | 5 | import ( 6 | "net/url" 7 | "strconv" 8 | ) 9 | 10 | // Returns an array with folders info. 11 | // http://[address]:[port]/api?method=get_folders[&secret=(secret)] 12 | // - secret (optional) - if a secret is specified, will return info about the folder with this secret 13 | func (c Client) Folders() (Folders, error) { 14 | v := &Folders{} 15 | err := c.call("get_folders", nil, v) 16 | return *v, err 17 | } 18 | 19 | // Returns Folder info about the folder with this secret. 20 | //http://[address]:[port]/api?method=get_folders[&secret=(secret)] 21 | func (c Client) Folder(secret string) (*Folder, error) { 22 | v := &Folders{} 23 | err := c.call("get_folders", url.Values{"secret": {secret}}, v) 24 | if v != nil { 25 | return (*v)[0], err 26 | } 27 | 28 | return nil, err 29 | } 30 | 31 | // Adds a folder to Sync. If a secret is not specified, it will be generated automatically. The folder will have to pre-exist on the disk and Sync will add it into a list of syncing folders. 32 | // Returns '0' if no errors, error code and error message otherwise. 33 | // http://[address]:[port]/api?method=add_folder&dir=(folderPath)[&secret=(secret)&selective_sync=1] 34 | // - dir (required) - specify path to the sync folder 35 | // - secret (optional) - specify folder secret 36 | // - selective_sync (optional) - specify sync mode, selective - 1, all files (default) - 0 37 | func (c Client) AddFolder(dir, secret string, selectiveSync int) (*OperationResult, error) { 38 | v := &OperationResult{} 39 | p := url.Values{"dir": {dir}, "selective_sync": {strconv.Itoa(selectiveSync)}} 40 | if secret != "" { 41 | p.Add( "secret", secret ) 42 | } 43 | 44 | err := c.call("add_folder", p, v) 45 | return v, err 46 | } 47 | 48 | // Removes folder from Sync while leaving actual folder and files on disk. It will remove a folder from the Sync 49 | // list of folders and does not touch any files or folders on disk. Returns '0' if no error, '1' if there’s no folder with specified secret. 50 | // { "error": 0 } 51 | // http://[address]:[port]/api?method=remove_folder&secret=(secret) 52 | // - secret (required) - specify folder secret 53 | func (c Client) RemoveFolder(secret string) (*OperationResult, error) { 54 | v := &OperationResult{} 55 | err := c.call("remove_folder", url.Values{"secret": {secret}}, v) 56 | return v, err 57 | } 58 | 59 | // Returns list of files within the specified directory. 60 | // If a directory is not specified, will return list of files and folders within the root folder. 61 | // Note that the Selective Sync function is only available in the API at this time. 62 | // http://[address]:[port]/api?method=get_files&secret=(secret)[&path=(path)] 63 | // - secret (required) - must specify folder secret 64 | // - path (optional) - specify path to a subfolder of the sync folder. 65 | func (c Client) Files(secret, path string) { 66 | // TODO 67 | } 68 | 69 | // Selects file for download for selective sync folders. Returns file information with applied preferences. 70 | // http://[address]:[port]/api?method=set_file_prefs&secret=(secret)&path=(path)&download=1 71 | // - secret (required) - must specify folder secret 72 | // - path (required) - specify path to a subfolder of the sync folder. 73 | // - download (required) - specify if file should be downloaded (yes - 1, no - 0) 74 | func (c Client) SelectFile(secret, path string, download bool) { 75 | // TODO 76 | } 77 | 78 | // Returns list of peers connected to the specified folder. 79 | // http://[address]:[port]/api?method=get_folder_peers&secret=(secret) 80 | // - secret (required) - must specify folder secret 81 | func (c Client) FolderPeers(secret string) { 82 | // TODO 83 | } 84 | 85 | // Generates read-write, read-only and encryption read-only secrets. If ‘secret’ parameter is specified, will return secrets available for sharing under this secret. 86 | // The Encryption Secret is new functionality. This is a secret for a read-only peer with encrypted content (the peer can sync files but can not see their content). 87 | // One example use is if a user wanted to backup files to an untrusted, unsecure, or public location. This is set to disabled by default for all users but included in the API. 88 | // http://[address]:[port]/api?method=get_secrets[&secret=(secret)&type=encryption] 89 | // - secret (required) - must specify folder secret 90 | // - type (optional) - if type=encrypted, generate secret with support of encrypted peer 91 | func (c Client) Secrets(secret string, encrypted bool) (*Secrets, error) { 92 | v := &Secrets{} 93 | p := url.Values{} 94 | 95 | if secret != "" { 96 | p.Add("secret", secret) 97 | } 98 | 99 | if encrypted { 100 | p.Add("type", "encryption") 101 | } 102 | 103 | err := c.call("get_secrets", p, v) 104 | return v, err 105 | } 106 | 107 | // Returns preferences for the specified sync folder. 108 | // http://[address]:[port]/api?method=get_folder_prefs&secret(secret) 109 | // - secret (required) - must specify folder secret 110 | func (c Client) FolderPreferences(secret string) { 111 | // TODO 112 | } 113 | 114 | // Sets preferences for the specified sync folder. Parameters are the same as in ‘Get folder preferences’. Returns current settings. 115 | // http://[address]:[port]/api?method=set_folder_prefs&secret=(secret)¶m1=value1¶m2=value2,... 116 | // - secret (required) - must specify folder secret 117 | // - params - { use_dht, use_hosts, search_lan, use_relay_server, use_tracker, use_sync_trash } 118 | func (c Client) SetFolderPreferences(secret string) { 119 | // TODO 120 | } 121 | 122 | // Returns list of predefined hosts for the folder, or error code if a secret is not specified. 123 | // http://[address]:[port]/api?method=get_folder_hosts&secret=(secret) 124 | // - secret (required) - must specify folder secret 125 | func (c Client) FolderHosts(secret string) { 126 | // TODO 127 | } 128 | 129 | // Set folder hosts 130 | // Sets one or several predefined hosts for the specified sync folder. Existing list of hosts will be replaced. Hosts should be added as values of the ‘host’ parameter and separated by commas. 131 | // Returns current hosts if set successfully, error code otherwise. 132 | // http://[address]:[port]/api?method=set_folder_hosts&secret=(secret)&hosts=host1:port1,host2:port2,... 133 | // - secret (required) - must specify folder secret 134 | // - hosts (required) - enter list of hosts separated by comma. Host should be represented as “[address]:[port]” 135 | func (c Client) SetFolderHosts(secret string, hosts []string) { 136 | // TODO 137 | } 138 | 139 | // Returns BitTorrent Sync preferences. Contains dictionary with advanced preferences. Please see Sync user guide for description of each option. 140 | // http://[address]:[port]/api?method=get_prefs 141 | func (c Client) Preferences() (*Preferences, error) { 142 | v := &Preferences{} 143 | err := c.call("get_prefs", nil, v) 144 | return v, err 145 | } 146 | 147 | // Set preferences 148 | // Sets BitTorrent Sync preferences. Parameters are the same as in ‘Get preferences’. Advanced preferences are set as general settings. Returns current settings. 149 | // http://[address]:[port]/api?method=set_prefs¶m1=value1¶m2=value2,... 150 | // - params - { device_name, download_limit, lang, listening_port, upload_limit, use_upnp } and advanced settings. You can find more information about advanced settings in user guide. 151 | func (c Client) SetPreferences() { 152 | // TODO 153 | } 154 | 155 | // Returns OS name where BitTorrent Sync is running. 156 | // http://[address]:[port]/api?method=get_os 157 | func (c Client) OSName() (*OS, error) { 158 | v := &OS{} 159 | err := c.call("get_os", nil, v) 160 | return v, err 161 | } 162 | 163 | // Returns BitTorrent Sync version. 164 | // http://[address]:[port]/api?method=get_version 165 | func (c Client) Version() (*Version, error) { 166 | v := &Version{} 167 | err := c.call("get_version", nil, v) 168 | return v, err 169 | } 170 | 171 | // Returns current upload and download speed. 172 | // http://[address]:[port]/api?method=get_speed 173 | func (c Client) Speed() (*Speed, error) { 174 | v := &Speed{} 175 | err := c.call("get_speed", nil, v) 176 | return v, err 177 | } 178 | 179 | // Gracefully stops Sync. 180 | // http://[address]:[port]/api?method=shutdown 181 | func (c Client) Shutdown() (*OperationResult, error) { 182 | v := &OperationResult{} 183 | err := c.call("shutdown", nil, v) 184 | return v, err 185 | } 186 | -------------------------------------------------------------------------------- /cli/cli.go: -------------------------------------------------------------------------------- 1 | // btsync-cli v0.3 by sergeyfast 2 | package main 3 | 4 | import ( 5 | "btsync" 6 | "flag" 7 | "fmt" 8 | "log" 9 | "path/filepath" 10 | ) 11 | 12 | const ( 13 | DefaultHost = "127.0.0.1" 14 | DefaultPassword = "123456" 15 | ) 16 | 17 | var host, port, user, password, addPath, delSecret, secret string 18 | var verbose, listFolders, generateSecret, resolvePath bool 19 | 20 | // Fatal on Error 21 | func err(err error) { 22 | if err != nil { 23 | log.Fatal(err) 24 | } 25 | } 26 | 27 | func main() { 28 | flag.StringVar(&host, "host", DefaultHost, "btsync hostname") 29 | flag.StringVar(&port, "port", "8888", "btsync port") 30 | flag.StringVar(&user, "u", "admin", "username") 31 | flag.StringVar(&password, "p", DefaultPassword, "password") 32 | flag.StringVar(&addPath, "a", "", "absolute path to add for index (use -r for relative path support)") 33 | flag.StringVar(&delSecret, "d", "", "delete folder by secret") 34 | flag.StringVar(&secret, "s", "", "secret, if empty will be autogenerated") 35 | flag.BoolVar(&listFolders, "l", false, "list folders (secret, read-only secret, type, path)") 36 | flag.BoolVar(&generateSecret, "g", false, "get new secret (use -s for readonly)") 37 | flag.BoolVar(&resolvePath, "r", false, "resolve relative path (for -a)") 38 | flag.BoolVar(&verbose, "v", false, "verbose mode on") 39 | 40 | flag.Parse() 41 | btsync.Debug = verbose 42 | 43 | c := btsync.NewClient(host, port, user, password) 44 | if _, err := c.Version(); err != nil { 45 | log.Print("Can't get version") 46 | return 47 | } 48 | 49 | switch { 50 | case addPath != "": 51 | if resolvePath { 52 | addPath, _ = filepath.Abs(addPath) 53 | } 54 | 55 | ra, e := c.AddFolder(addPath, secret, 0) 56 | err(e) 57 | 58 | if ra.Error == 0 && ra.Result == 0 { 59 | fmt.Println("Folder was added to sync folders") 60 | fmt.Printf("Error: %d\n", ra.Error) 61 | } else { 62 | fmt.Println( ra.Message ) 63 | fmt.Printf("Folder:\t%s\n", addPath) 64 | fmt.Printf("Status: %d\n", ra.Error) 65 | } 66 | 67 | case delSecret != "": 68 | ra, e := c.RemoveFolder(delSecret) 69 | err(e) 70 | 71 | if ra.Error == 0 && ra.Result == 0 { 72 | fmt.Println("Folder was removed from sync folders") 73 | fmt.Printf("Status: %d\n", ra.Error) 74 | } else { 75 | fmt.Println( ra.Message ) 76 | fmt.Printf("Folder was not removed by secret:\t%s\n", delSecret) 77 | fmt.Printf("Status: %d\n", ra.Error) 78 | } 79 | case listFolders: 80 | fi, e := c.Folders() 81 | err(e) 82 | 83 | if len(fi) > 0 { 84 | for _, f := range fi { 85 | s, e := c.Secrets( f.Secret, false ) 86 | if e != nil { 87 | err(e) 88 | } 89 | 90 | fmt.Printf("%s\t%s\t%s\t%s\n", f.Secret, s.ReadOnly, f.Type, f.Dir) 91 | } 92 | } 93 | case generateSecret: 94 | s, e := c.Secrets(secret, false) 95 | err(e) 96 | 97 | fmt.Printf("Secret:\t%s\n", s.ReadWrite) 98 | fmt.Printf("RO:\t%s\n", s.ReadOnly) 99 | case addPath == "": 100 | log.Fatal("Use -a, -l or -g flags. Flag -h for help.") 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /client.go: -------------------------------------------------------------------------------- 1 | package btsync 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "fmt" 7 | "io/ioutil" 8 | "log" 9 | "net/http" 10 | "net/url" 11 | ) 12 | 13 | var Debug bool 14 | 15 | func NewClient(host, port, user, password string) *Client { 16 | return &Client{host, port, user, password} 17 | } 18 | 19 | // Base Url http://host:port/api 20 | func (c Client) baseUrl() string { 21 | return fmt.Sprintf("http://%s:%s/api", c.Host, c.Port) 22 | } 23 | 24 | // Create HTTP Request and Parse JSON to struct 25 | func (c Client) call(method string, v url.Values, r interface{}) error { 26 | if v == nil { 27 | v = make(url.Values) 28 | } 29 | 30 | client := &http.Client{} 31 | req, err := http.NewRequest("GET", c.baseUrl(), nil) 32 | if err != nil { 33 | return err 34 | } 35 | 36 | v.Add("method", method) 37 | req.URL.RawQuery = v.Encode() 38 | req.SetBasicAuth(c.User, c.Password) 39 | 40 | if Debug { 41 | log.Printf("Request: %s\n", req.URL) 42 | } 43 | 44 | // Make Request 45 | resp, err := client.Do(req) 46 | if err != nil { 47 | return err 48 | } 49 | 50 | if resp.StatusCode != http.StatusOK { 51 | return errors.New(resp.Status) 52 | } 53 | 54 | data, _ := ioutil.ReadAll(resp.Body) 55 | if Debug { 56 | log.Printf("Response: %s\n", data) 57 | } 58 | 59 | if err = json.Unmarshal(data, &r); err != nil { 60 | return err 61 | } 62 | 63 | return nil 64 | } 65 | -------------------------------------------------------------------------------- /model.go: -------------------------------------------------------------------------------- 1 | package btsync 2 | 3 | // BTSync Client 4 | type Client struct { 5 | Host, Port, User, Password string 6 | } 7 | 8 | type Preferences struct { 9 | DeviceName string `json:"device_name"` 10 | DiskLowPriority bool `json:"disk_low_priority"` 11 | DownloadLimit int `json:"download_limit"` 12 | FolderRescanInterval int `json:"folder_rescan_interval"` 13 | LANEncryptData bool `json:"lan_encrypt_data"` 14 | LANUseTcp bool `json:"lan_use_tcp"` 15 | Lang int `json:"lang"` 16 | ListeningPort int `json:"listening_port"` 17 | MaxFileSizeDiffForPatching int64 `json:"max_file_size_diff_for_patching"` 18 | MaxFileSizeForVersioning int64 `json:"max_file_size_for_versioning"` 19 | RateLimitLocalPeers bool `json:"rate_limit_local_peers"` 20 | RecvBufSize int64 `json:"recv_buf_size"` 21 | SendBufSize int64 `json:"send_buf_size"` 22 | SyncMaxTimeDiff int64 `json:"sync_max_time_diff"` 23 | SyncTrashTtl int64 `json:"sync_trash_ttl"` 24 | UploadLimit int `json:"upload_limit"` 25 | UseUPnP bool `json:"use_upnp"` 26 | } 27 | 28 | type Folder struct { 29 | Dir string `json:"dir"` 30 | Secret string `json:"secret"` 31 | Size int64 `json:"size"` 32 | Type string `json:"type"` 33 | Files int64 `json:"files"` 34 | Error int `json:"error"` 35 | Indexing int `json:"indexing"` 36 | } 37 | 38 | type Secrets struct { 39 | ReadOnly string `json:"read_only"` 40 | ReadWrite string `json:"read_write"` 41 | Encryption string `json:"encryption"` 42 | } 43 | 44 | type OS struct { 45 | Name string `json:"os"` 46 | } 47 | 48 | type Version struct { 49 | Version string `json:"version"` 50 | } 51 | 52 | type Speed struct { 53 | Download int64 `json:"download"` 54 | Upload int64 `json:"upload"` 55 | } 56 | 57 | type OperationResult struct { 58 | Error int `json:"error"` 59 | Result int `json:"result"` 60 | Message string `json:"message"` 61 | } 62 | 63 | type Folders []*Folder 64 | --------------------------------------------------------------------------------