I'm trying to attach a picture to an email sent through the gmail golang api, but the image does not attach. I've looked extensively at the documentation, but I have not been able to figure out how to attach an image to an email. Here is the code that I have so far. It only sends the text.
// SendEmailEmbed2 Sends email through gmail
func (config *GmailConfig) SendEmailEmbed2(person int, formattedTemplate string) {
msg := formattedTemplate
mmbody := gmail.MessagePartBody{
Data: encodeWeb64String([]byte(msg)),
}
headers := []*gmail.MessagePartHeader{
&gmail.MessagePartHeader{
Name: "From",
Value: config.FromAddress,
},
&gmail.MessagePartHeader{
Name: "To",
Value: config.CSVReference.Elem(person, config.PlugIns[0].ToEmail).String(),
},
&gmail.MessagePartHeader{
Name: "Subject",
Value: config.EmailSubject,
},
&gmail.MessagePartHeader{
Name: "Content-Disposition",
Value: "./prac2_files/image001.jpg",
},
}
Parts := []*gmail.MessagePart{
&gmail.MessagePart{
Filename: "./prac2_files/image001.jpg",
},
}
partgmsg := gmail.MessagePart{
Body: &mmbody,
Headers: headers,
Parts: Parts,
MimeType: "multipart",
}
im, _ := os.Open("./prac2_files/image001.jpg")
gmsg := gmail.Message{
Payload: &partgmsg,
Raw: encodeWeb64String([]byte(msg)),
}
call, err := config.srv.Users.Messages.Send("me", &gmsg).Media(im).Do()
im.Close()
if err != nil {
log.Println("em %v, err %v", gmsg, err)
// return err
}
// return err
fmt.Println(person, ":", headers[1].Value, " : ", call.LabelIds)
}
func encodeWeb64String(b []byte) string {
s := base64.URLEncoding.EncodeToString(b)
var i = len(s) - 1
for s[i] == '=' {
i--
}
return s[0 : i+1]
}
Answer:
You need to attach the file inline in the body of the message.
Code:
message := gmail.Message
attachmentBytes := ioutil.ReadFile(fileDir + fileName)
attachmentMimeType: = http.DetectContentType(attachmentBytes)
attachmentData: = base64.StdEncoding.EncodeToString(attachmentBytes)
boundary: = randStr(32, "alphanum")
messageBody: = [] byte("Content-Type: multipart/mixed; boundary=" + boundary + " \n" +
"MIME-Version: 1.0\n" +
"to: " + config.CSVReference.Elem(person, config.PlugIns[0].ToEmail).String() + "\n" +
"from: " + config.FromAddress + "\n" +
"subject: " + config.EmailSubject + "\n\n" +
"--" + boundary + "\n" +
"Content-Type: text/plain; charset=" + string('"') + "UTF-8" + string('"') + "\n" +
"MIME-Version: 1.0\n" +
"Content-Transfer-Encoding: 7bit\n\n" +
content + "\n\n" +
"--" + boundary + "\n" +
"Content-Type: " + "image/jpg" + "; name=" + string('"') + fileName + string('"') + " \n" +
"MIME-Version: 1.0\n" +
"Content-Transfer-Encoding: base64\n" +
"Content-Disposition: attachment; filename=" + string('"') + fileName + string('"') + " \n\n" +
chunkSplit(attachmentData, 76, "\n") +
"--" + boundary + "--")
message.Raw = base64.URLEncoding.EncodeToString(messageBody)
_, err := service.Users.Messages.Send("me", &message).Do()
if err != nil {
log.Fatalf("Email sending failed: %v", err)
} else {
log.Println("Email message sent")
}
Related
Requirement
Hey there, I am trying to show the comments on an HTML page. Although it prints every comment in the terminal but does not show every comment on an HTML page. Instead, it shows only the first row.
Code info
After finding the data of comments in the database, I printed every comment in the terminal using the multidimensional array but it was difficult to write x and y each time. That's why I created two for-loops and a third loop to assign numbers to the values variable.
mdArray := [][]string{
values[0:4],
values[4:8],
// x:y
}
I used mdArray in the CommentData{} structure to assign values to the variables. After printing the data, it shows every comment that is inserted but when I return this function to be executed on the HTML page, it only prints the first row.
Code
type CommentData struct {
Fname string
Lname string
Email string
Message string
Date string
Time string
}
func SendData(w http.ResponseWriter, r *http.Request) CommentData {
note := models.AddComment{
Fname: r.FormValue("fname"),
Lname: r.FormValue("lname"),
Email: r.FormValue("email"),
Message: r.FormValue("message"),
}
dt := time.Now()
date := dt.Format("02-Jan-2006")
time := dt.Format("15:04:05")
values1 := [6]string{note.Fname, note.Lname, note.Email, note.Message, date, time}
_, match := database.FindAccount(note.Fname, note.Lname, note.Email)
if match {
database.InsertComment(values1)
values2 := database.FindComment(note.Fname, note.Lname, note.Email)
var store1, store2 []int
for i := 0; i <= len(values2); i++ {
if i%6 == 0 {
store1 = append(store1, i)
}
}
for j := 6; j <= len(values2); j++ {
if j%6 == 0 {
store2 = append(store2, j)
}
}
for i := 0; i < len(store2); i++ {
mdArray := [][]string{
values2[store1[i]:store2[i]],
}
// fmt.Println(mdArray[0][3])
hello := CommentData{
Fname: mdArray[0][0],
Lname: mdArray[0][1],
Email: mdArray[0][2],
Message: mdArray[0][3],
Date: "On " + mdArray[0][4],
Time: "At " + mdArray[0][5],
}
fmt.Println(hello)
return hello
}
} else {
http.Redirect(w, r, "/login", http.StatusFound)
}
return CommentData{}
}
func FirstBlog(w http.ResponseWriter, r *http.Request) error {
if r.Method == "GET" {
return FirstBlogTmpl.Execute(w, nil)
} else if r.Method == "POST" {
Newsletter(w, r)
hello := SendData(w, r)
return FirstBlogTmpl.Execute(w, hello)
}
return nil
}
HTML
<div>
{{.}}
</div>
Put resultatos in a slice:
…
var hellos []CommentData
for i := 0; i < len(store2); i++ {
mdArray := [][]string{
values2[store1[i]:store2[i]],
}
// fmt.Println(mdArray[0][3])
hello := CommentData{
Fname: mdArray[0][0],
Lname: mdArray[0][1],
Email: mdArray[0][2],
Message: mdArray[0][3],
Date: "On " + mdArray[0][4],
Time: "At " + mdArray[0][5],
}
fmt.Println(hello)
hellos = append(hellos, hello)
}
return hellos
…
Change function return type return type []CommentData.
Range over the resultatos in the template
{{range .}}
<div>
{{.}}
</div>
I have this scraper library, I would like to change my user agent if the first user agent returns error, but this code doesnt work, if first user agent doesnt work, I have send the 2nd attempt but this will never finish since onHTML is not triggered:
package scraper
import (
"net/http"
"github.com/davecgh/go-spew/spew"
"github.com/gocolly/colly"
)
const (
fbUserAgent = "ua 1"
userAgent = "ua 2"
)
type ScrapeResult struct {
Title string
Description string
SiteName string
URL string
Images []string
}
func Scrape2(url string) (*ScrapeResult, error) {
var (
res *ScrapeResult
scrapeErr error
done = make(chan bool, 1)
c = colly.NewCollector()
)
c.OnError(func(r *colly.Response, err error) {
if ua := r.Request.Headers.Get("User-Agent"); ua == fbUserAgent {
c.Request(
"GET",
url,
nil,
nil,
http.Header{
"User-Agent": []string{userAgent},
"Accept": []string{"*/*"},
},
)
} else {
scrapeErr = err
done <- true
}
})
c.OnHTML("html", func(e *colly.HTMLElement) {
spew.Dump("ON HTML")
res = &ScrapeResult{URL: url}
res.Title = FindTitle(e)
res.Description = FindDescription(e)
res.SiteName = FindSiteName(e)
res.Images = FindImages(e)
done <- true
})
c.Request(
"GET",
url,
nil,
nil,
http.Header{
"User-Agent": []string{fbUserAgent},
"Accept": []string{"*/*"}, // * / *
"Accept-Language": []string{"en-GB,en-US;q=0.9,en;q=0.8"},
"Accept-Encoding": []string{"gzip, deflate, br"},
"Connection": []string{"keep-alive"},
"sec-ch-ua": []string{` Not A;Brand";v="99", "Chromium";v="90", "Google Chrome";v="90`},
},
)
<- done
return res, scrapeErr
}
func FindTitle(e *colly.HTMLElement) string {
if content := e.ChildAttr(`meta[property="og:title"]`, "content"); len(content) > 0 {
return content
}
return ""
}
func FindDescription(e *colly.HTMLElement) string {
if content := e.ChildAttr(`meta[property="og:description"]`, "content"); len(content) > 0 {
return content
}
return ""
}
func FindSiteName(e *colly.HTMLElement) string {
if content := e.ChildAttr(`meta[property="og:site_name"]`, "content"); len(content) > 0 {
return content
}
return ""
}
func FindImages(e *colly.HTMLElement) []string {
images := make([]string, 0)
if content := e.ChildAttr(`meta[property="og:image"]`, "content"); len(content) > 0 {
images = append(images, content)
}
return images
}
How can I make colly request for the 2nd time and trigger the onHTML? thank you
You can set the property collector.CheckHead = true
What this does is ensures that you do a GetHEAD operation first to check connection issues and if it fails - there is a retry.
you will need /v2 of gocolly to have this feature included.
https://github.com/gocolly/colly/blob/master/colly.go#L110
Environment
Golang
GmailAPI(google.golang.org/api/gmail/v1)
Problem
When I tried to send a mail to a certain thread, with In-Reply-To and References headers set, the mail failed to be attached to the thread.
My Question: What do you think is the cause of this problem?
Mail Header
Here I attach the header of the mail.
Received: from 487462728342 named unknown by gmailapi.google.com with HTTPREST; Thu, 19 Nov 2020 22:34:45 -0800
In-Reply-To: <CAMZJxVNx_GfH3mryUr78WD_N6ayz__H3=x7zTocOrWzbo+kcnA#mail.gmail.com>
References: <CAMZJxVM8vEEeuMzzMUbVirk-Mx554putqNs=HyxikWGPSy-Ayw#mail.gmail.com> <CAMZJxVNx_GfH3mryUr78WD_N6ayz__H3=x7zTocOrWzbo+kcnA#mail.gmail.com>
X-Tracking-Address: hogehoge#gmail.com
Subject: Re: Hello
To: moriwm77#gmail.com
CC: hogehoge#gmail.com
BCC:
Content-Type: multipart/mixed; boundary="===============1605854084=="
Date: Thu, 19 Nov 2020 22:34:45 -0800
Message-Id: <CAMZJxVPX-6dZyNy2cSu3eAUMUZ4p-ai62uOH6=MZY7LYq6O5Lg#mail.gmail.com>
From: moriwm77#gmail.com
--===============1605854084==
Content-Type: text/plain; charset="utf-8"
If...
--===============1605854084==--
Code
This is a adapter function to call GSuite's gmail API.
// SendMail send gmail.
func (api *GSuiteAPI) SendMail(token *oauth2.Token, m Message) error {
ctx := context.Background()
srv, err := gmail.NewService(ctx, option.WithTokenSource(api.config.TokenSource(ctx, token)))
if err != nil {
return handleError(err)
}
boundary := "===============" + strconv.FormatInt(time.Now().Unix(), 10) + "=="
sMail := []string{}
for key, value := range m.Headers {
sMail = append(sMail, key+": "+value)
}
sMail = append(sMail, "Subject: =?UTF-8?B?"+base64.StdEncoding.EncodeToString([]byte(m.Subject))+"?=")
sMail = append(sMail, "To: "+strings.Join(m.To, ","))
sMail = append(sMail, "CC: "+strings.Join(m.CC, ","))
sMail = append(sMail, "BCC: "+strings.Join(m.BCC, ","))
sMail = append(sMail, "Content-Type: multipart/mixed; boundary=\""+boundary+"\"")
// Boby
sMail = append(sMail, "")
sMail = append(sMail, "--"+boundary)
sMail = append(sMail, "Content-Type: text/plain; charset=\"utf-8\"")
sMail = append(sMail, "")
sMail = append(sMail, m.Body)
log.Info().Msgf("%+v", strings.Join(sMail, "\r\n"))
// Attachment
for _, attachment := range m.Attachments {
name := strings.Replace(attachment.Name, "\"", "", -1)
sMail = append(sMail, "")
sMail = append(sMail, "--"+boundary)
sMail = append(sMail, "Content-Type: application/octet-stream; name=\""+name+"\"")
sMail = append(sMail, "Content-Disposition: attachment; filename=\""+name+"\"")
sMail = append(sMail, "Content-Transfer-Encoding: Base64")
sMail = append(sMail, "")
sMail = append(sMail, attachment.Data)
}
source := strings.Join(sMail, "\r\n")
var message gmail.Message
if api.mailConfig.Enable {
message.Raw = base64.
StdEncoding.
EncodeToString([]byte(source))
_, err = srv.Users.Messages.Send("me", &message).Do()
if err != nil {
return handleError(err)
}
}
return nil
}
"In-Reply-To" Header and "References" Header are included in "m.Headers".
Like
m.Headers["In-Reply-To"]
// => <CAMZJxVNx_GfH3mryUr78WD_N6ayz__H3=x7zTocOrWzbo+kcnA#mail.gmail.com>
m.Headers["References"]
// => <CAMZJxVM8vEEeuMzzMUbVirk-Mx554putqNs=HyxikWGPSy-Ayw#mail.gmail.com> <CAMZJxVNx_GfH3mryUr78WD_N6ayz__H3=x7zTocOrWzbo+kcnA#mail.gmail.com>
According to the Managing Threads with Gmail API documentation, if you want to add a draft or a message to a thread, you will have to fulfill these conditions:
The requested threadId must be specified on the Message or Draft.Message you supply with your request.
The References and In-Reply-To headers must be set in compliance with the RFC 2822 standard.
The Subject headers must match.
Therefore, the threadId must be specified in order to successfully insert the message.
Reference
Gmail API Managing Threads.
How can I get HTML.title in c.OnResponse - or is there a better alternative to fill the Struct with url/title/content
At the end I need to fill the below struct and post it to elasticsearch.
type WebPage struct {
Url string `json:"url"`
Title string `json:"title"`
Content string `json:"content"`
}
// Print the response
c.OnResponse(func(r *colly.Response) {
pageCount++
log.Println(r.Headers)
webpage := WebPage{
Url: r.Ctx.Get("url"), //- can be put in ctx c.OnRequest, and r.Ctx.Get("url")
Title: "my title", //string(r.title), // Where to get this?
Content: string(r.Body), //string(r.Body) - can be done in c.OnResponse
}
enc := json.NewEncoder(os.Stdout)
enc.SetIndent("", " ")
enc.Encode(webpage) // SEND it to elasticsearch
log.Println(fmt.Sprintf("%d DONE Visiting : %s", pageCount, urlVisited))
})
I can get title in method like below, however Ctx is not available so I cant put the "title" value in Ctx. Other options?
c.OnHTML("title", func(e *colly.HTMLElement) {
fmt.Println(e.Text)
e.Ctx.Put("title", e.Text) // NOT ACCESSIBLE!
})
Logs
2020/05/07 17:42:37 7 DONE Visiting : https://www.coursera.org/learn/build-portfolio-website-html-css
{
"url": "https://www.coursera.org/learn/build-portfolio-website-html-css",
"title": "my page title",
"content": "page html body bla "
}
2020/05/07 17:42:37 8 DONE Visiting : https://www.coursera.org/learn/build-portfolio-website-html-css
{
"url": "https://www.coursera.org/browse/social-sciences",
"title": "my page title",
"content": "page html body bla "
}
I created a global variable of that struct and kept filling it in different methods
Not sure if this is the best way.
fun main(){
....
webpage := WebPage{} //Is this a right way to declare a mutable struct?
c.OnRequest(func(r *colly.Request) { // url
webpage.Url = r.URL.String() // Is this the right way to mutate?
})
c.OnResponse(func(r *colly.Response) { //get body
pageCount++
log.Println(fmt.Sprintf("%d DONE Visiting : %s", pageCount, webpage.Url))
})
c.OnHTML("head title", func(e *colly.HTMLElement) { // Title
webpage.Title = e.Text
})
c.OnHTML("html body", func(e *colly.HTMLElement) { // Body / content
webpage.Content = e.Text // Can url title body be misrepresented in multithread scenario?
})
c.OnHTML("a[href]", func(e *colly.HTMLElement) { // href , callback
link := e.Attr("href")
e.Request.Visit(link)
})
c.OnError(func(r *colly.Response, err error) { // Set error handler
log.Println("Request URL:", r.Request.URL, "failed with response:", r, "\nError:", err)
})
c.OnScraped(func(r *colly.Response) { // DONE
enc := json.NewEncoder(os.Stdout)
enc.SetIndent("", " ")
enc.Encode(webpage)
})
I based my work on Espresso's answer...
I just get the whole html in the function and then query the head and body in it so everything is nice and encapsulated into one "c.OnHTML"
c2.OnHTML("html", func(html *colly.HTMLElement) {
slug := strings.Split(html.Request.URL.String(), "/")[4]
title := ""
descr := ""
h1 := ""
html.ForEach("head", func(_ int, head *colly.HTMLElement) {
title += head.ChildText("title")
head.ForEach("meta", func(_ int, meta *colly.HTMLElement) {
if meta.Attr("name") == "description" {
descr += meta.Attr("content")
}
})
})
html.ForEach("h1", func(_ int, h1El *colly.HTMLElement) {
h1 += h1El.Text
})
//Now you can do stuff with your elements from head and body
})
I'm trying to run a go program using LiteIDE x22 but I get the message
C:/Go/bin/go.exe build [C:/Users/admins/Desktop/desktp/worm_scraper-master]
worm_scraper.go:11:2: cannot find package "github.com/codegangsta/cli" in any of:
C:\Go\src\pkg\github.com\codegangsta\cli (from $GOROOT)
C:\users\admins\gostuff\src\github.com\codegangsta\cli (from $GOPATH)
worm_scraper.go:12:2: cannot find package "github.com/puerkitobio/goquery" in any of:
C:\Go\src\pkg\github.com\puerkitobio\goquery (from $GOROOT)
C:\users\admins\gostuff\src\github.com\puerkitobio\goquery (from $GOPATH)
Error: process exited with code 1.
I think this means it's looking for it on my harddrive instead of online right? (btw I'm pretty clueless about programming just trying to something some else wrote)
how to I get it to access the web?
here's the full code
package main
import (
"errors"
"fmt"
"os"
"os/exec"
"regexp"
"strings"
"github.com/codegangsta/cli"
"github.com/puerkitobio/goquery"
)
const (
MainSite = "https://parahumans.wordpress.com/"
TableOfContents = "https://parahumans.wordpress.com/table-of-contents/"
)
type Arc struct {
Identifier string
Title string
Chapters []Chapter
}
type Chapter struct {
Title string
Url string
Tags []string
Paragraphs []Paragraph
Retries int
DatePosted string
}
type Paragraph string
// Format the paragraph
func (p *Paragraph) Format() {
s := string(*p)
// Handle emphasis
s = strings.Replace(s, "<em>", "*", -1)
s = strings.Replace(s, "</em>", "*", -1)
s = strings.Replace(s, "<i>", "*", -1)
s = strings.Replace(s, "</i>", "*", -1)
// Handle bold
s = strings.Replace(s, "<strong>", "**", -1)
s = strings.Replace(s, "</strong>", "**", -1)
s = strings.Replace(s, "<b>", "**", -1)
s = strings.Replace(s, "</b>", "**", -1)
// Remove new lines
s = strings.Replace(s, "\n", "", -1)
// And random double spaces
s = strings.Replace(s, ". ", ". ", -1)
*p = Paragraph(s)
}
// Return the Arc that the given chapter belongs to
func (ch *Chapter) WhichArc(arcList []*Arc) (*Arc, error) {
for _, arc := range arcList {
if strings.Replace(ch.Title[:2], ".", "", -1) == arc.Identifier {
return arc, nil
}
}
return &Arc{}, errors.New("chapter '" + ch.Title + "' did not match any Arcs")
}
// Parse a chapter and return it
func (ch *Chapter) Parse(done chan bool) {
if ch.Retries > 3 {
panic("Chapter url '" + ch.Url + "' has timed out too many times")
}
// Get the chapter
if strings.HasPrefix(ch.Url, "http") == false {
// Make sure it begins with http so goquery can use it
ch.Url = "https://" + ch.Url
}
doc, err := goquery.NewDocument(ch.Url)
if err != nil {
// Try again
ch.Retries++
go ch.Parse(done)
return
}
// Set the new chapter title
ch.Title = doc.Find("h1.entry-title").Text()
// Set the tags
doc.Find(".entry-meta a[rel=tag]").Each(func(_ int, s *goquery.Selection) {
ch.Tags = append(ch.Tags, s.Text())
if len(ch.Tags) == 0 {
ch.Tags = append(ch.Tags, "NONE")
}
})
// Get the date it was posted
ch.DatePosted = doc.Find("time.entry-date").Text()
// Now we'll get all the paragraphs
doc.Find(".entry-content > p").Each(func(_ int, s *goquery.Selection) {
// Check for the previous/next links
if len(s.Find("a").Nodes) > 0 {
return
}
// Get the paragraph HTML
st, _ := s.Html()
para := Paragraph("")
// Get the actual paragraph
if val, exists := s.Attr("padding-left"); exists && val == "30px" {
// Check to see if the paragraph is special (indented) block
para = Paragraph(" " + st)
} else if val, exists := s.Attr("text-align"); exists && val == "center" {
// Otherwise check to see if it's a separator paragraph
para = Paragraph("----------")
} else {
// It's just a normal paragraph in this case
para = Paragraph(st)
}
// And add the paragraph to the chapter
para.Format()
ch.Paragraphs = append(ch.Paragraphs, para)
})
// Finally, let's signal a success
done <- true
}
// Return a slice of Arcs extracted from the table of contents
func ParseArcs(s string) []*Arc {
arcs := []*Arc{}
r, _ := regexp.Compile(`[0-9]+`)
for _, line := range strings.Split(s, "\n") {
line = strings.TrimSpace(line)
if strings.HasPrefix(line, "Arc") {
arcs = append(arcs, &Arc{
Identifier: r.FindString(line),
Title: line,
})
} else if strings.HasPrefix(line, "Epilogue") {
arcs = append(arcs, &Arc{
Identifier: "E",
Title: line,
})
}
}
return arcs
}
func main() {
// Define the app
app := cli.NewApp()
app.Name = "Worm Scraper"
app.Usage = "A tool to let you get an updated EPUB copy of the serial web novel Worm, by Wildbow"
app.Version = "1.0"
app.Author = "Benjamin Harris"
// Define the application flags
app.Flags = []cli.Flag{
cli.BoolFlag{"pdf", "Save the book as a PDF instead of an EPUB, if possible"},
cli.BoolFlag{"with-link", "Include a link to the chapter online"},
cli.BoolFlag{"with-tags", "Include the tags each chapter was posted under"},
cli.BoolFlag{"with-date", "Include the date each chapter was posted"},
}
// The heart of the application
app.Action = func(context *cli.Context) {
// Starting the program
fmt.Println("Starting to scrape Worm")
// Get the list of arcs from the table of contents
fmt.Println("Gathering links from table of contents...")
contents, err := goquery.NewDocument(TableOfContents)
if err != nil {
panic("Failed to get the table of contents! " + err.Error())
}
// Parse the arcs
arcs := ParseArcs(contents.Find(".entry-content").Text())
// Now get the links for the arc chapters
contents.Find(".entry-content a:not([class*=share-icon])").Each(func(_ int, s *goquery.Selection) {
ch := Chapter{}
ch.Title = strings.Replace(strings.TrimSpace(s.Text()), "\n", "", -1)
ch.Url, _ = s.Attr("href")
if ch.Title == "" {
return
}
arc, _ := ch.WhichArc(arcs)
arc.Chapters = append(arc.Chapters, ch)
})
// Manually add missing chapter in Epilogue
c := Chapter{
Title: "E.2",
Url: "https://parahumans.wordpress.com/2013/11/05/teneral-e-2/",
}
a, _ := c.WhichArc(arcs)
a.Chapters = append(a.Chapters, c)
copy(a.Chapters[1+1:], a.Chapters[1:])
a.Chapters[1] = c
// Now start getting the chapters
chapters := 0
done := make(chan bool)
for _, arc := range arcs {
for i, _ := range arc.Chapters {
chapters++
go arc.Chapters[i].Parse(done)
}
}
fmt.Println("Starting to parse", chapters, "chapters")
fmt.Print("Finished: ")
totalChapters := chapters
for {
select {
case <-done:
chapters--
fmt.Print(totalChapters-chapters, ",")
}
if chapters == 0 {
// We're done with all the chapters
close(done)
fmt.Println()
break
}
}
// And let's write all this stuff to a file now
fmt.Println("Saving results to file...")
f, err := os.OpenFile("Worm.md", os.O_RDWR|os.O_CREATE|os.O_EXCL, 0666)
if err != nil {
panic(err)
}
defer f.Close()
// Define pagebreak
PageBreak := "\n\n"
// Write the cover
f.WriteString("# Worm\n\n")
f.WriteString("By Wildbow\n\n")
f.WriteString("Website: " + MainSite)
// Now loop through the Arcs
for _, arc := range arcs {
f.WriteString(PageBreak + "# " + arc.Title)
for _, chapter := range arc.Chapters {
f.WriteString("\n\n")
f.WriteString("## " + chapter.Title + "\n\n")
if context.Bool("with-tags") {
f.WriteString("**Tags:** " + strings.Join(chapter.Tags, ", ") + " ")
}
if context.Bool("with-date") {
f.WriteString("**Date:** " + chapter.DatePosted + " ")
}
if context.Bool("with-link") {
f.WriteString("**Link:** " + chapter.Url + " ")
}
f.WriteString("\n\n")
// Now save the chapter's paragraphs
for _, p := range chapter.Paragraphs {
f.WriteString(string(p) + "\n\n")
}
}
}
// Now let's try to convert the markdown file into an ebook format (epub, pdf)
fmt.Print("Attempting to convert Markdown file... ")
cmdText := []string{"-S", "Worm.md", "--epub-chapter-level", "2", "-o", "Worm.epub"}
if context.Bool("pdf") {
cmdText = []string{"Worm.md", "-o", "Worm.pdf"}
PageBreak = `<div style="page-break-after: always;"></div>`
}
cmd := exec.Command("pandoc", cmdText...)
err = cmd.Run()
if err != nil {
fmt.Println("Conversion failed! Make sure you've installed Pandoc (http://johnmacfarlane.net/pandoc/installing.html) if you want to convert the generated Markdown file to an ebook compatible format. In the meantime, we've left you the Markdown file.")
} else {
_ = os.Remove("Worm.md")
fmt.Println("Completed!")
}
}
// Run the application
app.Run(os.Args)
}
oh also would it be possible to modify it to output as .txt or .mobi? if not I'll just convert using Calibre. Thanks in advance.
Oh if it matters I'm using windows 7 64-bit
The go compiler doesn't import the libraries directly from the internet but it does know how to fetch them for you. When you import something like github.com/codegangsta/cli it doesn't look for it on that URL but instead it looks for it on your GOPATH/src folder.
The go get command can fetch the library for you in it's URL and download it to your GOPATH.
If you have already setup your GOPATH (if not, read How to Write Go Code) then before running your code run the command go get library for the go tool to download it for you. In your example you should run the following commands:
go get github.com/codegangsta/cli
go get github.com/puerkitobio/goquery
That would download the libraries to GOPATH/src/github.com/codegangsta/cli and GOPATH/src/github.com/puerkitobio/goquery respectively.