mirror of https://github.com/writeas/writefreely
Merge pull request #463 from writefreely/wm-fix
Web Monetization fixes + exclusive contentpull/474/head
commit
e7245536f3
@ -0,0 +1,160 @@ |
||||
/* |
||||
* Copyright © 2020-2021 A Bunch Tell LLC. |
||||
* |
||||
* This file is part of WriteFreely. |
||||
* |
||||
* WriteFreely is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU Affero General Public License, included |
||||
* in the LICENSE file in this source code package. |
||||
*/ |
||||
|
||||
package writefreely |
||||
|
||||
import ( |
||||
"bytes" |
||||
"fmt" |
||||
"github.com/gorilla/mux" |
||||
"github.com/writeas/impart" |
||||
"github.com/writeas/web-core/log" |
||||
"io/ioutil" |
||||
"net/http" |
||||
"net/url" |
||||
"os" |
||||
"strings" |
||||
) |
||||
|
||||
func displayMonetization(monetization, alias string) string { |
||||
if monetization == "" { |
||||
return "" |
||||
} |
||||
|
||||
ptrURL, err := url.Parse(strings.Replace(monetization, "$", "https://", 1)) |
||||
if err == nil { |
||||
if strings.HasSuffix(ptrURL.Host, ".xrptipbot.com") { |
||||
// xrp tip bot doesn't support stream receipts, so return plain pointer
|
||||
return monetization |
||||
} |
||||
} |
||||
|
||||
u := os.Getenv("PAYMENT_HOST") |
||||
if u == "" { |
||||
return "$webmonetization.org/api/receipts/" + url.PathEscape(monetization) |
||||
} |
||||
u += "/" + alias |
||||
return u |
||||
} |
||||
|
||||
func handleSPSPEndpoint(app *App, w http.ResponseWriter, r *http.Request) error { |
||||
idStr := r.FormValue("id") |
||||
id, err := url.QueryUnescape(idStr) |
||||
if err != nil { |
||||
log.Error("Unable to unescape: %s", err) |
||||
return err |
||||
} |
||||
|
||||
var c *Collection |
||||
if strings.IndexRune(id, '.') > 0 && app.cfg.App.SingleUser { |
||||
c, err = app.db.GetCollectionByID(1) |
||||
} else { |
||||
c, err = app.db.GetCollection(id) |
||||
} |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
pointer := c.Monetization |
||||
if pointer == "" { |
||||
err := impart.HTTPError{http.StatusNotFound, "No monetization pointer."} |
||||
return err |
||||
} |
||||
|
||||
fmt.Fprintf(w, pointer) |
||||
return nil |
||||
} |
||||
|
||||
func handleGetSplitContent(app *App, w http.ResponseWriter, r *http.Request) error { |
||||
var collID int64 |
||||
var collLookupID string |
||||
var coll *Collection |
||||
var err error |
||||
vars := mux.Vars(r) |
||||
if collAlias := vars["alias"]; collAlias != "" { |
||||
// Fetch collection information, since an alias is provided
|
||||
coll, err = app.db.GetCollection(collAlias) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
collID = coll.ID |
||||
collLookupID = coll.Alias |
||||
} |
||||
|
||||
p, err := app.db.GetPost(vars["post"], collID) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
receipt := r.FormValue("receipt") |
||||
if receipt == "" { |
||||
return impart.HTTPError{http.StatusBadRequest, "No `receipt` given."} |
||||
} |
||||
err = verifyReceipt(receipt, collLookupID) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
d := struct { |
||||
Content string `json:"body"` |
||||
HTMLContent string `json:"html_body"` |
||||
}{} |
||||
|
||||
if exc := strings.Index(p.Content, shortCodePaid); exc > -1 { |
||||
baseURL := "" |
||||
if coll != nil { |
||||
baseURL = coll.CanonicalURL() |
||||
} |
||||
|
||||
d.Content = p.Content[exc+len(shortCodePaid):] |
||||
d.HTMLContent = applyMarkdown([]byte(d.Content), baseURL, app.cfg) |
||||
} |
||||
|
||||
return impart.WriteSuccess(w, d, http.StatusOK) |
||||
} |
||||
|
||||
func verifyReceipt(receipt, id string) error { |
||||
receiptsHost := os.Getenv("RECEIPTS_HOST") |
||||
if receiptsHost == "" { |
||||
receiptsHost = "https://webmonetization.org/api/receipts/verify?id=" + id |
||||
} else { |
||||
receiptsHost = fmt.Sprintf("%s/receipts?id=%s", receiptsHost, id) |
||||
} |
||||
|
||||
log.Info("Verifying receipt %s at %s", receipt, receiptsHost) |
||||
r, err := http.NewRequest("POST", receiptsHost, bytes.NewBufferString(receipt)) |
||||
if err != nil { |
||||
log.Error("Unable to create new request to %s: %s", receiptsHost, err) |
||||
return err |
||||
} |
||||
|
||||
resp, err := http.DefaultClient.Do(r) |
||||
if err != nil { |
||||
log.Error("Unable to Do() request to %s: %s", receiptsHost, err) |
||||
return err |
||||
} |
||||
if resp != nil && resp.Body != nil { |
||||
defer resp.Body.Close() |
||||
} |
||||
|
||||
body, err := ioutil.ReadAll(resp.Body) |
||||
if err != nil { |
||||
log.Error("Unable to read %s response body: %s", receiptsHost, err) |
||||
return err |
||||
} |
||||
log.Info("Status : %s", resp.Status) |
||||
log.Info("Response: %s", body) |
||||
|
||||
if resp.StatusCode != http.StatusOK { |
||||
log.Error("Bad response from %s:\nStatus: %d\n%s", receiptsHost, resp.StatusCode, string(body)) |
||||
return impart.HTTPError{resp.StatusCode, string(body)} |
||||
} |
||||
return nil |
||||
} |
After Width: | Height: | Size: 3.6 KiB |
@ -0,0 +1,94 @@ |
||||
/* |
||||
* Copyright © 2020-2021 A Bunch Tell LLC. |
||||
* |
||||
* This file is part of WriteFreely. |
||||
* |
||||
* WriteFreely is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU Affero General Public License, included |
||||
* in the LICENSE file in this source code package. |
||||
*/ |
||||
|
||||
let unlockingSplitContent = false; |
||||
let unlockedSplitContent = false; |
||||
let pendingSplitContent = false; |
||||
|
||||
function showWMPaywall($content, $split) { |
||||
let $readmoreSell = document.createElement('div') |
||||
$readmoreSell.id = 'readmore-sell'; |
||||
$content.insertAdjacentElement('beforeend', $readmoreSell); |
||||
$readmoreSell.appendChild($split); |
||||
$readmoreSell.insertAdjacentHTML("beforeend", '\n\n<p class="font sans">For <strong>$5 per month</strong>, you can read this and other great writing across our site and other websites that support Web Monetization.</p>') |
||||
$readmoreSell.insertAdjacentHTML("beforeend", '\n\n<p class="font sans"><a href="https://coil.com/signup?ref=writefreely" class="btn cta" target="coil">Get started</a> <a href="https://coil.com/?ref=writefreely" class="btn cta secondary">Learn more</a></p>') |
||||
} |
||||
|
||||
function initMonetization() { |
||||
let $content = document.querySelector('.e-content') |
||||
let $post = document.getElementById('post-body') |
||||
let $split = $post.querySelector('.split') |
||||
if (document.monetization === undefined || $split == null) { |
||||
if ($split) { |
||||
showWMPaywall($content, $split) |
||||
} |
||||
return |
||||
} |
||||
|
||||
document.monetization.addEventListener('monetizationstop', function(event) { |
||||
if (pendingSplitContent) { |
||||
// We've seen the 'pending' activity, so we can assume things will work
|
||||
document.monetization.removeEventListener('monetizationstop', progressHandler) |
||||
return |
||||
} |
||||
|
||||
// We're getting 'stop' without ever starting, so display the paywall.
|
||||
showWMPaywall($content, $split) |
||||
}); |
||||
|
||||
document.monetization.addEventListener('monetizationpending', function (event) { |
||||
pendingSplitContent = true |
||||
}) |
||||
|
||||
let progressHandler = function(event) { |
||||
if (unlockedSplitContent) { |
||||
document.monetization.removeEventListener('monetizationprogress', progressHandler) |
||||
return |
||||
} |
||||
if (!unlockingSplitContent && !unlockedSplitContent) { |
||||
unlockingSplitContent = true |
||||
getSplitContent(event.detail.receipt, function (status, data) { |
||||
unlockingSplitContent = false |
||||
if (status == 200) { |
||||
$split.textContent = "Your subscriber perks start here." |
||||
$split.insertAdjacentHTML("afterend", "\n\n"+data.data.html_body) |
||||
} else { |
||||
$split.textContent = "Something went wrong while unlocking subscriber content." |
||||
} |
||||
unlockedSplitContent = true |
||||
}) |
||||
} |
||||
} |
||||
|
||||
function getSplitContent(receipt, callback) { |
||||
let params = "receipt="+encodeURIComponent(receipt) |
||||
|
||||
let http = new XMLHttpRequest(); |
||||
http.open("POST", "/api/collections/" + window.collAlias + "/posts/" + window.postSlug + "/splitcontent", true); |
||||
|
||||
// Send the proper header information along with the request
|
||||
http.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); |
||||
|
||||
http.onreadystatechange = function () { |
||||
if (http.readyState == 4) { |
||||
callback(http.status, JSON.parse(http.responseText)); |
||||
} |
||||
} |
||||
http.send(params); |
||||
} |
||||
|
||||
document.monetization.addEventListener('monetizationstart', function() { |
||||
if (!unlockedSplitContent) { |
||||
$split.textContent = "Unlocking subscriber content..." |
||||
} |
||||
document.monetization.removeEventListener('monetizationstart', progressHandler) |
||||
}); |
||||
document.monetization.addEventListener('monetizationprogress', progressHandler); |
||||
} |
Loading…
Reference in new issue