7 Commits
1.9 ... 2.0.0

Author SHA1 Message Date
Pijus Kamandulis
ea2b866f9a Embed scraper into compiled binary, extract strings to resources file 2020-04-12 16:56:50 +03:00
Pijus Kamandulis
70c3faf17e Use go modules 2020-04-12 03:22:00 +03:00
Pijus Kamandulis
0a08f62dfe Fix lint issues 2020-04-09 18:53:45 +03:00
Pijus Kamandulis
bb6e924866 Add execute permission to binary file durring build 2020-04-09 18:32:11 +03:00
Pijus Kamandulis
af59659f41 TTDL-17 Added option to output failed items to file 2020-04-09 18:10:33 +03:00
Pijus Kamandulis
92006d864f TTDL-20 Wait for error/items beefore quiting 2020-04-08 23:26:50 +03:00
Pijus Kamandulis
668b050dee Added sonar-scaner
Create LICENSE

Update go.yml
2020-04-04 21:09:41 +03:00
43 changed files with 561 additions and 130 deletions

View File

@@ -33,6 +33,12 @@ jobs:
- name: Build
run: npm run build:dist
- name: Copy license
run: cp LICENSE out
- name: Add execute permission
run: chmod +x out/tiktok-dl
- name: Upload Unix Artifacts
if: startsWith(matrix.os, 'ubuntu-')

2
.gitignore vendored
View File

@@ -6,3 +6,5 @@ downloads
tiktok-dl
batch_file.txt
debug.log
.scannerwork
scraper.min.js

View File

