From 2e6db82301388056189699a086dbc0fbb6d38631 Mon Sep 17 00:00:00 2001
From: bill bittner
Date: Wed, 24 May 2017 11:07:43 -0700
Subject: [PATCH] replacing live-coded spee.ch with @billbitt js version
---
.gitignore | 2 +-
LBRY.class.php | 85 ------------------
LICENSE | 21 -----
README.md | 46 +++++++++-
helpers/lbryApi.js | 173 ++++++++++++++++++++++++++++++++++++
helpers/queueApi.js | 22 +++++
image.php | 39 --------
index.php | 45 ----------
package.json | 33 +++++++
public/favicon.ico | Bin 0 -> 180 bytes
public/fourOhfour.html | 14 +++
public/index.html | 106 ++++++++++++++++++++++
public/noClaims.html | 14 +++
public/publishingClaim.html | 14 +++
publish.php | 14 ---
routes/api-routes.js | 29 ++++++
routes/html-routes.js | 80 +++++++++++++++++
server.js | 20 +++++
worker.js | 31 +++++++
19 files changed, 579 insertions(+), 209 deletions(-)
delete mode 100644 LBRY.class.php
delete mode 100644 LICENSE
create mode 100644 helpers/lbryApi.js
create mode 100644 helpers/queueApi.js
delete mode 100644 image.php
delete mode 100644 index.php
create mode 100644 package.json
create mode 100644 public/favicon.ico
create mode 100644 public/fourOhfour.html
create mode 100644 public/index.html
create mode 100644 public/noClaims.html
create mode 100644 public/publishingClaim.html
delete mode 100644 publish.php
create mode 100644 routes/api-routes.js
create mode 100644 routes/html-routes.js
create mode 100644 server.js
create mode 100644 worker.js
diff --git a/.gitignore b/.gitignore
index 9f11b755..3c3629e6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1 @@
-.idea/
+node_modules
diff --git a/LBRY.class.php b/LBRY.class.php
deleted file mode 100644
index 7fdc413a..00000000
--- a/LBRY.class.php
+++ /dev/null
@@ -1,85 +0,0 @@
- $function, 'params' => $params]));
- curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
-
- $serverOutput = curl_exec($ch);
- curl_close($ch);
-
- if ($serverOutput)
- {
- $responseData = json_decode($serverOutput, true);
- if (isset($responseData['error']))
- {
- throw new Exception($responseData['error']['message'] ?? 'Something unknown went wrong');
- }
- if (isset($responseData['result']))
- {
- return $responseData['result'];
- }
- throw new Exception('Received unknown response format.');
- }
- }
-
- public static function publishPublicClaim($name, $tmpFileName)
- {
- $filePath = '/home/lbry/publishes/newupload-' . random_int(1, PHP_INT_MAX);
-
- move_uploaded_file($tmpFileName, $filePath);
-
- $apiResult = LBRY::api('publish', [
- 'name' => $name,
- 'bid' => 1,
- 'file_path' => $filePath,
- 'description' => 'An image published from spee.ch',
- 'author' => 'https://spee.ch',
- 'language' => 'en',
- 'license' => 'Public Domain',
- 'nsfw' => 0,
- 'title' => 'Image published from spee.ch'
- ]);
-
- return isset($apiResult['claim_id']);
- }
-
- public static function findTopPublicFreeClaim($name)
- {
- $claims = LBRY::api('claim_list', ['name' => $name]);
-
- if (!$claims || !isset($claims['claims']))
- {
- return null;
- }
-
- $freePublicClaims = array_filter($claims['claims'], function($claim) {
- $metadata = json_decode($claim['value'], true);
- return
- //TODO: Expand these checks AND verify it is an image claim!
- ($metadata['license'] == "Public Domain" || stripos($metadata['license'], 'Creative Commons') !== false) &&
- !isset($metadata['fee']);
- });
-
- if (count($freePublicClaims) > 1)
- {
- usort($freePublicClaims, function($claimA, $claimB) {
- if ($claimA['amount'] == $claimB['amount'])
- {
- return $claimA['height'] < $claimB['height'] ? -1 : 1;
- }
- return $claimA['amount'] > $claimB['amount'] ? -1 : 1;
- });
- }
-
- return reset($freePublicClaims);
- }
-}
diff --git a/LICENSE b/LICENSE
deleted file mode 100644
index 77909b45..00000000
--- a/LICENSE
+++ /dev/null
@@ -1,21 +0,0 @@
-MIT License
-
-Copyright (c) 2017 LBRY
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
diff --git a/README.md b/README.md
index c92edc8d..f68d744c 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,44 @@
-# spee.ch
-spee.ch is a simple but powerful image hosting service on top of the LBRY protocol.
+# spee.ch (js)
+this is a clone of spee.ch with a javascript backend
-It was built in real-time on March 29th, 2017.
+## how to use this repository
+* start lbry
+ * install the [`lbry`](https://github.com/lbryio/lbry) daemon
+ * start the `lbry` daemon
+* start RabbitMQ (this will handle the queue for background processing)
+ * install & run [RabbitMQ](https://www.rabbitmq.com/#getstarted)
+* clone this repo
+* run `npm install`
+* from your terminal, run `npm start`
+ * to run hot, run `nodemon server.js`
+* start at least one worker by running `node worker.js`
+* visit [localhost:3000](http://localhost:3000) and enjoy!
-You can watch the video and learn more from [https://spee.ch](https://spee.ch)
+## site navigation
+
+* spee.ch.
+ * To publish a file, navigate to the homepage.
+* spee.ch/
+ * To view the file with the largest bid at a claim.
+ * E.g. spee.ch/doitlive.
+* spee.ch/< the name of the claim >/< the claim_id >
+ * To view a specific file at a claim
+ * E.g. spee.ch/doitlive/c496c8c55ed79816fec39e36a78645aa4458edb5
+* spee.ch//all
+ * To view a batch of files at a claim
+ * E.g. spee.ch/doitlive/all
+
+## development to-do's
+* discover/explore functionality for home page
+* display a list of claims at /:name/all
+* fetching: a temporary page while the request is being made (with a loading bar?)
+* publishing: a temporary page while the request is being handled by the server (with a loading bar?)
+* publishing: after publishing, take the user to a temp page with the tx info and status of the tx (then redirect when the tx is complete)
+
+## API
+
+Note: these are being used for testing durring spee.ch development and may not be maintained
+
+* A GET request to spee.ch/claim_list/
+ * Will return the claim_list for the claim in json format.
+ * E.g. spee.ch/claim_list/doitlive
diff --git a/helpers/lbryApi.js b/helpers/lbryApi.js
new file mode 100644
index 00000000..e1ca554e
--- /dev/null
+++ b/helpers/lbryApi.js
@@ -0,0 +1,173 @@
+// load dependencies
+var path = require('path');
+var axios = require('axios');
+
+// helper function to filter an array of claims for only free, public claims
+function filterForFreePublicClaims(claimsListArray){
+ //console.log(">> filterForFreePublicClaims, claimsListArray:", claimsListArray);
+ if (!claimsListArray) {
+ return null;
+ };
+ var freePublicClaims = claimsListArray.filter(function(claim){
+ return (((claim.value.stream.metadata.license.indexOf('Public Domain') != -1) || (claim.value.stream.metadata.license.indexOf('Creative Commons') != -1)) &&
+ (!claim.value.stream.metadata.fee || claim.value.stream.metadata.fee === 0));
+ });
+ return freePublicClaims;
+}
+// helper function to decide if a claim is free and public
+function isFreePublicClaim(claim){
+ console.log(">> isFreePublicClaim, claim:", claim);
+ if ((claim.value.stream.metadata.license === 'Public Domain' || claim.value.stream.metadata.license === 'Creative Commons') &&
+ (!claim.value.stream.metadata.fee || claim.value.stream.metadata.fee.amount === 0)) {
+ return true;
+ } else {
+ return false;
+ }
+}
+// helper function to order a set of claims
+function orderTopClaims(claimsListArray){
+ console.log(">> orderTopClaims, claimsListArray:");
+ claimsListArray.sort(function(claimA, claimB){
+ if (claimA.amount === claimB.amount){
+ return (claimA.height > claimB.height);
+ } else {
+ return (claimA.amount < claimB.amount);
+ }
+ })
+ return claimsListArray;
+}
+
+module.exports = {
+ publishClaim: function(publishObject){
+ axios.post('http://localhost:5279/lbryapi', publishObject)
+ .then(function (response) {
+ // receive resonse from LBRY
+ // if successfull, (1) delete file (2) send response to the client
+ console.log(">> 'publish' success...");
+ console.log(">> 'publish' response.data:", response.data);
+ console.log(" [x] Done");
+ // return the claim we got
+ //res.status(200).send(JSON.stringify({msg: "you succsessfully published!", txData: response.data}));
+ }).catch(function(error){
+ // receive response from LBRY
+ // if not successfull, (1) delete file and (2) send response to the client
+ console.log(">> 'publish' error.response.data:", error.response.data);
+ console.log(" [x] Done");
+ //res.status(500).send(JSON.stringify({msg: "your file was not published", err: error.response.data.error.message}));
+ })
+ },
+ serveClaimBasedOnNameOnly: function(claimName, res){
+ // make a call to the daemon to get the claims list
+ axios.post('http://localhost:5279/lbryapi', {
+ method: "claim_list",
+ params: {
+ name: claimName
+ }
+ }
+ ).then(function (response) {
+ console.log(">> Claim_list success");
+ console.log(">> Number of claims:", response.data.result.claims.length)
+ // return early if no claims were found
+ if (response.data.result.claims.length === 0){
+ res.status(200).sendFile(path.join(__dirname, '../public', 'noClaims.html'));
+ return;
+ }
+ // filter the claims to return free, public claims
+ var freePublicClaims = filterForFreePublicClaims(response.data.result.claims);
+ // return early if no free, public claims were found
+ if (!freePublicClaims || (freePublicClaims.length === 0)){
+ res.status(200).sendFile(path.join(__dirname, '../public', 'noClaims.html'));
+ return;
+ }
+ // order the claims
+ var orderedPublcClaims = orderTopClaims(freePublicClaims);
+ // create the uri for the first (selected) claim
+ console.log(">> ordered free public claims", orderedPublcClaims);
+ var freePublicClaimUri = "lbry://" + orderedPublcClaims[0].name + "#" + orderedPublcClaims[0].claim_id;
+ console.log(">> your free public claim uri:", freePublicClaimUri);
+ // fetch the image to display
+ axios.post('http://localhost:5279/lbryapi', {
+ method: "get",
+ params: {
+ uri: freePublicClaimUri
+ }
+ }
+ ).then(function (getResponse) {
+ console.log(">> 'get claim' success...");
+ console.log(">> response data:", getResponse.data);
+ console.log(">> dl path =", getResponse.data.result.download_path)
+ // return the claim we got
+ res.status(200).sendFile(getResponse.data.result.download_path);
+ }).catch(function(getError){
+ console.log(">> /c/ 'get' error:", getError.response.data);
+ res.status(500).send(JSON.stringify({msg: "An error occurred while fetching the free, public claim by URI.", err: getError.response.data.error.message}));
+ })
+ }).catch(function(error){
+ console.log(">> /c/ error:", error.response.data);
+ res.status(500).send(JSON.stringify({msg: "An error occurred while getting the claim list.", err: error.response.data.error.message}));
+ })
+ },
+ serveClaimBasedOnUri: function(uri, res){
+ /*
+ to do: need to pass the URI through a test (use 'resolve') to see if it is free and public. Right now it is jumping straight to 'get'ing and serving the asset.
+ */
+ console.log(">> your uri:", uri);
+ // fetch the image to display
+ axios.post('http://localhost:5279/lbryapi', { // to do: abstract this code to a function that can be shared
+ method: "get",
+ params: {
+ uri: uri
+ }
+ }
+ ).then(function (getResponse) {
+ console.log(">> 'get claim' success...");
+ console.log(">> response data:", getResponse.data);
+ console.log(">> dl path =", getResponse.data.result.download_path)
+ /*
+ to do: make sure the file has completed downloading before serving back the file
+ */
+ // return the claim we got
+ res.status(200).sendFile(getResponse.data.result.download_path);
+
+ /* delete the file after a certain amount of time? */
+
+ }).catch(function(error){
+ console.log(">> /c/ 'get' error:", error.response.data);
+ res.status(500).send(JSON.stringify({msg: "an error occurred", err: error.response.data.error.message}));
+ })
+ },
+ serveAllClaims: function(claimName, res){
+ // make a call to the daemon to get the claims list
+ axios.post('http://localhost:5279/lbryapi', {
+ method: "claim_list",
+ params: {
+ name: claimName
+ }
+ }
+ ).then(function (response) {
+ console.log(">> Claim_list success");
+ console.log(">> Number of claims:", response.data.result.claims.length)
+ // return early if no claims were found
+ if (response.data.result.claims.length === 0){
+ res.status(200).sendFile(path.join(__dirname, '../public', 'noClaims.html'));
+ return;
+ }
+ // filter the claims to return free, public claims
+ var freePublicClaims = filterForFreePublicClaims(response.data.result.claims);
+ // return early if no free, public claims were found
+ if (!freePublicClaims || (freePublicClaims.length === 0)){
+ res.status(200).sendFile(path.join(__dirname, '../public', 'noClaims.html'));
+ return;
+ }
+ console.log(">> Number of free public claims:", freePublicClaims.length);
+ // order the claims
+ var orderedPublicClaims = orderTopClaims(freePublicClaims);
+ // serve the response
+ res.status(200).send(orderedPublicClaims); //to do: rather than returning json, serve a page of all these claims
+ }).catch(function(error){
+ console.log(">> /c/ error:", error.response.data);
+ // serve the response
+ res.status(500).send(JSON.stringify({msg: "An error occurred while finding the claim list.", err: error.response.data.error.message}));
+ })
+ }
+}
diff --git a/helpers/queueApi.js b/helpers/queueApi.js
new file mode 100644
index 00000000..6f8c5fef
--- /dev/null
+++ b/helpers/queueApi.js
@@ -0,0 +1,22 @@
+// require amqp library
+var amqp = require('amqplib/callback_api');
+
+module.exports = {
+ addNewTaskToQueue: function(task){
+ // connect to RabbitMQ server
+ amqp.connect('amqp://localhost', function(err, conn) {
+ // create a channel
+ conn.createChannel(function(err, ch) {
+ var q = 'task_queue2'; // declaring a que is idempotent (it will only be created if it doesnt already exist)
+ var msg = task || "request received with no task!";
+ // declare a queue
+ ch.assertQueue(q, {durable: true});
+ // publish a message to the queue
+ ch.sendToQueue(q, new Buffer.from(msg), {persistent: true});
+ console.log(` [x] Sent '${msg}' to ${q}`);
+ });
+ // close the connection and exit
+ setTimeout(function() {conn.close() }, 500);
+ });
+ }
+}
\ No newline at end of file
diff --git a/image.php b/image.php
deleted file mode 100644
index ca67139b..00000000
--- a/image.php
+++ /dev/null
@@ -1,39 +0,0 @@
- $name, 'claim_id' => $claim['claim_id']]);
-
- if (isset($getResult['completed']) && $getResult['completed'] && isset($getResult['download_path']))
- {
- $path = $getResult['download_path'];
-// $validType = isset($getResult['content_type']) && in_array($getResult['content_type'], ['image/jpeg', 'image/png']);
- header('Content-type: image/jpeg');
- header('Content-length: ' . filesize($path));
- readfile($getResult['download_path']);
- }
- elseif (isset($getResult['written_bytes']))
- {
- echo 'This image is on it\'s way... ';
- echo 'Received: ' . $getResult['written_bytes'] . " / " . $getResult['total_bytes'] . ' bytes';
- }
- else
- {
- echo 'There seems to be a valid claim, but are having trouble retrieving the content.';
- }
-}
-elseif (isset($_GET['new']) && $_GET['new'])
-{
- echo 'Your image is on the way. It can take a few minutes to reach the blockchain and be public. You can refresh this page to check the progress.';
-}
-else
-{
- echo 'No valid claim for this name. Make one!';
- include './publish.php';
-}
-
-exit(0);
diff --git a/index.php b/index.php
deleted file mode 100644
index 6446539d..00000000
--- a/index.php
+++ /dev/null
@@ -1,45 +0,0 @@
-Something went wrong publishing your content. We are only somewhat sorry.
';
- }
- exit(0);
-}
-?>
-
-
spee.ch
-
spee.ch is a single-serving site that reads and publishes images to and from the LBRY blockchain.