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