@@ -0,0 +1,12 @@
sonar.organization=pikami
sonar.projectKey=tiktok-dl
sonar.host.url=https://sonarcloud.io
sonar.sources=.
sonar.exclusions=**/*_test.go,**/node_modules/**
sonar.tests=.
sonar.test.inclusions=**/*_test.go
sonar.test.exclusions=**/node_modules/**
sonar.go.coverage.reportPaths=cov.out

21
LICENSE Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2020 pikami
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -1,30 +1,40 @@
# TikTok-DL
[![Go Report Card](https://goreportcard.com/badge/github.com/pikami/tiktok-dl)](https://goreportcard.com/report/github.com/pikami/tiktok-dl)
![GitHub Workflow Status](https://img.shields.io/github/workflow/status/pikami/tiktok-dl/tiktok-dl_CI)
[![tiktok-dl_CI](https://github.com/pikami/tiktok-dl/workflows/tiktok-dl_CI/badge.svg?branch=master)](https://github.com/pikami/tiktok-dl/actions)
A simple tiktok video downloader written in go
## Basic usage
## Basic usage examples
Download the executable from `https://github.com/pikami/tiktok-dl/releases`\
You can download all videos from user by running `./tiktok-dl [Options] TIKTOK_USERNAME`\
You can download single video by running `./tiktok-dl [Options] VIDEO_URL`\
You can download all videos by music by running `./tiktok-dl [Options] MUSIC_URL`\
You can download items listed in a text file by running `./tiktok-dl [OPTIONS] -batch-file path/to/items.txt`
You can download all videos from user by running `./tiktok-dl TIKTOK_USERNAME`\
You can download single video by running `./tiktok-dl VIDEO_URL`\
You can download items listed in a text file by running `./tiktok-dl -batch-file path/to/items.txt`
## Build instructions
Clone this repository and run `go build` to build the executable.
## Usage Manual
```
Usage: tiktok-dl [OPTION]... TARGET
or: tiktok-dl [OPTION]... -batch-file BATCH_FILE
In the 1st form, download given `TARGET`.
In the 2nd form, download all targets listed in given `BATCH_FILE`.
```
## Available options
* `-archive` - Download only videos not listed in the archive file. Record the IDs of all downloaded videos in it.
* `-batch-file` - File containing URLs/Usernames to download, one value per line. Lines starting with '#', are considered as comments and ignored.
* `-batch-file some_file` - File containing URLs/Usernames to download, one value per line. Lines starting with '#', are considered as comments and ignored.
* `-deadline` - Sets the timout for scraper logic in seconds (used as a workaround for context deadline exceeded error) (default 1500)
* `-debug` - enables debug mode
* `-fail-log some_file` - Write failed items to log file
* `-json` - Returns whole data, that was scraped from TikTok, in json
* `-limit` - Sets the max count of video that will be downloaded (default infinity)
* `-metadata` - Write video metadata to a .json file
* `-output some_directory` - Output path (default "./downloads")
* `-quiet` - Supress output
* `-quiet` - Suppress output
## Build instructions
1. Clone this repository
2. Run `go build` to build the executable.
## Acknowledgments
This software uses the **chromedp** for web scraping, it can be found here: https://github.com/chromedp/chromedp \

View File

@@ -10,9 +10,10 @@ import (
"github.com/chromedp/chromedp"
config "../models/config"
utils "../utils"
log "../utils/log"
config "github.com/pikami/tiktok-dl/models/config"
res "github.com/pikami/tiktok-dl/resources"
utils "github.com/pikami/tiktok-dl/utils"
log "github.com/pikami/tiktok-dl/utils/log"
)
// GetMusicUploads - Get all uploads by given music
@@ -54,7 +55,7 @@ func runScrapeQuiet(ctx context.Context, jsAction string, url string) (string, e
// Navigate to user's page
chromedp.Navigate(url),
// Execute url grabber script
chromedp.EvaluateAsDevTools(utils.ReadFileAsString("scraper.js"), &jsOutput),
chromedp.EvaluateAsDevTools(utils.GetScraper(), &jsOutput),
chromedp.EvaluateAsDevTools(jsAction, &jsOutput),
// Wait until custom js finishes
chromedp.WaitVisible(`video_urls`),
@@ -73,7 +74,7 @@ func runScrapeWithInfo(ctx context.Context, jsAction string, url string) (string
// Navigate to user's page
chromedp.Navigate(url),
// Execute url grabber script
chromedp.EvaluateAsDevTools(utils.ReadFileAsString("scraper.js"), &jsOutput),
chromedp.EvaluateAsDevTools(utils.GetScraper(), &jsOutput),
chromedp.EvaluateAsDevTools(jsAction, &jsOutput),
); err != nil {
return "", err
@@ -85,9 +86,9 @@ func runScrapeWithInfo(ctx context.Context, jsAction string, url string) (string
}
if jsOutput != "0" {
log.Logf("\rPreloading... %s items have been found.", jsOutput)
log.Logf(res.PreloadingItemsFound, jsOutput)
} else {
log.Logf("\rPreloading...")
log.Logf(res.Preloading)
}
if err := chromedp.Run(ctx, chromedp.EvaluateAsDevTools("currentState.finished.toString()", &jsOutput)); err != nil {
@@ -101,7 +102,7 @@ func runScrapeWithInfo(ctx context.Context, jsAction string, url string) (string
time.Sleep(50 * time.Millisecond)
}
log.Log("\nRetrieving items...")
log.Log(res.Retrieving)
if err := chromedp.Run(ctx,
// Wait until custom js finishes
chromedp.WaitVisible(`video_urls`),

View File

@@ -3,8 +3,8 @@ package client
import (
"fmt"
models "../models"
config "../models/config"
models "github.com/pikami/tiktok-dl/models"
config "github.com/pikami/tiktok-dl/models/config"
)
// GetHashtagUploads - Get all uploads marked with given hashtag

View File

@@ -3,8 +3,8 @@ package client
import (
"fmt"
models "../models"
config "../models/config"
models "github.com/pikami/tiktok-dl/models"
config "github.com/pikami/tiktok-dl/models/config"
)
// GetMusicUploads - Get all uploads by given music

View File

@@ -2,16 +2,18 @@ package client
import (
"context"
"github.com/chromedp/chromedp"
"io/ioutil"
"os"
"time"
config "../models/config"
log "../utils/log"
"github.com/chromedp/chromedp"
config "github.com/pikami/tiktok-dl/models/config"
log "github.com/pikami/tiktok-dl/utils/log"
)
func GetRedirectUrl(url string) (string, error) {
// GetRedirectURL - Returns URL that the given URL redirects to
func GetRedirectURL(url string) (string, error) {
dir, err := ioutil.TempDir("", "chromedp-example")
if err != nil {
return "", err

View File

@@ -3,8 +3,8 @@ package client
import (
"fmt"
models "../models"
config "../models/config"
models "github.com/pikami/tiktok-dl/models"
config "github.com/pikami/tiktok-dl/models/config"
)
// GetUserUploads - Get all uploads by user

View File

@@ -1,7 +1,7 @@
package client
import (
models "../models"
models "github.com/pikami/tiktok-dl/models"
)
// GetVideoDetails - returns details of video

64
generator/generator.go Normal file
View File

@@ -0,0 +1,64 @@
package main
import (
"fmt"
"os"
"strings"
checkErr "github.com/pikami/tiktok-dl/utils/checkErr"
fileio "github.com/pikami/tiktok-dl/utils/fileio"
)
type resource struct {
Package string
FileName string
Values map[string]string
}
func (r resource) generate() {
filename := fmt.Sprintf("%s/%s", outputDir, r.FileName)
f, err := os.OpenFile(filename, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644)
checkErr.CheckErr(err)
defer f.Close()
// Header
header := fmt.Sprintf("// Package %s - This file is automatically generated.\n"+
"// Do not edit this file manually.\n"+
"// Check `/generator/resources.go` to change generated content\n"+
"package %s\n", r.Package, r.Package)
if _, err := f.WriteString(header); err != nil {
checkErr.CheckErr(err)
}
// Values
for key, value := range r.Values {
value = strings.ReplaceAll(value, "\n", "\\n")
value = strings.ReplaceAll(value, "\r", "\\r")
valueLine := fmt.Sprintf("\n//%s -\nvar %s = \"%s\"\n", key, key, value)
if _, err := f.WriteString(valueLine); err != nil {
checkErr.CheckErr(err)
}
}
}
func fileContentsOrDefault(file string) string {
defer func() {
if r := recover(); r != nil {
fmt.Printf("Failed to load file: %s\n", file)
}
}()
return safeString(fileio.ReadFileToString(file))
}
func safeString(str string) string {
escaped := strings.ReplaceAll(str, "\"", "\\\"")
return strings.ReplaceAll(escaped, "\n", "")
}
func main() {
for _, i := range res {
i.generate()
}
}

87
generator/resources.go Normal file
View File

@@ -0,0 +1,87 @@
package main
var (
outputDir = "../resources"
res = []resource{
resource{
Package: "resources",
FileName: "scraper.go",
Values: map[string]string{
"ScraperPath": "scraper.js",
"ScraperScript": fileContentsOrDefault("../scraper.min.js"),
},
},
resource{
Package: "resources",
FileName: "errorStrings.go",
Values: map[string]string{
"ErrorCouldNotSerializeJSON": "Could not serialize json for video: %s\n",
"ErrorCouldNotRecogniseURL": "Could not recognise URL format of string %s",
"Error": "Error : %s\n",
"ErrorPathNotFound": "File path %s not found.",
"FailedOnItem": "Failed while scraping item: %s\n",
"FailedToLoadScraper": "Failed to load scraper",
},
},
resource{
Package: "resources",
FileName: "messages.go",
Values: map[string]string{
"PreloadingItemsFound": "\rPreloading... %s items have been found.",
"Preloading": "\rPreloading...",
"Retrieving": "\nRetrieving items...",
"ItemsFoundInArchive": "%d items, found in archive. Skipping...\n",
"Downloaded": "\r[%d/%d] Downloaded",
"UsageLine": "Usage: tiktok-dl [OPTIONS] TIKTOK_USERNAME|TIKTOK_URL\n" +
" or: tiktok-dl [OPTIONS] -batch-file path/to/users.txt",
},
},
resource{
Package: "resources",
FileName: "flags.go",
Values: map[string]string{
// Output
"OutputFlag": "output",
"OutputDefault": "./downloads",
"OutputDescription": "Output path",
// Batch file
"BatchFlag": "batch-file",
"BatchDefault": "",
"BatchDescription": "File containing URLs/Usernames to download, one value per line. Lines starting with '#', are considered as comments and ignored.",
// Archive
"ArchiveFlag": "archive",
"ArchiveDefault": "",
"ArchiveDescription": "Download only videos not listed in the archive file. Record the IDs of all downloaded videos in it.",
// Fail log
"FailLogFlag": "fail-log",
"FailLogDefault": "",
"FailLogDescription": "Write failed items to log file",
// Debug
"DebugFlag": "debug",
"DebugDefault": "false",
"DebugDescription": "Enables debug mode",
// Metadata
"MetadataFlag": "metadata",
"MetadataDefault": "false",
"MetadataDescription": "Write video metadata to a .json file",
// Quiet
"QuietFlag": "quiet",
"QuietDefault": "false",
"QuietDescription": "Suppress output",
// JSON only
"JsonFlag": "json",
"JsonDefault": "false",
"JsonDescription": "Just get JSON data from scraper (without video downloading)",
// Deadline
"DeadlineFlag": "deadline",
"DeadlineDefault": "1500",
"DeadlineDescription": "Sets the timout for scraper logic in seconds (used as a workaround for 'context deadline exceeded' error)",
// Limit
"LimitFlag": "limit",
"LimitDefault": "0",
"LimitDescription": "Sets the videos count limit (useful when there too many videos from the user or by hashtag)",
},
},
}
)

5
go.mod Normal file
View File

@@ -0,0 +1,5 @@
module github.com/pikami/tiktok-dl
go 1.14
require github.com/chromedp/chromedp v0.5.3

16
go.sum Normal file
View File

@@ -0,0 +1,16 @@
github.com/chromedp/cdproto v0.0.0-20200116234248-4da64dd111ac h1:T7V5BXqnYd55Hj/g5uhDYumg9Fp3rMTS6bykYtTIFX4=
github.com/chromedp/cdproto v0.0.0-20200116234248-4da64dd111ac/go.mod h1:PfAWWKJqjlGFYJEidUM6aVIWPr0EpobeyVWEEmplX7g=
github.com/chromedp/chromedp v0.5.3 h1:F9LafxmYpsQhWQBdCs+6Sret1zzeeFyHS5LkRF//Ffg=
github.com/chromedp/chromedp v0.5.3/go.mod h1:YLdPtndaHQ4rCpSpBG+IPpy9JvX0VD+7aaLxYgYj28w=
github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee h1:s+21KNqlpePfkah2I+gwHF8xmJWRjooY+5248k6m4A0=
github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo=
github.com/gobwas/pool v0.2.0 h1:QEmUOlnSjWtnpRGHF3SauEiOsy82Cup83Vf2LcMlnc8=
github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
github.com/gobwas/ws v1.0.2 h1:CoAavW/wd/kulfZmSIBt6p24n4j7tHgNVCjsfHVNUbo=
github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
github.com/knq/sysutil v0.0.0-20191005231841-15668db23d08 h1:V0an7KRw92wmJysvFvtqtKMAPmvS5O0jtB0nYo6t+gs=
github.com/knq/sysutil v0.0.0-20191005231841-15668db23d08/go.mod h1:dFWs1zEqDjFtnBXsd1vPOZaLsESovai349994nHx3e0=
github.com/mailru/easyjson v0.7.0 h1:aizVhC/NAAcKWb+5QsU1iNOZb4Yws5UO2I+aIprQITM=
github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42 h1:vEOn+mP2zCOVzKckCZy6YsCtDblrpj/w7B9nxGNELpg=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=

View File

@@ -1,8 +1,8 @@
package main
import (
config "./models/config"
workflows "./workflows"
config "github.com/pikami/tiktok-dl/models/config"
workflows "github.com/pikami/tiktok-dl/workflows"
)
func main() {

View File

@@ -4,6 +4,9 @@ import (
"flag"
"fmt"
"os"
"strconv"
res "github.com/pikami/tiktok-dl/resources"
)
// Config - Runtime configuration
@@ -12,6 +15,7 @@ var Config struct {
OutputPath string
BatchFilePath string
ArchiveFilePath string
FailLogFilePath string
Debug bool
MetaData bool
Quiet bool
@@ -22,21 +26,21 @@ var Config struct {
// GetConfig - Returns Config object
func GetConfig() {
outputPath := flag.String("output", "./downloads", "Output path")
batchFilePath := flag.String("batch-file", "", "File containing URLs/Usernames to download, one value per line. Lines starting with '#', are considered as comments and ignored.")
archive := flag.String("archive", "", "Download only videos not listed in the archive file. Record the IDs of all downloaded videos in it.")
debug := flag.Bool("debug", false, "Enables debug mode")
metadata := flag.Bool("metadata", false, "Write video metadata to a .json file")
quiet := flag.Bool("quiet", false, "Supress output")
jsonOnly := flag.Bool("json", false, "Just get JSON data from scraper (without video downloading)")
deadline := flag.Int("deadline", 1500, "Sets the timout for scraper logic in seconds (used as a workaround for 'context deadline exceeded' error)")
limit := flag.Int("limit", 0, "Sets the videos count limit (useful when there too many videos from the user or by hashtag)")
outputPath := flag.String(res.OutputFlag, res.OutputDefault, res.OutputDescription)
batchFilePath := flag.String(res.BatchFlag, res.BatchDefault, res.BatchDescription)
archive := flag.String(res.ArchiveFlag, res.ArchiveDefault, res.ArchiveDescription)
failLogPath := flag.String(res.FailLogFlag, res.FailLogDefault, res.FailLogDescription)
debug := flag.Bool(res.DebugFlag, parseBool(res.DebugDefault), res.DebugDescription)
metadata := flag.Bool(res.MetadataFlag, parseBool(res.MetadataDefault), res.MetadataDescription)
quiet := flag.Bool(res.QuietFlag, parseBool(res.QuietDefault), res.QuietDescription)
jsonOnly := flag.Bool(res.JsonFlag, parseBool(res.JsonDefault), res.JsonDescription)
deadline := flag.Int(res.DeadlineFlag, parseInt(res.DeadlineDefault), res.DeadlineDescription)
limit := flag.Int(res.LimitFlag, parseInt(res.LimitDefault), res.LimitDescription)
flag.Parse()
args := flag.Args()
if len(args) < 1 && *batchFilePath == "" {
fmt.Println("Usage: tiktok-dl [OPTIONS] TIKTOK_USERNAME|TIKTOK_URL")
fmt.Println(" or: tiktok-dl [OPTIONS] -batch-file path/to/users.txt")
fmt.Println(res.UsageLine)
os.Exit(2)
}
@@ -48,6 +52,7 @@ func GetConfig() {
Config.OutputPath = *outputPath
Config.BatchFilePath = *batchFilePath
Config.ArchiveFilePath = *archive
Config.FailLogFilePath = *failLogPath
Config.Debug = *debug
Config.MetaData = *metadata
Config.Quiet = *quiet
@@ -58,3 +63,19 @@ func GetConfig() {
Config.Deadline = *deadline
Config.Limit = *limit
}
func parseBool(str string) bool {
val, err := strconv.ParseBool(str)
if err != nil {
panic(err)
}
return val
}
func parseInt(str string) int {
val, err := strconv.Atoi(str)
if err != nil {
panic(err)
}
return val
}

View File

@@ -5,9 +5,9 @@ import (
"os"
"strings"
res "../resources"
checkErr "../utils/checkErr"
log "../utils/log"
res "github.com/pikami/tiktok-dl/resources"
checkErr "github.com/pikami/tiktok-dl/utils/checkErr"
log "github.com/pikami/tiktok-dl/utils/log"
)
// Upload - Upload object

View File

@@ -4,8 +4,8 @@ import (
"os"
"testing"
testUtil "../unitTestUtil"
fileio "../utils/fileio"
testUtil "github.com/pikami/tiktok-dl/unitTestUtil"
fileio "github.com/pikami/tiktok-dl/utils/fileio"
)
func TestParseUploads(t *testing.T) {

View File

@@ -1,14 +1,17 @@
{
"name": "tiktok-dl",
"version": "0.0.1",
"version": "2.0.0",
"scripts": {
"install-dependencies": "go get -v -t -d ./...",
"test:coverage": "go test -short -coverprofile=cov.out ./models ./utils",
"test": "go test -v ./models && go test -v ./utils",
"clean": "rm -rf out",
"build:scraper": "node node_modules/terser/bin/terser -c -m -- scraper.js > out/scraper.js",
"build:app": "go build -o out/ -v .",
"build:dist": "mkdir out && npm run build:app && npm run build:scraper",
"build": "go build -v ."
"clean": "rm -rf out && rm -f scraper.min.js",
"build:scraper": "node node_modules/terser/bin/terser -c -m -- scraper.js > scraper.min.js",
"build:res": "cd generator && go run . && cd ..",
"build:app": "go build -o out/tiktok-dl -v .",
"build:dist": "npm run clean && mkdir out && npm run build:scraper && npm run build:res && npm run build:app",
"build": "go build -v .",
"sonar": "sonar-scanner -Dsonar.login=${SONAR_LOGIN} -Dproject.settings=.sonar/sonar-project.properties"
},
"dependencies": {
"terser": "^4.6.3"

22
resources/errorStrings.go Normal file
View File

@@ -0,0 +1,22 @@
// Package resources - This file is automatically generated.
// Do not edit this file manually.
// Check `/generator/resources.go` to change generated content
package resources
//ErrorCouldNotSerializeJSON -
var ErrorCouldNotSerializeJSON = "Could not serialize json for video: %s\n"
//ErrorCouldNotRecogniseURL -
var ErrorCouldNotRecogniseURL = "Could not recognise URL format of string %s"
//Error -
var Error = "Error : %s\n"
//ErrorPathNotFound -
var ErrorPathNotFound = "File path %s not found."
//FailedOnItem -
var FailedOnItem = "Failed while scraping item: %s\n"
//FailedToLoadScraper -
var FailedToLoadScraper = "Failed to load scraper"

94
resources/flags.go Normal file
View File

@@ -0,0 +1,94 @@
// Package resources - This file is automatically generated.
// Do not edit this file manually.
// Check `/generator/resources.go` to change generated content
package resources
//BatchDescription -
var BatchDescription = "File containing URLs/Usernames to download, one value per line. Lines starting with '#', are considered as comments and ignored."
//LimitDefault -
var LimitDefault = "0"
//DebugDescription -
var DebugDescription = "Enables debug mode"
//QuietDescription -
var QuietDescription = "Suppress output"
//MetadataDefault -
var MetadataDefault = "false"
//LimitDescription -
var LimitDescription = "Sets the videos count limit (useful when there too many videos from the user or by hashtag)"
//OutputDescription -
var OutputDescription = "Output path"
//BatchDefault -
var BatchDefault = ""
//FailLogDefault -
var FailLogDefault = ""
//MetadataFlag -
var MetadataFlag = "metadata"
//ArchiveFlag -
var ArchiveFlag = "archive"
//MetadataDescription -
var MetadataDescription = "Write video metadata to a .json file"
//QuietDefault -
var QuietDefault = "false"
//DeadlineDefault -
var DeadlineDefault = "1500"
//JsonFlag -
var JsonFlag = "json"
//JsonDefault -
var JsonDefault = "false"
//DeadlineDescription -
var DeadlineDescription = "Sets the timout for scraper logic in seconds (used as a workaround for 'context deadline exceeded' error)"
//OutputFlag -
var OutputFlag = "output"
//OutputDefault -
var OutputDefault = "./downloads"
//FailLogFlag -
var FailLogFlag = "fail-log"
//DebugDefault -
var DebugDefault = "false"
//ArchiveDefault -
var ArchiveDefault = ""
//FailLogDescription -
var FailLogDescription = "Write failed items to log file"
//BatchFlag -
var BatchFlag = "batch-file"
//QuietFlag -
var QuietFlag = "quiet"
//DeadlineFlag -
var DeadlineFlag = "deadline"
//LimitFlag -
var LimitFlag = "limit"
//ArchiveDescription -
var ArchiveDescription = "Download only videos not listed in the archive file. Record the IDs of all downloaded videos in it."
//DebugFlag -
var DebugFlag = "debug"
//JsonDescription -
var JsonDescription = "Just get JSON data from scraper (without video downloading)"

22
resources/messages.go Normal file
View File

@@ -0,0 +1,22 @@
// Package resources - This file is automatically generated.
// Do not edit this file manually.
// Check `/generator/resources.go` to change generated content
package resources
//PreloadingItemsFound -
var PreloadingItemsFound = "\rPreloading... %s items have been found."
//Preloading -
var Preloading = "\rPreloading..."
//Retrieving -
var Retrieving = "\nRetrieving items..."
//ItemsFoundInArchive -
var ItemsFoundInArchive = "%d items, found in archive. Skipping...\n"
//Downloaded -
var Downloaded = "\r[%d/%d] Downloaded"
//UsageLine -
var UsageLine = "Usage: tiktok-dl [OPTIONS] TIKTOK_USERNAME|TIKTOK_URL\n or: tiktok-dl [OPTIONS] -batch-file path/to/users.txt"

10
resources/scraper.go Normal file
View File

@@ -0,0 +1,10 @@
// Package resources - This file is automatically generated.
// Do not edit this file manually.
// Check `/generator/resources.go` to change generated content
package resources
//ScraperScript -
var ScraperScript = "optStrings={selectors:{feedLoading:\"div.tiktok-loading.feed-loading\",modalArrowRight:\"div.video-card-modal > div > img.arrow-right\",modalClose:\".video-card-modal > div > div.close\",modalPlayer:\"div > div > main > div.video-card-modal > div > div.video-card-big > div.video-card-container > div > div > video\",modalShareInput:\".copy-link-container > input\",modalCaption:\"div.video-card-big > div.content-container > div.video-meta-info > h1\",modalSoundLink:\"div.content-container > div.video-meta-info > h2.music-info > a\",modalUploader:\".user-username\",videoPlayer:\"div.video-card-container > div > div > video\",videoShareInput:\"div.content-container.border > div.copy-link-container > input\",videoCaption:\"div.content-container.border > div.video-meta-info > h1\",videoSoundLink:\"div.content-container.border > div.video-meta-info > h2.music-info > a\",videoUploader:\".user-username\"},classes:{feedVideoItem:\"video-feed-item-wrapper\",modalCloseDisabled:\"disabled\",titleMessage:\"title\"},tags:{resultTag:\"video_urls\",resultParentTag:\"body\"},attributes:{src:\"src\"},tiktokMessages:[\"Couldn't find this account\",\"No videos yet\",\"Video currently unavailable\"]},currentState={preloadCount:0,finished:!1,limit:0},checkForErrors=function(){var e=document.getElementsByClassName(optStrings.classes.titleMessage);if(e&&e.length){var t=Array.from(e).find(e=>optStrings.tiktokMessages.includes(e.textContent)).textContent;if(t)return createVidUrlElement(\"ERR: \"+t),!0}return!1},createVidUrlElement=function(e){var t=document.createElement(optStrings.tags.resultTag);t.innerText=JSON.stringify(e),document.getElementsByTagName(optStrings.tags.resultParentTag)[0].appendChild(t),currentState.finished=!0},buldVidUrlArray=function(e){document.getElementsByClassName(optStrings.classes.feedVideoItem)[0].click();var t=[],r=window.setInterval(o=>{t.push(getCurrentModalVideo()),currentState.limit>0&&t.length>=currentState.limit&&(window.clearInterval(r),document.querySelector(optStrings.selectors.modalClose).click(),e(t));var i=document.querySelectorAll(optStrings.selectors.modalArrowRight)[0];i.classList.contains(optStrings.classes.modalCloseDisabled)?(window.clearInterval(r),document.querySelector(optStrings.selectors.modalClose).click(),e(t)):i.click()},20)},getCurrentModalVideo=function(){var e=document.querySelector(optStrings.selectors.modalPlayer).getAttribute(optStrings.attributes.src),t=document.querySelector(optStrings.selectors.modalShareInput).value,r=document.querySelector(optStrings.selectors.modalCaption).textContent,o=document.querySelector(optStrings.selectors.modalSoundLink),i=document.querySelector(optStrings.selectors.modalUploader).textContent,n=o.getAttribute(\"href\");return{url:e,shareLink:t,caption:r,uploader:i,sound:{title:o.text,link:n}}},getCurrentVideo=function(){if(!checkForErrors()){var e=document.querySelector(optStrings.selectors.videoPlayer).getAttribute(optStrings.attributes.src),t=document.querySelector(optStrings.selectors.videoShareInput).value,r=document.querySelector(optStrings.selectors.videoCaption).textContent,o=document.querySelector(optStrings.selectors.videoSoundLink),i=document.querySelector(optStrings.selectors.videoUploader).textContent,n=o.getAttribute(\"href\");return{url:e,shareLink:t,caption:r,uploader:i,sound:{title:o.text,link:n}}}},scrollBottom=()=>window.scrollTo(0,document.body.scrollHeight),scrollWhileNew=function(e){var t={count:0},r=window.setInterval(o=>{scrollBottom();var i=t.count;if(t.count=document.getElementsByClassName(optStrings.classes.feedVideoItem).length,currentState.limit>0&&(currentState.preloadCount>=currentState.limit||t.count>=currentState.limit)&&(e(createVidUrlElement),window.clearInterval(r)),checkForErrors())window.clearInterval(r);else if(0!=t.count)if(i!==t.count)currentState.preloadCount=t.count;else{if(document.querySelector(optStrings.selectors.feedLoading))return;window.clearInterval(r),e(createVidUrlElement)}},1e3)},bootstrapIteratingVideos=function(e){return currentState.limit=e,scrollWhileNew(buldVidUrlArray),\"bootstrapIteratingVideos\"},bootstrapGetCurrentVideo=function(){var e=getCurrentVideo();return createVidUrlElement(e),\"bootstrapGetCurrentVideo\"},init=()=>{const e=navigator.__proto__;return delete e.webdriver,navigator.__proto__=e,\"script initialized\"},init();"
//ScraperPath -
var ScraperPath = "scraper.js"

View File

@@ -1,13 +0,0 @@
package resources
// ErrorCouldNotSerializeJSON -
var ErrorCouldNotSerializeJSON = "Could not serialize json for video: %s\n"
// ErrorCouldNotRecogniseURL -
var ErrorCouldNotRecogniseURL = "Could not recognise URL format of string %s"
// ErrorCouldNotGetUserUploads -
var ErrorCouldNotGetUserUploads = "Failed to get user uploads: %s\n"
// ErrorPathNotFound -
var ErrorPathNotFound = "File path %s not found."

View File

@@ -129,9 +129,12 @@ getCurrentVideo = function() {
};
};
scrollBottom = () => window.scrollTo(0, document.body.scrollHeight);
scrollWhileNew = function(finishCallback) {
var state = { count: 0 };
var intervalID = window.setInterval(x => {
scrollBottom();
var oldCount = state.count;
state.count = document.getElementsByClassName(optStrings.classes.feedVideoItem).length;
if(currentState.limit > 0) {
@@ -143,13 +146,13 @@ scrollWhileNew = function(finishCallback) {
if(checkForErrors()) {
window.clearInterval(intervalID);
return;
} else if (state.count == 0) {
return;
}
if (oldCount !== state.count) {
currentState.preloadCount = state.count;
window.scrollTo(0, document.body.scrollHeight);
} else {
if (document.querySelector(optStrings.selectors.feedLoading)) {
window.scrollTo(0, document.body.scrollHeight);
return;
}
window.clearInterval(intervalID);

View File

@@ -1,10 +1,11 @@
package utils
import (
models "../models"
config "../models/config"
fileio "./fileio"
log "./log"
models "github.com/pikami/tiktok-dl/models"
config "github.com/pikami/tiktok-dl/models/config"
res "github.com/pikami/tiktok-dl/resources"
fileio "github.com/pikami/tiktok-dl/utils/fileio"
log "github.com/pikami/tiktok-dl/utils/log"
)
// IsItemInArchive - Checks if the item is already archived
@@ -36,7 +37,7 @@ func RemoveArchivedItems(uploads []models.Upload) []models.Upload {
removedCount := lenBeforeRemoval - len(uploads)
if removedCount > 0 {
log.Logf("%d items, found in archive. Skipping...\n", removedCount)
log.Logf(res.ItemsFoundInArchive, removedCount)
}
return uploads

View File

@@ -5,7 +5,7 @@ import (
"net/http"
"os"
checkErr "./checkErr"
checkErr "github.com/pikami/tiktok-dl/utils/checkErr"
)
// DownloadFile - Downloads content from `url` and stores it in `outputPath`

View File

@@ -5,7 +5,7 @@ import (
"io/ioutil"
"os"
checkErr "../checkErr"
checkErr "github.com/pikami/tiktok-dl/utils/checkErr"
)
type delegateString func(string)

View File

@@ -1,9 +1,10 @@
package utils
import (
res "../resources"
"fmt"
"strings"
res "github.com/pikami/tiktok-dl/resources"
)
// GetHashtagFromURL - Get's tag name from passed url

19
utils/getScraper.go Normal file
View File

@@ -0,0 +1,19 @@
package utils
import (
resources "github.com/pikami/tiktok-dl/resources"
fileio "github.com/pikami/tiktok-dl/utils/fileio"
)
// GetScraper - Retrieve scraper
func GetScraper() string {
if fileio.CheckIfExists(resources.ScraperPath) {
return ReadFileAsString(resources.ScraperPath)
}
if resources.ScraperScript != "" {
return resources.ScraperScript
}
panic(resources.FailedToLoadScraper)
}

View File

@@ -1,11 +1,12 @@
package utils
import (
config "../models/config"
res "../resources"
"fmt"
"regexp"
"strings"
config "github.com/pikami/tiktok-dl/models/config"
res "github.com/pikami/tiktok-dl/resources"
)
// GetUsername - Get's username from passed URL param

View File

@@ -1,9 +1,10 @@
package utils
import (
config "../models/config"
testUtil "../unitTestUtil"
"testing"
config "github.com/pikami/tiktok-dl/models/config"
testUtil "github.com/pikami/tiktok-dl/unitTestUtil"
)
func TestGetUsername(t *testing.T) {

View File

@@ -4,7 +4,7 @@ import (
"fmt"
"os"
config "../../models/config"
config "github.com/pikami/tiktok-dl/models/config"
)
// Log - Write to std out

View File

@@ -3,7 +3,7 @@ package utils
import (
"io/ioutil"
checkErr "./checkErr"
checkErr "github.com/pikami/tiktok-dl/utils/checkErr"
)
// ReadFileAsString - Returns contents of given file

20
workflows/common.go Normal file
View File

@@ -0,0 +1,20 @@
package workflows
import (
config "github.com/pikami/tiktok-dl/models/config"
res "github.com/pikami/tiktok-dl/resources"
fileio "github.com/pikami/tiktok-dl/utils/fileio"
log "github.com/pikami/tiktok-dl/utils/log"
)
// OnWorkflowFail - Function called when workflow fails
func OnWorkflowFail(err error, workItem string) {
failLogFilePath := config.Config.FailLogFilePath
if failLogFilePath != "" {
fileio.AppendToFile(workItem, failLogFilePath)
}
log.LogErr(res.Error, err.Error())
log.LogErr(res.FailedOnItem, workItem)
}

View File

@@ -1,9 +1,9 @@
package workflows
import (
res "../resources"
fileio "../utils/fileio"
log "../utils/log"
res "github.com/pikami/tiktok-dl/resources"
fileio "github.com/pikami/tiktok-dl/utils/fileio"
log "github.com/pikami/tiktok-dl/utils/log"
)
// CanUseDownloadBatchFile - Check's if DownloadBatchFile can be used

View File

@@ -4,12 +4,12 @@ import (
"fmt"
"strings"
client "../client"
config "../models/config"
res "../resources"
utils "../utils"
fileio "../utils/fileio"
log "../utils/log"
client "github.com/pikami/tiktok-dl/client"
config "github.com/pikami/tiktok-dl/models/config"
res "github.com/pikami/tiktok-dl/resources"
utils "github.com/pikami/tiktok-dl/utils"
fileio "github.com/pikami/tiktok-dl/utils/fileio"
log "github.com/pikami/tiktok-dl/utils/log"
)
// CanUseDownloadHashtag - Test's if this workflow can be used for parameter
@@ -22,7 +22,7 @@ func CanUseDownloadHashtag(url string) bool {
func DownloadHashtag(url string) {
uploads, err := client.GetHashtagUploads(url)
if err != nil {
log.LogErr(res.ErrorCouldNotGetUserUploads, err.Error())
OnWorkflowFail(err, url)
return
}
@@ -36,7 +36,7 @@ func DownloadHashtag(url string) {
for index, upload := range uploads {
downloadVideo(upload, downloadDir)
log.Logf("\r[%d/%d] Downloaded", index+1, uploadCount)
log.Logf(res.Downloaded, index+1, uploadCount)
}
log.Log()
}
@@ -45,7 +45,7 @@ func DownloadHashtag(url string) {
func GetHashtagJSON(url string) {
uploads, err := client.GetHashtagUploadsJSON(url)
if err != nil {
log.LogErr(res.ErrorCouldNotGetUserUploads, err.Error())
OnWorkflowFail(err, url)
return
}
fmt.Printf("%s", uploads)

View File

@@ -4,12 +4,12 @@ import (
"fmt"
"regexp"
client "../client"
config "../models/config"
res "../resources"
utils "../utils"
fileio "../utils/fileio"
log "../utils/log"
client "github.com/pikami/tiktok-dl/client"
config "github.com/pikami/tiktok-dl/models/config"
res "github.com/pikami/tiktok-dl/resources"
utils "github.com/pikami/tiktok-dl/utils"
fileio "github.com/pikami/tiktok-dl/utils/fileio"
log "github.com/pikami/tiktok-dl/utils/log"
)
// CanUseDownloadMusic - Check's if DownloadMusic can be used for parameter
@@ -22,7 +22,7 @@ func CanUseDownloadMusic(url string) bool {
func DownloadMusic(url string) {
uploads, err := client.GetMusicUploads(url)
if err != nil {
log.LogErr(res.ErrorCouldNotGetUserUploads, err.Error())
OnWorkflowFail(err, url)
return
}
@@ -35,7 +35,7 @@ func DownloadMusic(url string) {
fileio.InitOutputDirectory(downloadDir)
downloadVideo(upload, downloadDir)
log.Logf("\r[%d/%d] Downloaded", index+1, uploadCount)
log.Logf(res.Downloaded, index+1, uploadCount)
}
log.Log()
}
@@ -44,7 +44,7 @@ func DownloadMusic(url string) {
func GetMusicJSON(url string) {
uploads, err := client.GetMusicUploadsJSON(url)
if err != nil {
log.LogErr(res.ErrorCouldNotGetUserUploads, err.Error())
OnWorkflowFail(err, url)
return
}
fmt.Printf("%s", uploads)

View File

@@ -1,10 +1,10 @@
package workflows
import (
client "../client"
res "../resources"
log "../utils/log"
"regexp"
client "github.com/pikami/tiktok-dl/client"
log "github.com/pikami/tiktok-dl/utils/log"
)
// CanUseDownloadShareLink - Check's if DownloadShareLink can be used
@@ -17,9 +17,9 @@ func CanUseDownloadShareLink(url string) bool {
func DownloadShareLink(url string) {
log.Logf("Resolving share link: %s\n", url)
finalURL, err := client.GetRedirectUrl(url)
finalURL, err := client.GetRedirectURL(url)
if err != nil {
log.LogErr(res.ErrorCouldNotGetUserUploads, err.Error())
OnWorkflowFail(err, url)
return
}

View File

@@ -5,12 +5,12 @@ import (
"regexp"
"strings"
client "../client"
config "../models/config"
res "../resources"
utils "../utils"
fileio "../utils/fileio"
log "../utils/log"
client "github.com/pikami/tiktok-dl/client"
config "github.com/pikami/tiktok-dl/models/config"
res "github.com/pikami/tiktok-dl/resources"
utils "github.com/pikami/tiktok-dl/utils"
fileio "github.com/pikami/tiktok-dl/utils/fileio"
log "github.com/pikami/tiktok-dl/utils/log"
)
// CanUseDownloadUser - Test's if this workflow can be used for parameter
@@ -24,7 +24,7 @@ func CanUseDownloadUser(url string) bool {
func DownloadUser(username string) {
uploads, err := client.GetUserUploads(username)
if err != nil {
log.LogErr(res.ErrorCouldNotGetUserUploads, err.Error())
OnWorkflowFail(err, username)
return
}
@@ -37,7 +37,7 @@ func DownloadUser(username string) {
for index, upload := range uploads {
downloadVideo(upload, downloadDir)
log.Logf("\r[%d/%d] Downloaded", index+1, uploadCount)
log.Logf(res.Downloaded, index+1, uploadCount)
}
log.Log()
}
@@ -46,7 +46,7 @@ func DownloadUser(username string) {
func GetUserVideosJSON(username string) {
uploads, err := client.GetUserUploadsJSON(username)
if err != nil {
log.LogErr(res.ErrorCouldNotGetUserUploads, err.Error())
OnWorkflowFail(err, username)
return
}
fmt.Printf("%s", uploads)

View File

@@ -4,13 +4,13 @@ import (
"fmt"
"regexp"
client "../client"
models "../models"
config "../models/config"
res "../resources"
utils "../utils"
fileio "../utils/fileio"
log "../utils/log"
client "github.com/pikami/tiktok-dl/client"
models "github.com/pikami/tiktok-dl/models"
config "github.com/pikami/tiktok-dl/models/config"
res "github.com/pikami/tiktok-dl/resources"
utils "github.com/pikami/tiktok-dl/utils"
fileio "github.com/pikami/tiktok-dl/utils/fileio"
log "github.com/pikami/tiktok-dl/utils/log"
)
// CanUseDownloadSingleVideo - Check's if DownloadSingleVideo can be used for parameter
@@ -24,7 +24,7 @@ func DownloadSingleVideo(url string) {
username := utils.GetUsernameFromString(url)
upload, err := client.GetVideoDetails(url)
if err != nil {
log.LogErr(res.ErrorCouldNotGetUserUploads, err.Error())
OnWorkflowFail(err, url)
return
}
@@ -35,7 +35,7 @@ func DownloadSingleVideo(url string) {
fileio.InitOutputDirectory(downloadDir)
downloadVideo(upload, downloadDir)
log.Log("[1/1] Downloaded\n")
log.Logf(res.Downloaded, 1, 1)
}
// DownloadVideo - Downloads one video

View File

@@ -1,10 +1,10 @@
package workflows
import (
config "../models/config"
res "../resources"
utils "../utils"
log "../utils/log"
config "github.com/pikami/tiktok-dl/models/config"
res "github.com/pikami/tiktok-dl/resources"
utils "github.com/pikami/tiktok-dl/utils"
log "github.com/pikami/tiktok-dl/utils/log"
)
// StartWorkflowByParameter - Start needed workflow by given parameter