mirror of
https://github.com/NohamR/papeer.git
synced 2026-05-25 20:00:47 +00:00
Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0009435769 | ||
|
|
4e9b0611e8 | ||
|
|
e7ffd8c66c | ||
|
|
84e6ad8585 | ||
|
|
d593a74e6e |
10
Makefile
Normal file
10
Makefile
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
install:
|
||||||
|
go install
|
||||||
|
|
||||||
|
format:
|
||||||
|
gofmt -s -w .
|
||||||
|
|
||||||
|
clean:
|
||||||
|
find . -maxdepth 1 -not -name 'README.md' -name '*.md' -delete
|
||||||
|
find . -maxdepth 1 -name '*.epub' -delete
|
||||||
|
find . -maxdepth 1 -name '*.mobi' -delete
|
||||||
139
README.md
139
README.md
@@ -1,53 +1,6 @@
|
|||||||
```
|
# Papeer
|
||||||
❯ papeer get --format=epub --recursive --delay=500 --limit=10 https://news.ycombinator.com/
|
|
||||||
[===============================================>--------------------] Chapters 7 / 10
|
|
||||||
[====================================================================] 1. Three ex-US intelligence officers admit hacking for UAE
|
|
||||||
[====================================================================] 2. Show HN: Time Travel Debugger
|
|
||||||
[====================================================================] 3. How much faster is Java 17?
|
|
||||||
[====================================================================] 4. The First Webcam Was Invented to Keep an Eye on a Coffee Pot
|
|
||||||
[====================================================================] 5. Nikon's 2021 Photomicrography Competition Winners
|
|
||||||
[====================================================================] 6. HTTP Status 418 – I'm a teapot
|
|
||||||
[====================================================================] 7. H3: Hexagonal hierarchical geospatial indexing system
|
|
||||||
[--------------------------------------------------------------------] 8. Automatic cipher suite ordering in Go’s crypto/tls
|
|
||||||
[--------------------------------------------------------------------] 9. Find engineering roles at over 800 YC-funded startups
|
|
||||||
[--------------------------------------------------------------------] 10. Futarchy: Robin Hanson on prediction markets
|
|
||||||
Ebook saved to "Hacker_News.epub"
|
|
||||||
```
|
|
||||||
|
|
||||||
# Installation
|
Papeer is a tool that lets you scrape content from the internet. It can scrape any web page, keeping only relevant content (formatted text and images) and removing ads and menus. You can export the content to Markdown, EPUB or MOBI files.
|
||||||
|
|
||||||
## From source
|
|
||||||
|
|
||||||
```sh
|
|
||||||
go get -u github.com/lapwat/papeer
|
|
||||||
```
|
|
||||||
|
|
||||||
## From binary
|
|
||||||
|
|
||||||
### On Linux / MacOS
|
|
||||||
|
|
||||||
```sh
|
|
||||||
platform=linux
|
|
||||||
# platform=darwin for MacOS
|
|
||||||
curl -L https://github.com/lapwat/papeer/releases/download/v0.2.0/papeer-v0.2.0-$platform-amd64 > papeer
|
|
||||||
chmod +x papeer
|
|
||||||
sudo mv papeer /usr/local/bin
|
|
||||||
```
|
|
||||||
|
|
||||||
### On Windows
|
|
||||||
|
|
||||||
Download [latest release](https://github.com/lapwat/papeer/releases/download/v0.2.0/papeer-v0.2.0-windows-amd64.exe).
|
|
||||||
|
|
||||||
## Install kindlegen to export websites to MOBI (optional)
|
|
||||||
|
|
||||||
```sh
|
|
||||||
TMPDIR=$(mktemp -d -t papeer-XXXXX)
|
|
||||||
curl -L https://github.com/lapwat/papeer/releases/download/kindlegen/kindlegen_linux_2.6_i386_v2_9.tar.gz > $TMPDIR/kindlegen.tar.gz
|
|
||||||
tar xzvf $TMPDIR/kindlegen.tar.gz -C $TMPDIR
|
|
||||||
chmod +x $TMPDIR/kindlegen
|
|
||||||
sudo mv $TMPDIR/kindlegen /usr/local/bin
|
|
||||||
rm -rf $TMPDIR
|
|
||||||
```
|
|
||||||
|
|
||||||
# Usage
|
# Usage
|
||||||
|
|
||||||
@@ -66,22 +19,104 @@ Available Commands:
|
|||||||
version Print the version number of papeer
|
version Print the version number of papeer
|
||||||
|
|
||||||
Flags:
|
Flags:
|
||||||
|
-a, --author string book author
|
||||||
-d, --delay int time to wait before downloading next chapter, in milliseconds (default -1)
|
-d, --delay int time to wait before downloading next chapter, in milliseconds (default -1)
|
||||||
-f, --format string file format [md, epub, mobi] (default "md")
|
-f, --format string file format [stdout, md, epub, mobi] (default "stdout")
|
||||||
-h, --help help for papeer
|
-h, --help help for papeer
|
||||||
--images retrieve images only
|
--images retrieve images only
|
||||||
-i, --include include URL as first chapter, in resursive mode
|
-i, --include include URL as first chapter, in resursive mode
|
||||||
-l, --limit int limit number of chapters, in recursive mode (default -1)
|
-l, --limit int limit number of chapters, in recursive mode (default -1)
|
||||||
|
-n, --name string book name (default: page title)
|
||||||
-o, --offset int skip first chapters, in recursive mode
|
-o, --offset int skip first chapters, in recursive mode
|
||||||
--output string output file
|
--output string file name (default: book name)
|
||||||
-r, --recursive create one chapter per natigation item
|
-r, --recursive create one chapter per natigation item
|
||||||
-s, --selector string table of content CSS selector, in resursive mode
|
-s, --selector string table of content CSS selector, in resursive mode
|
||||||
--stdout print to standard output
|
|
||||||
-t, --threads int download concurrency, in recursive mode (default -1)
|
-t, --threads int download concurrency, in recursive mode (default -1)
|
||||||
|
|
||||||
Use "papeer [command] --help" for more information about a command.
|
Use "papeer [command] --help" for more information about a command.
|
||||||
```
|
```
|
||||||
|
|
||||||
|
# Examples
|
||||||
|
|
||||||
|
## Grab a single page
|
||||||
|
|
||||||
|
The `get` command lets you retrieve the content of a web page.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
papeer get https://www.eff.org/cyberspace-independence
|
||||||
|
# A Declaration of the Independence of Cyberspace
|
||||||
|
# ===============================================
|
||||||
|
|
||||||
|
# Governments of the Industrial World, you weary giants of flesh and steel, I come from Cyberspace, the new home of Mind. On behalf of the future, I ask you of the past to leave us alone. You are not welcome among us. You have no sovereignty where we gather...
|
||||||
|
```
|
||||||
|
|
||||||
|
## Grab several pages (recursive mode)
|
||||||
|
|
||||||
|
The `recursive` option lets you extract the table of content of a website, then scrape the content of each one of its pages.
|
||||||
|
|
||||||
|
### Display table of content
|
||||||
|
|
||||||
|
Before trying the `recursive` option, it is a good idea to use the `ls` option, which lets you vizualize the content that will be retrieved. You can use several options to customize the table of content extraction, such as `selector`, `limit`, `offset` and `include`. Type `papeer help` for more information about those options.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
papeer ls https://news.ycombinator.com/ --limit=5
|
||||||
|
# # NAME URL
|
||||||
|
# 1 Tailwind CSS v3.0 https://tailwindcss.com/blog/tailwindcss-v3
|
||||||
|
# 2 A molten salt storage solution using sodium hydroxide https://sifted.eu/articles/salt-energy-storage-seaborg-hyme/
|
||||||
|
# 3 HashiCorp IPO today https://www.hashicorp.com/blog/a-new-chapter-for-hashicorp
|
||||||
|
# 4 Stack Graphs https://github.blog/2021-12-09-introducing-stack-graphs/
|
||||||
|
# 5 ‘Tipping point’ makes partisan polarization irreversible https://news.cornell.edu/stories/2021/12/tipping-point-makes-partisan-polarization-irreversible
|
||||||
|
```
|
||||||
|
|
||||||
|
### Scrape time
|
||||||
|
|
||||||
|
Once you are satisfied with the table of content listed by the `ls` command, you can actually scrape the content of those pages. You can use the same options that you specified for the `ls` command. In recursive mode, you also have the possibility to use `delay` and `threads` options.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
papeer get https://news.ycombinator.com/ --recursive --delay=500 --limit=5 --format=md
|
||||||
|
# [========================================>---------------------------] Chapters 3 / 5
|
||||||
|
# [====================================================================] 1. Tailwind CSS v3.0
|
||||||
|
# [====================================================================] 2. A molten salt storage solution using sodium hydroxide
|
||||||
|
# [====================================================================] 3. HashiCorp IPO today
|
||||||
|
# [--------------------------------------------------------------------] 4. Stack Graphs
|
||||||
|
# [--------------------------------------------------------------------] 5. ‘Tipping point’ makes partisan polarization irreversible
|
||||||
|
# Markdown saved to "Hacker News.md"
|
||||||
|
```
|
||||||
|
|
||||||
|
# Installation
|
||||||
|
|
||||||
|
## From source
|
||||||
|
|
||||||
|
```sh
|
||||||
|
go get -u github.com/lapwat/papeer
|
||||||
|
```
|
||||||
|
|
||||||
|
## From binary
|
||||||
|
|
||||||
|
### On Linux / MacOS
|
||||||
|
|
||||||
|
```sh
|
||||||
|
platform=linux # use platform=darwin for MacOS
|
||||||
|
curl -L https://github.com/lapwat/papeer/releases/download/v0.3.1/papeer-v0.3.1-$platform-amd64 > papeer
|
||||||
|
chmod +x papeer
|
||||||
|
sudo mv papeer /usr/local/bin
|
||||||
|
```
|
||||||
|
|
||||||
|
### On Windows
|
||||||
|
|
||||||
|
Download [latest release](https://github.com/lapwat/papeer/releases/download/v0.3.1/papeer-v0.3.1-windows-amd64.exe).
|
||||||
|
|
||||||
|
## Install kindlegen to export websites to MOBI (optional)
|
||||||
|
|
||||||
|
```sh
|
||||||
|
TMPDIR=$(mktemp -d -t papeer-XXXXX)
|
||||||
|
curl -L https://github.com/lapwat/papeer/releases/download/kindlegen/kindlegen_linux_2.6_i386_v2_9.tar.gz > $TMPDIR/kindlegen.tar.gz
|
||||||
|
tar xzvf $TMPDIR/kindlegen.tar.gz -C $TMPDIR
|
||||||
|
chmod +x $TMPDIR/kindlegen
|
||||||
|
sudo mv $TMPDIR/kindlegen /usr/local/bin
|
||||||
|
rm -rf $TMPDIR
|
||||||
|
```
|
||||||
|
|
||||||
# Autocompletion
|
# Autocompletion
|
||||||
|
|
||||||
Execute this command in your current shell, or add it to your `.bashrc`.
|
Execute this command in your current shell, or add it to your `.bashrc`.
|
||||||
|
|||||||
@@ -14,25 +14,34 @@ import (
|
|||||||
colly "github.com/gocolly/colly/v2"
|
colly "github.com/gocolly/colly/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewBookFromURL(url, selector string, recursive, include, images bool, limit, offset, delay, threads int) book {
|
func NewBookFromURL(url, selector, name, author string, recursive, include bool, limit, offset, delay, threads int) book {
|
||||||
|
var chapters []chapter
|
||||||
|
var home chapter
|
||||||
|
|
||||||
if recursive {
|
if recursive {
|
||||||
chapters := tableOfContent(url, selector, limit, offset, delay, threads, include, images)
|
chapters, home = tableOfContent(url, selector, limit, offset, delay, threads, include)
|
||||||
|
|
||||||
b := New(chapters[0].Name(), chapters[0].Author())
|
|
||||||
for _, c := range chapters {
|
|
||||||
b.AddChapter(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
return b
|
|
||||||
} else {
|
} else {
|
||||||
c := NewChapterFromURL(url, images)
|
chapters = []chapter{NewChapterFromURL(url)}
|
||||||
b := New(c.Name(), c.Author())
|
home = chapters[0]
|
||||||
b.AddChapter(c)
|
|
||||||
return b
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(name) == 0 {
|
||||||
|
name = home.Name()
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(author) == 0 {
|
||||||
|
author = home.Author()
|
||||||
|
}
|
||||||
|
|
||||||
|
b := New(name, author)
|
||||||
|
for _, c := range chapters {
|
||||||
|
b.AddChapter(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewChapterFromURL(url string, images bool) chapter {
|
func NewChapterFromURL(url string) chapter {
|
||||||
article, err := readability.FromURL(url, 30*time.Second)
|
article, err := readability.FromURL(url, 30*time.Second)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("failed to parse %s, %v\n", url, err)
|
log.Fatalf("failed to parse %s, %v\n", url, err)
|
||||||
@@ -40,29 +49,31 @@ func NewChapterFromURL(url string, images bool) chapter {
|
|||||||
|
|
||||||
content := strings.ReplaceAll(article.Content, "\n", "")
|
content := strings.ReplaceAll(article.Content, "\n", "")
|
||||||
|
|
||||||
if images {
|
// if images {
|
||||||
// Load the HTML document
|
// // parse html content
|
||||||
doc, err := goquery.NewDocumentFromReader(strings.NewReader(content))
|
// doc, err := goquery.NewDocumentFromReader(strings.NewReader(content))
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
log.Fatal(err)
|
// log.Fatal(err)
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Find the review items
|
// // extract images only
|
||||||
doc.Find("img").Each(func(i int, s *goquery.Selection) {
|
// content = ""
|
||||||
content, _ = goquery.OuterHtml(s)
|
// doc.Find("img").Each(func(i int, s *goquery.Selection) {
|
||||||
})
|
// newContent, _ := goquery.OuterHtml(s)
|
||||||
}
|
// content += newContent
|
||||||
|
// })
|
||||||
|
// }
|
||||||
|
|
||||||
return chapter{article.Title, article.Byline, content}
|
return chapter{article.Title, article.Byline, content}
|
||||||
}
|
}
|
||||||
|
|
||||||
func tableOfContent(url, selector string, limit, offset, delay, threads int, include, images bool) []chapter {
|
func tableOfContent(url, selector string, limit, offset, delay, threads int, include bool) ([]chapter, chapter) {
|
||||||
base, err := urllib.Parse(url)
|
base, err := urllib.Parse(url)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
links, err := GetLinks(base, selector, limit, offset, include)
|
links, home, err := GetLinks(base, selector, limit, offset, include)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -80,7 +91,7 @@ func tableOfContent(url, selector string, limit, offset, delay, threads int, inc
|
|||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
chapters[index] = NewChapterFromURL(u.String(), images)
|
chapters[index] = NewChapterFromURL(u.String())
|
||||||
progress.Incr(index)
|
progress.Incr(index)
|
||||||
|
|
||||||
// short sleep for last chapter to let the progress bar update
|
// short sleep for last chapter to let the progress bar update
|
||||||
@@ -114,7 +125,7 @@ func tableOfContent(url, selector string, limit, offset, delay, threads int, inc
|
|||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
chapters[index] = NewChapterFromURL(u.String(), images)
|
chapters[index] = NewChapterFromURL(u.String())
|
||||||
progress.Incr(index)
|
progress.Incr(index)
|
||||||
|
|
||||||
<-semaphore
|
<-semaphore
|
||||||
@@ -122,7 +133,8 @@ func tableOfContent(url, selector string, limit, offset, delay, threads int, inc
|
|||||||
}
|
}
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
}
|
}
|
||||||
return chapters
|
|
||||||
|
return chapters, home
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetPath(elm *goquery.Selection) string {
|
func GetPath(elm *goquery.Selection) string {
|
||||||
@@ -142,7 +154,7 @@ func GetPath(elm *goquery.Selection) string {
|
|||||||
return join
|
return join
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetLinks(url *urllib.URL, selector string, limit, offset int, include bool) ([]link, error) {
|
func GetLinks(url *urllib.URL, selector string, limit, offset int, include bool) ([]link, chapter, error) {
|
||||||
selectorSet := true
|
selectorSet := true
|
||||||
if selector == "" {
|
if selector == "" {
|
||||||
selector = "a"
|
selector = "a"
|
||||||
@@ -180,7 +192,7 @@ func GetLinks(url *urllib.URL, selector string, limit, offset int, include bool)
|
|||||||
|
|
||||||
links := pathLinks[pathMax]
|
links := pathLinks[pathMax]
|
||||||
if len(links) == 0 {
|
if len(links) == 0 {
|
||||||
return []link{}, fmt.Errorf("no link found for selector: %s", selector)
|
return []link{}, chapter{}, fmt.Errorf("no link found for selector: %s", selector)
|
||||||
}
|
}
|
||||||
|
|
||||||
end := len(links)
|
end := len(links)
|
||||||
@@ -190,11 +202,12 @@ func GetLinks(url *urllib.URL, selector string, limit, offset int, include bool)
|
|||||||
|
|
||||||
links = links[offset:end]
|
links = links[offset:end]
|
||||||
|
|
||||||
|
home := NewChapterFromURL(url.String())
|
||||||
|
|
||||||
if include {
|
if include {
|
||||||
c := NewChapterFromURL(url.String(), false)
|
l := NewLink(url.String(), home.Name())
|
||||||
l := NewLink(url.String(), c.Name())
|
|
||||||
links = append([]link{l}, links...)
|
links = append([]link{l}, links...)
|
||||||
}
|
}
|
||||||
|
|
||||||
return links, nil
|
return links, home, nil
|
||||||
}
|
}
|
||||||
|
|||||||
149
cmd/get.go
149
cmd/get.go
@@ -9,14 +9,15 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
md "github.com/JohannesKaufmann/html-to-markdown"
|
md "github.com/JohannesKaufmann/html-to-markdown"
|
||||||
|
"github.com/PuerkitoBio/goquery"
|
||||||
epub "github.com/bmaupin/go-epub"
|
epub "github.com/bmaupin/go-epub"
|
||||||
cobra "github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
"github.com/lapwat/papeer/book"
|
"github.com/lapwat/papeer/book"
|
||||||
)
|
)
|
||||||
|
|
||||||
var stdout, recursive, include, images bool
|
var recursive, include, images bool
|
||||||
var format, output, selector string
|
var format, output, selector, name, author string
|
||||||
var limit, offset, delay, threads int
|
var limit, offset, delay, threads int
|
||||||
|
|
||||||
var getCmd = &cobra.Command{
|
var getCmd = &cobra.Command{
|
||||||
@@ -28,20 +29,16 @@ var getCmd = &cobra.Command{
|
|||||||
}
|
}
|
||||||
|
|
||||||
formatEnum := map[string]bool{
|
formatEnum := map[string]bool{
|
||||||
"md": true,
|
"stdout": true,
|
||||||
"epub": true,
|
"md": true,
|
||||||
"mobi": true,
|
"epub": true,
|
||||||
|
"mobi": true,
|
||||||
}
|
}
|
||||||
if formatEnum[format] != true {
|
if formatEnum[format] != true {
|
||||||
return fmt.Errorf("invalid format specified: %s", format)
|
return fmt.Errorf("invalid format specified: %s", format)
|
||||||
}
|
}
|
||||||
|
|
||||||
if format == "epub" || format == "mobi" {
|
// add .mobi to filename if not specified
|
||||||
if stdout {
|
|
||||||
return errors.New("cannot print EPUB/MOBI file to standard output")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if format == "mobi" {
|
if format == "mobi" {
|
||||||
if len(output) > 0 && strings.HasSuffix(output, ".mobi") == false {
|
if len(output) > 0 && strings.HasSuffix(output, ".mobi") == false {
|
||||||
output = fmt.Sprintf("%s.mobi", output)
|
output = fmt.Sprintf("%s.mobi", output)
|
||||||
@@ -80,7 +77,7 @@ var getCmd = &cobra.Command{
|
|||||||
},
|
},
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
url := args[0]
|
url := args[0]
|
||||||
b := book.NewBookFromURL(url, selector, recursive, include, images, limit, offset, delay, threads)
|
b := book.NewBookFromURL(url, selector, name, author, recursive, include, limit, offset, delay, threads)
|
||||||
|
|
||||||
if len(output) == 0 {
|
if len(output) == 0 {
|
||||||
// set default output
|
// set default output
|
||||||
@@ -89,19 +86,10 @@ var getCmd = &cobra.Command{
|
|||||||
output = fmt.Sprintf("%s.%s", output, format)
|
output = fmt.Sprintf("%s.%s", output, format)
|
||||||
}
|
}
|
||||||
|
|
||||||
if format == "md" {
|
if format == "stdout" {
|
||||||
var f *os.File
|
|
||||||
var err error
|
|
||||||
|
|
||||||
if !stdout {
|
|
||||||
f, err = os.Create(output)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, c := range b.Chapters() {
|
for _, c := range b.Chapters() {
|
||||||
|
// convert to markdown
|
||||||
content, err := md.NewConverter("", true, nil).ConvertString(c.Content())
|
content, err := md.NewConverter("", true, nil).ConvertString(c.Content())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
@@ -109,20 +97,38 @@ var getCmd = &cobra.Command{
|
|||||||
|
|
||||||
text := fmt.Sprintf("%s\n%s\n\n%s\n\n\n", c.Name(), strings.Repeat("=", len(c.Name())), content)
|
text := fmt.Sprintf("%s\n%s\n\n%s\n\n\n", c.Name(), strings.Repeat("=", len(c.Name())), content)
|
||||||
|
|
||||||
if stdout {
|
// write to stdout
|
||||||
fmt.Println(text)
|
fmt.Println(text)
|
||||||
} else {
|
}
|
||||||
_, err := f.WriteString(text)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if format == "md" {
|
||||||
|
|
||||||
|
// create markdown file
|
||||||
|
f, err := os.Create(output)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
for _, c := range b.Chapters() {
|
||||||
|
// convert to markdown
|
||||||
|
content, err := md.NewConverter("", true, nil).ConvertString(c.Content())
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
text := fmt.Sprintf("%s\n%s\n\n%s\n\n\n", c.Name(), strings.Repeat("=", len(c.Name())), content)
|
||||||
|
|
||||||
|
// write to markdown file
|
||||||
|
_, err = f.WriteString(text)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if stdout == false {
|
fmt.Printf("Markdown saved to \"%s\"\n", output)
|
||||||
fmt.Printf("Markdown saved to \"%s\"\n", output)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if format == "epub" {
|
if format == "epub" {
|
||||||
@@ -130,15 +136,35 @@ var getCmd = &cobra.Command{
|
|||||||
e.SetAuthor(b.Author())
|
e.SetAuthor(b.Author())
|
||||||
|
|
||||||
for _, c := range b.Chapters() {
|
for _, c := range b.Chapters() {
|
||||||
if images {
|
var content string
|
||||||
e.AddSection(c.Content(), "", "", "")
|
|
||||||
} else {
|
|
||||||
html := fmt.Sprintf("<h1>%s</h1>%s", c.Name(), c.Content())
|
|
||||||
|
|
||||||
_, err := e.AddSection(html, c.Name(), "", "")
|
if images == false {
|
||||||
if err != nil {
|
content = c.Content()
|
||||||
log.Fatal(err)
|
}
|
||||||
|
|
||||||
|
// parse content
|
||||||
|
doc, err := goquery.NewDocumentFromReader(strings.NewReader(c.Content()))
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// retrieve images and download it
|
||||||
|
doc.Find("img").Each(func(i int, s *goquery.Selection) {
|
||||||
|
src, _ := s.Attr("src")
|
||||||
|
imagePath, _ := e.AddImage(src, "")
|
||||||
|
|
||||||
|
if images {
|
||||||
|
imageTag, _ := goquery.OuterHtml(s)
|
||||||
|
content += imageTag
|
||||||
}
|
}
|
||||||
|
|
||||||
|
content = strings.ReplaceAll(content, src, imagePath)
|
||||||
|
})
|
||||||
|
|
||||||
|
html := fmt.Sprintf("<h1>%s</h1>%s", c.Name(), content)
|
||||||
|
_, err = e.AddSection(html, c.Name(), "", "")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -154,8 +180,37 @@ var getCmd = &cobra.Command{
|
|||||||
e := epub.NewEpub(b.Name())
|
e := epub.NewEpub(b.Name())
|
||||||
e.SetAuthor(b.Author())
|
e.SetAuthor(b.Author())
|
||||||
|
|
||||||
for _, chapter := range b.Chapters() {
|
for _, c := range b.Chapters() {
|
||||||
e.AddSection(chapter.Content(), chapter.Name(), "", "")
|
var content string
|
||||||
|
|
||||||
|
if images == false {
|
||||||
|
content = c.Content()
|
||||||
|
}
|
||||||
|
|
||||||
|
// parse content
|
||||||
|
doc, err := goquery.NewDocumentFromReader(strings.NewReader(c.Content()))
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// retrieve images and download it
|
||||||
|
doc.Find("img").Each(func(i int, s *goquery.Selection) {
|
||||||
|
src, _ := s.Attr("src")
|
||||||
|
imagePath, _ := e.AddImage(src, "")
|
||||||
|
|
||||||
|
if images {
|
||||||
|
imageTag, _ := goquery.OuterHtml(s)
|
||||||
|
content += imageTag
|
||||||
|
}
|
||||||
|
|
||||||
|
content = strings.ReplaceAll(content, src, imagePath)
|
||||||
|
})
|
||||||
|
|
||||||
|
html := fmt.Sprintf("<h1>%s</h1>%s", c.Name(), content)
|
||||||
|
_, err = e.AddSection(html, c.Name(), "", "")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
outputEPUB := strings.ReplaceAll(output, ".mobi", ".epub")
|
outputEPUB := strings.ReplaceAll(output, ".mobi", ".epub")
|
||||||
@@ -166,16 +221,16 @@ var getCmd = &cobra.Command{
|
|||||||
}
|
}
|
||||||
|
|
||||||
exec.Command("kindlegen", outputEPUB).Run()
|
exec.Command("kindlegen", outputEPUB).Run()
|
||||||
// exec command always return status 1 even if it fails
|
// exec command always return status 1 even if it succeed
|
||||||
// if err != nil {
|
// if err != nil {
|
||||||
// log.Fatal(err)
|
// log.Fatal(err)
|
||||||
// }
|
// }
|
||||||
|
|
||||||
fmt.Printf("Ebook saved to \"%s\"\n", output)
|
fmt.Printf("Ebook saved to \"%s\"\n", output)
|
||||||
|
|
||||||
err2 := os.Remove(outputEPUB)
|
err = os.Remove(outputEPUB)
|
||||||
if err2 != nil {
|
if err != nil {
|
||||||
log.Fatal(err2)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ var listCmd = &cobra.Command{
|
|||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
links, err := book.GetLinks(base, selector, limit, offset, include)
|
links, _, err := book.GetLinks(base, selector, limit, offset, include)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,12 +23,13 @@ func Execute() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
rootCmd.PersistentFlags().StringVarP(&format, "format", "f", "md", "file format [md, epub, mobi]")
|
rootCmd.PersistentFlags().StringVarP(&name, "name", "n", "", "book name (default: page title)")
|
||||||
rootCmd.PersistentFlags().StringVarP(&output, "output", "", "", "output file")
|
rootCmd.PersistentFlags().StringVarP(&author, "author", "a", "", "book author")
|
||||||
|
rootCmd.PersistentFlags().StringVarP(&format, "format", "f", "stdout", "file format [stdout, md, epub, mobi]")
|
||||||
|
rootCmd.PersistentFlags().StringVarP(&output, "output", "", "", "file name (default: book name)")
|
||||||
rootCmd.PersistentFlags().StringVarP(&selector, "selector", "s", "", "table of content CSS selector, in resursive mode")
|
rootCmd.PersistentFlags().StringVarP(&selector, "selector", "s", "", "table of content CSS selector, in resursive mode")
|
||||||
rootCmd.PersistentFlags().BoolVarP(&recursive, "recursive", "r", false, "create one chapter per natigation item")
|
rootCmd.PersistentFlags().BoolVarP(&recursive, "recursive", "r", false, "create one chapter per natigation item")
|
||||||
rootCmd.PersistentFlags().BoolVarP(&include, "include", "i", false, "include URL as first chapter, in resursive mode")
|
rootCmd.PersistentFlags().BoolVarP(&include, "include", "i", false, "include URL as first chapter, in resursive mode")
|
||||||
rootCmd.PersistentFlags().BoolVarP(&stdout, "stdout", "", false, "print to standard output")
|
|
||||||
rootCmd.PersistentFlags().BoolVarP(&images, "images", "", false, "retrieve images only")
|
rootCmd.PersistentFlags().BoolVarP(&images, "images", "", false, "retrieve images only")
|
||||||
rootCmd.PersistentFlags().IntVarP(&limit, "limit", "l", -1, "limit number of chapters, in recursive mode")
|
rootCmd.PersistentFlags().IntVarP(&limit, "limit", "l", -1, "limit number of chapters, in recursive mode")
|
||||||
rootCmd.PersistentFlags().IntVarP(&offset, "offset", "o", 0, "skip first chapters, in recursive mode")
|
rootCmd.PersistentFlags().IntVarP(&offset, "offset", "o", 0, "skip first chapters, in recursive mode")
|
||||||
|
|||||||
@@ -14,6 +14,6 @@ var versionCmd = &cobra.Command{
|
|||||||
Use: "version",
|
Use: "version",
|
||||||
Short: "Print the version number of papeer",
|
Short: "Print the version number of papeer",
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
fmt.Println("papeer v0.1.1")
|
fmt.Println("papeer v0.3.1")
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user