diff --git a/ZiniGo/main.go b/ZiniGo/main.go index 0a3f69f..440c245 100644 --- a/ZiniGo/main.go +++ b/ZiniGo/main.go @@ -5,10 +5,9 @@ import ( "encoding/json" "flag" "fmt" - wkhtml "github.com/SebastiaanKlippert/go-wkhtmltopdf" "github.com/icza/gox/stringsx" "github.com/pdfcpu/pdfcpu/pkg/api" - "github.com/playwright-community/playwright-go" + "github.com/pdfcpu/pdfcpu/pkg/pdfcpu" "github.com/tidwall/gjson" "github.com/tidwall/sjson" "io/ioutil" @@ -16,7 +15,6 @@ import ( "math/rand" "net/http" "os" - "os/exec" "path/filepath" "regexp" "strconv" @@ -40,8 +38,8 @@ func main() { passwordPtr := flag.String("p", "", "Zinio Password") chromePtr := flag.String("c", "google-chrome", "Chrome executable") zinioHostPtr := flag.String("e", "api-sec.ziniopro.com", "Zinio Host (Excluding port and URI Scheme). Known: `api-sec`, `api-sec-2`") - exportUsingWKHTML := flag.String("wkhtml", "false", "Use WKHTML instead of Chrome to generate PDF (false by default)") - exportUsingPlaywright := flag.String("playwright", "false", "Use Playwright Chromium instead of local Chrome to generate PDF (false by default)") + //exportUsingWKHTML := flag.String("wkhtml", "false", "Use WKHTML instead of Chrome to generate PDF (false by default)") + //exportUsingPlaywright := flag.String("playwright", "false", "Use Playwright Chromium instead of local Chrome to generate PDF (false by default)") deviceFingerprintPtr := flag.String("fingerprint", "abcd123", "This devices fingerprint - presented to Zinio API") flag.Parse() @@ -97,6 +95,7 @@ func main() { issues := GetLibrary(loginToken, *zinioHostPtr) for i := range issues { issueList := issues[i] + //fmt.Println("Found " + strconv.Itoa(len(issues.Data)) + " issues in library.") fmt.Println("Loading HTML template") @@ -116,6 +115,13 @@ func main() { } for _, issue := range issueList.Data { + + issueDetails := GetIssueDetails(loginToken, issue.Id) + isLegacy := issueDetails.Data.Issue.Publication.LegacyContent == 1 + passwordToUse := issueDetails.Data.Issue.Hash + if isLegacy { + passwordToUse = issueDetails.Data.Issue.LegacyHash + } fmt.Println(issue) issuePath := filepath.Join(issueDirectory, strconv.Itoa(issue.Id)) @@ -133,19 +139,19 @@ func main() { pages := GetPages(loginToken, issue, *zinioHostPtr) var filenames []string - + conf := pdfcpu.NewAESConfiguration(passwordToUse, passwordToUse, 256) for i := 0; i < len(pages.Data); i++ { - if len(pages.Data[i].Source) == 0 { + if len(pages.Data[i].Src) == 0 { fmt.Println("No Download URL for page ", i) continue } - fmt.Println("Source ", pages.Data[i].Source) + fmt.Println("Source ", pages.Data[i].Src) fmt.Println("ID: ", pages.Data[i].Index) pathString := issuePath + "_" + pages.Data[i].Index - resp, err := http.Get(pages.Data[i].Source) + resp, err := http.Get(pages.Data[i].Src) // handle the error if there is one if err != nil { panic(err) @@ -157,75 +163,15 @@ func main() { if err != nil { panic(err) } - // show the HTML code as a string %s - - htmldata := strings.Replace(string(template), "SVG_PATH", string(html), -1) - - //convert to pdf - - if strings.ToLower(*exportUsingPlaywright) == "true" { - pw, err := playwright.Run() - if err != nil { - log.Fatalf("could not start playwright: %v", err) - } - browser, err := pw.Chromium.Launch() - if err != nil { - log.Fatalf("could not launch browser: %v", err) - } - context, err := browser.NewContext() - - page, err := context.NewPage() - _ = page.SetContent(htmldata, playwright.PageSetContentOptions{WaitUntil: playwright.WaitUntilStateNetworkidle}) - _, err = page.PDF(playwright.PagePdfOptions{ - Path: playwright.String(pathString + ".pdf"), - }) - - } else if strings.ToLower(*exportUsingWKHTML) == "true" { - - pdfg, err := wkhtml.NewPDFGenerator() - if err != nil { - return - } - pdfg.MarginBottom.Set(0) - pdfg.MarginTop.Set(0) - pdfg.MarginLeft.Set(0) - pdfg.MarginRight.Set(0) - pdfg.NoOutline.Set(true) - //pdfg.PageSize.Set(wkhtml.PageSizeCustom) - pdfg.AddPage(wkhtml.NewPageReader(strings.NewReader(htmldata))) - - // Create PDF document in internal buffer - err = pdfg.Create() - if err != nil { - log.Fatal(err) - } - - //Your Pdf Name - err = pdfg.WriteFile(pathString + ".pdf") - if err != nil { - log.Fatal(err) - } - - } else { - //write html file, embedding svg - ioutil.WriteFile(pathString+".html", []byte(htmldata), 0644) - cmd := exec.Command(*chromePtr, "--headless", "--disable-gpu", "--print-to-pdf="+pathString+".pdf", "--no-margins", pathString+".html") - fmt.Println(cmd.Args) - err := cmd.Run() - if err != nil { - fmt.Printf("cmd.Run() failed with %s\n. You should retry this page.", err) - } - - } - - _ = os.Remove(pathString + ".html") - _ = os.Remove(pathString + ".svg") + ioutil.WriteFile(pathString+".pdf", html, 0644) + api.DecryptFile(pathString+".pdf", "", conf) filenames = append(filenames, pathString+".pdf") } for i := range filenames { //remove last page + err = retry(5, 2*time.Second, func() (err error) { err = api.RemovePagesFile(filenames[i], "", []string{"2-"}, nil) if err != nil { @@ -249,27 +195,43 @@ func main() { } -func GetPages(userToken LoginResponse, issue LibraryData, endpoint string) Response { - +func GetIssueDetails(userToken LoginResponse, id int) IssueDetails { client := &http.Client{} - //req, _ := http.NewRequest("GET", "https://"+endpoint+"/newsstand/v2/newsstands/134/issues/"+strconv.Itoa(issue.Id)+"/content/pages?format=svg&application_id=9901&css_content=true&user_id="+userToken.Data.User.UserIDString, nil) - req, _ := http.NewRequest("GET", "https://zinio.com/api/newsstand/newsstands/101/issues/"+strconv.Itoa(issue.Id)+"/content/pages?format=svg&application_id=9901&css_content=true&user_id="+userToken.Data.User.UserIDString, nil) + req, _ := http.NewRequest("GET", "https://www.zinio.com/api/reader/content?issue_id="+strconv.Itoa(id)+"&newsstand_id=101&user_id="+userToken.Data.User.UserIDString+"&format=pdf&project=99&logger=null", nil) req.Header.Add("Content-Type", "application/json") - //req.AddCookie(&http.Cookie{Name: "zwrt", Value: userToken.Data.AccessToken}) for _, cookie := range userToken.Data.Cookies { req.AddCookie(cookie) } - //req.Header.Add("Authorization", "bearer "+token) - //req.Header.Add("Authorization", token) + resp, _ := client.Do(req) + data, _ := ioutil.ReadAll(resp.Body) + + responseType := IssueDetails{} + + _ = json.Unmarshal([]byte(data), &responseType) + + return responseType +} + +func GetPages(userToken LoginResponse, issue LibraryData, endpoint string) AutoGenerated { + + client := &http.Client{} + + req, _ := http.NewRequest("GET", "https://zinio.com/api/newsstand/newsstands/101/issues/"+strconv.Itoa(issue.Id)+"/content/pages?format=pdf&application_id=9901&css_content=true&user_id="+userToken.Data.User.UserIDString, nil) + + req.Header.Add("Content-Type", "application/json") + for _, cookie := range userToken.Data.Cookies { + req.AddCookie(cookie) + + } resp, _ := client.Do(req) data, _ := ioutil.ReadAll(resp.Body) - responseType := Response{} + responseType := AutoGenerated{} _ = json.Unmarshal([]byte(data), &responseType) @@ -417,10 +379,13 @@ type LibraryData struct { Id int `json:"id"` Name string `json:"name"` Publication Publication `json:"publication"` + LegacyHash string `json:"legacy_hash"` + Hash string `json:"hash"` } type Publication struct { - Name string `json:"name"` + Name string `json:"name"` + LegacyContent int `json:"legacy_content"` } // https://stackoverflow.com/questions/47606761/repeat-code-if-an-error-occured @@ -517,3 +482,291 @@ func randSeq(n int) string { } return string(b) } + +type IssueDetails struct { + Data struct { + Issue struct { + ID int `json:"id"` + PublicationID int `json:"publication_id"` + Name string `json:"name"` + InternalName string `json:"internal_name"` + Issn string `json:"issn"` + VolumeNo string `json:"volume_no"` + IssueNo string `json:"issue_no"` + SequenceNo string `json:"sequence_no"` + Description string `json:"description"` + Slug string `json:"slug"` + Code string `json:"code"` + CoverImage string `json:"cover_image"` + CoverDate string `json:"cover_date"` + PublishDate string `json:"publish_date"` + PublishEffectiveDate string `json:"publish_effective_date"` + RemoteIdentifier string `json:"remote_identifier"` + LegacyIssueID int `json:"legacy_issue_id"` + LegacyIdentifier string `json:"legacy_identifier"` + Status int `json:"status"` + CreatedAt string `json:"created_at"` + ModifiedAt string `json:"modified_at"` + CreatedBy int `json:"created_by"` + ModifiedBy int `json:"modified_by"` + FilePath string `json:"file_path"` + Type int `json:"type"` + Preview int `json:"preview"` + HasXML int `json:"has_xml"` + HasPdf int `json:"has_pdf"` + Binding int `json:"binding"` + FulfilmentCode string `json:"fulfilment_code"` + AllowPrinting int `json:"allow_printing"` + Watermark int `json:"watermark"` + CoverPrice int `json:"cover_price"` + CoverCurrency string `json:"cover_currency"` + NoOfPages int `json:"no_of_pages"` + Classification int `json:"classification"` + ContentRevision any `json:"content_revision"` + Publication struct { + ID int `json:"id"` + Name string `json:"name"` + Frequency string `json:"frequency"` + LegacyIdentifier string `json:"legacy_identifier"` + InternalName string `json:"internal_name"` + Description string `json:"description"` + PublisherID int `json:"publisher_id"` + ContentRating int `json:"content_rating"` + RemoteIdentifier string `json:"remote_identifier"` + CreatedAt string `json:"created_at"` + ModifiedAt string `json:"modified_at"` + CreatedBy int `json:"created_by"` + ModifiedBy int64 `json:"modified_by"` + SiteID int `json:"site_id"` + Status int `json:"status"` + Type int `json:"type"` + NoOfIssues int `json:"no_of_issues"` + Logo string `json:"logo"` + AllowXML int `json:"allow_xml"` + AllowPdf int `json:"allow_pdf"` + LatinName string `json:"latin_name"` + Tagline string `json:"tagline"` + ParentID any `json:"parent_id"` + SeoKeywords any `json:"seo_keywords"` + SearchKeywords string `json:"search_keywords"` + Issn string `json:"issn"` + CirculationType int `json:"circulation_type"` + Binding int `json:"binding"` + Watermark int `json:"watermark"` + AllowPrinting int `json:"allow_printing"` + AllowIntegratedFulfilment int `json:"allow_integrated_fulfilment"` + FulfilmentHouseID string `json:"fulfilment_house_id"` + FulfilmentCode string `json:"fulfilment_code"` + DefaultCurrencyCode string `json:"default_currency_code"` + Slug string `json:"slug"` + SourceType int `json:"source_type"` + LegacyContent int `json:"legacy_content"` + HasToGetPreviousIssuePrice any `json:"has_to_get_previous_issue_price"` + Country struct { + Format string `json:"format"` + Name string `json:"name"` + Code string `json:"code"` + } `json:"country"` + Locale struct { + Format string `json:"format"` + Name string `json:"name"` + Code string `json:"code"` + } `json:"locale"` + Language struct { + Format string `json:"format"` + Name string `json:"name"` + Code string `json:"code"` + } `json:"language"` + Publisher struct { + ID string `json:"id"` + Name string `json:"name"` + InternalName string `json:"internal_name"` + Description any `json:"description"` + Slug any `json:"slug"` + Code any `json:"code"` + Logo any `json:"logo"` + RemoteIdentifier any `json:"remote_identifier"` + Status int `json:"status"` + Country struct { + Format string `json:"format"` + Name string `json:"name"` + Code string `json:"code"` + } `json:"country"` + } `json:"publisher"` + } `json:"publication"` + Metadata []any `json:"metadata"` + Product struct { + ID int `json:"id"` + Code string `json:"code"` + Type int `json:"type"` + Rrp any `json:"rrp"` + RrpCurrencyCode any `json:"rrp_currency_code"` + Name string `json:"name"` + Description any `json:"description"` + RemoteIdentifier any `json:"remote_identifier"` + LegacyID any `json:"legacy_id"` + ProjectID any `json:"project_id"` + PublicationID int `json:"publication_id"` + IssueID int `json:"issue_id"` + CatalogID any `json:"catalog_id"` + TermAmount any `json:"term_amount"` + TermUnits any `json:"term_units"` + SaleTier int `json:"sale_tier"` + Credits any `json:"credits"` + Duration any `json:"duration"` + Status int `json:"status"` + AvailabilityDate string `json:"availability_date"` + CreatedAt string `json:"created_at"` + ModifiedAt string `json:"modified_at"` + } `json:"product"` + Prices []struct { + ID int `json:"id"` + PublicationID int `json:"publication_id"` + ProjectID int `json:"project_id"` + NewsstandID any `json:"newsstand_id"` + ProductType int `json:"product_type"` + DefaultProduct int `json:"default_product"` + ProductID any `json:"product_id"` + IssueID any `json:"issue_id"` + SaleTier any `json:"sale_tier"` + IssueType any `json:"issue_type"` + Country any `json:"country"` + Price any `json:"price"` + TaxInclusivePrice any `json:"tax_inclusive_price"` + PriceAfterCoupon any `json:"price_after_coupon"` + TaxInclusivePriceAfterCoupon any `json:"tax_inclusive_price_after_coupon"` + Coupon any `json:"coupon"` + Currency any `json:"currency"` + Tier string `json:"tier"` + DistributionPlatform int `json:"distribution_platform"` + ReferencePriceID int `json:"reference_price_id"` + TaxRate any `json:"tax_rate"` + ExchangeRate any `json:"exchange_rate"` + CreatedBy int `json:"created_by"` + ModifiedBy any `json:"modified_by"` + CreatedAt string `json:"created_at"` + ModifiedAt string `json:"modified_at"` + Sku string `json:"sku"` + DisplayPrice any `json:"display_price"` + TaxInclusiveDisplayPrice any `json:"tax_inclusive_display_price"` + DisplayPriceAfterCoupon any `json:"display_price_after_coupon"` + TaxInclusiveDisplayPriceAfterCoupon any `json:"tax_inclusive_display_price_after_coupon"` + DisplayCurrency any `json:"display_currency"` + } `json:"prices"` + AllowXML int `json:"allow_xml"` + AllowPdf int `json:"allow_pdf"` + LegacyHash string `json:"legacy_hash"` + Hash string `json:"hash"` + } `json:"issue"` + Pages []struct { + Index string `json:"index"` + FolioNumber string `json:"folio_number"` + Src string `json:"src"` + Checksum string `json:"checksum"` + Preview string `json:"preview"` + Type string `json:"type"` + PdfTag string `json:"pdf_tag"` + Mine string `json:"mine"` + Width int `json:"width"` + Height int `json:"height"` + Links []any `json:"links"` + Thumbnail string `json:"thumbnail"` + } `json:"pages"` + Stories []struct { + ID int `json:"id"` + UniqueStoryID string `json:"unique_story_id"` + Title string `json:"title"` + SubTitle string `json:"sub_title"` + StrapLine string `json:"strap_line"` + Intro string `json:"intro"` + Authors []any `json:"authors"` + Preview int `json:"preview"` + Priority int `json:"priority"` + Tag string `json:"tag"` + StartingPage string `json:"starting_page"` + PageRange string `json:"page_range"` + ModifiedDate time.Time `json:"modified_date"` + Template struct { + ID int `json:"id"` + Code string `json:"code"` + Name string `json:"name"` + CSS []string `json:"css"` + Fonts []string `json:"fonts"` + Images []any `json:"images"` + } `json:"template"` + Content string `json:"content"` + FeatureImage string `json:"feature_image"` + Images []any `json:"images"` + Section struct { + ID int `json:"id"` + Name string `json:"name"` + Description string `json:"description"` + Priority int `json:"priority"` + } `json:"section"` + Excerpt string `json:"excerpt"` + ManualTags []any `json:"manual_tags"` + RelatedObjects struct { + Image []any `json:"image"` + } `json:"related_objects"` + } `json:"stories"` + Ads []struct { + ID string `json:"id"` + UniqueStoryID string `json:"unique_story_id"` + AdvertiseCode string `json:"advertise_code"` + RelativeObjectID string `json:"relative_object_id"` + RelativeRemoteID string `json:"relative_remote_id"` + Priority string `json:"priority"` + Position string `json:"position"` + Folio string `json:"folio"` + Version string `json:"version"` + RemoteID string `json:"remote_id"` + AdsType string `json:"ads_type"` + IssuePdfImageAds struct { + LocalFileURL string `json:"local_file_url"` + Portrait string `json:"portrait"` + Landscape string `json:"landscape"` + ClickthroughURL string `json:"clickthrough_url"` + Checksum string `json:"checksum"` + } `json:"issue_pdf_image_ads"` + Created string `json:"created"` + CreatedBy string `json:"created_by"` + } `json:"ads"` + Entitlement struct { + DeliveryID any `json:"delivery_id"` + LegacyIdentifier any `json:"legacy_identifier"` + Type int `json:"type"` + ID int64 `json:"id"` + UserID int64 `json:"user_id"` + DeviceID int `json:"device_id"` + IssueID int `json:"issue_id"` + PublicationID int `json:"publication_id"` + ProjectID int `json:"project_id"` + OrderID int `json:"order_id"` + LabelID int64 `json:"label_id"` + Status int `json:"status"` + Archived bool `json:"archived"` + ArchivedStatus int `json:"archived_status"` + CreatedAt time.Time `json:"created_at"` + ArchivedAt any `json:"archived_at"` + ModifiedAt time.Time `json:"modified_at"` + } `json:"entitlement"` + } `json:"data"` +} + +type AutoGenerated struct { + Status bool `json:"status"` + Data []struct { + Index string `json:"index"` + FolioNumber string `json:"folio_number"` + Src string `json:"src"` + Checksum string `json:"checksum"` + Preview string `json:"preview"` + Type string `json:"type"` + PdfTag string `json:"pdf_tag"` + Mine string `json:"mine"` + Width int `json:"width"` + Height int `json:"height"` + Links []any `json:"links"` + Thumbnail string `json:"thumbnail"` + } `json:"data"` +} diff --git a/built/Zinigo_Linux_AMD64 b/built/Zinigo_Linux_AMD64 index 7f4be4f..32d3137 100755 Binary files a/built/Zinigo_Linux_AMD64 and b/built/Zinigo_Linux_AMD64 differ diff --git a/built/Zinigo_Macos_AppleSilicon b/built/Zinigo_Macos_AppleSilicon index 7f4be4f..0056329 100755 Binary files a/built/Zinigo_Macos_AppleSilicon and b/built/Zinigo_Macos_AppleSilicon differ diff --git a/built/Zinigo_Macos_Intel b/built/Zinigo_Macos_Intel index 7f4be4f..3bbfa0f 100755 Binary files a/built/Zinigo_Macos_Intel and b/built/Zinigo_Macos_Intel differ diff --git a/built/Zinigo_Windows_x64.exe b/built/Zinigo_Windows_x64.exe index ad3908a..c5730ad 100755 Binary files a/built/Zinigo_Windows_x64.exe and b/built/Zinigo_Windows_x64.exe differ diff --git a/built/Zinigo_Windows_x86.exe b/built/Zinigo_Windows_x86.exe index cc79070..4b1c85a 100755 Binary files a/built/Zinigo_Windows_x86.exe and b/built/Zinigo_Windows_x86.exe differ