Compare commits
41 commits
Author | SHA1 | Date | |
---|---|---|---|
|
78d5c8c6fa | ||
|
caca92a6bc | ||
|
9ef1b7800b | ||
|
ea3315d1d6 | ||
|
68132c65a9 | ||
|
57e017ec8f | ||
|
42db3782ec | ||
|
9d93799d86 | ||
|
d93f463386 | ||
|
77988c1682 | ||
|
c79e07c9fa | ||
|
e454cdb4c9 | ||
|
98a10d1269 | ||
|
4f6748ae83 | ||
|
c1b2117df5 | ||
|
c4207338c8 | ||
|
5a01983203 | ||
|
ee8eb83d07 | ||
|
8d0f762067 | ||
|
8fa1482d18 | ||
|
00ae404642 | ||
|
230bfe4a41 | ||
|
df33fb9263 | ||
|
d72be1d920 | ||
|
7d12a90139 | ||
|
e1689a2a6c | ||
|
a8a6347d52 | ||
|
bdee1b4092 | ||
|
0d0d39380c | ||
|
7ff1a009da | ||
|
e3a332c7e1 | ||
|
33ee6e4b94 | ||
|
f6cde976a6 | ||
|
17944fa46a | ||
|
3c18ae8de2 | ||
|
84790720ff | ||
|
23690731af | ||
|
75628d8530 | ||
|
6e819b20f6 | ||
|
da0b6e5b79 | ||
|
28791f317b |
23 changed files with 1061 additions and 575 deletions
4
.gitignore
vendored
4
.gitignore
vendored
|
@ -4,3 +4,7 @@ e2e/supporty/supporty
|
||||||
.env
|
.env
|
||||||
blobsfiles
|
blobsfiles
|
||||||
ytsync_docker
|
ytsync_docker
|
||||||
|
|
||||||
|
e2e/config.json
|
||||||
|
|
||||||
|
e2e/cookies.txt
|
||||||
|
|
|
@ -2,7 +2,7 @@ os: linux
|
||||||
dist: bionic
|
dist: bionic
|
||||||
language: go
|
language: go
|
||||||
go:
|
go:
|
||||||
- 1.16.3
|
- 1.17.x
|
||||||
|
|
||||||
install: true
|
install: true
|
||||||
|
|
||||||
|
|
37
README.md
37
README.md
|
@ -8,23 +8,17 @@ With the support of said database, the tool is also able to keep all the channel
|
||||||
|
|
||||||
|
|
||||||
# Requirements
|
# Requirements
|
||||||
- lbrynet SDK https://github.com/lbryio/lbry/releases (We strive to keep the latest release of ytsync compatible with the latest major release of the SDK)
|
- lbrynet SDK https://github.com/lbryio/lbry-sdk/releases (We strive to keep the latest release of ytsync compatible with the latest major release of the SDK)
|
||||||
- a lbrycrd node running (localhost or on a remote machine) with credits in it
|
- a lbrycrd node running (localhost or on a remote machine) with credits in it
|
||||||
|
- internal-apis (you cannot run this one yourself)
|
||||||
|
- python3-pip
|
||||||
|
- yt-dlp (`pip3 install -U yt-dlp`)
|
||||||
|
- ffmpeg (latest)
|
||||||
|
|
||||||
# Setup
|
# Setup
|
||||||
- make sure daemon is stopped and can be controlled through `systemctl` (find example below)
|
- make sure daemon is stopped and can be controlled through `systemctl` (find example below)
|
||||||
- extract the ytsync binary anywhere
|
- extract the ytsync binary anywhere
|
||||||
- add the environment variables necessary to the tool
|
- create and fill `config.json` using [this example](config.json.example)
|
||||||
- export SLACK_TOKEN="a-token-to-spam-your-slack"
|
|
||||||
- export SLACK_CHANNEL="youtube-status"
|
|
||||||
- export YOUTUBE_API_KEY="youtube-api-key"
|
|
||||||
- export LBRY_WEB_API="https://lbry-api-url-here"
|
|
||||||
- export LBRY_API_TOKEN="internal-apis-token-for-ytsync-user"
|
|
||||||
- export LBRYCRD_STRING="tcp://user:password@host:5429"
|
|
||||||
- export AWS_S3_ID="THE-ID-LIES-HERE"
|
|
||||||
- export AWS_S3_SECRET="THE-SECRET-LIES-HERE"
|
|
||||||
- export AWS_S3_REGION="us-east-1"
|
|
||||||
- export AWS_S3_BUCKET="ytsync-wallets"
|
|
||||||
|
|
||||||
## systemd script example
|
## systemd script example
|
||||||
`/etc/systemd/system/lbrynet.service`
|
`/etc/systemd/system/lbrynet.service`
|
||||||
|
@ -55,23 +49,26 @@ Usage:
|
||||||
|
|
||||||
Flags:
|
Flags:
|
||||||
--after int Specify from when to pull jobs [Unix time](Default: 0)
|
--after int Specify from when to pull jobs [Unix time](Default: 0)
|
||||||
--before int Specify until when to pull jobs [Unix time](Default: current Unix time) (default current timestamp)
|
--before int Specify until when to pull jobs [Unix time](Default: current Unix time) (default 1669311891)
|
||||||
--channelID string If specified, only this channel will be synced.
|
--channelID string If specified, only this channel will be synced.
|
||||||
--concurrent-jobs int how many jobs to process concurrently (default 1)
|
--concurrent-jobs int how many jobs to process concurrently (default 1)
|
||||||
-h, --help help for ytsync
|
-h, --help help for ytsync
|
||||||
--limit int limit the amount of channels to sync
|
--limit int limit the amount of channels to sync
|
||||||
--max-length float Maximum video length to process (in hours) (default 2)
|
--max-length int Maximum video length to process (in hours) (default 2)
|
||||||
--max-size int Maximum video size to process (in MB) (default 2048)
|
--max-size int Maximum video size to process (in MB) (default 2048)
|
||||||
--max-tries int Number of times to try a publish that fails (default 3)
|
--max-tries int Number of times to try a publish that fails (default 3)
|
||||||
|
--no-transfers Skips the transferring process of videos, channels and supports
|
||||||
|
--quick Look up only the last 50 videos from youtube
|
||||||
--remove-db-unpublished Remove videos from the database that are marked as published but aren't really published
|
--remove-db-unpublished Remove videos from the database that are marked as published but aren't really published
|
||||||
--run-once Whether the process should be stopped after one cycle or not
|
--run-once Whether the process should be stopped after one cycle or not
|
||||||
--skip-space-check Do not perform free space check on startup
|
--skip-space-check Do not perform free space check on startup
|
||||||
--status string Specify which queue to pull from. Overrides --update
|
--status string Specify which queue to pull from. Overrides --update
|
||||||
--stop-on-error If a publish fails, stop all publishing and exit
|
--status2 string Specify which secondary queue to pull from.
|
||||||
--takeover-existing-channel If channel exists and we don't own it, take over the channel
|
--takeover-existing-channel If channel exists and we don't own it, take over the channel
|
||||||
--update Update previously synced channels instead of syncing new ones
|
--update Update previously synced channels instead of syncing new ones
|
||||||
--upgrade-metadata Upgrade videos if they're on the old metadata version
|
--upgrade-metadata Upgrade videos if they're on the old metadata version
|
||||||
--videos-limit int how many videos to process per channel (default 1000)
|
--videos-limit int how many videos to process per channel (leave 0 for automatic detection)
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Running from Source
|
## Running from Source
|
||||||
|
@ -92,13 +89,13 @@ We take security seriously. Please contact [security@lbry.io](mailto:security@lb
|
||||||
|
|
||||||
## Contact
|
## Contact
|
||||||
|
|
||||||
The primary contact for this project is [Niko Storni](https://github.com/nikooo777) (niko@lbry.io).
|
The primary contact for this project is [Niko Storni](https://github.com/nikooo777) (niko@lbry.com).
|
||||||
|
|
||||||
## Additional Info and Links
|
## Additional Info and Links
|
||||||
|
|
||||||
- [https://lbry.io](https://lbry.io) - The live LBRY website
|
- [https://lbry.com](https://lbry.com) - The live LBRY website
|
||||||
- [Discord Chat](https://chat.lbry.io) - A chat room for the LBRYians
|
- [Discord Chat](https://chat.lbry.com) - A chat room for the LBRYians
|
||||||
- [Email us](mailto:hello@lbry.io) - LBRY Support email
|
- [Email us](mailto:hello@lbry.com) - LBRY Support email
|
||||||
- [Twitter](https://twitter.com/@lbryio) - LBRY Twitter page
|
- [Twitter](https://twitter.com/@lbryio) - LBRY Twitter page
|
||||||
- [Facebook](https://www.facebook.com/lbryio/) - LBRY Facebook page
|
- [Facebook](https://www.facebook.com/lbryio/) - LBRY Facebook page
|
||||||
- [Reddit](https://reddit.com/r/lbry) - LBRY Reddit page
|
- [Reddit](https://reddit.com/r/lbry) - LBRY Reddit page
|
||||||
|
|
35
config.json.example
Normal file
35
config.json.example
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
{
|
||||||
|
"slack_token": "",
|
||||||
|
"slack_channel": "ytsync-dev",
|
||||||
|
"internal_apis_endpoint": "http://localhost:15400",
|
||||||
|
"internal_apis_auth_token": "ytsyntoken",
|
||||||
|
"lbrycrd_string": "tcp://lbry:lbry@localhost:15200",
|
||||||
|
"wallet_s3_config": {
|
||||||
|
"id": "",
|
||||||
|
"secret": "",
|
||||||
|
"region": "us-east-1",
|
||||||
|
"bucket": "ytsync-wallets",
|
||||||
|
"endpoint": ""
|
||||||
|
},
|
||||||
|
"blockchaindb_s3_config": {
|
||||||
|
"id": "",
|
||||||
|
"secret": "",
|
||||||
|
"region": "us-east-1",
|
||||||
|
"bucket": "blockchaindbs",
|
||||||
|
"endpoint": ""
|
||||||
|
},
|
||||||
|
"thumbnails_s3_config": {
|
||||||
|
"id": "",
|
||||||
|
"secret": "",
|
||||||
|
"region": "us-east-1",
|
||||||
|
"bucket": "thumbnails.lbry.com",
|
||||||
|
"endpoint": ""
|
||||||
|
},
|
||||||
|
"aws_thumbnails_s3_config": {
|
||||||
|
"id": "",
|
||||||
|
"secret": "",
|
||||||
|
"region": "us-east-1",
|
||||||
|
"bucket": "thumbnails.lbry.com",
|
||||||
|
"endpoint": ""
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,10 +1,14 @@
|
||||||
package configs
|
package configs
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"os"
|
||||||
|
"regexp"
|
||||||
|
|
||||||
"github.com/lbryio/lbry.go/v2/extras/errors"
|
"github.com/lbryio/lbry.go/v2/extras/errors"
|
||||||
|
|
||||||
"github.com/aws/aws-sdk-go/aws"
|
"github.com/aws/aws-sdk-go/aws"
|
||||||
"github.com/aws/aws-sdk-go/aws/credentials"
|
"github.com/aws/aws-sdk-go/aws/credentials"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
"github.com/tkanos/gonfig"
|
"github.com/tkanos/gonfig"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -50,3 +54,22 @@ func (s *S3Configs) GetS3AWSConfig() *aws.Config {
|
||||||
S3ForcePathStyle: aws.Bool(true),
|
S3ForcePathStyle: aws.Bool(true),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
func (c *Configs) GetHostname() string {
|
||||||
|
var hostname string
|
||||||
|
|
||||||
|
var err error
|
||||||
|
hostname, err = os.Hostname()
|
||||||
|
if err != nil {
|
||||||
|
log.Error("could not detect system hostname")
|
||||||
|
hostname = "ytsync_unknown"
|
||||||
|
}
|
||||||
|
reg, err := regexp.Compile("[^a-zA-Z0-9_]+")
|
||||||
|
if err == nil {
|
||||||
|
hostname = reg.ReplaceAllString(hostname, "_")
|
||||||
|
|
||||||
|
}
|
||||||
|
if len(hostname) > 30 {
|
||||||
|
hostname = hostname[0:30]
|
||||||
|
}
|
||||||
|
return hostname
|
||||||
|
}
|
||||||
|
|
|
@ -39,7 +39,6 @@ func GetPlaylistVideoIDs(channelName string, maxVideos int, stopChan stop.Chan,
|
||||||
if v == "" {
|
if v == "" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
logrus.Debugf("%d - video id %s", i, v)
|
|
||||||
if i >= maxVideos {
|
if i >= maxVideos {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
@ -50,7 +49,7 @@ func GetPlaylistVideoIDs(channelName string, maxVideos int, stopChan stop.Chan,
|
||||||
|
|
||||||
const releaseTimeFormat = "2006-01-02, 15:04:05 (MST)"
|
const releaseTimeFormat = "2006-01-02, 15:04:05 (MST)"
|
||||||
|
|
||||||
func GetVideoInformation(config *sdk.APIConfig, videoID string, stopChan stop.Chan, ip *net.TCPAddr, pool *ip_manager.IPPool) (*ytdl.YtdlVideo, error) {
|
func GetVideoInformation(videoID string, stopChan stop.Chan, pool *ip_manager.IPPool) (*ytdl.YtdlVideo, error) {
|
||||||
args := []string{
|
args := []string{
|
||||||
"--skip-download",
|
"--skip-download",
|
||||||
"--write-info-json",
|
"--write-info-json",
|
||||||
|
@ -80,50 +79,6 @@ func GetVideoInformation(config *sdk.APIConfig, videoID string, stopChan stop.Ch
|
||||||
return nil, errors.Err(err)
|
return nil, errors.Err(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// now get an accurate time
|
|
||||||
const maxTries = 5
|
|
||||||
tries := 0
|
|
||||||
GetTime:
|
|
||||||
tries++
|
|
||||||
t, err := getUploadTime(config, videoID, ip, video.UploadDate)
|
|
||||||
if err != nil {
|
|
||||||
//slack(":warning: Upload time error: %v", err)
|
|
||||||
if tries <= maxTries && (errors.Is(err, errNotScraped) || errors.Is(err, errUploadTimeEmpty) || errors.Is(err, errStatusParse) || errors.Is(err, errConnectionIssue)) {
|
|
||||||
err := triggerScrape(videoID, ip)
|
|
||||||
if err == nil {
|
|
||||||
time.Sleep(2 * time.Second) // let them scrape it
|
|
||||||
goto GetTime
|
|
||||||
} else {
|
|
||||||
//slack("triggering scrape returned error: %v", err)
|
|
||||||
}
|
|
||||||
} else if !errors.Is(err, errNotScraped) && !errors.Is(err, errUploadTimeEmpty) {
|
|
||||||
//slack(":warning: Error while trying to get accurate upload time for %s: %v", videoID, err)
|
|
||||||
if t == "" {
|
|
||||||
return nil, errors.Err(err)
|
|
||||||
} else {
|
|
||||||
t = "" //TODO: get rid of the other piece below?
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// do fallback below
|
|
||||||
}
|
|
||||||
//slack("After all that, upload time for %s is %s", videoID, t)
|
|
||||||
|
|
||||||
if t != "" {
|
|
||||||
parsed, err := time.Parse("2006-01-02, 15:04:05 (MST)", t) // this will probably be UTC, but Go's timezone parsing is fucked up. it ignores the timezone in the date
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Err(err)
|
|
||||||
}
|
|
||||||
//slack(":exclamation: Got an accurate time for %s", videoID)
|
|
||||||
video.UploadDateForReal = parsed
|
|
||||||
} else { //TODO: this is the piece that isn't needed!
|
|
||||||
slack(":warning: Could not get accurate time for %s. Falling back to time from upload ytdl: %s.", videoID, video.UploadDate)
|
|
||||||
// fall back to UploadDate from youtube-dl
|
|
||||||
video.UploadDateForReal, err = time.Parse("20060102", video.UploadDate)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return video, nil
|
return video, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -213,46 +168,8 @@ func getUploadTime(config *sdk.APIConfig, videoID string, ip *net.TCPAddr, uploa
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if time.Now().AddDate(0, 0, -3).After(ytdlUploadDate) {
|
|
||||||
return ytdlUploadDate.Format(releaseTimeFormat), nil
|
return ytdlUploadDate.Format(releaseTimeFormat), nil
|
||||||
}
|
}
|
||||||
client := getClient(ip)
|
|
||||||
req, err := http.NewRequest(http.MethodGet, "https://caa.iti.gr/get_verificationV3?url=https://www.youtube.com/watch?v="+videoID, nil)
|
|
||||||
if err != nil {
|
|
||||||
return ytdlUploadDate.Format(releaseTimeFormat), errors.Err(err)
|
|
||||||
}
|
|
||||||
req.Header.Set("User-Agent", ChromeUA)
|
|
||||||
|
|
||||||
res, err := client.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
return ytdlUploadDate.Format(releaseTimeFormat), errors.Err(err)
|
|
||||||
}
|
|
||||||
defer res.Body.Close()
|
|
||||||
|
|
||||||
var uploadTime struct {
|
|
||||||
Time string `json:"video_upload_time"`
|
|
||||||
Message string `json:"message"`
|
|
||||||
Status string `json:"status"`
|
|
||||||
}
|
|
||||||
err = json.NewDecoder(res.Body).Decode(&uploadTime)
|
|
||||||
if err != nil {
|
|
||||||
return ytdlUploadDate.Format(releaseTimeFormat), errors.Err(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if uploadTime.Status == "ERROR1" {
|
|
||||||
return ytdlUploadDate.Format(releaseTimeFormat), errNotScraped
|
|
||||||
}
|
|
||||||
|
|
||||||
if uploadTime.Status == "" && strings.HasPrefix(uploadTime.Message, "CANNOT_RETRIEVE_REPORT_FOR_VIDEO_") {
|
|
||||||
return ytdlUploadDate.Format(releaseTimeFormat), errors.Err("cannot retrieve report for video")
|
|
||||||
}
|
|
||||||
|
|
||||||
if uploadTime.Time == "" {
|
|
||||||
return ytdlUploadDate.Format(releaseTimeFormat), errUploadTimeEmpty
|
|
||||||
}
|
|
||||||
|
|
||||||
return uploadTime.Time, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getClient(ip *net.TCPAddr) *http.Client {
|
func getClient(ip *net.TCPAddr) *http.Client {
|
||||||
if ip == nil {
|
if ip == nil {
|
||||||
|
@ -277,7 +194,7 @@ func getClient(ip *net.TCPAddr) *http.Client {
|
||||||
|
|
||||||
const (
|
const (
|
||||||
GoogleBotUA = "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)"
|
GoogleBotUA = "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)"
|
||||||
ChromeUA = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.77 Safari/537.36"
|
ChromeUA = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.0.0 Safari/537.36"
|
||||||
maxAttempts = 3
|
maxAttempts = 3
|
||||||
extractionError = "YouTube said: Unable to extract video data"
|
extractionError = "YouTube said: Unable to extract video data"
|
||||||
throttledError = "HTTP Error 429"
|
throttledError = "HTTP Error 429"
|
||||||
|
@ -368,7 +285,8 @@ func runCmd(cmd *exec.Cmd, stopChan stop.Chan) ([]string, error) {
|
||||||
return nil, errors.Err("interrupted by user")
|
return nil, errors.Err("interrupted by user")
|
||||||
case err := <-done:
|
case err := <-done:
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Prefix("yt-dlp "+strings.Join(cmd.Args, " ")+" ["+string(errorLog)+"]", err)
|
//return nil, errors.Prefix("yt-dlp "+strings.Join(cmd.Args, " ")+" ["+string(errorLog)+"]", err)
|
||||||
|
return nil, errors.Prefix(string(errorLog), err)
|
||||||
}
|
}
|
||||||
return strings.Split(strings.Replace(string(outLog), "\r\n", "\n", -1), "\n"), nil
|
return strings.Split(strings.Replace(string(outLog), "\r\n", "\n", -1), "\n"), nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,11 @@ package downloader
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/lbryio/ytsync/v5/ip_manager"
|
||||||
"github.com/lbryio/ytsync/v5/sdk"
|
"github.com/lbryio/ytsync/v5/sdk"
|
||||||
|
|
||||||
|
"github.com/lbryio/lbry.go/v2/extras/stop"
|
||||||
|
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
@ -19,19 +23,18 @@ func TestGetPlaylistVideoIDs(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetVideoInformation(t *testing.T) {
|
func TestGetVideoInformation(t *testing.T) {
|
||||||
video, err := GetVideoInformation(nil, "zj7pXM9gE5M", nil, nil, nil)
|
s := stop.New()
|
||||||
if err != nil {
|
ip, err := ip_manager.GetIPPool(s)
|
||||||
logrus.Error(err)
|
assert.NoError(t, err)
|
||||||
}
|
video, err := GetVideoInformation("kDGOHNpRjzc", s.Ch(), ip)
|
||||||
if video != nil {
|
assert.NoError(t, err)
|
||||||
|
assert.NotNil(t, video)
|
||||||
logrus.Info(video.ID)
|
logrus.Info(video.ID)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
func Test_getUploadTime(t *testing.T) {
|
func Test_getUploadTime(t *testing.T) {
|
||||||
configs := sdk.APIConfig{}
|
configs := sdk.APIConfig{}
|
||||||
got, err := getUploadTime(&configs, "kDGOHNpRjzc", nil, "20060102")
|
got, err := getUploadTime(&configs, "kDGOHNpRjzc", nil, "20060102")
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
t.Log(got)
|
t.Log(got)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,149 +2,136 @@ package ytdl
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/lbryio/ytsync/v5/sdk"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
type YtdlVideo struct {
|
type YtdlVideo struct {
|
||||||
UploadDate string `json:"upload_date"`
|
|
||||||
UploadDateForReal time.Time // you need to manually set this since the value in the API doesn't include the time
|
|
||||||
Extractor string `json:"extractor"`
|
|
||||||
Series interface{} `json:"series"`
|
|
||||||
Format string `json:"format"`
|
|
||||||
Vbr interface{} `json:"vbr"`
|
|
||||||
Chapters interface{} `json:"chapters"`
|
|
||||||
Height int `json:"height"`
|
|
||||||
LikeCount interface{} `json:"like_count"`
|
|
||||||
Duration int `json:"duration"`
|
|
||||||
Fulltitle string `json:"fulltitle"`
|
|
||||||
PlaylistIndex interface{} `json:"playlist_index"`
|
|
||||||
Album interface{} `json:"album"`
|
|
||||||
ViewCount int `json:"view_count"`
|
|
||||||
Playlist interface{} `json:"playlist"`
|
|
||||||
Title string `json:"title"`
|
|
||||||
Filename string `json:"_filename"`
|
|
||||||
Creator interface{} `json:"creator"`
|
|
||||||
Ext string `json:"ext"`
|
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
DislikeCount interface{} `json:"dislike_count"`
|
Title string `json:"title"`
|
||||||
AverageRating float64 `json:"average_rating"`
|
|
||||||
Abr float64 `json:"abr"`
|
|
||||||
UploaderURL string `json:"uploader_url"`
|
|
||||||
Categories []string `json:"categories"`
|
|
||||||
Fps float64 `json:"fps"`
|
|
||||||
StretchedRatio interface{} `json:"stretched_ratio"`
|
|
||||||
SeasonNumber interface{} `json:"season_number"`
|
|
||||||
Annotations interface{} `json:"annotations"`
|
|
||||||
WebpageURLBasename string `json:"webpage_url_basename"`
|
|
||||||
Acodec string `json:"acodec"`
|
|
||||||
DisplayID string `json:"display_id"`
|
|
||||||
//RequestedFormats []RequestedFormat `json:"requested_formats"`
|
|
||||||
//AutomaticCaptions struct{} `json:"automatic_captions"`
|
|
||||||
Description string `json:"description"`
|
|
||||||
Tags []string `json:"tags"`
|
|
||||||
Track interface{} `json:"track"`
|
|
||||||
RequestedSubtitles interface{} `json:"requested_subtitles"`
|
|
||||||
StartTime interface{} `json:"start_time"`
|
|
||||||
Uploader string `json:"uploader"`
|
|
||||||
ExtractorKey string `json:"extractor_key"`
|
|
||||||
FormatID string `json:"format_id"`
|
|
||||||
EpisodeNumber interface{} `json:"episode_number"`
|
|
||||||
UploaderID string `json:"uploader_id"`
|
|
||||||
//Subtitles struct{} `json:"subtitles"`
|
|
||||||
ReleaseYear interface{} `json:"release_year"`
|
|
||||||
Thumbnails []Thumbnail `json:"thumbnails"`
|
Thumbnails []Thumbnail `json:"thumbnails"`
|
||||||
License interface{} `json:"license"`
|
Description string `json:"description"`
|
||||||
Artist interface{} `json:"artist"`
|
|
||||||
AgeLimit int `json:"age_limit"`
|
|
||||||
ReleaseDate interface{} `json:"release_date"`
|
|
||||||
AltTitle interface{} `json:"alt_title"`
|
|
||||||
Thumbnail string `json:"thumbnail"`
|
|
||||||
ChannelID string `json:"channel_id"`
|
ChannelID string `json:"channel_id"`
|
||||||
IsLive interface{} `json:"is_live"`
|
Duration int `json:"duration"`
|
||||||
Width int `json:"width"`
|
Categories []string `json:"categories"`
|
||||||
EndTime interface{} `json:"end_time"`
|
Tags []string `json:"tags"`
|
||||||
WebpageURL string `json:"webpage_url"`
|
IsLive bool `json:"is_live"`
|
||||||
Formats []Format `json:"formats"`
|
LiveStatus string `json:"live_status"`
|
||||||
ChannelURL string `json:"channel_url"`
|
ReleaseTimestamp *int64 `json:"release_timestamp"`
|
||||||
Resolution interface{} `json:"resolution"`
|
uploadDateForReal *time.Time
|
||||||
Vcodec string `json:"vcodec"`
|
Availability string `json:"availability"`
|
||||||
}
|
ReleaseDate string `json:"release_date"`
|
||||||
|
UploadDate string `json:"upload_date"`
|
||||||
|
|
||||||
type RequestedFormat struct {
|
//WasLive bool `json:"was_live"`
|
||||||
Asr interface{} `json:"asr"`
|
//Formats interface{} `json:"formats"`
|
||||||
Tbr float64 `json:"tbr"`
|
//Thumbnail string `json:"thumbnail"`
|
||||||
Container string `json:"container"`
|
//Uploader string `json:"uploader"`
|
||||||
Language interface{} `json:"language"`
|
//UploaderID string `json:"uploader_id"`
|
||||||
Format string `json:"format"`
|
//UploaderURL string `json:"uploader_url"`
|
||||||
URL string `json:"url"`
|
//ChannelURL string `json:"channel_url"`
|
||||||
Vcodec string `json:"vcodec"`
|
//ViewCount int `json:"view_count"`
|
||||||
FormatNote string `json:"format_note"`
|
//AverageRating interface{} `json:"average_rating"`
|
||||||
Height int `json:"height"`
|
//AgeLimit int `json:"age_limit"`
|
||||||
Width int `json:"width"`
|
//WebpageURL string `json:"webpage_url"`
|
||||||
Ext string `json:"ext"`
|
//PlayableInEmbed bool `json:"playable_in_embed"`
|
||||||
FragmentBaseURL string `json:"fragment_base_url"`
|
//AutomaticCaptions interface{} `json:"automatic_captions"`
|
||||||
Filesize interface{} `json:"filesize"`
|
//Subtitles interface{} `json:"subtitles"`
|
||||||
Fps float64 `json:"fps"`
|
//Chapters interface{} `json:"chapters"`
|
||||||
ManifestURL string `json:"manifest_url"`
|
//LikeCount int `json:"like_count"`
|
||||||
Protocol string `json:"protocol"`
|
//Channel string `json:"channel"`
|
||||||
FormatID string `json:"format_id"`
|
//ChannelFollowerCount int `json:"channel_follower_count"`
|
||||||
HTTPHeaders struct {
|
//OriginalURL string `json:"original_url"`
|
||||||
AcceptCharset string `json:"Accept-Charset"`
|
//WebpageURLBasename string `json:"webpage_url_basename"`
|
||||||
AcceptLanguage string `json:"Accept-Language"`
|
//WebpageURLDomain string `json:"webpage_url_domain"`
|
||||||
AcceptEncoding string `json:"Accept-Encoding"`
|
//Extractor string `json:"extractor"`
|
||||||
Accept string `json:"Accept"`
|
//ExtractorKey string `json:"extractor_key"`
|
||||||
UserAgent string `json:"User-Agent"`
|
//Playlist interface{} `json:"playlist"`
|
||||||
} `json:"http_headers"`
|
//PlaylistIndex interface{} `json:"playlist_index"`
|
||||||
Fragments []struct {
|
//DisplayID string `json:"display_id"`
|
||||||
Path string `json:"path"`
|
//Fulltitle string `json:"fulltitle"`
|
||||||
Duration float64 `json:"duration,omitempty"`
|
//DurationString string `json:"duration_string"`
|
||||||
} `json:"fragments"`
|
//RequestedSubtitles interface{} `json:"requested_subtitles"`
|
||||||
Acodec string `json:"acodec"`
|
//HasDrm bool `json:"__has_drm"`
|
||||||
Abr int `json:"abr,omitempty"`
|
//RequestedFormats interface{} `json:"requested_formats"`
|
||||||
}
|
//Format string `json:"format"`
|
||||||
|
//FormatID string `json:"format_id"`
|
||||||
type Format struct {
|
//Ext string `json:"ext"`
|
||||||
Asr int `json:"asr"`
|
//Protocol string `json:"protocol"`
|
||||||
Filesize int `json:"filesize"`
|
//Language interface{} `json:"language"`
|
||||||
FormatID string `json:"format_id"`
|
//FormatNote string `json:"format_note"`
|
||||||
FormatNote string `json:"format_note"`
|
//FilesizeApprox int `json:"filesize_approx"`
|
||||||
Fps interface{} `json:"fps"`
|
//Tbr float64 `json:"tbr"`
|
||||||
Height interface{} `json:"height"`
|
//Width int `json:"width"`
|
||||||
Quality int `json:"quality"`
|
//Height int `json:"height"`
|
||||||
Tbr float64 `json:"tbr"`
|
//Resolution string `json:"resolution"`
|
||||||
URL string `json:"url"`
|
//Fps int `json:"fps"`
|
||||||
Width interface{} `json:"width"`
|
//DynamicRange string `json:"dynamic_range"`
|
||||||
Ext string `json:"ext"`
|
//Vcodec string `json:"vcodec"`
|
||||||
Vcodec string `json:"vcodec"`
|
//Vbr float64 `json:"vbr"`
|
||||||
Acodec string `json:"acodec"`
|
//StretchedRatio interface{} `json:"stretched_ratio"`
|
||||||
Abr float64 `json:"abr,omitempty"`
|
//Acodec string `json:"acodec"`
|
||||||
DownloaderOptions struct {
|
//Abr float64 `json:"abr"`
|
||||||
HTTPChunkSize int `json:"http_chunk_size"`
|
//Asr int `json:"asr"`
|
||||||
} `json:"downloader_options,omitempty"`
|
//Epoch int `json:"epoch"`
|
||||||
Container string `json:"container,omitempty"`
|
//Filename string `json:"filename"`
|
||||||
Format string `json:"format"`
|
//Urls string `json:"urls"`
|
||||||
Protocol string `json:"protocol"`
|
//Type string `json:"_type"`
|
||||||
HTTPHeaders struct {
|
|
||||||
UserAgent string `json:"User-Agent"`
|
|
||||||
AcceptCharset string `json:"Accept-Charset"`
|
|
||||||
Accept string `json:"Accept"`
|
|
||||||
AcceptEncoding string `json:"Accept-Encoding"`
|
|
||||||
AcceptLanguage string `json:"Accept-Language"`
|
|
||||||
} `json:"http_headers"`
|
|
||||||
Vbr float64 `json:"vbr,omitempty"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Thumbnail struct {
|
type Thumbnail struct {
|
||||||
URL string `json:"url"`
|
URL string `json:"url"`
|
||||||
Width int `json:"width"`
|
Preference int `json:"preference"`
|
||||||
Resolution string `json:"resolution"`
|
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
Height int `json:"height"`
|
Height int `json:"height,omitempty"`
|
||||||
|
Width int `json:"width,omitempty"`
|
||||||
|
Resolution string `json:"resolution,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type HTTPHeaders struct {
|
func (v *YtdlVideo) GetUploadTime() time.Time {
|
||||||
AcceptCharset string `json:"Accept-Charset"`
|
//priority list:
|
||||||
AcceptLanguage string `json:"Accept-Language"`
|
// release timestamp from yt
|
||||||
AcceptEncoding string `json:"Accept-Encoding"`
|
// release timestamp from morty
|
||||||
Accept string `json:"Accept"`
|
// release date from yt
|
||||||
UserAgent string `json:"User-Agent"`
|
// upload date from yt
|
||||||
|
if v.uploadDateForReal != nil {
|
||||||
|
return *v.uploadDateForReal
|
||||||
|
}
|
||||||
|
|
||||||
|
var ytdlReleaseTimestamp time.Time
|
||||||
|
if v.ReleaseTimestamp != nil && *v.ReleaseTimestamp > 0 {
|
||||||
|
ytdlReleaseTimestamp = time.Unix(*v.ReleaseTimestamp, 0).UTC()
|
||||||
|
}
|
||||||
|
//get morty timestamp
|
||||||
|
var mortyReleaseTimestamp time.Time
|
||||||
|
mortyRelease, err := sdk.GetAPIsConfigs().GetReleasedDate(v.ID)
|
||||||
|
if err != nil {
|
||||||
|
logrus.Error(err)
|
||||||
|
} else if mortyRelease != nil {
|
||||||
|
mortyReleaseTimestamp, err = time.ParseInLocation(time.RFC3339, mortyRelease.ReleaseTime, time.UTC)
|
||||||
|
if err != nil {
|
||||||
|
logrus.Error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ytdlReleaseDate, err := time.Parse("20060102", v.ReleaseDate)
|
||||||
|
if err != nil {
|
||||||
|
logrus.Error(err)
|
||||||
|
}
|
||||||
|
ytdlUploadDate, err := time.Parse("20060102", v.UploadDate)
|
||||||
|
if err != nil {
|
||||||
|
logrus.Error(err)
|
||||||
|
}
|
||||||
|
if !ytdlReleaseTimestamp.IsZero() {
|
||||||
|
v.uploadDateForReal = &ytdlReleaseTimestamp
|
||||||
|
} else if !mortyReleaseTimestamp.IsZero() {
|
||||||
|
v.uploadDateForReal = &mortyReleaseTimestamp
|
||||||
|
} else if !ytdlReleaseDate.IsZero() {
|
||||||
|
v.uploadDateForReal = &ytdlReleaseDate
|
||||||
|
} else {
|
||||||
|
v.uploadDateForReal = &ytdlUploadDate
|
||||||
|
}
|
||||||
|
|
||||||
|
return *v.uploadDateForReal
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,7 +21,7 @@ services:
|
||||||
## Wallet Server ##
|
## Wallet Server ##
|
||||||
###################
|
###################
|
||||||
walletserver:
|
walletserver:
|
||||||
image: lbry/wallet-server:latest-release
|
image: lbry/wallet-server:v0.101.1
|
||||||
restart: always
|
restart: always
|
||||||
environment:
|
environment:
|
||||||
- DB_DIRECTORY=/database
|
- DB_DIRECTORY=/database
|
||||||
|
@ -81,6 +81,7 @@ services:
|
||||||
- walletserver
|
- walletserver
|
||||||
environment:
|
environment:
|
||||||
- LBRY_STREAMING_SERVER=0.0.0.0:5280
|
- LBRY_STREAMING_SERVER=0.0.0.0:5280
|
||||||
|
- LBRY_FEE_PER_NAME_CHAR=0
|
||||||
volumes:
|
volumes:
|
||||||
- "./persist/.lbrynet:/home/lbrynet"
|
- "./persist/.lbrynet:/home/lbrynet"
|
||||||
- ".:/etc/lbry" #Put your daemon_settings.yml here
|
- ".:/etc/lbry" #Put your daemon_settings.yml here
|
||||||
|
@ -109,7 +110,7 @@ services:
|
||||||
## Internal APIs ##
|
## Internal APIs ##
|
||||||
###################
|
###################
|
||||||
internalapis:
|
internalapis:
|
||||||
image: lbry/internal-apis:master
|
image: odyseeteam/internal-apis:master
|
||||||
restart: "no"
|
restart: "no"
|
||||||
ports:
|
ports:
|
||||||
- "15400:8080"
|
- "15400:8080"
|
||||||
|
@ -127,7 +128,7 @@ services:
|
||||||
## Chainquery ##
|
## Chainquery ##
|
||||||
################
|
################
|
||||||
chainquery:
|
chainquery:
|
||||||
image: lbry/chainquery:master
|
image: odyseeteam/chainquery:master
|
||||||
restart: "no"
|
restart: "no"
|
||||||
ports:
|
ports:
|
||||||
- 6300:6300
|
- 6300:6300
|
||||||
|
|
166
go.mod
166
go.mod
|
@ -1,3 +1,5 @@
|
||||||
|
go 1.17
|
||||||
|
|
||||||
module github.com/lbryio/ytsync/v5
|
module github.com/lbryio/ytsync/v5
|
||||||
|
|
||||||
replace github.com/btcsuite/btcd => github.com/lbryio/lbrycrd.go v0.0.0-20200203050410-e1076f12bf19
|
replace github.com/btcsuite/btcd => github.com/lbryio/lbrycrd.go v0.0.0-20200203050410-e1076f12bf19
|
||||||
|
@ -6,38 +8,144 @@ replace github.com/btcsuite/btcd => github.com/lbryio/lbrycrd.go v0.0.0-20200203
|
||||||
//replace github.com/lbryio/reflector.go => /home/niko/go/src/github.com/lbryio/reflector.go/
|
//replace github.com/lbryio/reflector.go => /home/niko/go/src/github.com/lbryio/reflector.go/
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/Microsoft/go-winio v0.4.14 // indirect
|
|
||||||
github.com/abadojack/whatlanggo v1.0.1
|
github.com/abadojack/whatlanggo v1.0.1
|
||||||
github.com/asaskevich/govalidator v0.0.0-20200819183940-29e1ff8eb0bb
|
github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d
|
||||||
github.com/aws/aws-sdk-go v1.25.9
|
github.com/aws/aws-sdk-go v1.44.6
|
||||||
github.com/davecgh/go-spew v1.1.1
|
github.com/davecgh/go-spew v1.1.1
|
||||||
github.com/docker/distribution v2.7.1+incompatible // indirect
|
github.com/docker/docker v20.10.17+incompatible
|
||||||
github.com/docker/docker v1.13.1
|
github.com/lbryio/lbry.go/v2 v2.7.2-0.20220815204100-2adb8af5b68c
|
||||||
github.com/docker/go-connections v0.4.0 // indirect
|
github.com/lbryio/reflector.go v1.1.3-0.20220730181028-f5d30b1a6e79
|
||||||
github.com/docker/go-units v0.4.0 // indirect
|
github.com/mitchellh/go-ps v1.0.0
|
||||||
github.com/hashicorp/go-immutable-radix v1.1.0 // indirect
|
github.com/prometheus/client_golang v1.12.1
|
||||||
github.com/hashicorp/go-sockaddr v1.0.2 // indirect
|
github.com/shopspring/decimal v1.3.1
|
||||||
github.com/hashicorp/memberlist v0.1.5 // indirect
|
github.com/sirupsen/logrus v1.9.0
|
||||||
github.com/hashicorp/serf v0.8.5 // indirect
|
github.com/spf13/cobra v1.4.0
|
||||||
github.com/kr/pretty v0.2.1 // indirect
|
github.com/stretchr/testify v1.7.1
|
||||||
github.com/lbryio/lbry.go/v2 v2.7.2-0.20210824154606-3e18b74da08b
|
|
||||||
github.com/lbryio/reflector.go v1.1.3-0.20210412225256-4392c9724262
|
|
||||||
github.com/miekg/dns v1.1.22 // indirect
|
|
||||||
github.com/mitchellh/go-ps v0.0.0-20190716172923-621e5597135b
|
|
||||||
github.com/opencontainers/go-digest v1.0.0-rc1 // indirect
|
|
||||||
github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2 // indirect
|
|
||||||
github.com/prometheus/client_golang v0.9.3
|
|
||||||
github.com/shopspring/decimal v0.0.0-20191009025716-f1972eb1d1f5
|
|
||||||
github.com/sirupsen/logrus v1.4.2
|
|
||||||
github.com/spf13/cobra v0.0.5
|
|
||||||
github.com/spf13/pflag v1.0.5 // indirect
|
|
||||||
github.com/stretchr/testify v1.7.0
|
|
||||||
github.com/tkanos/gonfig v0.0.0-20210106201359-53e13348de2f
|
github.com/tkanos/gonfig v0.0.0-20210106201359-53e13348de2f
|
||||||
github.com/vbauerster/mpb/v7 v7.0.2
|
github.com/vbauerster/mpb/v7 v7.4.1
|
||||||
google.golang.org/appengine v1.6.5 // indirect
|
gopkg.in/vansante/go-ffprobe.v2 v2.0.3
|
||||||
gopkg.in/ini.v1 v1.60.2 // indirect
|
|
||||||
gopkg.in/vansante/go-ffprobe.v2 v2.0.2
|
|
||||||
gotest.tools v2.2.0+incompatible
|
gotest.tools v2.2.0+incompatible
|
||||||
)
|
)
|
||||||
|
|
||||||
go 1.13
|
require (
|
||||||
|
github.com/Microsoft/go-winio v0.5.1 // indirect
|
||||||
|
github.com/VividCortex/ewma v1.2.0 // indirect
|
||||||
|
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d // indirect
|
||||||
|
github.com/armon/go-metrics v0.0.0-20190430140413-ec5e00d3c878 // indirect
|
||||||
|
github.com/beorn7/perks v1.0.1 // indirect
|
||||||
|
github.com/bluele/gcache v0.0.2 // indirect
|
||||||
|
github.com/brk0v/directio v0.0.0-20190225130936-69406e757cf7 // indirect
|
||||||
|
github.com/btcsuite/btcd v0.0.0-20190824003749-130ea5bddde3 // indirect
|
||||||
|
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f // indirect
|
||||||
|
github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d // indirect
|
||||||
|
github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd // indirect
|
||||||
|
github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792 // indirect
|
||||||
|
github.com/c2h5oh/datasize v0.0.0-20200825124411-48ed595a09d2 // indirect
|
||||||
|
github.com/cespare/xxhash/v2 v2.1.2 // indirect
|
||||||
|
github.com/cheekybits/genny v1.0.0 // indirect
|
||||||
|
github.com/docker/distribution v2.8.1+incompatible // indirect
|
||||||
|
github.com/docker/go-connections v0.4.0 // indirect
|
||||||
|
github.com/docker/go-units v0.4.0 // indirect
|
||||||
|
github.com/ekyoung/gin-nice-recovery v0.0.0-20160510022553-1654dca486db // indirect
|
||||||
|
github.com/fatih/structs v1.1.0 // indirect
|
||||||
|
github.com/fsnotify/fsnotify v1.4.9 // indirect
|
||||||
|
github.com/ghodss/yaml v1.0.0 // indirect
|
||||||
|
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||||
|
github.com/gin-gonic/gin v1.7.7 // indirect
|
||||||
|
github.com/go-errors/errors v1.1.1 // indirect
|
||||||
|
github.com/go-ini/ini v1.48.0 // indirect
|
||||||
|
github.com/go-playground/locales v0.13.0 // indirect
|
||||||
|
github.com/go-playground/universal-translator v0.17.0 // indirect
|
||||||
|
github.com/go-playground/validator/v10 v10.4.1 // indirect
|
||||||
|
github.com/go-sql-driver/mysql v1.6.0 // indirect
|
||||||
|
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect
|
||||||
|
github.com/gofrs/uuid v3.2.0+incompatible // indirect
|
||||||
|
github.com/gogo/protobuf v1.3.2 // indirect
|
||||||
|
github.com/golang/protobuf v1.5.2 // indirect
|
||||||
|
github.com/google/btree v1.0.1 // indirect
|
||||||
|
github.com/google/go-cmp v0.5.7 // indirect
|
||||||
|
github.com/gorilla/mux v1.8.0 // indirect
|
||||||
|
github.com/gorilla/rpc v1.2.0 // indirect
|
||||||
|
github.com/gorilla/websocket v1.4.2 // indirect
|
||||||
|
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||||
|
github.com/hashicorp/go-immutable-radix v1.1.0 // indirect
|
||||||
|
github.com/hashicorp/go-msgpack v0.5.5 // indirect
|
||||||
|
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
||||||
|
github.com/hashicorp/go-sockaddr v1.0.2 // indirect
|
||||||
|
github.com/hashicorp/golang-lru v0.5.4 // indirect
|
||||||
|
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||||
|
github.com/hashicorp/memberlist v0.3.0 // indirect
|
||||||
|
github.com/hashicorp/serf v0.9.7 // indirect
|
||||||
|
github.com/inconshreveable/go-update v0.0.0-20160112193335-8152e7eb6ccf // indirect
|
||||||
|
github.com/inconshreveable/mousetrap v1.0.0 // indirect
|
||||||
|
github.com/jmespath/go-jmespath v0.4.0 // indirect
|
||||||
|
github.com/johntdyer/slack-go v0.0.0-20180213144715-95fac1160b22 // indirect
|
||||||
|
github.com/johntdyer/slackrus v0.0.0-20211215141436-33e4a270affb // indirect
|
||||||
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
|
github.com/karrick/godirwalk v1.17.0 // indirect
|
||||||
|
github.com/kr/text v0.2.0 // indirect
|
||||||
|
github.com/lbryio/chainquery v1.9.0 // indirect
|
||||||
|
github.com/lbryio/lbry.go v1.1.2 // indirect
|
||||||
|
github.com/lbryio/types v0.0.0-20220224142228-73610f6654a6 // indirect
|
||||||
|
github.com/leodido/go-urn v1.2.0 // indirect
|
||||||
|
github.com/lucas-clemente/quic-go v0.28.1 // indirect
|
||||||
|
github.com/lyoshenka/bencode v0.0.0-20180323155644-b7abd7672df5 // indirect
|
||||||
|
github.com/magiconair/properties v1.8.1 // indirect
|
||||||
|
github.com/marten-seemann/qpack v0.2.1 // indirect
|
||||||
|
github.com/marten-seemann/qtls-go1-16 v0.1.5 // indirect
|
||||||
|
github.com/marten-seemann/qtls-go1-17 v0.1.2 // indirect
|
||||||
|
github.com/marten-seemann/qtls-go1-18 v0.1.2 // indirect
|
||||||
|
github.com/marten-seemann/qtls-go1-19 v0.1.0-beta.1 // indirect
|
||||||
|
github.com/mattn/go-isatty v0.0.12 // indirect
|
||||||
|
github.com/mattn/go-runewidth v0.0.13 // indirect
|
||||||
|
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect
|
||||||
|
github.com/miekg/dns v1.1.41 // indirect
|
||||||
|
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||||
|
github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 // indirect
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
|
github.com/morikuni/aec v1.0.0 // indirect
|
||||||
|
github.com/nxadm/tail v1.4.8 // indirect
|
||||||
|
github.com/onsi/ginkgo v1.16.4 // indirect
|
||||||
|
github.com/onsi/gomega v1.17.0 // indirect
|
||||||
|
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||||
|
github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799 // indirect
|
||||||
|
github.com/pelletier/go-toml v1.9.3 // indirect
|
||||||
|
github.com/pkg/errors v0.9.1 // indirect
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
|
github.com/prometheus/client_model v0.2.0 // indirect
|
||||||
|
github.com/prometheus/common v0.32.1 // indirect
|
||||||
|
github.com/prometheus/procfs v0.7.3 // indirect
|
||||||
|
github.com/rivo/uniseg v0.2.0 // indirect
|
||||||
|
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 // indirect
|
||||||
|
github.com/slack-go/slack v0.10.3 // indirect
|
||||||
|
github.com/spf13/afero v1.4.1 // indirect
|
||||||
|
github.com/spf13/cast v1.4.1 // indirect
|
||||||
|
github.com/spf13/jwalterweatherman v1.0.0 // indirect
|
||||||
|
github.com/spf13/pflag v1.0.5 // indirect
|
||||||
|
github.com/spf13/viper v1.7.1 // indirect
|
||||||
|
github.com/subosito/gotenv v1.2.0 // indirect
|
||||||
|
github.com/ugorji/go/codec v1.1.7 // indirect
|
||||||
|
github.com/volatiletech/inflect v0.0.0-20170731032912-e7201282ae8d // indirect
|
||||||
|
github.com/volatiletech/null v8.0.0+incompatible // indirect
|
||||||
|
github.com/volatiletech/sqlboiler v3.4.0+incompatible // indirect
|
||||||
|
github.com/ybbus/jsonrpc v2.1.2+incompatible // indirect
|
||||||
|
go.uber.org/atomic v1.9.0 // indirect
|
||||||
|
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 // indirect
|
||||||
|
golang.org/x/mod v0.4.2 // indirect
|
||||||
|
golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e // indirect
|
||||||
|
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect
|
||||||
|
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 // indirect
|
||||||
|
golang.org/x/text v0.3.7 // indirect
|
||||||
|
golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac // indirect
|
||||||
|
golang.org/x/tools v0.1.5 // indirect
|
||||||
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
|
||||||
|
google.golang.org/protobuf v1.27.1 // indirect
|
||||||
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
|
||||||
|
gopkg.in/ini.v1 v1.60.2 // indirect
|
||||||
|
gopkg.in/nullbio/null.v6 v6.0.0-20161116030900-40264a2e6b79 // indirect
|
||||||
|
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
|
||||||
|
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
|
||||||
|
gotest.tools/v3 v3.2.0 // indirect
|
||||||
|
)
|
||||||
|
|
26
main.go
26
main.go
|
@ -9,7 +9,6 @@ import (
|
||||||
|
|
||||||
"github.com/lbryio/ytsync/v5/configs"
|
"github.com/lbryio/ytsync/v5/configs"
|
||||||
"github.com/lbryio/ytsync/v5/manager"
|
"github.com/lbryio/ytsync/v5/manager"
|
||||||
"github.com/lbryio/ytsync/v5/sdk"
|
|
||||||
"github.com/lbryio/ytsync/v5/shared"
|
"github.com/lbryio/ytsync/v5/shared"
|
||||||
ytUtils "github.com/lbryio/ytsync/v5/util"
|
ytUtils "github.com/lbryio/ytsync/v5/util"
|
||||||
|
|
||||||
|
@ -33,6 +32,10 @@ var (
|
||||||
func main() {
|
func main() {
|
||||||
rand.Seed(time.Now().UnixNano())
|
rand.Seed(time.Now().UnixNano())
|
||||||
log.SetLevel(log.DebugLevel)
|
log.SetLevel(log.DebugLevel)
|
||||||
|
customFormatter := new(log.TextFormatter)
|
||||||
|
customFormatter.TimestampFormat = "2006-01-02 15:04:05"
|
||||||
|
customFormatter.FullTimestamp = true
|
||||||
|
log.SetFormatter(customFormatter)
|
||||||
http.Handle("/metrics", promhttp.Handler())
|
http.Handle("/metrics", promhttp.Handler())
|
||||||
go func() {
|
go func() {
|
||||||
log.Error(http.ListenAndServe(":2112", nil))
|
log.Error(http.ListenAndServe(":2112", nil))
|
||||||
|
@ -75,22 +78,11 @@ func ytSync(cmd *cobra.Command, args []string) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("could not parse configuration file: %s", errors.FullTrace(err))
|
log.Fatalf("could not parse configuration file: %s", errors.FullTrace(err))
|
||||||
}
|
}
|
||||||
var hostname string
|
|
||||||
|
|
||||||
if configs.Configuration.SlackToken == "" {
|
if configs.Configuration.SlackToken == "" {
|
||||||
log.Error("A slack token was not present in the config! Slack messages disabled!")
|
log.Error("A slack token was not present in the config! Slack messages disabled!")
|
||||||
} else {
|
} else {
|
||||||
var err error
|
util.InitSlack(configs.Configuration.SlackToken, configs.Configuration.SlackChannel, configs.Configuration.GetHostname())
|
||||||
hostname, err = os.Hostname()
|
|
||||||
if err != nil {
|
|
||||||
log.Error("could not detect system hostname")
|
|
||||||
hostname = "ytsync-unknown"
|
|
||||||
}
|
|
||||||
if len(hostname) > 30 {
|
|
||||||
hostname = hostname[0:30]
|
|
||||||
}
|
|
||||||
|
|
||||||
util.InitSlack(configs.Configuration.SlackToken, configs.Configuration.SlackChannel, hostname)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if cliFlags.Status != "" && !util.InSlice(cliFlags.Status, shared.SyncStatuses) {
|
if cliFlags.Status != "" && !util.InSlice(cliFlags.Status, shared.SyncStatuses) {
|
||||||
|
@ -131,17 +123,9 @@ func ytSync(cmd *cobra.Command, args []string) {
|
||||||
|
|
||||||
blobsDir := ytUtils.GetBlobsDir()
|
blobsDir := ytUtils.GetBlobsDir()
|
||||||
|
|
||||||
apiConfig := &sdk.APIConfig{
|
|
||||||
ApiURL: configs.Configuration.InternalApisEndpoint,
|
|
||||||
ApiToken: configs.Configuration.InternalApisAuthToken,
|
|
||||||
HostName: hostname,
|
|
||||||
}
|
|
||||||
|
|
||||||
sm := manager.NewSyncManager(
|
sm := manager.NewSyncManager(
|
||||||
cliFlags,
|
cliFlags,
|
||||||
blobsDir,
|
blobsDir,
|
||||||
configs.Configuration.LbrycrdString,
|
|
||||||
apiConfig,
|
|
||||||
)
|
)
|
||||||
err = sm.Start()
|
err = sm.Start()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -8,6 +8,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/lbryio/ytsync/v5/blobs_reflector"
|
"github.com/lbryio/ytsync/v5/blobs_reflector"
|
||||||
|
"github.com/lbryio/ytsync/v5/configs"
|
||||||
"github.com/lbryio/ytsync/v5/ip_manager"
|
"github.com/lbryio/ytsync/v5/ip_manager"
|
||||||
"github.com/lbryio/ytsync/v5/namer"
|
"github.com/lbryio/ytsync/v5/namer"
|
||||||
"github.com/lbryio/ytsync/v5/sdk"
|
"github.com/lbryio/ytsync/v5/sdk"
|
||||||
|
@ -29,12 +30,12 @@ type SyncManager struct {
|
||||||
channelsToSync []Sync
|
channelsToSync []Sync
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSyncManager(cliFlags shared.SyncFlags, blobsDir, lbrycrdDsn string, apiConfig *sdk.APIConfig) *SyncManager {
|
func NewSyncManager(cliFlags shared.SyncFlags, blobsDir string) *SyncManager {
|
||||||
return &SyncManager{
|
return &SyncManager{
|
||||||
CliFlags: cliFlags,
|
CliFlags: cliFlags,
|
||||||
blobsDir: blobsDir,
|
blobsDir: blobsDir,
|
||||||
LbrycrdDsn: lbrycrdDsn,
|
LbrycrdDsn: configs.Configuration.LbrycrdString,
|
||||||
ApiConfig: apiConfig,
|
ApiConfig: sdk.GetAPIsConfigs(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
func (s *SyncManager) enqueueChannel(channel *shared.YoutubeChannel) {
|
func (s *SyncManager) enqueueChannel(channel *shared.YoutubeChannel) {
|
||||||
|
@ -137,7 +138,7 @@ func (s *SyncManager) Start() error {
|
||||||
"WALLET HAS NOT BEEN MOVED TO THE WALLET BACKUP DIR",
|
"WALLET HAS NOT BEEN MOVED TO THE WALLET BACKUP DIR",
|
||||||
"NotEnoughFunds",
|
"NotEnoughFunds",
|
||||||
"no space left on device",
|
"no space left on device",
|
||||||
"failure uploading wallet",
|
"there was a problem uploading the wallet",
|
||||||
"the channel in the wallet is different than the channel in the database",
|
"the channel in the wallet is different than the channel in the database",
|
||||||
"this channel does not belong to this wallet!",
|
"this channel does not belong to this wallet!",
|
||||||
"You already have a stream claim published under the name",
|
"You already have a stream claim published under the name",
|
||||||
|
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/lbryio/ytsync/v5/configs"
|
"github.com/lbryio/ytsync/v5/configs"
|
||||||
"github.com/lbryio/ytsync/v5/util"
|
"github.com/lbryio/ytsync/v5/util"
|
||||||
|
@ -84,9 +85,9 @@ func (s *Sync) downloadWallet() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Sync) downloadBlockchainDB() error {
|
func (s *Sync) downloadBlockchainDB() error {
|
||||||
//if util.IsRegTest() {
|
if util.IsRegTest() {
|
||||||
// return nil // tests fail if we re-use the same blockchain DB
|
return nil // tests fail if we re-use the same blockchain DB
|
||||||
//}
|
}
|
||||||
defaultBDBPath, defaultTempBDBPath, key, err := s.getBlockchainDBPaths()
|
defaultBDBPath, defaultTempBDBPath, key, err := s.getBlockchainDBPaths()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Err(err)
|
return errors.Err(err)
|
||||||
|
@ -143,7 +144,10 @@ func (s *Sync) downloadBlockchainDB() error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Prefix("error extracting blockchain.db files", err)
|
return errors.Prefix("error extracting blockchain.db files", err)
|
||||||
}
|
}
|
||||||
|
err = os.Remove(defaultTempBDBPath)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Err(err)
|
||||||
|
}
|
||||||
log.Printf("blockchain.db data downloaded and extracted to %s", blockchainDbDir)
|
log.Printf("blockchain.db data downloaded and extracted to %s", blockchainDbDir)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -212,13 +216,22 @@ func (s *Sync) uploadWallet() error {
|
||||||
}
|
}
|
||||||
defer file.Close()
|
defer file.Close()
|
||||||
|
|
||||||
|
start := time.Now()
|
||||||
|
|
||||||
|
for time.Since(start) < 30*time.Minute {
|
||||||
_, err = uploader.Upload(&s3manager.UploadInput{
|
_, err = uploader.Upload(&s3manager.UploadInput{
|
||||||
Bucket: aws.String(configs.Configuration.WalletS3Config.Bucket),
|
Bucket: aws.String(configs.Configuration.WalletS3Config.Bucket),
|
||||||
Key: key,
|
Key: key,
|
||||||
Body: file,
|
Body: file,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
time.Sleep(30 * time.Second)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return errors.Prefix("there was a problem uploading the wallet to S3", errors.Err(err))
|
||||||
}
|
}
|
||||||
log.Println("wallet uploaded to S3")
|
log.Println("wallet uploaded to S3")
|
||||||
|
|
||||||
|
@ -264,5 +277,9 @@ func (s *Sync) uploadBlockchainDB() error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
log.Println("blockchain.db files uploaded to S3")
|
log.Println("blockchain.db files uploaded to S3")
|
||||||
|
err = os.Remove(tarPath)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Err(err)
|
||||||
|
}
|
||||||
return os.Remove(defaultBDBDir)
|
return os.Remove(defaultBDBDir)
|
||||||
}
|
}
|
||||||
|
|
|
@ -103,6 +103,9 @@ func (s *Sync) walletSetup() error {
|
||||||
videosOnYoutube = s.Manager.CliFlags.VideosToSync(s.DbChannelData.TotalSubscribers)
|
videosOnYoutube = s.Manager.CliFlags.VideosToSync(s.DbChannelData.TotalSubscribers)
|
||||||
}
|
}
|
||||||
unallocatedVideos := videosOnYoutube - (publishedCount + failedCount)
|
unallocatedVideos := videosOnYoutube - (publishedCount + failedCount)
|
||||||
|
if unallocatedVideos < 0 {
|
||||||
|
unallocatedVideos = 0
|
||||||
|
}
|
||||||
channelFee := channelClaimAmount
|
channelFee := channelClaimAmount
|
||||||
channelAlreadyClaimed := s.DbChannelData.ChannelClaimID != ""
|
channelAlreadyClaimed := s.DbChannelData.ChannelClaimID != ""
|
||||||
if channelAlreadyClaimed {
|
if channelAlreadyClaimed {
|
||||||
|
@ -110,7 +113,7 @@ func (s *Sync) walletSetup() error {
|
||||||
}
|
}
|
||||||
requiredBalance := float64(unallocatedVideos)*(publishAmount+estimatedMaxTxFee) + channelFee
|
requiredBalance := float64(unallocatedVideos)*(publishAmount+estimatedMaxTxFee) + channelFee
|
||||||
if s.Manager.CliFlags.UpgradeMetadata {
|
if s.Manager.CliFlags.UpgradeMetadata {
|
||||||
requiredBalance += float64(notUpgradedCount) * 0.001
|
requiredBalance += float64(notUpgradedCount) * estimatedMaxTxFee
|
||||||
}
|
}
|
||||||
|
|
||||||
refillAmount := 0.0
|
refillAmount := 0.0
|
||||||
|
@ -127,6 +130,12 @@ func (s *Sync) walletSetup() error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Err(err)
|
return errors.Err(err)
|
||||||
}
|
}
|
||||||
|
} else if balance > requiredBalance {
|
||||||
|
extraLBC := balance - requiredBalance
|
||||||
|
if extraLBC > 5 {
|
||||||
|
sendBackAmount := extraLBC - 1
|
||||||
|
logUtils.SendInfoToSlack("channel %s has %.1f credits which is %.1f more than it requires (%.1f). We should send at least %.1f that back.", s.DbChannelData.ChannelId, balance, extraLBC, requiredBalance, sendBackAmount)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
claimAddress, err := s.daemon.AddressList(nil, nil, 1, 20)
|
claimAddress, err := s.daemon.AddressList(nil, nil, 1, 20)
|
||||||
|
@ -366,14 +375,12 @@ func (s *Sync) ensureChannelOwnership() error {
|
||||||
|
|
||||||
channelUsesOldMetadata := false
|
channelUsesOldMetadata := false
|
||||||
if channelToUse != nil {
|
if channelToUse != nil {
|
||||||
channelUsesOldMetadata = channelToUse.Value.GetThumbnail() == nil
|
channelUsesOldMetadata = channelToUse.Value.GetThumbnail() == nil || (len(channelToUse.Value.GetLanguages()) == 0 && s.DbChannelData.Language != "")
|
||||||
if !channelUsesOldMetadata {
|
if !channelUsesOldMetadata {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
channelBidAmount := channelClaimAmount
|
|
||||||
|
|
||||||
balanceResp, err := s.daemon.AccountBalance(nil)
|
balanceResp, err := s.daemon.AccountBalance(nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -385,8 +392,8 @@ func (s *Sync) ensureChannelOwnership() error {
|
||||||
return errors.Err(err)
|
return errors.Err(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if balance.LessThan(decimal.NewFromFloat(channelBidAmount)) {
|
if balance.LessThan(decimal.NewFromFloat(channelClaimAmount)) {
|
||||||
err = s.addCredits(channelBidAmount + 0.3)
|
err = s.addCredits(channelClaimAmount + estimatedMaxTxFee*3)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -424,18 +431,16 @@ func (s *Sync) ensureChannelOwnership() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
var languages []string = nil
|
var languages []string = nil
|
||||||
//we don't have this data without the API
|
if s.DbChannelData.Language != "" {
|
||||||
//if channelInfo.DefaultLanguage != "" {
|
languages = []string{s.DbChannelData.Language}
|
||||||
// if channelInfo.DefaultLanguage == "iw" {
|
}
|
||||||
// channelInfo.DefaultLanguage = "he"
|
|
||||||
// }
|
|
||||||
// languages = []string{channelInfo.DefaultLanguage}
|
|
||||||
//}
|
|
||||||
var locations []jsonrpc.Location = nil
|
var locations []jsonrpc.Location = nil
|
||||||
if channelInfo.Topbar.DesktopTopbarRenderer.CountryCode != "" {
|
if channelInfo.Topbar.DesktopTopbarRenderer.CountryCode != "" {
|
||||||
locations = []jsonrpc.Location{{Country: &channelInfo.Topbar.DesktopTopbarRenderer.CountryCode}}
|
locations = []jsonrpc.Location{{Country: &channelInfo.Topbar.DesktopTopbarRenderer.CountryCode}}
|
||||||
}
|
}
|
||||||
var c *jsonrpc.TransactionSummary
|
var c *jsonrpc.TransactionSummary
|
||||||
|
var recoveredChannelClaimID string
|
||||||
claimCreateOptions := jsonrpc.ClaimCreateOptions{
|
claimCreateOptions := jsonrpc.ClaimCreateOptions{
|
||||||
Title: &channelInfo.Microformat.MicroformatDataRenderer.Title,
|
Title: &channelInfo.Microformat.MicroformatDataRenderer.Title,
|
||||||
Description: &channelInfo.Metadata.ChannelMetadataRenderer.Description,
|
Description: &channelInfo.Metadata.ChannelMetadataRenderer.Description,
|
||||||
|
@ -445,12 +450,20 @@ func (s *Sync) ensureChannelOwnership() error {
|
||||||
ThumbnailURL: &thumbnailURL,
|
ThumbnailURL: &thumbnailURL,
|
||||||
}
|
}
|
||||||
if channelUsesOldMetadata {
|
if channelUsesOldMetadata {
|
||||||
|
da, err := s.getDefaultAccount()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
if s.DbChannelData.TransferState <= 1 {
|
if s.DbChannelData.TransferState <= 1 {
|
||||||
c, err = s.daemon.ChannelUpdate(s.DbChannelData.ChannelClaimID, jsonrpc.ChannelUpdateOptions{
|
c, err = s.daemon.ChannelUpdate(s.DbChannelData.ChannelClaimID, jsonrpc.ChannelUpdateOptions{
|
||||||
ClearTags: util.PtrToBool(true),
|
ClearTags: util.PtrToBool(true),
|
||||||
ClearLocations: util.PtrToBool(true),
|
ClearLocations: util.PtrToBool(true),
|
||||||
ClearLanguages: util.PtrToBool(true),
|
ClearLanguages: util.PtrToBool(true),
|
||||||
ChannelCreateOptions: jsonrpc.ChannelCreateOptions{
|
ChannelCreateOptions: jsonrpc.ChannelCreateOptions{
|
||||||
|
AccountID: &da,
|
||||||
|
FundingAccountIDs: []string{
|
||||||
|
da,
|
||||||
|
},
|
||||||
ClaimCreateOptions: claimCreateOptions,
|
ClaimCreateOptions: claimCreateOptions,
|
||||||
CoverURL: bannerURL,
|
CoverURL: bannerURL,
|
||||||
},
|
},
|
||||||
|
@ -460,20 +473,50 @@ func (s *Sync) ensureChannelOwnership() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
c, err = s.daemon.ChannelCreate(s.DbChannelData.DesiredChannelName, channelBidAmount, jsonrpc.ChannelCreateOptions{
|
c, err = s.daemon.ChannelCreate(s.DbChannelData.DesiredChannelName, channelClaimAmount, jsonrpc.ChannelCreateOptions{
|
||||||
ClaimCreateOptions: claimCreateOptions,
|
ClaimCreateOptions: claimCreateOptions,
|
||||||
CoverURL: bannerURL,
|
CoverURL: bannerURL,
|
||||||
})
|
})
|
||||||
|
if err != nil {
|
||||||
|
claimId, err2 := s.getChannelClaimIDForTimedOutCreation()
|
||||||
|
if err2 != nil {
|
||||||
|
err = errors.Prefix(err2.Error(), err)
|
||||||
|
} else {
|
||||||
|
recoveredChannelClaimID = claimId
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if recoveredChannelClaimID != "" {
|
||||||
|
s.DbChannelData.ChannelClaimID = recoveredChannelClaimID
|
||||||
|
} else {
|
||||||
s.DbChannelData.ChannelClaimID = c.Outputs[0].ClaimID
|
s.DbChannelData.ChannelClaimID = c.Outputs[0].ClaimID
|
||||||
|
}
|
||||||
return s.Manager.ApiConfig.SetChannelClaimID(s.DbChannelData.ChannelId, s.DbChannelData.ChannelClaimID)
|
return s.Manager.ApiConfig.SetChannelClaimID(s.DbChannelData.ChannelId, s.DbChannelData.ChannelClaimID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//getChannelClaimIDForTimedOutCreation is a raw function that returns the only channel that exists in the wallet
|
||||||
|
// this is used because the SDK sucks and can't figure out when to return when creating a claim...
|
||||||
|
func (s *Sync) getChannelClaimIDForTimedOutCreation() (string, error) {
|
||||||
|
channels, err := s.daemon.ChannelList(nil, 1, 500, nil)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
} else if channels == nil {
|
||||||
|
return "", errors.Err("no channel response")
|
||||||
|
}
|
||||||
|
if len((*channels).Items) != 1 {
|
||||||
|
return "", errors.Err("more than one channel found when trying to recover from SDK failure in creating the channel")
|
||||||
|
}
|
||||||
|
desiredChannel := (*channels).Items[0]
|
||||||
|
if desiredChannel.Name != s.DbChannelData.DesiredChannelName {
|
||||||
|
return "", errors.Err("the channel found in the wallet has a different name than the one we expected")
|
||||||
|
}
|
||||||
|
|
||||||
|
return desiredChannel.ClaimID, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Sync) addCredits(amountToAdd float64) error {
|
func (s *Sync) addCredits(amountToAdd float64) error {
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
defer func(start time.Time) {
|
defer func(start time.Time) {
|
||||||
|
|
|
@ -241,7 +241,7 @@ func transferVideos(s *Sync) error {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Bid: util.PtrToString("0.005"), // Todo - Dont hardcode
|
Bid: util.PtrToString(fmt.Sprintf("%.5f", publishAmount/2.)),
|
||||||
}
|
}
|
||||||
videoStatus := shared.VideoStatus{
|
videoStatus := shared.VideoStatus{
|
||||||
ChannelID: s.DbChannelData.ChannelId,
|
ChannelID: s.DbChannelData.ChannelId,
|
||||||
|
@ -293,7 +293,7 @@ func (s *Sync) streamUpdate(ui *updateInfo) error {
|
||||||
timing.TimedComponent("transferStreamUpdate").Add(time.Since(start))
|
timing.TimedComponent("transferStreamUpdate").Add(time.Since(start))
|
||||||
if updateError != nil {
|
if updateError != nil {
|
||||||
ui.videoStatus.FailureReason = updateError.Error()
|
ui.videoStatus.FailureReason = updateError.Error()
|
||||||
ui.videoStatus.Status = shared.VideoStatusTranferFailed
|
ui.videoStatus.Status = shared.VideoStatusTransferFailed
|
||||||
ui.videoStatus.IsTransferred = util.PtrToBool(false)
|
ui.videoStatus.IsTransferred = util.PtrToBool(false)
|
||||||
} else {
|
} else {
|
||||||
ui.videoStatus.IsTransferred = util.PtrToBool(len(result.Outputs) != 0)
|
ui.videoStatus.IsTransferred = util.PtrToBool(len(result.Outputs) != 0)
|
||||||
|
|
|
@ -33,11 +33,10 @@ import (
|
||||||
|
|
||||||
const (
|
const (
|
||||||
channelClaimAmount = 0.01
|
channelClaimAmount = 0.01
|
||||||
estimatedMaxTxFee = 0.1
|
estimatedMaxTxFee = 0.0015
|
||||||
minimumAccountBalance = 1.0
|
minimumAccountBalance = 1.0
|
||||||
minimumRefillAmount = 1
|
minimumRefillAmount = 1
|
||||||
publishAmount = 0.01
|
publishAmount = 0.002
|
||||||
maxReasonLength = 500
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Sync stores the options that control how syncing happens
|
// Sync stores the options that control how syncing happens
|
||||||
|
@ -286,6 +285,8 @@ func (s *Sync) setChannelTerminationStatus(e *error) {
|
||||||
"interrupted during daemon startup",
|
"interrupted during daemon startup",
|
||||||
"interrupted by user",
|
"interrupted by user",
|
||||||
"use --skip-space-check to ignore",
|
"use --skip-space-check to ignore",
|
||||||
|
"failure uploading blockchain DB",
|
||||||
|
"default_wallet already exists",
|
||||||
}
|
}
|
||||||
dbWipeConditions := []string{
|
dbWipeConditions := []string{
|
||||||
"Missing inputs",
|
"Missing inputs",
|
||||||
|
@ -335,7 +336,7 @@ func (s *Sync) waitForDaemonStart() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Sync) stopAndUploadWallet(e *error) {
|
func (s *Sync) stopAndUploadWallet(e *error) {
|
||||||
log.Printf("Stopping daemon")
|
log.Println("Stopping daemon")
|
||||||
shutdownErr := logUtils.StopDaemon()
|
shutdownErr := logUtils.StopDaemon()
|
||||||
if shutdownErr != nil {
|
if shutdownErr != nil {
|
||||||
logShutdownError(shutdownErr)
|
logShutdownError(shutdownErr)
|
||||||
|
@ -350,9 +351,9 @@ func (s *Sync) stopAndUploadWallet(e *error) {
|
||||||
err := s.uploadWallet()
|
err := s.uploadWallet()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if *e == nil {
|
if *e == nil {
|
||||||
e = &err
|
*e = err
|
||||||
} else {
|
} else {
|
||||||
*e = errors.Prefix("failure uploading wallet", *e)
|
*e = errors.Prefix(fmt.Sprintf("%s + original error", errors.FullTrace(err)), *e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
err = s.uploadBlockchainDB()
|
err = s.uploadBlockchainDB()
|
||||||
|
@ -495,7 +496,7 @@ func (s *Sync) updateRemoteDB(claims []jsonrpc.Claim, ownClaims []jsonrpc.Claim)
|
||||||
claimMarkedUnpublished := claimInDatabase && !sv.Published
|
claimMarkedUnpublished := claimInDatabase && !sv.Published
|
||||||
_, isOwnClaim := ownClaimsInfo[videoID]
|
_, isOwnClaim := ownClaimsInfo[videoID]
|
||||||
transferred := !isOwnClaim || s.DbChannelData.TransferState == 3
|
transferred := !isOwnClaim || s.DbChannelData.TransferState == 3
|
||||||
transferStatusMismatch := sv.Transferred != transferred
|
transferStatusMismatch := claimInDatabase && sv.Transferred != transferred
|
||||||
|
|
||||||
if metadataDiffers {
|
if metadataDiffers {
|
||||||
log.Debugf("%s: Mismatch in database for metadata. DB: %d - Blockchain: %d", videoID, sv.MetadataVersion, chainInfo.MetadataVersion)
|
log.Debugf("%s: Mismatch in database for metadata. DB: %d - Blockchain: %d", videoID, sv.MetadataVersion, chainInfo.MetadataVersion)
|
||||||
|
@ -556,7 +557,11 @@ func (s *Sync) updateRemoteDB(claims []jsonrpc.Claim, ownClaims []jsonrpc.Claim)
|
||||||
if sv.Transferred || sv.IsLbryFirst {
|
if sv.Transferred || sv.IsLbryFirst {
|
||||||
_, ok := allClaimsInfo[vID]
|
_, ok := allClaimsInfo[vID]
|
||||||
if !ok && sv.Published {
|
if !ok && sv.Published {
|
||||||
searchResponse, err := s.daemon.ClaimSearch(nil, &sv.ClaimID, nil, nil, 1, 20)
|
searchResponse, err := s.daemon.ClaimSearch(jsonrpc.ClaimSearchArgs{
|
||||||
|
ClaimID: &sv.ClaimID,
|
||||||
|
Page: 1,
|
||||||
|
PageSize: 20,
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error(err.Error())
|
log.Error(err.Error())
|
||||||
continue
|
continue
|
||||||
|
@ -672,7 +677,8 @@ func (s *Sync) checkIntegrity() error {
|
||||||
|
|
||||||
if pubsOnWallet > pubsOnDB { //This case should never happen
|
if pubsOnWallet > pubsOnDB { //This case should never happen
|
||||||
logUtils.SendInfoToSlack("We're claiming to have published %d videos but in reality we published %d (%s)", pubsOnDB, pubsOnWallet, s.DbChannelData.ChannelId)
|
logUtils.SendInfoToSlack("We're claiming to have published %d videos but in reality we published %d (%s)", pubsOnDB, pubsOnWallet, s.DbChannelData.ChannelId)
|
||||||
return errors.Err("not all published videos are in the database")
|
//we never really done anything about those. it happens when a user updates the channel for a publish to another ytsync channel
|
||||||
|
//return errors.Err("not all published videos are in the database")
|
||||||
}
|
}
|
||||||
if pubsOnWallet < pubsOnDB {
|
if pubsOnWallet < pubsOnDB {
|
||||||
logUtils.SendInfoToSlack("we're claiming to have published %d videos but we only published %d (%s)", pubsOnDB, pubsOnWallet, s.DbChannelData.ChannelId)
|
logUtils.SendInfoToSlack("we're claiming to have published %d videos but we only published %d (%s)", pubsOnDB, pubsOnWallet, s.DbChannelData.ChannelId)
|
||||||
|
@ -861,7 +867,7 @@ func (s *Sync) enqueueYoutubeVideos() error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
videos, err := ytapi.GetVideosToSync(s.Manager.ApiConfig, s.DbChannelData.ChannelId, s.syncedVideos, s.Manager.CliFlags.QuickSync, s.Manager.CliFlags.VideosToSync(s.DbChannelData.TotalSubscribers), ytapi.VideoParams{
|
videos, err := ytapi.GetVideosToSync(s.DbChannelData.ChannelId, s.syncedVideos, s.Manager.CliFlags.QuickSync, s.Manager.CliFlags.VideosToSync(s.DbChannelData.TotalSubscribers), ytapi.VideoParams{
|
||||||
VideoDir: s.videoDirectory,
|
VideoDir: s.videoDirectory,
|
||||||
Stopper: s.grp,
|
Stopper: s.grp,
|
||||||
IPPool: ipPool,
|
IPPool: ipPool,
|
||||||
|
|
|
@ -1,31 +1,17 @@
|
||||||
package metrics
|
package metrics
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"github.com/lbryio/ytsync/v5/configs"
|
||||||
"regexp"
|
|
||||||
|
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
"github.com/prometheus/client_golang/prometheus/promauto"
|
"github.com/prometheus/client_golang/prometheus/promauto"
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
Durations = promauto.NewHistogramVec(prometheus.HistogramOpts{
|
Durations = promauto.NewHistogramVec(prometheus.HistogramOpts{
|
||||||
Namespace: "ytsync",
|
Namespace: "ytsync",
|
||||||
Subsystem: getHostname(),
|
Subsystem: configs.Configuration.GetHostname(),
|
||||||
Name: "duration",
|
Name: "duration",
|
||||||
Help: "The durations of the individual modules",
|
Help: "The durations of the individual modules",
|
||||||
}, []string{"path"})
|
}, []string{"path"})
|
||||||
)
|
)
|
||||||
|
|
||||||
func getHostname() string {
|
|
||||||
hostname, err := os.Hostname()
|
|
||||||
if err != nil {
|
|
||||||
hostname = "ytsync_unknown"
|
|
||||||
}
|
|
||||||
reg, err := regexp.Compile("[^a-zA-Z0-9_]+")
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
return reg.ReplaceAllString(hostname, "_")
|
|
||||||
}
|
|
||||||
|
|
54
sdk/api.go
54
sdk/api.go
|
@ -13,6 +13,7 @@ import (
|
||||||
|
|
||||||
"github.com/lbryio/lbry.go/v2/extras/errors"
|
"github.com/lbryio/lbry.go/v2/extras/errors"
|
||||||
"github.com/lbryio/lbry.go/v2/extras/null"
|
"github.com/lbryio/lbry.go/v2/extras/null"
|
||||||
|
"github.com/lbryio/ytsync/v5/configs"
|
||||||
"github.com/lbryio/ytsync/v5/shared"
|
"github.com/lbryio/ytsync/v5/shared"
|
||||||
|
|
||||||
"github.com/lbryio/ytsync/v5/util"
|
"github.com/lbryio/ytsync/v5/util"
|
||||||
|
@ -30,6 +31,19 @@ type APIConfig struct {
|
||||||
HostName string
|
HostName string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var instance *APIConfig
|
||||||
|
|
||||||
|
func GetAPIsConfigs() *APIConfig {
|
||||||
|
if instance == nil {
|
||||||
|
instance = &APIConfig{
|
||||||
|
ApiURL: configs.Configuration.InternalApisEndpoint,
|
||||||
|
ApiToken: configs.Configuration.InternalApisAuthToken,
|
||||||
|
HostName: configs.Configuration.GetHostname(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return instance
|
||||||
|
}
|
||||||
|
|
||||||
func (a *APIConfig) FetchChannels(status string, cliFlags *shared.SyncFlags) ([]shared.YoutubeChannel, error) {
|
func (a *APIConfig) FetchChannels(status string, cliFlags *shared.SyncFlags) ([]shared.YoutubeChannel, error) {
|
||||||
type apiJobsResponse struct {
|
type apiJobsResponse struct {
|
||||||
Success bool `json:"success"`
|
Success bool `json:"success"`
|
||||||
|
@ -47,13 +61,10 @@ func (a *APIConfig) FetchChannels(status string, cliFlags *shared.SyncFlags) ([]
|
||||||
"channel_id": {cliFlags.ChannelID},
|
"channel_id": {cliFlags.ChannelID},
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if strings.Contains(err.Error(), "EOF") {
|
util.SendErrorToSlack("error while trying to call %s. Waiting to retry: %s", endpoint, err.Error())
|
||||||
util.SendErrorToSlack("EOF error while trying to call %s. Waiting to retry", endpoint)
|
|
||||||
time.Sleep(30 * time.Second)
|
time.Sleep(30 * time.Second)
|
||||||
return a.FetchChannels(status, cliFlags)
|
return a.FetchChannels(status, cliFlags)
|
||||||
}
|
}
|
||||||
return nil, errors.Err(err)
|
|
||||||
}
|
|
||||||
defer res.Body.Close()
|
defer res.Body.Close()
|
||||||
body, _ := ioutil.ReadAll(res.Body)
|
body, _ := ioutil.ReadAll(res.Body)
|
||||||
if res.StatusCode != http.StatusOK {
|
if res.StatusCode != http.StatusOK {
|
||||||
|
@ -110,13 +121,10 @@ func (a *APIConfig) SetChannelCert(certHex string, channelID string) error {
|
||||||
"auth_token": {a.ApiToken},
|
"auth_token": {a.ApiToken},
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if strings.Contains(err.Error(), "EOF") {
|
util.SendErrorToSlack("error while trying to call %s. Waiting to retry: %s", endpoint, err.Error())
|
||||||
util.SendErrorToSlack("EOF error while trying to call %s. Waiting to retry", endpoint)
|
|
||||||
time.Sleep(30 * time.Second)
|
time.Sleep(30 * time.Second)
|
||||||
return a.SetChannelCert(certHex, channelID)
|
return a.SetChannelCert(certHex, channelID)
|
||||||
}
|
}
|
||||||
return errors.Err(err)
|
|
||||||
}
|
|
||||||
defer res.Body.Close()
|
defer res.Body.Close()
|
||||||
body, _ := ioutil.ReadAll(res.Body)
|
body, _ := ioutil.ReadAll(res.Body)
|
||||||
if res.StatusCode != http.StatusOK {
|
if res.StatusCode != http.StatusOK {
|
||||||
|
@ -158,13 +166,10 @@ func (a *APIConfig) SetChannelStatus(channelID string, status string, failureRea
|
||||||
}
|
}
|
||||||
res, err := http.PostForm(endpoint, params)
|
res, err := http.PostForm(endpoint, params)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if strings.Contains(err.Error(), "EOF") {
|
util.SendErrorToSlack("error while trying to call %s. Waiting to retry: %s", endpoint, err.Error())
|
||||||
util.SendErrorToSlack("EOF error while trying to call %s. Waiting to retry", endpoint)
|
|
||||||
time.Sleep(30 * time.Second)
|
time.Sleep(30 * time.Second)
|
||||||
return a.SetChannelStatus(channelID, status, failureReason, transferState)
|
return a.SetChannelStatus(channelID, status, failureReason, transferState)
|
||||||
}
|
}
|
||||||
return nil, nil, errors.Err(err)
|
|
||||||
}
|
|
||||||
defer res.Body.Close()
|
defer res.Body.Close()
|
||||||
body, _ := ioutil.ReadAll(res.Body)
|
body, _ := ioutil.ReadAll(res.Body)
|
||||||
if res.StatusCode >= http.StatusInternalServerError {
|
if res.StatusCode >= http.StatusInternalServerError {
|
||||||
|
@ -208,13 +213,10 @@ func (a *APIConfig) SetChannelClaimID(channelID string, channelClaimID string) e
|
||||||
"channel_claim_id": {channelClaimID},
|
"channel_claim_id": {channelClaimID},
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if strings.Contains(err.Error(), "EOF") {
|
util.SendErrorToSlack("error while trying to call %s. Waiting to retry: %s", endpoint, err.Error())
|
||||||
util.SendErrorToSlack("EOF error while trying to call %s. Waiting to retry", endpoint)
|
|
||||||
time.Sleep(30 * time.Second)
|
time.Sleep(30 * time.Second)
|
||||||
return a.SetChannelClaimID(channelID, channelClaimID)
|
return a.SetChannelClaimID(channelID, channelClaimID)
|
||||||
}
|
}
|
||||||
return errors.Err(err)
|
|
||||||
}
|
|
||||||
defer res.Body.Close()
|
defer res.Body.Close()
|
||||||
body, _ := ioutil.ReadAll(res.Body)
|
body, _ := ioutil.ReadAll(res.Body)
|
||||||
if res.StatusCode != http.StatusOK {
|
if res.StatusCode != http.StatusOK {
|
||||||
|
@ -252,13 +254,10 @@ func (a *APIConfig) DeleteVideos(videos []string) error {
|
||||||
}
|
}
|
||||||
res, err := http.PostForm(endpoint, vals)
|
res, err := http.PostForm(endpoint, vals)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if strings.Contains(err.Error(), "EOF") {
|
util.SendErrorToSlack("error while trying to call %s. Waiting to retry: %s", endpoint, err.Error())
|
||||||
util.SendErrorToSlack("EOF error while trying to call %s. Waiting to retry", endpoint)
|
|
||||||
time.Sleep(30 * time.Second)
|
time.Sleep(30 * time.Second)
|
||||||
return a.DeleteVideos(videos)
|
return a.DeleteVideos(videos)
|
||||||
}
|
}
|
||||||
return errors.Err(err)
|
|
||||||
}
|
|
||||||
defer res.Body.Close()
|
defer res.Body.Close()
|
||||||
body, _ := ioutil.ReadAll(res.Body)
|
body, _ := ioutil.ReadAll(res.Body)
|
||||||
if res.StatusCode != http.StatusOK {
|
if res.StatusCode != http.StatusOK {
|
||||||
|
@ -318,13 +317,10 @@ func (a *APIConfig) MarkVideoStatus(status shared.VideoStatus) error {
|
||||||
}
|
}
|
||||||
res, err := http.PostForm(endpoint, vals)
|
res, err := http.PostForm(endpoint, vals)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if strings.Contains(err.Error(), "EOF") {
|
util.SendErrorToSlack("error while trying to call %s. Waiting to retry: %s", endpoint, err.Error())
|
||||||
util.SendErrorToSlack("EOF error while trying to call %s for %s. Waiting to retry", endpoint, status.ClaimName)
|
|
||||||
time.Sleep(30 * time.Second)
|
time.Sleep(30 * time.Second)
|
||||||
return a.MarkVideoStatus(status)
|
return a.MarkVideoStatus(status)
|
||||||
}
|
}
|
||||||
return errors.Err(err)
|
|
||||||
}
|
|
||||||
defer res.Body.Close()
|
defer res.Body.Close()
|
||||||
body, _ := ioutil.ReadAll(res.Body)
|
body, _ := ioutil.ReadAll(res.Body)
|
||||||
if res.StatusCode != http.StatusOK {
|
if res.StatusCode != http.StatusOK {
|
||||||
|
@ -360,13 +356,10 @@ func (a *APIConfig) VideoState(videoID string) (string, error) {
|
||||||
|
|
||||||
res, err := http.PostForm(endpoint, vals)
|
res, err := http.PostForm(endpoint, vals)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if strings.Contains(err.Error(), "EOF") {
|
util.SendErrorToSlack("error while trying to call %s. Waiting to retry: %s", endpoint, err.Error())
|
||||||
util.SendErrorToSlack("EOF error while trying to call %s. Waiting to retry", endpoint)
|
|
||||||
time.Sleep(30 * time.Second)
|
time.Sleep(30 * time.Second)
|
||||||
return a.VideoState(videoID)
|
return a.VideoState(videoID)
|
||||||
}
|
}
|
||||||
return "", errors.Err(err)
|
|
||||||
}
|
|
||||||
defer res.Body.Close()
|
defer res.Body.Close()
|
||||||
body, _ := ioutil.ReadAll(res.Body)
|
body, _ := ioutil.ReadAll(res.Body)
|
||||||
if res.StatusCode == http.StatusNotFound {
|
if res.StatusCode == http.StatusNotFound {
|
||||||
|
@ -414,13 +407,10 @@ func (a *APIConfig) GetReleasedDate(videoID string) (*VideoRelease, error) {
|
||||||
|
|
||||||
res, err := http.PostForm(endpoint, vals)
|
res, err := http.PostForm(endpoint, vals)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if strings.Contains(err.Error(), "EOF") {
|
util.SendErrorToSlack("error while trying to call %s. Waiting to retry: %s", endpoint, err.Error())
|
||||||
util.SendErrorToSlack("EOF error while trying to call %s. Waiting to retry", endpoint)
|
|
||||||
time.Sleep(30 * time.Second)
|
time.Sleep(30 * time.Second)
|
||||||
return a.GetReleasedDate(videoID)
|
return a.GetReleasedDate(videoID)
|
||||||
}
|
}
|
||||||
return nil, errors.Err(err)
|
|
||||||
}
|
|
||||||
defer res.Body.Close()
|
defer res.Body.Close()
|
||||||
body, _ := ioutil.ReadAll(res.Body)
|
body, _ := ioutil.ReadAll(res.Body)
|
||||||
if res.StatusCode == http.StatusNotFound {
|
if res.StatusCode == http.StatusNotFound {
|
||||||
|
|
|
@ -26,6 +26,7 @@ type YoutubeChannel struct {
|
||||||
SizeLimit int `json:"size_limit"`
|
SizeLimit int `json:"size_limit"`
|
||||||
LastUploadedVideo string `json:"last_uploaded_video"`
|
LastUploadedVideo string `json:"last_uploaded_video"`
|
||||||
WipeDB bool `json:"wipe_db"`
|
WipeDB bool `json:"wipe_db"`
|
||||||
|
Language string `json:"language"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type PublishAddress struct {
|
type PublishAddress struct {
|
||||||
|
@ -77,6 +78,7 @@ var ErrorsNoRetry = []string{
|
||||||
"giving up after 0 fragment retries",
|
"giving up after 0 fragment retries",
|
||||||
"Sorry about that",
|
"Sorry about that",
|
||||||
"This video is not available",
|
"This video is not available",
|
||||||
|
"Video unavailable",
|
||||||
"requested format not available",
|
"requested format not available",
|
||||||
"interrupted by user",
|
"interrupted by user",
|
||||||
"Sign in to confirm your age",
|
"Sign in to confirm your age",
|
||||||
|
@ -89,6 +91,7 @@ var ErrorsNoRetry = []string{
|
||||||
"Premiere will begin shortly",
|
"Premiere will begin shortly",
|
||||||
"cannot unmarshal number 0.0",
|
"cannot unmarshal number 0.0",
|
||||||
"default youtube thumbnail found",
|
"default youtube thumbnail found",
|
||||||
|
"livestream is likely bugged",
|
||||||
}
|
}
|
||||||
var WalletErrors = []string{
|
var WalletErrors = []string{
|
||||||
"Not enough funds to cover this transaction",
|
"Not enough funds to cover this transaction",
|
||||||
|
@ -115,6 +118,8 @@ var NeverRetryFailures = []string{
|
||||||
"Playback on other websites has been disabled by the video owner",
|
"Playback on other websites has been disabled by the video owner",
|
||||||
"uploader has not made this video available in your country",
|
"uploader has not made this video available in your country",
|
||||||
"This video has been removed by the uploader",
|
"This video has been removed by the uploader",
|
||||||
|
"Video unavailable",
|
||||||
|
"Video is not available - hardcoded fix",
|
||||||
}
|
}
|
||||||
|
|
||||||
type SyncFlags struct {
|
type SyncFlags struct {
|
||||||
|
@ -152,7 +157,7 @@ func (f *SyncFlags) VideosToSync(totalSubscribers uint) int {
|
||||||
800: 250,
|
800: 250,
|
||||||
600: 200,
|
600: 200,
|
||||||
200: 80,
|
200: 80,
|
||||||
100: 50,
|
100: 20,
|
||||||
1: 10,
|
1: 10,
|
||||||
}
|
}
|
||||||
videosToSync := 0
|
videosToSync := 0
|
||||||
|
@ -191,9 +196,10 @@ const (
|
||||||
StatusFailed = "failed"
|
StatusFailed = "failed"
|
||||||
StatusFinalized = "finalized" // no more changes allowed
|
StatusFinalized = "finalized" // no more changes allowed
|
||||||
StatusAbandoned = "abandoned" // deleted on youtube or banned
|
StatusAbandoned = "abandoned" // deleted on youtube or banned
|
||||||
|
StatusAgeRestricted = "agerestricted" // one or more videos are age restricted and should be reprocessed with special keys
|
||||||
)
|
)
|
||||||
|
|
||||||
var SyncStatuses = []string{StatusPending, StatusPendingEmail, StatusPendingUpgrade, StatusQueued, StatusSyncing, StatusSynced, StatusFailed, StatusFinalized, StatusAbandoned, StatusWipeDb}
|
var SyncStatuses = []string{StatusPending, StatusPendingEmail, StatusPendingUpgrade, StatusQueued, StatusSyncing, StatusSynced, StatusFailed, StatusFinalized, StatusAbandoned, StatusWipeDb, StatusAgeRestricted}
|
||||||
|
|
||||||
const LatestMetadataVersion = 2
|
const LatestMetadataVersion = 2
|
||||||
|
|
||||||
|
@ -202,9 +208,11 @@ const (
|
||||||
VideoStatusFailed = "failed"
|
VideoStatusFailed = "failed"
|
||||||
VideoStatusUpgradeFailed = "upgradefailed"
|
VideoStatusUpgradeFailed = "upgradefailed"
|
||||||
VideoStatusUnpublished = "unpublished"
|
VideoStatusUnpublished = "unpublished"
|
||||||
VideoStatusTranferFailed = "transferfailed"
|
VideoStatusTransferFailed = "transferfailed"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var VideoSyncStatuses = []string{VideoStatusPublished, VideoStatusFailed, VideoStatusUpgradeFailed, VideoStatusUnpublished, VideoStatusTransferFailed}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
TransferStateNotTouched = iota
|
TransferStateNotTouched = iota
|
||||||
TransferStatePending
|
TransferStatePending
|
||||||
|
|
|
@ -16,17 +16,12 @@ import (
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/abadojack/whatlanggo"
|
|
||||||
"github.com/lbryio/ytsync/v5/downloader"
|
"github.com/lbryio/ytsync/v5/downloader"
|
||||||
"github.com/lbryio/ytsync/v5/downloader/ytdl"
|
"github.com/lbryio/ytsync/v5/downloader/ytdl"
|
||||||
"github.com/lbryio/ytsync/v5/shared"
|
|
||||||
"github.com/vbauerster/mpb/v7"
|
|
||||||
"github.com/vbauerster/mpb/v7/decor"
|
|
||||||
"gopkg.in/vansante/go-ffprobe.v2"
|
|
||||||
|
|
||||||
"github.com/lbryio/ytsync/v5/ip_manager"
|
"github.com/lbryio/ytsync/v5/ip_manager"
|
||||||
"github.com/lbryio/ytsync/v5/namer"
|
"github.com/lbryio/ytsync/v5/namer"
|
||||||
"github.com/lbryio/ytsync/v5/sdk"
|
"github.com/lbryio/ytsync/v5/sdk"
|
||||||
|
"github.com/lbryio/ytsync/v5/shared"
|
||||||
"github.com/lbryio/ytsync/v5/tags_manager"
|
"github.com/lbryio/ytsync/v5/tags_manager"
|
||||||
"github.com/lbryio/ytsync/v5/thumbs"
|
"github.com/lbryio/ytsync/v5/thumbs"
|
||||||
"github.com/lbryio/ytsync/v5/timing"
|
"github.com/lbryio/ytsync/v5/timing"
|
||||||
|
@ -37,8 +32,12 @@ import (
|
||||||
"github.com/lbryio/lbry.go/v2/extras/stop"
|
"github.com/lbryio/lbry.go/v2/extras/stop"
|
||||||
"github.com/lbryio/lbry.go/v2/extras/util"
|
"github.com/lbryio/lbry.go/v2/extras/util"
|
||||||
|
|
||||||
|
"github.com/abadojack/whatlanggo"
|
||||||
"github.com/shopspring/decimal"
|
"github.com/shopspring/decimal"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
|
"github.com/vbauerster/mpb/v7"
|
||||||
|
"github.com/vbauerster/mpb/v7/decor"
|
||||||
|
"gopkg.in/vansante/go-ffprobe.v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
type YoutubeVideo struct {
|
type YoutubeVideo struct {
|
||||||
|
@ -107,7 +106,7 @@ func NewYoutubeVideo(directory string, videoData *ytdl.YtdlVideo, playlistPositi
|
||||||
title: videoData.Title,
|
title: videoData.Title,
|
||||||
description: videoData.Description,
|
description: videoData.Description,
|
||||||
playlistPosition: playlistPosition,
|
playlistPosition: playlistPosition,
|
||||||
publishedAt: videoData.UploadDateForReal,
|
publishedAt: videoData.GetUploadTime(),
|
||||||
dir: directory,
|
dir: directory,
|
||||||
youtubeInfo: videoData,
|
youtubeInfo: videoData,
|
||||||
mocked: false,
|
mocked: false,
|
||||||
|
@ -176,7 +175,7 @@ func (v *YoutubeVideo) getFullPath() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *YoutubeVideo) getAbbrevDescription() string {
|
func (v *YoutubeVideo) getAbbrevDescription() string {
|
||||||
maxLength := 2800
|
maxLength := 6500
|
||||||
description := strings.TrimSpace(v.description)
|
description := strings.TrimSpace(v.description)
|
||||||
additionalDescription := "\nhttps://www.youtube.com/watch?v=" + v.id
|
additionalDescription := "\nhttps://www.youtube.com/watch?v=" + v.id
|
||||||
khanAcademyClaimID := "5fc52291980268b82413ca4c0ace1b8d749f3ffb"
|
khanAcademyClaimID := "5fc52291980268b82413ca4c0ace1b8d749f3ffb"
|
||||||
|
@ -321,7 +320,7 @@ func (v *YoutubeVideo) download() error {
|
||||||
//speedThrottleRetries := 3
|
//speedThrottleRetries := 3
|
||||||
for i := 0; i < len(qualities); i++ {
|
for i := 0; i < len(qualities); i++ {
|
||||||
quality := qualities[i]
|
quality := qualities[i]
|
||||||
argsWithFilters := append(ytdlArgs, "-fbestvideo[ext=mp4][vcodec!*=av01][height<="+quality+"]+bestaudio[ext!=webm][format_id!=258][format_id!=251][format_id!=256][format_id!=327]")
|
argsWithFilters := append(ytdlArgs, "-fbestvideo[ext=mp4][vcodec!*=av01][height<="+quality+"]+bestaudio[ext!=webm][format_id!=258][format_id!=380][format_id!=251][format_id!=256][format_id!=327][format_id!=328]")
|
||||||
argsWithFilters = append(argsWithFilters, userAgent...)
|
argsWithFilters = append(argsWithFilters, userAgent...)
|
||||||
//if speedThrottleRetries > 0 {
|
//if speedThrottleRetries > 0 {
|
||||||
// speedThrottleRetries--
|
// speedThrottleRetries--
|
||||||
|
@ -515,21 +514,28 @@ func (v *YoutubeVideo) trackProgressBar(argsWithFilters []string, ticker *time.T
|
||||||
bar.Completed()
|
bar.Completed()
|
||||||
bar.Abort(true)
|
bar.Abort(true)
|
||||||
}()
|
}()
|
||||||
|
origSize := int64(0)
|
||||||
|
lastUpdate := time.Now()
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-done.Ch():
|
case <-done.Ch():
|
||||||
return
|
return
|
||||||
case <-ticker.C:
|
case <-ticker.C:
|
||||||
|
var err error
|
||||||
size, err := logUtils.DirSize(v.videoDir())
|
size, err := logUtils.DirSize(v.videoDir())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("error while getting size of download directory: %s", errors.FullTrace(err))
|
log.Errorf("error while getting size of download directory: %s", errors.FullTrace(err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if size > origSize {
|
||||||
|
origSize = size
|
||||||
bar.SetCurrent(size)
|
bar.SetCurrent(size)
|
||||||
if size > int64(videoSize+audioSize) {
|
if size > int64(videoSize+audioSize) {
|
||||||
bar.SetTotal(size+2048, false)
|
bar.SetTotal(size+2048, false)
|
||||||
}
|
}
|
||||||
bar.DecoratorEwmaUpdate(400 * time.Millisecond)
|
bar.DecoratorEwmaUpdate(time.Since(lastUpdate))
|
||||||
|
lastUpdate = time.Now()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
@ -774,6 +780,9 @@ func (v *YoutubeVideo) Sync(daemon *jsonrpc.Client, params SyncParams, existingV
|
||||||
|
|
||||||
func (v *YoutubeVideo) downloadAndPublish(daemon *jsonrpc.Client, params SyncParams) (*SyncSummary, error) {
|
func (v *YoutubeVideo) downloadAndPublish(daemon *jsonrpc.Client, params SyncParams) (*SyncSummary, error) {
|
||||||
var err error
|
var err error
|
||||||
|
if v.youtubeInfo == nil {
|
||||||
|
return nil, errors.Err("Video is not available - hardcoded fix")
|
||||||
|
}
|
||||||
|
|
||||||
dur := time.Duration(v.youtubeInfo.Duration) * time.Second
|
dur := time.Duration(v.youtubeInfo.Duration) * time.Second
|
||||||
minDuration := 7 * time.Second
|
minDuration := 7 * time.Second
|
||||||
|
@ -781,6 +790,9 @@ func (v *YoutubeVideo) downloadAndPublish(daemon *jsonrpc.Client, params SyncPar
|
||||||
if v.youtubeInfo.IsLive == true {
|
if v.youtubeInfo.IsLive == true {
|
||||||
return nil, errors.Err("video is a live stream and hasn't completed yet")
|
return nil, errors.Err("video is a live stream and hasn't completed yet")
|
||||||
}
|
}
|
||||||
|
if v.youtubeInfo.Availability != "public" {
|
||||||
|
return nil, errors.Err("video is not public")
|
||||||
|
}
|
||||||
if dur > v.maxVideoLength {
|
if dur > v.maxVideoLength {
|
||||||
logUtils.SendErrorToSlack("%s is %s long and the limit is %s", v.id, dur.String(), v.maxVideoLength.String())
|
logUtils.SendErrorToSlack("%s is %s long and the limit is %s", v.id, dur.String(), v.maxVideoLength.String())
|
||||||
return nil, errors.Err("video is too long to process")
|
return nil, errors.Err("video is too long to process")
|
||||||
|
@ -789,6 +801,11 @@ func (v *YoutubeVideo) downloadAndPublish(daemon *jsonrpc.Client, params SyncPar
|
||||||
logUtils.SendErrorToSlack("%s is %s long and the minimum is %s", v.id, dur.String(), minDuration.String())
|
logUtils.SendErrorToSlack("%s is %s long and the minimum is %s", v.id, dur.String(), minDuration.String())
|
||||||
return nil, errors.Err("video is too short to process")
|
return nil, errors.Err("video is too short to process")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
buggedLivestream := v.youtubeInfo.LiveStatus == "post_live"
|
||||||
|
if buggedLivestream && dur >= 2*time.Hour {
|
||||||
|
return nil, errors.Err("livestream is likely bugged as it was recently published and has a length of %s which is more than 2 hours", dur.String())
|
||||||
|
}
|
||||||
for {
|
for {
|
||||||
err = v.download()
|
err = v.download()
|
||||||
if err != nil && strings.Contains(err.Error(), "HTTP Error 429") {
|
if err != nil && strings.Contains(err.Error(), "HTTP Error 429") {
|
||||||
|
@ -858,7 +875,11 @@ func (v *YoutubeVideo) getMetadata() (languages []string, locations []jsonrpc.Lo
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *YoutubeVideo) reprocess(daemon *jsonrpc.Client, params SyncParams, existingVideoData *sdk.SyncedVideo) (*SyncSummary, error) {
|
func (v *YoutubeVideo) reprocess(daemon *jsonrpc.Client, params SyncParams, existingVideoData *sdk.SyncedVideo) (*SyncSummary, error) {
|
||||||
c, err := daemon.ClaimSearch(nil, &existingVideoData.ClaimID, nil, nil, 1, 20)
|
c, err := daemon.ClaimSearch(jsonrpc.ClaimSearchArgs{
|
||||||
|
ClaimID: &existingVideoData.ClaimID,
|
||||||
|
Page: 1,
|
||||||
|
PageSize: 20,
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Err(err)
|
return nil, errors.Err(err)
|
||||||
}
|
}
|
||||||
|
@ -927,6 +948,7 @@ func (v *YoutubeVideo) reprocess(daemon *jsonrpc.Client, params SyncParams, exis
|
||||||
Height: util.PtrToUint(720),
|
Height: util.PtrToUint(720),
|
||||||
Width: util.PtrToUint(1280),
|
Width: util.PtrToUint(1280),
|
||||||
Fee: fee,
|
Fee: fee,
|
||||||
|
ReleaseTime: util.PtrToInt64(v.publishedAt.Unix()),
|
||||||
}
|
}
|
||||||
|
|
||||||
v.walletLock.RLock()
|
v.walletLock.RLock()
|
||||||
|
|
|
@ -14,7 +14,9 @@ func SendErrorToSlack(format string, a ...interface{}) {
|
||||||
message = fmt.Sprintf(format, a...)
|
message = fmt.Sprintf(format, a...)
|
||||||
}
|
}
|
||||||
log.Errorln(message)
|
log.Errorln(message)
|
||||||
err := util.SendToSlack(":sos: " + message)
|
log.SetLevel(log.InfoLevel) //I don't want to change the underlying lib so this will do...
|
||||||
|
err := util.SendToSlack(":sos: ```" + message + "```")
|
||||||
|
log.SetLevel(log.DebugLevel)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorln(err)
|
log.Errorln(err)
|
||||||
}
|
}
|
||||||
|
@ -27,7 +29,9 @@ func SendInfoToSlack(format string, a ...interface{}) {
|
||||||
message = fmt.Sprintf(format, a...)
|
message = fmt.Sprintf(format, a...)
|
||||||
}
|
}
|
||||||
log.Infoln(message)
|
log.Infoln(message)
|
||||||
|
log.SetLevel(log.InfoLevel) //I don't want to change the underlying lib so this will do...
|
||||||
err := util.SendToSlack(":information_source: " + message)
|
err := util.SendToSlack(":information_source: " + message)
|
||||||
|
log.SetLevel(log.DebugLevel)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorln(err)
|
log.Errorln(err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -55,7 +55,7 @@ type VideoParams struct {
|
||||||
|
|
||||||
var mostRecentlyFailedChannel string // TODO: fix this hack!
|
var mostRecentlyFailedChannel string // TODO: fix this hack!
|
||||||
|
|
||||||
func GetVideosToSync(config *sdk.APIConfig, channelID string, syncedVideos map[string]sdk.SyncedVideo, quickSync bool, maxVideos int, videoParams VideoParams, lastUploadedVideo string) ([]Video, error) {
|
func GetVideosToSync(channelID string, syncedVideos map[string]sdk.SyncedVideo, quickSync bool, maxVideos int, videoParams VideoParams, lastUploadedVideo string) ([]Video, error) {
|
||||||
var videos []Video
|
var videos []Video
|
||||||
if quickSync && maxVideos > 50 {
|
if quickSync && maxVideos > 50 {
|
||||||
maxVideos = 50
|
maxVideos = 50
|
||||||
|
@ -94,7 +94,7 @@ func GetVideosToSync(config *sdk.APIConfig, channelID string, syncedVideos map[s
|
||||||
mostRecentlyFailedChannel = channelID
|
mostRecentlyFailedChannel = channelID
|
||||||
}
|
}
|
||||||
|
|
||||||
vids, err := getVideos(config, channelID, videoIDs, videoParams.Stopper.Ch(), videoParams.IPPool)
|
vids, err := getVideos(channelID, videoIDs, videoParams.Stopper.Ch(), videoParams.IPPool)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -109,7 +109,8 @@ func GetVideosToSync(config *sdk.APIConfig, channelID string, syncedVideos map[s
|
||||||
}
|
}
|
||||||
|
|
||||||
for k, v := range syncedVideos {
|
for k, v := range syncedVideos {
|
||||||
if !v.Published {
|
newMetadataVersion := int8(2)
|
||||||
|
if !v.Published && v.MetadataVersion >= newMetadataVersion {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if _, ok := playlistMap[k]; !ok {
|
if _, ok := playlistMap[k]; !ok {
|
||||||
|
@ -203,7 +204,8 @@ func ChannelInfo(channelID string) (*YoutubeStatsResponse, error) {
|
||||||
return &decodedResponse, nil
|
return &decodedResponse, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getVideos(config *sdk.APIConfig, channelID string, videoIDs []string, stopChan stop.Chan, ipPool *ip_manager.IPPool) ([]*ytdl.YtdlVideo, error) {
|
func getVideos(channelID string, videoIDs []string, stopChan stop.Chan, ipPool *ip_manager.IPPool) ([]*ytdl.YtdlVideo, error) {
|
||||||
|
config := sdk.GetAPIsConfigs()
|
||||||
var videos []*ytdl.YtdlVideo
|
var videos []*ytdl.YtdlVideo
|
||||||
for _, videoID := range videoIDs {
|
for _, videoID := range videoIDs {
|
||||||
if len(videoID) < 5 {
|
if len(videoID) < 5 {
|
||||||
|
@ -215,11 +217,6 @@ func getVideos(config *sdk.APIConfig, channelID string, videoIDs []string, stopC
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
|
|
||||||
//ip, err := ipPool.GetIP(videoID)
|
|
||||||
//if err != nil {
|
|
||||||
// return nil, err
|
|
||||||
//}
|
|
||||||
//video, err := downloader.GetVideoInformation(videoID, &net.TCPAddr{IP: net.ParseIP(ip)})
|
|
||||||
state, err := config.VideoState(videoID)
|
state, err := config.VideoState(videoID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Err(err)
|
return nil, errors.Err(err)
|
||||||
|
@ -227,7 +224,7 @@ func getVideos(config *sdk.APIConfig, channelID string, videoIDs []string, stopC
|
||||||
if state == "published" {
|
if state == "published" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
video, err := downloader.GetVideoInformation(config, videoID, stopChan, nil, ipPool)
|
video, err := downloader.GetVideoInformation(videoID, stopChan, ipPool)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errSDK := config.MarkVideoStatus(shared.VideoStatus{
|
errSDK := config.MarkVideoStatus(shared.VideoStatus{
|
||||||
ChannelID: channelID,
|
ChannelID: channelID,
|
||||||
|
|
Loading…
Reference in a new issue