+ |
+ |
+
+
+
+
+
+
+ |
+
diff --git a/.gitignore b/.gitignore index 91be6e46..10b9903f 100644 --- a/.gitignore +++ b/.gitignore @@ -3,8 +3,10 @@ /data/config.php /data/writeable/* /web/css/* +/web/js/*.gz /log /web/zohoverify nbproject - +composer .ht* +/vendor/ diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..c6ccaeb7 --- /dev/null +++ b/LICENSE @@ -0,0 +1,15 @@ +The MIT License (MIT) + +Copyright (c) 2015-2016 LBRY Inc + +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/autoload.php b/autoload.php index e79cdbe1..7729339e 100644 --- a/autoload.php +++ b/autoload.php @@ -11,7 +11,7 @@ class Autoloader } $class = strtolower($class); - $path = isset(static::$classes[$class]) ? static::$classes[$class] : false; + $path = static::$classes[$class] ?? false; if ($path) { diff --git a/composer.json b/composer.json new file mode 100644 index 00000000..c4c0daee --- /dev/null +++ b/composer.json @@ -0,0 +1,7 @@ +{ + "name": "lbry/lbry.io", + "require": { + "php": ">=7.0", + "ext-curl": "*" + } +} \ No newline at end of file diff --git a/composer.lock b/composer.lock new file mode 100644 index 00000000..51f22aa5 --- /dev/null +++ b/composer.lock @@ -0,0 +1,21 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", + "This file is @generated automatically" + ], + "hash": "37d65c68b462975921781df87dbfb52a", + "content-hash": "a9b88596dfa02452b6d63461accdf07f", + "packages": [], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": ">=7.0", + "ext-curl": "*" + }, + "platform-dev": [] +} diff --git a/posts/bio/alex-grintsvayg.md b/content/bio/alex-grintsvayg.md similarity index 69% rename from posts/bio/alex-grintsvayg.md rename to content/bio/alex-grintsvayg.md index b0cb6f6e..838913d0 100644 --- a/posts/bio/alex-grintsvayg.md +++ b/content/bio/alex-grintsvayg.md @@ -7,4 +7,4 @@ One of Alex's job titles is Wizard, so named because he can seemingly understand Alex designs and manages scalable infrastructure solutions for SaaS firms, and is leveraging that experience to ensure LBRY's architecture is rock-solid. -Alex is one of three dual-degree graduate from RPI on this team, receiving degrees in Computer Science and Psychology. He is also an alumni of Stuyvesant High School. +Alex is one of three dual-degree graduates from RPI on this team, receiving degrees in Computer Science and Psychology. He is also an alumnus of Stuyvesant High School. diff --git a/posts/bio/alex-liebowitz.md b/content/bio/alex-liebowitz.md similarity index 100% rename from posts/bio/alex-liebowitz.md rename to content/bio/alex-liebowitz.md diff --git a/posts/bio/alex-tabarrok.md b/content/bio/alex-tabarrok.md similarity index 100% rename from posts/bio/alex-tabarrok.md rename to content/bio/alex-tabarrok.md diff --git a/posts/bio/jack-robison.md b/content/bio/jack-robison.md similarity index 100% rename from posts/bio/jack-robison.md rename to content/bio/jack-robison.md diff --git a/posts/bio/jeremy-kauffman.md b/content/bio/jeremy-kauffman.md similarity index 100% rename from posts/bio/jeremy-kauffman.md rename to content/bio/jeremy-kauffman.md diff --git a/posts/bio/jimmy-kiselak.md b/content/bio/jimmy-kiselak.md similarity index 100% rename from posts/bio/jimmy-kiselak.md rename to content/bio/jimmy-kiselak.md diff --git a/posts/bio/job-evers-meltzer.md b/content/bio/job-evers-meltzer.md similarity index 100% rename from posts/bio/job-evers-meltzer.md rename to content/bio/job-evers-meltzer.md diff --git a/posts/bio/josh-finer.md b/content/bio/josh-finer.md similarity index 100% rename from posts/bio/josh-finer.md rename to content/bio/josh-finer.md diff --git a/posts/bio/michael-huemer.md b/content/bio/michael-huemer.md similarity index 100% rename from posts/bio/michael-huemer.md rename to content/bio/michael-huemer.md diff --git a/posts/bio/michael-zargham.md b/content/bio/michael-zargham.md similarity index 100% rename from posts/bio/michael-zargham.md rename to content/bio/michael-zargham.md diff --git a/posts/bio/mike-vine.md b/content/bio/mike-vine.md similarity index 100% rename from posts/bio/mike-vine.md rename to content/bio/mike-vine.md diff --git a/content/bio/ray-carballada.md b/content/bio/ray-carballada.md new file mode 100644 index 00000000..bf6357e6 --- /dev/null +++ b/content/bio/ray-carballada.md @@ -0,0 +1,10 @@ +--- +name: Ray Carballada +role: Media Advisor +--- +Ray Carballada is a creative, accomplished senior business leader in the converging worlds of content: advertising, television, motion pictures, social, and online media. + +As former CEO of Alkemy X, Ray led its transformation from a small production company into a fully integrated creative content company with offices in Philadelphia and New York. Clients include global ad agencies such as BBDO, Gray, and 360i; Fortune 500 brands including Campbell Soup, Samsung, Pepsi and BMW; TV networks including Discovery, Food Network, and HBO; and motion picture studios including Sony and the Weinstein Company. + +Alkemy X made the INC 5000 list of the fastest growing companies in the U.S. six times since 2008, and received the “Creative Economy Award for Distinction" from the Arts and Business Council of Greater Philadelphia, as well as 60 Addy and Emmy awards, among others. Ray received his BS in Photography from Rochester Institute of Technology and a Masters in Business Administration from Bucknell University. + diff --git a/posts/bio/reilly-smith.md b/content/bio/reilly-smith.md similarity index 100% rename from posts/bio/reilly-smith.md rename to content/bio/reilly-smith.md diff --git a/posts/bio/stephan-kinsella.md b/content/bio/stephan-kinsella.md similarity index 100% rename from posts/bio/stephan-kinsella.md rename to content/bio/stephan-kinsella.md diff --git a/posts/bounty/bittorrent-support.md b/content/bounty/bittorrent-support.md similarity index 100% rename from posts/bounty/bittorrent-support.md rename to content/bounty/bittorrent-support.md diff --git a/posts/bounty/custom-project.md b/content/bounty/custom-project.md similarity index 100% rename from posts/bounty/custom-project.md rename to content/bounty/custom-project.md diff --git a/posts/bounty/lbry-club.md b/content/bounty/lbry-club.md similarity index 100% rename from posts/bounty/lbry-club.md rename to content/bounty/lbry-club.md diff --git a/posts/bounty/modified-block-explorer.md b/content/bounty/modified-block-explorer.md similarity index 98% rename from posts/bounty/modified-block-explorer.md rename to content/bounty/modified-block-explorer.md index 93804f70..d44972f8 100644 --- a/posts/bounty/modified-block-explorer.md +++ b/content/bounty/modified-block-explorer.md @@ -2,7 +2,7 @@ category: code title: Modified Block Explorer award: 1500 -status: available +status: complete date: '2016-07-01' --- diff --git a/posts/bounty/pkg-installer-for-osx.md b/content/bounty/pkg-installer-for-osx.md similarity index 100% rename from posts/bounty/pkg-installer-for-osx.md rename to content/bounty/pkg-installer-for-osx.md diff --git a/posts/bounty/pr-for-lbry.md b/content/bounty/pr-for-lbry.md similarity index 100% rename from posts/bounty/pr-for-lbry.md rename to content/bounty/pr-for-lbry.md diff --git a/posts/bounty/publish-open-content.md b/content/bounty/publish-open-content.md similarity index 100% rename from posts/bounty/publish-open-content.md rename to content/bounty/publish-open-content.md diff --git a/posts/bounty/refer-publisher.md b/content/bounty/refer-publisher.md similarity index 100% rename from posts/bounty/refer-publisher.md rename to content/bounty/refer-publisher.md diff --git a/posts/bounty/slack-greeting.md b/content/bounty/slack-greeting.md similarity index 100% rename from posts/bounty/slack-greeting.md rename to content/bounty/slack-greeting.md diff --git a/posts/bounty/slack-lbry-url-handler.md b/content/bounty/slack-lbry-url-handler.md similarity index 95% rename from posts/bounty/slack-lbry-url-handler.md rename to content/bounty/slack-lbry-url-handler.md index d2d05b1d..15735079 100644 --- a/posts/bounty/slack-lbry-url-handler.md +++ b/content/bounty/slack-lbry-url-handler.md @@ -2,7 +2,7 @@ category: slack title: Create a URL Handler for Slack award: 1000 -status: available +status: complete date: '2016-07-01' --- diff --git a/posts/bounty/social-media-cover-images.md b/content/bounty/social-media-cover-images.md similarity index 100% rename from posts/bounty/social-media-cover-images.md rename to content/bounty/social-media-cover-images.md diff --git a/posts/bounty/transaction-history.md b/content/bounty/transaction-history.md similarity index 100% rename from posts/bounty/transaction-history.md rename to content/bounty/transaction-history.md diff --git a/posts/bounty/wallet-ui.md b/content/bounty/wallet-ui.md similarity index 100% rename from posts/bounty/wallet-ui.md rename to content/bounty/wallet-ui.md diff --git a/posts/bounty/web-i18n.md b/content/bounty/web-i18n.md similarity index 86% rename from posts/bounty/web-i18n.md rename to content/bounty/web-i18n.md index 187839c5..f26b7164 100644 --- a/posts/bounty/web-i18n.md +++ b/content/bounty/web-i18n.md @@ -2,7 +2,7 @@ category: web title: Internationalization of lbry.io award: 1000 -status: available +status: complete date: '2016-07-01' --- @@ -14,4 +14,4 @@ To complete this bounty, lbry.io must be modified to: - Storing selected language in the user session - Reverse look-up your IP and choose a default language if a user has not selected one -[`lbry.io` on GitHub](https://github.com/lbryio/lbry.io) \ No newline at end of file +[`lbry.io` on GitHub](https://github.com/lbryio/lbry.io) diff --git a/posts/faq/api-help.md b/content/faq/api-help.md similarity index 100% rename from posts/faq/api-help.md rename to content/faq/api-help.md diff --git a/posts/faq/block-rewards.md b/content/faq/block-rewards.md similarity index 100% rename from posts/faq/block-rewards.md rename to content/faq/block-rewards.md diff --git a/posts/faq/blockchain-explorer.md b/content/faq/blockchain-explorer.md similarity index 100% rename from posts/faq/blockchain-explorer.md rename to content/faq/blockchain-explorer.md diff --git a/posts/faq/bounties.md b/content/faq/bounties.md similarity index 100% rename from posts/faq/bounties.md rename to content/faq/bounties.md diff --git a/posts/faq/claimtrie-implementation.md b/content/faq/claimtrie-implementation.md similarity index 100% rename from posts/faq/claimtrie-implementation.md rename to content/faq/claimtrie-implementation.md diff --git a/posts/faq/credit-policy.md b/content/faq/credit-policy.md similarity index 92% rename from posts/faq/credit-policy.md rename to content/faq/credit-policy.md index 98c421a1..18af6f2a 100644 --- a/posts/faq/credit-policy.md +++ b/content/faq/credit-policy.md @@ -23,7 +23,8 @@ LBRY issues a quarterly report every 3 months in January, April, July, and Octob | Date | Report | Balance Sheet | | ---- | ------ | ------------ | -| July 2016 | [report](/faq/quarterly-report-july-2016) | [sheet](https://docs.google.com/spreadsheets/d/1r7puheE4Ut4c08R47uCDZbDdMHAoQa0WDqw470gjMIw/edit#gid=0) | +| Q2 2016 | [report](/faq/quarterly-report-july-2016) | [sheet](https://docs.google.com/spreadsheets/d/1r7puheE4Ut4c08R47uCDZbDdMHAoQa0WDqw470gjMIw/edit#gid=0) | +| Q3 2016 | [report](/faq/quarterly-report-3q-2016) | [sheet](https://docs.google.com/spreadsheets/d/1zPG58YuLPqpB3yzypntRWouoEVc4saDOifpnvnwS8Rc/edit?ts=57f28d0e#gid=799352054) | Specific details of historic, ongoing, and anticipated fund expenditures can be seen in the most recent quarterly report. diff --git a/posts/faq/custom-ui.md b/content/faq/custom-ui.md similarity index 100% rename from posts/faq/custom-ui.md rename to content/faq/custom-ui.md diff --git a/posts/faq/earn-credits.md b/content/faq/earn-credits.md similarity index 93% rename from posts/faq/earn-credits.md rename to content/faq/earn-credits.md index 2e3e3b66..99ad48f6 100644 --- a/posts/faq/earn-credits.md +++ b/content/faq/earn-credits.md @@ -7,7 +7,7 @@ Currently, there are five ways to obtain LBRY Credits, or LBC for short. 1. Beta testers get an LBC gift. Talk to us on [Slack](https://slack.lbry.io/) if you're interested. -1. Buy then: see [Exchanges](/faq/exchanges) +1. Buy them: see [Exchanges](/faq/exchanges) 1. Mine them: see [Mining](/faq/mining-credits). 1. Host content: see [Hosting](/faq/host-content) for details. Note that hosting requires the LBRY app, which is currently open to beta testers only. diff --git a/posts/faq/exchanges.md b/content/faq/exchanges.md similarity index 67% rename from posts/faq/exchanges.md rename to content/faq/exchanges.md index 7c0a2f0c..0bd3eb5d 100644 --- a/posts/faq/exchanges.md +++ b/content/faq/exchanges.md @@ -7,3 +7,6 @@ We are listed on several exchanges. You can buy or sell credits at one of these: - [Bittrex](https://bittrex.com/Market/Index?MarketName=BTC-LBC) - [Poloniex](https://poloniex.com/exchange#btc_lbc) +- [Shapeshift](https://shapeshift.io) +- [Changelly](https://changelly.com/exchange/BTC/LBC/1) +- [BitSquare](https://bitsquare.io/) \ No newline at end of file diff --git a/posts/faq/gpg-key.md b/content/faq/gpg-key.md similarity index 100% rename from posts/faq/gpg-key.md rename to content/faq/gpg-key.md diff --git a/posts/faq/host-content.md b/content/faq/host-content.md similarity index 100% rename from posts/faq/host-content.md rename to content/faq/host-content.md diff --git a/posts/faq/how-do-i-check-my-lbc-address.md b/content/faq/how-do-i-check-my-lbc-address.md similarity index 100% rename from posts/faq/how-do-i-check-my-lbc-address.md rename to content/faq/how-do-i-check-my-lbc-address.md diff --git a/posts/faq/how-to-backup-wallet.md b/content/faq/how-to-backup-wallet.md similarity index 100% rename from posts/faq/how-to-backup-wallet.md rename to content/faq/how-to-backup-wallet.md diff --git a/posts/faq/how-to-check-hashrate.md b/content/faq/how-to-check-hashrate.md similarity index 69% rename from posts/faq/how-to-check-hashrate.md rename to content/faq/how-to-check-hashrate.md index 073496e4..cb745495 100644 --- a/posts/faq/how-to-check-hashrate.md +++ b/content/faq/how-to-check-hashrate.md @@ -3,6 +3,6 @@ title: How do I check my hashrate? category: mining --- -IF GPU mining please use the pool dashboard or local mining client UI. +If GPU mining please use the pool dashboard or local mining client UI. If CPU mining, you check your hashrate using `lbrycrd-cli gethashespersec`. diff --git a/posts/faq/how-to-check-mining-balance.md b/content/faq/how-to-check-mining-balance.md similarity index 100% rename from posts/faq/how-to-check-mining-balance.md rename to content/faq/how-to-check-mining-balance.md diff --git a/posts/faq/how-to-encrypt-wallet.md b/content/faq/how-to-encrypt-wallet.md similarity index 100% rename from posts/faq/how-to-encrypt-wallet.md rename to content/faq/how-to-encrypt-wallet.md diff --git a/posts/faq/how-to-generate-receiving-address.md b/content/faq/how-to-generate-receiving-address.md similarity index 100% rename from posts/faq/how-to-generate-receiving-address.md rename to content/faq/how-to-generate-receiving-address.md diff --git a/posts/faq/how-to-get-lbry-command-line.md b/content/faq/how-to-get-lbry-command-line.md similarity index 100% rename from posts/faq/how-to-get-lbry-command-line.md rename to content/faq/how-to-get-lbry-command-line.md diff --git a/posts/faq/how-to-report-bugs.md b/content/faq/how-to-report-bugs.md similarity index 100% rename from posts/faq/how-to-report-bugs.md rename to content/faq/how-to-report-bugs.md diff --git a/posts/faq/how-to-run-lbry-with-lbrycrdd.md b/content/faq/how-to-run-lbry-with-lbrycrdd.md similarity index 100% rename from posts/faq/how-to-run-lbry-with-lbrycrdd.md rename to content/faq/how-to-run-lbry-with-lbrycrdd.md diff --git a/posts/faq/how-to-run-lbry.md b/content/faq/how-to-run-lbry.md similarity index 100% rename from posts/faq/how-to-run-lbry.md rename to content/faq/how-to-run-lbry.md diff --git a/posts/faq/how-to-stop-lbry.md b/content/faq/how-to-stop-lbry.md similarity index 100% rename from posts/faq/how-to-stop-lbry.md rename to content/faq/how-to-stop-lbry.md diff --git a/posts/faq/is-lbry-open-source.md b/content/faq/is-lbry-open-source.md similarity index 100% rename from posts/faq/is-lbry-open-source.md rename to content/faq/is-lbry-open-source.md diff --git a/posts/faq/lbry-directories.md b/content/faq/lbry-directories.md similarity index 100% rename from posts/faq/lbry-directories.md rename to content/faq/lbry-directories.md diff --git a/posts/faq/mining-credits.md b/content/faq/mining-credits.md similarity index 78% rename from posts/faq/mining-credits.md rename to content/faq/mining-credits.md index 677ae431..284b67b7 100644 --- a/posts/faq/mining-credits.md +++ b/content/faq/mining-credits.md @@ -7,19 +7,19 @@ Block rewards increase every 100 blocks by 1LBC, peak at 500, and decline slowly For GPU mining, please see our list of [pools](https://lbry.io/faq/mining-pools). Each pool has a slightly different setup so please check their Getting Started page. -For CPU mining, LBRY binaries are out for OS X and Ubuntu. Others may try compiling from source. +For CPU mining, LBRY binaries are out for OS X, Windows, and Ubuntu. Others may try compiling from source. + +You can download the latest binaries [here](https://github.com/lbryio/lbrycrd/releases/latest) ## Mining on Ubuntu -1. `wget https://s3.amazonaws.com/files.lbry.io/bins.zip` -1. `unzip bins.zip` +1. unzip the binaries, and `cd` into the directory containing them 1. `./lbrycrdd -server -printtoconsole -gen` 1. If you need to start over, run `rm -rf bins.zip lbry* ~/.lbry*`. **Note:** this will delete your wallet and any credits you may have. ## Mining on macOS -1. `wget https://s3.amazonaws.com/files.lbry.io/osxbins.zip` -1. `unzip osxbins.zip` +1. unzip the binaries, and `cd` into the directory containing them 1. `mkdir ~/Library/Application\ Support/lbrycrd` 1. `sudo chown -R "$(whoami)" ~/Library/Application\ Support/lbrycrd` 1. `echo -e "rpcuser=lbryrpc\nrpcpassword=$(env LC_CTYPE=C LC_ALL=C tr -dc A-Za-z0-9 < /dev/urandom | head -c 16 | xargs)" > ~/Library/Application\ Support/lbrycrd/lbrycrd.conf` diff --git a/posts/faq/mining-pools.md b/content/faq/mining-pools.md similarity index 100% rename from posts/faq/mining-pools.md rename to content/faq/mining-pools.md diff --git a/content/faq/quarterly-report-3q-2016.md b/content/faq/quarterly-report-3q-2016.md new file mode 100644 index 00000000..f9ca1442 --- /dev/null +++ b/content/faq/quarterly-report-3q-2016.md @@ -0,0 +1,36 @@ +--- +title: "Quarterly Credit Report: Third Quarter 2016" +category: policy +--- +## Summary + +This is LBRY's second quarterly report. This quarter we outlaid only 267,778 of the 2,000,000 allocated Community credits. 100,000 operational credits were exchanged with Shapeshift to provide liquidity. No institutional credits were moved or spent. + +## Current Balance Sheet + +[Available here](https://docs.google.com/spreadsheets/d/1zPG58YuLPqpB3yzypntRWouoEVc4saDOifpnvnwS8Rc/edit?ts=57f28d0e#gid=799352054) + +## Overview By Fund + +### Community Fund + +- 10,600 credits outlaid to community managers +- 171,251 credits outlaid to Alpha and Beta testers +- 19,150 credits outlaid to community members for completing bounties +- 75,777 credits outlaid in Slack tips* + +*Some tip outlays were for bounties and to reward early people who stepped up to manage the community before we had a formal program. We have ceased using Slack tips for bounties and community management and will be able to better differentiate these moving forward. + +Over the next quarter we anticipate continued substantial use of the Community credits to grow both the user base and the publishing base. We expect these outlays to match or exceed those of this quarter, but not to dramatically exceed these outlays. + +### Operational Fund + +- 100,000 credits exchanged to ShapeShift to provide liquidity on their platform. + +ShapeShift required ownership over a pool of credits to maintain their exchange services. These credits were sold at near-market rates. + +We do not anticipate any expenditures from this fund through Q2 2017. + +### Institutional Fund + +We have no current plans to expend any credits from this fund this quarter, but are examining possibilities. Any institutional fund expenditures at this time would likely come with restrictions that prevent exchange or sale. diff --git a/posts/faq/quarterly-report-july-2016.md b/content/faq/quarterly-report-july-2016.md similarity index 100% rename from posts/faq/quarterly-report-july-2016.md rename to content/faq/quarterly-report-july-2016.md diff --git a/posts/faq/referrals.md b/content/faq/referrals.md similarity index 100% rename from posts/faq/referrals.md rename to content/faq/referrals.md diff --git a/posts/faq/to-log-to-console.md b/content/faq/to-log-to-console.md similarity index 100% rename from posts/faq/to-log-to-console.md rename to content/faq/to-log-to-console.md diff --git a/posts/news/01-the-lbry-opens.md b/content/news/01-the-lbry-opens.md similarity index 100% rename from posts/news/01-the-lbry-opens.md rename to content/news/01-the-lbry-opens.md diff --git a/posts/news/02-as-reddit-burns-it-powers-the-world.md b/content/news/02-as-reddit-burns-it-powers-the-world.md similarity index 100% rename from posts/news/02-as-reddit-burns-it-powers-the-world.md rename to content/news/02-as-reddit-burns-it-powers-the-world.md diff --git a/posts/news/03-5-questions-about-lbry.md b/content/news/03-5-questions-about-lbry.md similarity index 100% rename from posts/news/03-5-questions-about-lbry.md rename to content/news/03-5-questions-about-lbry.md diff --git a/posts/news/09-introducing-lbry-the-bitcoin-of-content.md b/content/news/09-introducing-lbry-the-bitcoin-of-content.md similarity index 100% rename from posts/news/09-introducing-lbry-the-bitcoin-of-content.md rename to content/news/09-introducing-lbry-the-bitcoin-of-content.md diff --git a/posts/news/10-slides-from-media-demo.md b/content/news/10-slides-from-media-demo.md similarity index 100% rename from posts/news/10-slides-from-media-demo.md rename to content/news/10-slides-from-media-demo.md diff --git a/posts/news/11-testimony-to-subcommittee-on-hb552-to-legalize-bitcoin-for-payments-of-taxes-and-fees.md b/content/news/11-testimony-to-subcommittee-on-hb552-to-legalize-bitcoin-for-payments-of-taxes-and-fees.md similarity index 100% rename from posts/news/11-testimony-to-subcommittee-on-hb552-to-legalize-bitcoin-for-payments-of-taxes-and-fees.md rename to content/news/11-testimony-to-subcommittee-on-hb552-to-legalize-bitcoin-for-payments-of-taxes-and-fees.md diff --git a/posts/news/12-why-not-use-bitcoin-a-dialogue.md b/content/news/12-why-not-use-bitcoin-a-dialogue.md similarity index 100% rename from posts/news/12-why-not-use-bitcoin-a-dialogue.md rename to content/news/12-why-not-use-bitcoin-a-dialogue.md diff --git a/posts/news/13-mike-vine-joins-lbry-as-technology-evangelist-2.md b/content/news/13-mike-vine-joins-lbry-as-technology-evangelist-2.md similarity index 100% rename from posts/news/13-mike-vine-joins-lbry-as-technology-evangelist-2.md rename to content/news/13-mike-vine-joins-lbry-as-technology-evangelist-2.md diff --git a/posts/news/14-lbry-gets-content-creators-out-of-precarious-position-daily-decrypts-amanda-b-johnson.md b/content/news/14-lbry-gets-content-creators-out-of-precarious-position-daily-decrypts-amanda-b-johnson.md similarity index 100% rename from posts/news/14-lbry-gets-content-creators-out-of-precarious-position-daily-decrypts-amanda-b-johnson.md rename to content/news/14-lbry-gets-content-creators-out-of-precarious-position-daily-decrypts-amanda-b-johnson.md diff --git a/posts/news/15-renowned-ip-attorney-kinsella-joins-lbry-cryptoapp-as-legal-advisor.md b/content/news/15-renowned-ip-attorney-kinsella-joins-lbry-cryptoapp-as-legal-advisor.md similarity index 100% rename from posts/news/15-renowned-ip-attorney-kinsella-joins-lbry-cryptoapp-as-legal-advisor.md rename to content/news/15-renowned-ip-attorney-kinsella-joins-lbry-cryptoapp-as-legal-advisor.md diff --git a/posts/news/16-digging-into-lbry.md b/content/news/16-digging-into-lbry.md similarity index 100% rename from posts/news/16-digging-into-lbry.md rename to content/news/16-digging-into-lbry.md diff --git a/posts/news/17-rpi-hackers-meet-lbry-rcos-presentation.md b/content/news/17-rpi-hackers-meet-lbry-rcos-presentation.md similarity index 100% rename from posts/news/17-rpi-hackers-meet-lbry-rcos-presentation.md rename to content/news/17-rpi-hackers-meet-lbry-rcos-presentation.md diff --git a/posts/news/18-its-time-to-liberate-anne-franks-diary.md b/content/news/18-its-time-to-liberate-anne-franks-diary.md similarity index 100% rename from posts/news/18-its-time-to-liberate-anne-franks-diary.md rename to content/news/18-its-time-to-liberate-anne-franks-diary.md diff --git a/posts/news/19-free-lbry-credits-come-and-get-em.md b/content/news/19-free-lbry-credits-come-and-get-em.md similarity index 100% rename from posts/news/19-free-lbry-credits-come-and-get-em.md rename to content/news/19-free-lbry-credits-come-and-get-em.md diff --git a/posts/news/20-open-a-valve-to-gush-classic-movies.md b/content/news/20-open-a-valve-to-gush-classic-movies.md similarity index 100% rename from posts/news/20-open-a-valve-to-gush-classic-movies.md rename to content/news/20-open-a-valve-to-gush-classic-movies.md diff --git a/posts/news/21-gmu-economist-alex-tabarrok-joins-lbry.md b/content/news/21-gmu-economist-alex-tabarrok-joins-lbry.md similarity index 100% rename from posts/news/21-gmu-economist-alex-tabarrok-joins-lbry.md rename to content/news/21-gmu-economist-alex-tabarrok-joins-lbry.md diff --git a/posts/news/22-building-the-web-3-0-decentralizing-content-in-an-age-of-centralization.md b/content/news/22-building-the-web-3-0-decentralizing-content-in-an-age-of-centralization.md similarity index 100% rename from posts/news/22-building-the-web-3-0-decentralizing-content-in-an-age-of-centralization.md rename to content/news/22-building-the-web-3-0-decentralizing-content-in-an-age-of-centralization.md diff --git a/posts/news/23-bravenewcoin.md b/content/news/23-bravenewcoin.md similarity index 100% rename from posts/news/23-bravenewcoin.md rename to content/news/23-bravenewcoin.md diff --git a/posts/news/24-our-christmas-surprise.md b/content/news/24-our-christmas-surprise.md similarity index 100% rename from posts/news/24-our-christmas-surprise.md rename to content/news/24-our-christmas-surprise.md diff --git a/posts/news/25-huemer-joins-lbry-ethical-advisor.md b/content/news/25-huemer-joins-lbry-ethical-advisor.md similarity index 100% rename from posts/news/25-huemer-joins-lbry-ethical-advisor.md rename to content/news/25-huemer-joins-lbry-ethical-advisor.md diff --git a/posts/news/26-jack-robison-escaped-60-years-in-prison-now-hes-revolutionizing-the-internet.md b/content/news/26-jack-robison-escaped-60-years-in-prison-now-hes-revolutionizing-the-internet.md similarity index 100% rename from posts/news/26-jack-robison-escaped-60-years-in-prison-now-hes-revolutionizing-the-internet.md rename to content/news/26-jack-robison-escaped-60-years-in-prison-now-hes-revolutionizing-the-internet.md diff --git a/posts/news/27-lbry-adds-chief-growth-officer-josh-finer.md b/content/news/27-lbry-adds-chief-growth-officer-josh-finer.md similarity index 100% rename from posts/news/27-lbry-adds-chief-growth-officer-josh-finer.md rename to content/news/27-lbry-adds-chief-growth-officer-josh-finer.md diff --git a/posts/news/28-the-dmcas-chilling-effect-on-security-research-and-innovation.md b/content/news/28-the-dmcas-chilling-effect-on-security-research-and-innovation.md similarity index 100% rename from posts/news/28-the-dmcas-chilling-effect-on-security-research-and-innovation.md rename to content/news/28-the-dmcas-chilling-effect-on-security-research-and-innovation.md diff --git a/posts/news/29-lbry-app-sneak-peak-big-questions-answered-lbry-on-blocktalk-last-night.md b/content/news/29-lbry-app-sneak-peak-big-questions-answered-lbry-on-blocktalk-last-night.md similarity index 100% rename from posts/news/29-lbry-app-sneak-peak-big-questions-answered-lbry-on-blocktalk-last-night.md rename to content/news/29-lbry-app-sneak-peak-big-questions-answered-lbry-on-blocktalk-last-night.md diff --git a/posts/news/30-try-lbry-on-os-x-el-capitan.md b/content/news/30-try-lbry-on-os-x-el-capitan.md similarity index 100% rename from posts/news/30-try-lbry-on-os-x-el-capitan.md rename to content/news/30-try-lbry-on-os-x-el-capitan.md diff --git a/posts/news/31-annefrank2.md b/content/news/31-annefrank2.md similarity index 100% rename from posts/news/31-annefrank2.md rename to content/news/31-annefrank2.md diff --git a/posts/news/32-if-lbry-succeeds-humanity-wins-lbry-ceo-on-lions-of-liberty-podcast.md b/content/news/32-if-lbry-succeeds-humanity-wins-lbry-ceo-on-lions-of-liberty-podcast.md similarity index 100% rename from posts/news/32-if-lbry-succeeds-humanity-wins-lbry-ceo-on-lions-of-liberty-podcast.md rename to content/news/32-if-lbry-succeeds-humanity-wins-lbry-ceo-on-lions-of-liberty-podcast.md diff --git a/posts/news/33-zarghamjoins.md b/content/news/33-zarghamjoins.md similarity index 100% rename from posts/news/33-zarghamjoins.md rename to content/news/33-zarghamjoins.md diff --git a/posts/news/35-why-doesnt-lbry-just-use-bitcoin.md b/content/news/35-why-doesnt-lbry-just-use-bitcoin.md similarity index 100% rename from posts/news/35-why-doesnt-lbry-just-use-bitcoin.md rename to content/news/35-why-doesnt-lbry-just-use-bitcoin.md diff --git a/posts/news/36-built-for-artists-by-autists-lbry-takes-autism-personally.md b/content/news/36-built-for-artists-by-autists-lbry-takes-autism-personally.md similarity index 100% rename from posts/news/36-built-for-artists-by-autists-lbry-takes-autism-personally.md rename to content/news/36-built-for-artists-by-autists-lbry-takes-autism-personally.md diff --git a/posts/news/38-information-gatekeepers-make-our-culture-sick.md b/content/news/38-information-gatekeepers-make-our-culture-sick.md similarity index 100% rename from posts/news/38-information-gatekeepers-make-our-culture-sick.md rename to content/news/38-information-gatekeepers-make-our-culture-sick.md diff --git a/posts/news/39-bandcamp-cool-growing-but-still-take-15-percent.md b/content/news/39-bandcamp-cool-growing-but-still-take-15-percent.md similarity index 100% rename from posts/news/39-bandcamp-cool-growing-but-still-take-15-percent.md rename to content/news/39-bandcamp-cool-growing-but-still-take-15-percent.md diff --git a/posts/news/40-tipping-in-LBC.md b/content/news/40-tipping-in-LBC.md similarity index 100% rename from posts/news/40-tipping-in-LBC.md rename to content/news/40-tipping-in-LBC.md diff --git a/posts/news/41-apple-lost-my-music.md b/content/news/41-apple-lost-my-music.md similarity index 100% rename from posts/news/41-apple-lost-my-music.md rename to content/news/41-apple-lost-my-music.md diff --git a/posts/news/42-bittorrents-new-streaming-service.md b/content/news/42-bittorrents-new-streaming-service.md similarity index 100% rename from posts/news/42-bittorrents-new-streaming-service.md rename to content/news/42-bittorrents-new-streaming-service.md diff --git a/posts/news/43-ultimate-wizard-meet-alex-grin.md b/content/news/43-ultimate-wizard-meet-alex-grin.md similarity index 98% rename from posts/news/43-ultimate-wizard-meet-alex-grin.md rename to content/news/43-ultimate-wizard-meet-alex-grin.md index 3ed1433e..1fd8ee9e 100644 --- a/posts/news/43-ultimate-wizard-meet-alex-grin.md +++ b/content/news/43-ultimate-wizard-meet-alex-grin.md @@ -8,7 +8,7 @@ Alex Grintsvayg (aka “Grin”) played ultimate frisbee with LBRY founders Jere Grin has been named LBRY’s *Chief Infrastructure Officer*, but *Wizard* is probably more descriptive of his role on the team. He will contribute to the technical side of the operation, ensuring LBRY’s underlying infrastructure is rock-solid. - + With degrees in computer science and psychology from Rensselaer Polytechnic Institute, Grin has a unique ability to understand and apply new technologies. He also brings plenty of business knowledge to the table, with six years of experience running startup tech companies. Add to all of that his ultimate-frisbee skills – he played for a national-caliber team for seven years and professionally for the Philly Spinners – and you can see why we’ve dubbed him the Wizard. diff --git a/posts/news/44-meet-reilly-smith-storyteller.md b/content/news/44-meet-reilly-smith-storyteller.md similarity index 100% rename from posts/news/44-meet-reilly-smith-storyteller.md rename to content/news/44-meet-reilly-smith-storyteller.md diff --git a/posts/news/45-lbry-blockchain-live-mine-lbc-now.md b/content/news/45-lbry-blockchain-live-mine-lbc-now.md similarity index 100% rename from posts/news/45-lbry-blockchain-live-mine-lbc-now.md rename to content/news/45-lbry-blockchain-live-mine-lbc-now.md diff --git a/posts/news/46-lbry-at-porcfest-first-public-screening-film-via-blockchain.md b/content/news/46-lbry-at-porcfest-first-public-screening-film-via-blockchain.md similarity index 100% rename from posts/news/46-lbry-at-porcfest-first-public-screening-film-via-blockchain.md rename to content/news/46-lbry-at-porcfest-first-public-screening-film-via-blockchain.md diff --git a/posts/news/47-beta-live-declare-independence-big-media.md b/content/news/47-beta-live-declare-independence-big-media.md similarity index 100% rename from posts/news/47-beta-live-declare-independence-big-media.md rename to content/news/47-beta-live-declare-independence-big-media.md diff --git a/posts/news/48-lbry-credits-on-bittrex.md b/content/news/48-lbry-credits-on-bittrex.md similarity index 100% rename from posts/news/48-lbry-credits-on-bittrex.md rename to content/news/48-lbry-credits-on-bittrex.md diff --git a/posts/news/49-mysterious-100k-lbc-revealed.md b/content/news/49-mysterious-100k-lbc-revealed.md similarity index 100% rename from posts/news/49-mysterious-100k-lbc-revealed.md rename to content/news/49-mysterious-100k-lbc-revealed.md diff --git a/posts/news/50-$1.2B-market-cap-we-dont-care.md b/content/news/50-1.2B-market-cap-we-dont-care.md similarity index 100% rename from posts/news/50-$1.2B-market-cap-we-dont-care.md rename to content/news/50-1.2B-market-cap-we-dont-care.md diff --git a/posts/news/51-launch-a-disaster.md b/content/news/51-launch-a-disaster.md similarity index 100% rename from posts/news/51-launch-a-disaster.md rename to content/news/51-launch-a-disaster.md diff --git a/posts/news/52-day-in-shoes-venezuelan.md b/content/news/52-day-in-shoes-venezuelan.md similarity index 100% rename from posts/news/52-day-in-shoes-venezuelan.md rename to content/news/52-day-in-shoes-venezuelan.md diff --git a/posts/news/53-publish-tools-live-earn-1000-dollars.md b/content/news/53-publish-tools-live-earn-1000-dollars.md similarity index 100% rename from posts/news/53-publish-tools-live-earn-1000-dollars.md rename to content/news/53-publish-tools-live-earn-1000-dollars.md diff --git a/posts/news/54-shapeshift-adds-lbc.md b/content/news/54-shapeshift-adds-lbc.md similarity index 100% rename from posts/news/54-shapeshift-adds-lbc.md rename to content/news/54-shapeshift-adds-lbc.md diff --git a/posts/news/55-fight-of-century.md b/content/news/55-fight-of-century.md similarity index 100% rename from posts/news/55-fight-of-century.md rename to content/news/55-fight-of-century.md diff --git a/posts/news/56-mde-on-lbry.md b/content/news/56-mde-on-lbry.md similarity index 100% rename from posts/news/56-mde-on-lbry.md rename to content/news/56-mde-on-lbry.md diff --git a/posts/news/57-lbry-bounties.md b/content/news/57-lbry-bounties.md similarity index 100% rename from posts/news/57-lbry-bounties.md rename to content/news/57-lbry-bounties.md diff --git a/posts/news/58-reweighting-wait-list.md b/content/news/58-reweighting-wait-list.md similarity index 100% rename from posts/news/58-reweighting-wait-list.md rename to content/news/58-reweighting-wait-list.md diff --git a/posts/news/59-ui-publishing-tools-upgrades.md b/content/news/59-ui-publishing-tools-upgrades.md similarity index 100% rename from posts/news/59-ui-publishing-tools-upgrades.md rename to content/news/59-ui-publishing-tools-upgrades.md diff --git a/posts/news/60-heckbender-charney-on-lbry.md b/content/news/60-heckbender-charney-on-lbry.md similarity index 100% rename from posts/news/60-heckbender-charney-on-lbry.md rename to content/news/60-heckbender-charney-on-lbry.md diff --git a/content/news/61-nsfw-lbry-settings.md b/content/news/61-nsfw-lbry-settings.md new file mode 100644 index 00000000..bbd4284d --- /dev/null +++ b/content/news/61-nsfw-lbry-settings.md @@ -0,0 +1,29 @@ +--- +author: lbry +title: 'NSFW: LBRY Bares All (Then Quickly Covers Up)' +date: '2016-08-29 00:06:18' +cover: 'nude.png' +--- +The internet abhors a vacuum, and by that we mean any open forum without boobs. LBRY is apparently no different. + +It’s well known that the internet and VHS tapes quickly became popular sources for adult content as the public began to explore these new forms of media technology. The same thing happened in LBRY as soon as we created some Featured Content spots on the app for users to control. + +Any LBRY beta user can upload original content and bid for the names “[one](lbry://one)”, “[two](lbry://two)”, “[three](lbry://three)”, “[four](lbry://four)”. The content at those names appears on LBRY’s home screen to the right of our featured publishing partners: + + + +As you can see, when the above screenshot was snapped, an explicit piece of content was on display. In fact, when these slots became available to the public, we got lots of featured content that everyday users might not want to have pop up on their screen during, say, a break at work or a first date. (You do show off LBRY on the first date, right? … No? Just us? Hmm.) + +The LBRY protocol will remain free of censorship by design – and in keeping with our principles of free speech, no gatekeepers, and creator control. But the LBRY app we’re creating is for “all audiences” as Hollywood would say. We want everyone to be able to use the app in keeping with their own comfort and any legal considerations in their country. + +So what’s the answer? A setting, of course! + +Creators can mark their works “NSFW,” which will blur the title and preview image and offer a warning if users click on it, as you can see here: + + + +If you are of legal age and don’t mind seeing NSFW content, you can disable this setting globally by navigating to your personal LBRY settings: + + + +Still don’t like the idea of NSFW content showing up on LBRY’s home screen even in a moderated way? *Then go bid on those names!* LBRY is a neutral platform: it responds to the market. You can steer LBRY your way by getting involved. If you’d rather not bid, you can still get your original content featured on the homepage by [applying to be a publishing partner](https://lbry.io/publish) (and earn $1,000 in LBC at the same time!). Or [join our Slack](http://slack.lbry.io/) to talk with content publishers, ask questions of our developers, or just hang out in a friendly online community. diff --git a/content/news/62-content-without-borders.md b/content/news/62-content-without-borders.md new file mode 100644 index 00000000..8d79192c --- /dev/null +++ b/content/news/62-content-without-borders.md @@ -0,0 +1,21 @@ +--- +author: lbry +title: 'Content Without Borders' +date: '2016-08-31 00:06:18' +cover: 'berlinwall.png' +--- +National borders shouldn’t stop the flow of digital content. But they often do. + + + +As Estonian President Toomas Hendrik explained in an interview with Ars Technica, [he can’t buy a song on iTunes for his wife](http://arstechnica.com/business/2016/05/why-cant-the-estonian-president-buy-a-song-off-itunes-for-his-latvian-wife/): + +>"iTunes are based on credit cards. Credit cards are national. I cannot buy an iTunes record for my wife who has a Latvian credit card. I cannot buy her an iTunes record because I have an Estonian iTunes. This is true of virtually everything that is connected to digital services. And certainly this is why Estonia is at the forefront of the European Digital Single Market. As I like to say, it’s easier to ship a bottle of Portuguese wine from southern Portugal in the Algarve and sell it in northern Lapland, than it is for me to buy an iTunes record across the Estonian-Latvian border." + +As Ronald Reagan said, “Tear down this wall!” + +LBRY does just that. + +Using LBRY Credits (LBC), you can access content from anywhere in the world, from wherever you happen to be. You don’t have to worry about credit cards, country-based lock-in by subscription services, or local censorship. + +Together we can build a world of content without borders! diff --git a/content/news/63-joaquin-pixan-singleton-band.md b/content/news/63-joaquin-pixan-singleton-band.md new file mode 100644 index 00000000..b3fed0fb --- /dev/null +++ b/content/news/63-joaquin-pixan-singleton-band.md @@ -0,0 +1,29 @@ +--- +author: lbry +title: '#NewKidsOnTheBlockchain Thursday: Indie Rockers from Ukraine & Classical Singing de la Spain' +date: '2016-09-01 00:06:18' +cover: 'joaquin-pixan2.jpg' +--- +It’s amazing to watch full resolution films with no ads on LBRY, but the protocol is also built to carry music, ebooks, and more. On today’s #NewKids Thursday, we’re announcing something sweet for your auditory nerves. + +**[Joaquín Pixán](http://www.joaquinpixan.com/)** + + + +The true treat of LBRY is finding brilliant gems from corners of the globe, like master Spanish vocalist Joaquín Pixán. + +> "...The special qualities of our singer’s voice, the attractive colour of his timbre, adapt wonderfully to the texts and notes and in particular seem tailor-made to bring out the passages which hint at or openly display a Spanishness of the greatest pedigree; a forthrightness even verging on the rustic or even a refined degree of stylisation of the popular Spanish copla." +**– Arturo Reverter** + +His live performances and catalog of studio recordings will keep you coming back again and again. Search “joaquin pixan” for his all-time greats. And then mix it up a bit with... + +**[Singleton Band](http://singleton.com.ua)** + + + +This indie rock outfit of more than a decade brings their soaring crescendos and brilliant vocals to LBRY. It’s a familiar indie rock sound packed with an emotional punch. Search “Singleton” and keep an eye for their albums and music videos. From their official biography: + + +> "Being in the avant-garde of Ukrainian indie music since 2004, Singleton has an extensive touring experience, with numerous shows in Ukraine, Russia, Belarus and Germany." + +**Want to share your jams on LBRY?** [Get an invite here](https://lbry.io/get). Or if you can’t wait, skip our waiting list for a chance to earn $1,000 in LBRY Credits at the same time. [Learn more here](https://lbry.io/publish). diff --git a/content/news/64-500k-vc-funds-pillar.md b/content/news/64-500k-vc-funds-pillar.md new file mode 100644 index 00000000..9f692d51 --- /dev/null +++ b/content/news/64-500k-vc-funds-pillar.md @@ -0,0 +1,16 @@ +--- +author: lbry +title: '$500K in VC Funds from Pillar et al' +date: '2016-09-07 00:06:18' +--- +We’ve hinted at this news in the past week, but now it’s official: [Pillar VC](http://pillar.vc/) is leading a $500,000 seed funding round for LBRY! + + + +Pillar is a perfect fit for LBRY, with the mission of “treating founders the way we would want to be treated.” This is precisely what LBRY is trying to achieve for digital content creators – a distribution system that connects them directly to their fans with no middleman taking a cut of their profits. We want to give creators the power to say, “No, thank you!” to YouTube and other big media companies that exercise control over their users’ original content. + +LBRY is humbled at this endorsement. Beyond helping us grow our dev team and deliver better software faster, we hope this news also serves to show that we take our primary goal seriously: every film, song, book, and app ever made – available anywhere. Pillar VC is operated by determined visionaries who conducted careful due diligence in funding LBRY. With their help, we will provide the content distribution protocol of the future. + +These funds will be used to advance the development of our beta as quickly as possible. We will have a Windows version released this month. By the end of this year, we are anticipating opening up the beta app – and going to full product release in 2017. No more waiting lists! + +[Click Here to Read the Full Press Release](https://lbry.io/press/500k-fundraising-round-pillar-vc.md) diff --git a/content/news/65-animal-robot-on-lbry.md b/content/news/65-animal-robot-on-lbry.md new file mode 100644 index 00000000..26968a84 --- /dev/null +++ b/content/news/65-animal-robot-on-lbry.md @@ -0,0 +1,31 @@ +--- +author: reilly +title: 'All Mashed Up' +date: '2016-09-08 00:06:18' +cover: 'animalrobot1.png' +--- +New week, new subwoofer. We’re so excited to share this artist with you on LBRY. + +Introducing, [AnimalRobot](https://www.facebook.com/animalrobot). Their work has won a Webby award and has been featured on @Time, @HuffingtonPost, @Slate, @VICE, @nerdist, @peoplemag, @MTV & more. This is a one-of-a-kind artist who has perfected the art of audio-visual mash up. + +First out the gate: if you’re feeling nostalgic for [Los Santos](http://gta.wikia.com/wiki/Los_Santos_(HD_Universe)), [Franklin](http://gta.wikia.com/wiki/Franklin_Clinton) is here to bring you back to his roots. + + + +lbry://gtasoc + +And in the LBRY sequel to HeckBender’s *Pug Rap God* (lbry://pugrapgod), AnimalRobot steps up the rap god game with his mad masterpiece, *Rick and Morty + Rap God*. You’ve never heard Rick spit like this before. + + + +lbry://rickandmortyrapgod + +AnimalRobot spins all night, every night on LBRY: + +- *[The Blow Up: Blackalicious vs. Mr. Fox](lbry://theblowup)* – Wes Anderson + Blackalicious = Profit??? +- *[Hypnotize: Notorious B.I.G. vs. Earl Sinclair](lbry://hypnotize)* – The dad joke of a dinosaur channels his best Biggie impression. +- *[Dopeman: Redman vs. Homer Simpson](lbry://dopeman)* – Homer lends Redman a hand in selling his latest album to remixed results. +- *[Rap God: Eminem vs. Rick and Morty](lbry://rickandmortyrapgod)* – Rap God pt. 2 +- *[Straight Outta Compton: A Los Santos Story](lbry://gtasoc)* – Franklin and friends take to Los Santos with their own take on NWA’s legendary hit, Straight Outta Compton + +**Not on LBRY yet?** [Get an invite here](https://lbry.io/get). Just can’t wait? If you’re a creator, skip our waiting list line for a chance to earn $1,000 in LBRY Credits at the same time. [Learn more here](https://lbry.io/publish). diff --git a/content/news/66-brookes-eggleston-character-design-forge.md b/content/news/66-brookes-eggleston-character-design-forge.md new file mode 100644 index 00000000..7f1b69e9 --- /dev/null +++ b/content/news/66-brookes-eggleston-character-design-forge.md @@ -0,0 +1,25 @@ +--- +author: reilly +title: 'Storytelling Art 101' +date: '2016-09-15 00:06:00' +cover: 'brookes-cover.jpg' +--- +LBRY is more than a digital entertainment outlet. A key motivation behind LBRY was enabling global access to lessons from the masters of their craft. We want to share content that empowers everyone to raise the bar. + +We’re starting that trend this week with [Character Design Forge, AKA Brookes Eggleston](http://brookeseggleston.com/). Arguably the most important element of storytelling is strong characters. Brookes gives illustrators the tools they need to start their stories off on the right foot. + + + +>”Characters aren’t just meant to sell people on an idea. Characters are vessels for feelings and personalities, that can connect to your audience. With a well designed character, our brains don’t really understand them to be artificial.” **- Brookes Eggleston** + +The best place to start is *[5 Tips for Better Drawing](lbry://drawbetter)*. + +Once you’ve covered the basics, check out the rest of the courses: + +- *[How to Be a Character Designer](lbry://characterdesigner)* – Learn the qualities that empower character designers in their work. +- *[Drawing a Comic Page from Start to Finish](lbry://drawingcomics)* – Walk through the steps taken to create a comic page in Photoshop. +- *[Play to Your Strengths](lbry://playtoyourstrengths)* – Time is finite, make sure you're really working on what you'd like to be. We're talking about the ways to play to your strengths and how to balance the internet treadmill with bigger projects. +- *[5 Ways to Make a Character More Likable](lbry://likeablecharacters)* – The appeal of a character is essential in getting an audience to like them. Use these tips to keep them in your corner. + + +**Not on LBRY yet?** [Get an invite here](https://lbry.io/get). Just can’t wait? If you’re a creator, skip our waiting list line for a chance to earn $1,000 in LBRY Credits at the same time. [Learn more here](https://lbry.io/publish). diff --git a/content/news/67-ray-carballada-media-advisor.md b/content/news/67-ray-carballada-media-advisor.md new file mode 100644 index 00000000..88a4eb78 --- /dev/null +++ b/content/news/67-ray-carballada-media-advisor.md @@ -0,0 +1,22 @@ +--- +author: lbry +title: 'LBRY’s New Media Advisor: An Artist, A Businessman, A Gentleman' +date: '2016-09-20 00:06:18' +--- +Ray Carballada, our new media advisor, is equally comfortable walking in the worlds of business and fine art. He believes LBRY is a giant leap forward in the distribution of media content: + +>“It's smart; it's cool; it's empowering. It will be what color was to black and white, cable was to broadcast, and Netflix was to cable.” + + + +Ray is a well-established senior business leader in the converging world of digital content, advertising, movies, and social media. As former CEO of [Alkemy X](http://www.alkemy-x.com/), Ray led the transformation of this small production house into an integrated content company with Fortune 500 companies, TV networks, and motion picture studios as major clients. Some of the well-known companies Ray has worked with include Campbell Soup, Samsung, Pepsi, BMW, Discovery, Food Network, HBO, Sony, and the Weinstein Company. + +Alkemy X also won major awards while Ray was at the helm. The company made INC 5000’s list of the fastest growing companies six times, earned the “Creative Economy Award for Distinction" from the Arts and Business Council of Greater Philadelphia, and garnered 60 Addy and Emmy awards along the way. + +Ray will primarily help LBRY in its relationships with media and other content companies. + +Ray isn’t just a businessman; he’s an artist himself. He earned a BS in photography from Rochester Institute of Technology (along with an MBA from Bucknell), and he’s actively involved in the Philadelphia arts scene. + +Ray said he is most excited about how LBRY will benefit artists and content creators: + +>“LBRY promises to cut out layers upon layers of middlemen so more of the value goes to the creators – the artists - the talent! It's the answer to a problem that just couldn't be solved without this technology.” diff --git a/content/news/68-skate-yrself-clean.md b/content/news/68-skate-yrself-clean.md new file mode 100644 index 00000000..298d3342 --- /dev/null +++ b/content/news/68-skate-yrself-clean.md @@ -0,0 +1,21 @@ +--- +author: reilly +title: 'Skate, Relax, Enjoy' +date: '2016-09-22 00:06:00' +cover: 'skate-banner.png' +--- +This week, we’re keeping it short and sweet with LBRY’s first short film: *SKATE YRSELF CLEAN*. + +Los Angeles-based actor-director Janna Jude paints a loving portrait of one Ricky Lee Robinson in her first documentary film. + +Part biography, part LCD Soundsystem music video, *SKATE* captures the American dream—and its obstacles—through the intimate character lens of a Floridian freestyle roller skater. He owns a floor cleaning business by day, shreds up the rink by night, and charms your pants off 24/7. + + + +>”I’d like to be buried with my skates on, definitely. Or laying beside me somewhere, you know, in the casket… put a burger in my hand and my roller skates on and... bury me.” **- Ricky Lee Robinson** + +Janna’s six minute short won the Grand Jury Prize at [Florida Film Festival for Best Short Doc](http://articles.orlandosentinel.com/2013-05-01/entertainment/os-florida-film-festival-winners-20130501_1_florida-film-festival-award-winners-audience-award), as well as being selected by [Nashville Film Festival](https://nashvillefilmfestival.org/news/full-short-film-lineup/) and [DOC NYC](http://www.docnyc.net/film/obsessions/). + +It can be seen at **[lbry://skateyrselfclean](lbry://skateyrselfclean)** now. + +**Not on LBRY yet?** [Get an invite here](https://lbry.io/get). Or if you’re a creator, skip our wait list for a chance to earn $1,000 in LBRY Credits. [Learn more here](https://lbry.io/publish). diff --git a/content/news/69-reddit-ama-answers.md b/content/news/69-reddit-ama-answers.md new file mode 100644 index 00000000..7dd10aba --- /dev/null +++ b/content/news/69-reddit-ama-answers.md @@ -0,0 +1,184 @@ +--- +author: mike +title: 'Answers to the Big Questions from Our Reddit AMA' +date: '2016-09-28 00:06:18' +--- +A few weeks ago, in conjunction with announcing a [$500,000 funding round](https://lbry.io/news/500k-vc-funds-pillar), we hit #2 on Reddit’s front page with [our first AMA](https://www.reddit.com/r/IAmA/comments/50tyub/were_the_nerds_behind_lbry_a_decentralized/). + +While tens of thousands joined [our beta](https://lbry.io/get), including hundreds of YouTube channels, our first major wave of attention outside of blockchain circles has taught us some hard lessons: + +1. LBRY is hard to understand at first. +2. We are not that great at explaining it. +3. Not every innovation that looks great on the whiteboard is a hit in the real world. + + + +You see, as we hit Reddit’s front page, we were also climbing the ranks of /r/AMAdisasters. Many redditors were confused by our responses, didn’t like our daffy tone, and HATED the bidding system to reserve names in LBRY. + +(In fairness, many others got what we were doing immediately and sent us an outpouring of passionate support.) + +But if LBRY is going to improve the lives of millions of watchers, readers, listeners, and gamers over the coming years, people have to know what it is and want to be a part of it. + +To that end, we took some time to review the questions we received and how we answered them. **Below is a compilation of top questions from our AMA with clearer answers than we gave on the fly.** + +Take a look, then give us your feedback on [our subreddit /r/lbry](https://www.reddit.com/r/lbry/). If there’s one thing we wish we had stressed better in the AMA, it’s that LBRY is still taking shape. Your feedback will not fall on deaf ears – in fact, you have our undivided attention. + +- [What’s with the name LBRY?](#thename) +- [How does LBRY benefit content creators? Can I make money using LBRY?](#makemoney) +- [How does LBRY benefit content consumers? Why should I bother caring?](#consumers) +- [What is LBRY exactly – is it a protocol, an app, a website, a company?](#whatis) +- [LBRY’s “auction-based” name system sounds unworkable. Why don’t you just assign names the same way as internet domains?](#auction) +- [If I don’t want to deal with name auctions at all, what are my options?](#noauction) +- [How does the company behind LBRY make money?](#lbryinc) +- [What is LBRY’s development status right now? Plans for the future?](#status) +- [Why should I care about LBRY if I can’t even use it yet?](#whycare) + + +**What’s with the name LBRY?** +---- + +The very first question of newcomers is often, “How do you pronounce it?” Answer: library. + +“Is it an acronym?” No. + +“Then why confuse people with the all-caps and no vowels?” + +First and foremost, LBRY is an internet protocol, just like HTTP. Content on LBRY is served to users via “LBRY names,” which look like this: lbry://itsawonderfullife. Very similar to the URL you type into your internet browser. LBRY is not just our branded name, but the character string we’ve chosen to lead our URIs (Uniform Resource Identifier). + +It also serves as a truncated form of “library,” which reflects our mission: every film, song, book, and app ever made – available anywhere. Our vision for LBRY is to create a massive media repository for the 21st century that is built on a decentralized network controlled by its users. LBRY is to a traditional library what Amazon is to a department store. + +Is it an odd name? Perhaps. But we would kindly point to the success of brands like Hulu, Yahoo!, Etsy, Skype, Tumblr, and Zillow. In the end, a good company with a strong user base will be remembered regardless of its name. And a company with a brand as straightforward as Pets.com can still fail. + +LBRY is working well as a brand so far. SEO is a top consideration for startup branding, and LBRY already dominates the search results for our brand name. + + +**How does LBRY benefit content creators? Can I make money using LBRY?** +---- + +In our AMA title, we pitched LBRY as a “community-driven” YouTube alternative that could “save the internet.” That’s a lot of big talk, but what does it mean for the people who care most about the changes happening at YouTube – content creators? + +If you are earning money from your videos on YouTube right now, you are likely familiar with the recent controversies over videos being “de-monetized” for containing content unfriendly to advertisers. It’s no surprise that YouTube caters to advertisers, because those are its ultimate customers. + +LBRY is not an ad-driven media service. In fact, it’s not a traditional media service at all. LBRY is an open protocol that allows you to publish your videos to the network at no cost to you and set a price per stream or download. Because LBRY is a protocol and not a corporate store, there is no approval process for content to be listed and no authority that can “de-monetize” your videos. That’s between you and your fans. + +With LBRY, pricing is completely at the discretion of the creator and 100% of that price goes to the creator. Compare this to iTunes’ fixed pricing tiers and 30% cut of every sale. + +Because LBRY uses digital currency (a la Bitcoin), creators can accept micropayments for every view without worrying about credit card processing fees. Or a studio could use LBRY to distribute a theatrical release to independent theaters and charge thousands of dollars per download. The only constraint on pricing is what your viewers are willing to pay. + +With YouTube monetization, creators earn a variable amount based on viewers’ engagement with ads. There is no set formula, but we’ve found a reasonable guesstimate of around [$2 per thousand views](https://www.quora.com/How-much-does-YouTube-pay-partners-for-their-content). This works out to a penny for 5 views. So on LBRY, if you charge just one penny per view (a price any viewer would pay without a second thought), you may get 5X the per-view earnings you’d get from YouTube instantly! + +What’s more, you can share content of any type – video, music, ebooks, images, podcasts – all on the same platform. + + +**How does LBRY benefit content consumers? Why should I bother caring?** +---- + +Do you watch YouTube? Imagine paying a few cents to eliminate all ads. 100% of the payment will go directly to the content creator – and they’ll still be earning more than YouTube offers, so they’ll want to make even more of the content you love. + +Do you use BitTorrent? Imagine getting paid to seed files into the network. Because there is a marketplace for these files, you can finally find rare songs and films that can’t support a torrent swarm based on popularity alone. + +Do you shop on the iTunes Store? Imagine paying less for songs, TV episodes, and music videos and having 100% of the price directly to the creators. + +LBRY is a digital media library at your fingertips. It can store any kind of content and make it available at low cost on demand. In a few years time, LBRY may become your one-stop-shop for everything digital, from ebooks to video games to movies. One app to rule them all – but still leaving more power in your hands because it is decentralized by design. + + +**What is LBRY exactly – is it a protocol, an app, a website, a company?** +---- + +LBRY is many components working together. For most users, it will just be a place where they can find great videos, music, ebooks, and more. A vast digital library available on all of your devices. + +But behind that experience is an ecosystem that can be hard to understand at first – especially because we tend to refer to all the pieces and the system-as-a-whole as “LBRY”. (We’re working on clearing that up.) + +It might be easier to start with what LBRY is not: it is not just another corporate media service like YouTube or iTunes or Spotify. It is first and foremost a new *protocol* that allows artists to upload their content to a network of hosts (like BitTorrent) and set a price per stream or download (like iTunes) or give it away for free (like YouTube without ads). What makes this all possible is the blockchain technology developed by the founder of Bitcoin. Do you have to understand any of this to use and enjoy LBRY? No. Does it still matter to users? Yes! + +Gmail has built an extremely popular email service on top of the near-universal *SMTP protocol* that everyone uses to exchange emails. Anyone sending email with SMTP can communicate with Gmail addresses, no matter what email platform they use (Yahoo!, AOL, iCloud, etc.). Google can’t interfere with someone emailing from an @yahoo.com address to an @aol.com address – and users are free to switch between services at any time, taking their emails with them. Users have a lot of power in open protocols that is often taken for granted. + +Compare this to a proprietary, centrally controlled service like Facebook Messenger. If you conduct all of your social communications via Messenger, you’re stuck in that environment – you cannot move your messages or contacts over to Google Chat or Skype. And if Facebook changes the way Messenger functions by censoring conversations or sharing your information with advertisers or governments, tough luck. + +Even platforms that are ostensibly designed with the user’s control and privacy in mind are susceptible to corruption if they are centrally controlled. WhatsApp comes to mind. WhatsApp built a huge global user base claiming to put users above advertisers. Then Facebook bought it. Now users may well have their personal phone numbers and metadata mined for Facebook’s advertising algorithms. + +There is no such risk of top-down corruption with the LBRY protocol. Content uploaded to the decentralized LBRY network remains publicly accessible so long as the community finds it valuable and continues to host it. + +Now, the LBRY project is more than just a revolutionary new protocol. It is also a company, *LBRY Inc.*, which is developing a *LBRY app* to allow users to easily interact with the protocol. So it’s as if Google had developed the email protocol, released it to the world for free, and then built Gmail to help people make use of it. Not only is our app completely open-source, but others are welcome to create competing apps that also use the LBRY protocol. For a content creator, your uploaded content will be available to all of these apps at the same time. + +Do you see the difference here? YouTube can afford to push around its creators and users because they’ve created tremendous lock-in. LBRY is challenging this model from the ground-up. Everything we’ve built is open-source, decentralized, and belongs to the community using it. LBRY Inc. could go bankrupt tomorrow and the LBRY protocol will live on. Can YouTube say that? + + +**LBRY’s “auction-based” name system sounds unworkable. Why don’t you just assign names the same way as internet domains?** +---- + +First, it’s important to know that assigning names in a decentralized system is one of the hardest problems in computer science. Just because we’re all accustomed to certain solutions doesn’t mean they aren’t seriously flawed. + +Let’s look at the internet’s standard domain name system (DNS). DNS is a centralized service run by an organization called ICANN. [There’s actually a bit of controversy going on right now about who should control ICANN](http://www.economist.com/news/leaders/21707538-internet-not-american-whatever-ted-cruz-thinks-road-surfdom), which until now has been under the supervision of the US Department of Commerce. ICANN grants registrars the ability to lease domain names for 1-year terms. Registrars pay enormous fees to participate in this system, and individuals/companies can end up paying substantial fees to maintain a particular domain. That is to say, ICANN is a pretty lucrative racket for those involved. + +And the results aren’t even that good! Not only are domain names still very awkward (http://www.THENAMEIWANT.somethingelse), but they are highly vulnerable to squatters. Domain name squatting has become an industry unto itself, with speculators viewing it like owning real estate. Unfortunately, as with real estate, the market is opaque and transaction costs are high. Unlike real estate, the scarcity of ICANN domains is basically artificial, depending on a committee to approve new top-level domains (TLDs) at their whim. + +So we thought, “what if there were a better way?” Consulting with economists, we devised LBRY’s nameclaim system. LBRY names are awarded to the highest bidder. Our team believes that with a system like this, names will be controlled by the people who get the most value out of them – which is almost always the creator of the content. Radiohead would get a lot more value out of lbry://amoonshapedpool than a squatter, pirate, or troll. + +Before jumping to conclusions about the nameclaim system, here are a few key details: + +1. **Names aren’t bought, only reserved – no credits are lost, only put on deposit.** If you win the auction for a name, your credits are held with that name until you decide to withdraw them (at any time you wish). You aren’t buying the name from anyone and no one profits off of the transfer of names. It’s just a test of who is willing to deposit the most credits toward a name. The only cost is that you can’t spend the credits on content or cash them out while they are reserving a name. + +2. **The longer a name is held, the longer the holder has to counterbid.** You don’t just lose the name immediately if a bigger bidder comes along – especially if you’ve held it for awhile. The time to counterbid scales up to ~1 week. + +3. **Other users can pledge credits to support the nameclaim of a creator they like.** If you claim lbry://bestmovieever and your film lives up to the hype, users may show their support by pledging some credits to make sure you hold onto that name. + +4. **Names are not like Youtube channels; they’re more like search terms.** Publishers will each get a unique ID that serves as their “channel” and shows all the content they upload. This ID will form permanent links so you can embed your videos on websites and include the link in promotional materials. The bidding system is only meant to get traffic from users trying to “discover” your content through the naming system. Since every comedy video would want to be at lbry://comedy, the nameclaim system allows the name to go to the creator who can make the most revenue off of it. + +Our Reddit AMA made clear that this system makes people uncomfortable. No doubt it is unlike anything we've seen before on the net. For creators, it's a tradeoff. You might lose a valuable name, but you also don't have to worry about people squatting on the best names. Squatting has plagued projects like Namecoin and is only (poorly) resolved by ICANN at the cost of much expense and centralization. + +Our economic advisor Alex Tabarrok notes: + +>“Auctions have many great properties, but the public doesn’t like auctions very much. Although participating in an auction is fun for some; others find it annoying. It requires inputs of time and risk, and no one likes being outbid at the last minute.” + +So, the short answer is that we’re aware that this an experiment within an experiment. We’re trying to solve a very hard problem in a novel way. It’s important to note that LBRY doesn't depend on the naming system, but we're committed to giving it a chance. + + +**If I don’t want to deal with name auctions at all, what are my options?** +---- + +We don’t want to see the naming system scare off potential users. And if it fails to work as planned, we don’t want it to take down the whole LBRY project. + +Rest assured, we’re implementing permanent URLs that are **always yours**. You will have one link that contains all your LBRY content you can use in promotional materials or share online – just like a Youtube channel URL. + +Using this approach, you can bypass the auction system entirely. You could even have a regular domain name, like www.jackscodingclass.com, point to all of your content on LBRY. + +More details on this update will be released shortly. [Subscribe to our mailing list](https://lbry.io/get) to stay up to date. + + +**How does the company behind LBRY make money?** +---- + +The LBRY protocol has a built-in digital currency that allows it to function, called LBRY credits. These credits are very similar to bitcoins. Having a built-in digital currency creates an opportunity for a new kind of business that has never existed: [the protocol-first enterprise](https://medium.com/the-coinbase-blog/app-coins-and-the-dawn-of-the-decentralized-business-model-8b8c951e734f#.6mr8znoiu). LBRY Inc. has reserved 10% of all LBRY credits to fund continued development and provide profit for the founders. Since credits only gain value as the use of the protocol grows, the company has an incentive to continue developing this open-source project. And we can do it all without taking a percentage of anyone’s transactions. + +Here’s how LBRY Inc. **doesn’t** make money: + +- **We do not take a cut of any transactions.** When you buy content on LBRY, 100% of the listed price goes to the publisher. There is also a fee paid to the decentralized network of hosts that store and deliver the content to you. LBRY Inc. doesn’t take any of that unless the company is publishing or hosting content itself. The same rules apply to everyone. +- **We do not earn profits from auctioning of names**, nor do we benefit from bidding wars over names. Our goal with the naming system is simply to allocate names most efficiently to benefit users. +- **This is not a “pump and dump” scheme or vaporware.** See my essay [$1.2B Valuation and We Don’t Care](https://lbry.io/news/1.2b-market-cap-we-dont-care). LBRY credits have already experienced a bubble and we paid it no mind. LBRY is real software with a live blockchain and thousands of active users. Our goal is to increase the long-term value of the protocol, which if adopted globally will make our reserve many times more valuable than any short-term bubble. We’ve already invested 10,000 man hours into this project and it will take many more, but we’re patient and focused on the future. + +We are also exploring ways to generate revenue above our credit reserve, including providing value-added services to LBRY users and/or consulting to large content producers who want to harness LBRY. But remember, by design, **we are not able** to singlehandedly change the rules on users in an attempt to “monetize” all of your contributions to the network. + + +**What is LBRY’s development status right now? Plans for the future?** +---- + +LBRY is currently in invite-only beta, with desktop apps for Linux, MacOS, and a pre-release version of Windows. There are approximately 5K active users and hundreds of content creators. The network already hosts big name content like the [feature film *It’s A Disaster*](https://lbry.io/news/launch-a-disaster) and videos from [Adult Swim’s MillionDollarExtreme](https://lbry.io/news/mde-on-lbry). We announce new featured content every Thursday on our [blog](https://lbry.io/news), [Twitter](https://twitter.com/lbryio), and [Facebook](https://www.facebook.com/lbryio). + +The waitlist to get into the beta is ~150K people long. This shocks even us, and we’re racing to get the user experience and backend technologies to a level where we’re comfortable opening the beta to everyone. The target for open beta is this winter. Mushy target, we know, but these things are hard to time. + +In the meantime, we could use your encouragement. This is a difficult project that cannot succeed without people demonstrating they want an alternative to Silicon Valley megacorp media services. So [join the beta](https://lbry.io/get), [share our blog posts](https://lbry.io/news), and [join our subreddit /r/lbry](https://www.reddit.com/r/lbry/). Your excitement is like Red Bull to our weary coders! + + +**Why should I care about LBRY if I can’t even use it yet?** +---- + +A lot of people won’t care about LBRY until it can deliver them immediate benefits. That’s fair and we understand. + +The advantage of getting involved right now is the same as following a local band that you’d like to see make it big. You have the opportunity to have very direct contact with our dev team. You can make suggestions and shape the course of LBRY. You can [earn bounties](https://lbry.io/bounty) for helping to build and promote LBRY. You can connect with our truly amazing community of people, like [Javier from Venezuela who says LBRY gave him new hope for life](https://lbry.io/news/day-in-shoes-venezuelan). You can [post your content to the network](https://lbry.io/publish) before it is mainstream and get attention and appreciation from our early adopters – not to mention a possible prize of $1000 in LBC. + +Or get involved because it’s rare to get an opportunity to be part of a project that could reshape the fabric of the internet and creative media for years to come. + +*Did this FAQ address your concerns? Still think we’re full of it? Sound off at [reddit.com/r/lbry/](https://www.reddit.com/r/lbry/). Thank you for taking the time to learn about LBRY!* + +**Not on LBRY yet?** [Get an invite here](https://lbry.io/get). Just can’t wait? If you’re a creator, skip our waiting list line for a chance to earn $1,000 in LBRY Credits at the same time. [Learn more here](https://lbry.io/publish). diff --git a/content/news/70-fleischer-superman.md b/content/news/70-fleischer-superman.md new file mode 100644 index 00000000..def7a15a --- /dev/null +++ b/content/news/70-fleischer-superman.md @@ -0,0 +1,29 @@ +--- +author: reilly +title: 'It’s a Bird, It’s a Plane, It’s Pub Domain Superman!' +date: '2016-09-29 00:06:00' +cover: 'superman-banner.png' +--- +The grand goal of building the ultimate online library must start with the ultimate free library already in existence: the public domain. + +We’re kicking off a trend of LBRY being the number one place to find top public domain content on the planet. It starts with the original Max Fleischer Superman cartoons. + +**
Re-introducing Superman, from the original 1940s serial cartoons.
** + + + +>A total of seventeen shorts were produced. They were originally produced by [Fleischer Studios](https://en.wikipedia.org/wiki/Fleischer_Studios), releasing the pilot and eight cartoons in 1941 and 1942 before being taken over in May by [Famous Studios](https://en.wikipedia.org/wiki/Famous_Studios), a successor company, who produced eight more in 1942 and 1943. - [Wikipedia](https://en.wikipedia.org/wiki/Superman_(1940s_cartoons)) + +These are true gems in the hall of film history. Check out the first nine episodes today: + +- [*Superman* (a.k.a. *The Mad Scientist*)](lbry://superman1940-e1) +- [*The Mechanical Monsters*](lbry://superman1940-e2) +- [*Billion Dollar Limited*](lbry://superman1940-e3) +- [*The Arctic Giant*](lbry://superman1940-e4) +- [*The Bulleteers*](lbry://superman1940-e5) +- [*The Magnetic Telescope*](lbry://superman1940-e6) +- [*Electric Earthquake*](lbry://superman1940-e7) +- [*Volcano*](lbry://superman1940-e8) +- [*Terror on the Midway*](lbry://superman1940-e9) + +**Not on LBRY yet?** [Get an invite here](https://lbry.io/get). Know some great public domain content to share? Have high quality scans? Email reilly@lbry.io for a trip to the front of the line. diff --git a/content/news/71-credit-policy-third-quarter-report.md b/content/news/71-credit-policy-third-quarter-report.md new file mode 100644 index 00000000..6002baff --- /dev/null +++ b/content/news/71-credit-policy-third-quarter-report.md @@ -0,0 +1,31 @@ +--- +author: lbry +title: 'Credit Policy and 3rd Quarter Credit Report' +date: '2016-10-05 00:06:18' +cover: 'lbry-3d-1000.png' +--- +As Mike Vine explained in [our recent FAQ](https://lbry.io/news/reddit-ama-answers#whatis), LBRY-the-protocol is quite different from LBRY-the-company. With the launch of LBRY’s live blockchain, the company created three reserve pools of credits to serve specific purposes. LBRY, Inc. will administer these reserves to promote the use and growth of the protocol. + +**The three reserve funds:** + +- **Community Fund:** (200M credits) Reward early adopters, new users, and community contributions. +- **Institutional Fund:** (100M credits) For strategic partnerships or assistance with charities, non-profits, and other institutions. +- **Operational Fund:** (100M credits) To allow LBRY, Inc. to function and profit. + +**LBRY, Inc. is committed to transparency in our use of these funds.** You can learn more about the purposes of these funds and track the public wallets associated with them on our Credit Policy page: + +[**LBRY Credit Policy**](https://lbry.io/faq/credit-policy) + +LBRY, Inc. publishes a Quarterly Credit Report that details all expenditures from these three funds and forecasts anticipated expenditures over the following quarter. + +The [Third Quarter 2016 Report](https://lbry.io/faq/quarterly-report-3q-2016) is now available to review. + +**Highlights:** + +- All fund expenditures were substantially below estimates. +- 276,778 LBC were distributed from the [Community Fund](https://docs.google.com/spreadsheets/d/1zPG58YuLPqpB3yzypntRWouoEVc4saDOifpnvnwS8Rc/edit?ts=57f28d0e#gid=0) to alpha testers, beta testers, community managers, and [bounty hunters](https://lbry.io/bounty). +- 100,000 LBC were distributed from the Operational Fund to ShapeShift to provide liquidity on their platform. +- No institutional credits were moved or spent. +- No anticipated sales from operational reserve until at least the Second Quarter 2017. + +**Not on LBRY yet?** [Get an invite here](https://lbry.io/get). Know some great public domain content to share? Have high quality scans? Email reilly@lbry.io for a trip to the front of the line. diff --git a/content/news/72-sporthocking.md b/content/news/72-sporthocking.md new file mode 100644 index 00000000..8ded45be --- /dev/null +++ b/content/news/72-sporthocking.md @@ -0,0 +1,28 @@ +--- +author: reilly +title: 'Will the Real Slim Shady Please Sit Down' +date: '2016-10-06 00:06:00' +cover: 'sporthock-banner.jpg' +--- +One of the great things about working at LBRY is discovering entire worlds and niches that defy categorization. + +Have you heard of Sporthocking? Me neither. Resident LBRY superfan and sporthocker Trenton Rawdon introduced us to the sport. And we haven’t stood up since. + +Hocker is the German word for stool. So Sporthocker literally means SportStool. + + + +In his first video, [TJ in Berlin](lbry://sporthock-berlin), Trenton “TJ” Rawdon shows us various tricks and moves taken from disciplines such as juggling, breakdancing, board sports and acrobatics. + + + +Inspired by guys freestyling tricks with homemade stools in the German city of Kiel, the Landschutz brothers designed the first Sporthocker prototype in 2006. Today, they own a Sporthocker brand and product design company called [Salzig in Berlin, Germany](http://www.sporthocker.com/en/). They’ve travelled two European tours and hold annual events for the budding sport. + +This is a sport worth checking out. See the tour of Europe today: + +- [*Hock Tour Madrid*](lbry://sporthock-madrid) +- [*Hock Tour Rome*](lbry://sporthock-rome) +- [*Hock Tour Paris*](lbry://sporthock-paris) +- [*Hock Tour Rotterdam*](lbry://sporthock-rotterdam) + +**Not on LBRY yet?** [Get an invite here](https://lbry.io/get). Want to share your niche with the world? Email reilly@lbry.io for a trip to the front of the line. diff --git a/posts/press-kit.md b/content/press-kit.md similarity index 84% rename from posts/press-kit.md rename to content/press-kit.md index 17328dbc..87f6449a 100644 --- a/posts/press-kit.md +++ b/content/press-kit.md @@ -14,14 +14,19 @@ LBRY uses blockchain technology to provide a new way for people to publish and s - [LBRY in 100 Seconds (Video)](https://www.youtube.com/watch?v=qkUA0vTWM7g) - [How Does LBRY Work, Exactly?](https://lbry.io/news/introducing-lbry-the-bitcoin-of-content) - [Why Doesn't LBRY Just Use Bitcoin?](https://lbry.io/news/why-doesnt-lbry-just-use-bitcoin) + +###Official Press Releases + +- [LBRY Announces $500K Raise to Build the First Community-Controlled YouTube Alternative](https://lbry.io/press/500k-fundraising-round-pillar-vc.md), **September 7, 2016** ### Media Mentions +- [Blockchain content sharing app LBRY declares independent from Google, Apple and Amazon](http://www.ibtimes.co.uk/blockchain-content-sharing-app-lbry-declares-independent-google-apple-amazon-1568755), **International Business Times, July 2016** - [LBRY and decentralised apps - an interview with Jeremy Kauffman](http://goodtechnologyproject.org/blog/2016/02/07/lbry-and-decentralised-apps-an-interview-with-jeremy-kauffman/), **Good Technology Project, February 2016** - [The Appcoin Revolution: Interview with Mike Vine of LBRY](http://cointelegraph.com/news/the-appcoin-revolution-interview-with-mike-vine-of-lbry), **CoinTelegraph, February 2016** - [Alexandria vs LBRY - Which will be the file sharing application of the next generation?](http://bravenewcoin.com/news/alexandria-vs-lbry-which-will-be-the-file-sharing-application-of-the-next-generation/), **BraveNewCoin, December 2015** - [LBRY: The Lovechild of Bitcoin, BitTorrent & Storj](http://cointelegraph.com/news/lbry-the-lovechild-of-bitcoin-bittorrent-storj), **CoinTelegraph, October 2015** -- [This New Hampshire Startup's Plan to Fight Netflix is Equal Parts BitTorrent and Bitcoin](http://bostinno.streetwise.co/2015/09/18/bitcoin-startups-lbry-combines-bittorrent-and-bitcoin-to-fight-netflix/), **Bostinno, September 2015** +- [This New Hampshire Startup's Plan to Fight Netflix is Equal Parts BitTorrent and Bitcoin](http://bostinno.streetwise.co/2015/09/18/bitcoin-startups-lbry-combines-bittorrent-and-bitcoin-to-fight-netflix/), **BostInno, September 2015** ### Contact & Social Media diff --git a/content/press/500k-fundraising-round-pillar-vc.md b/content/press/500k-fundraising-round-pillar-vc.md new file mode 100644 index 00000000..b2e282c3 --- /dev/null +++ b/content/press/500k-fundraising-round-pillar-vc.md @@ -0,0 +1,27 @@ +--- +title: LBRY Announces $500K Raise to Build the First Community-Controlled YouTube Alternative +--- +September 7, 2016 + +MANCHESTER, NH – LBRY, a community-controlled content marketplace, today announced a $500K fundraising round. The round is led by Pillar, a Boston-based venture capital firm that focuses on transformative technologies. The company will use this capital primarily to expand its staff and fund development toward a full public release of its consumer app. LBRY’s ultimate goal is for users to have access to every film, song, book, and app ever made – on any device. + +LBRY’s marketplace was science-fiction material just a few short years ago. It was the emergence of Bitcoin and its blockchain technology that laid the groundwork for LBRY’s platform. Using this technology, LBRY enables fans to pay creators directly without relying on an intermediary like Apple, Amazon, or Google. People across the world are all able to use the same system to store and view content, without a single central point of control. + +In the wake of the [recent controversy](http://observer.com/2016/09/youtube-phillyd-eric-schiffer/) around YouTube censoring its content creators for ad dollars, LBRY offers to fill the void with a service that, by design, responds to standards set by the community – not advertisers. With over 125,000 people on the company’s beta waitlist, there is clearly strong demand for an alternative. + +LBRY was founded in 2015 by repeat entrepreneur Jeremy Kauffman, CEO. The company’s management team includes Michael Zargham, CTO, Mike Vine, Head of Marketing, and Josh Finer, COO/CFO, an MBA with deep roots in the cryptocurrency community. There are a total of 11 people on the LBRY team, and the company is headquartered in New Hampshire, with additional team members located in Philadelphia, Connecticut, New Orleans, Massachusetts, and California. + +“We have always said that LBRY will be built no matter the financial situation,” explained co-founder Mike Vine. “This funding allows us to accelerate our efforts. The ambitions behind LBRY are grand. Any company now serving you media – whether that is a streaming subscription or pay-per-download – is vulnerable to a market that connects creators to fans without taking a cut. The essence of entrepreneurship is finding a way to deliver greater quality at a lower price than anything that exists. We’ve found a way.” + +**About LBRY** + +LBRY is a content-sharing and publishing platform that is decentralized and controlled by its users. It allows content creators to post their works to the hosting network, set their price per view/download, and collect payment. LBRY is an open-source protocol, as opposed to a centralized service, so there is no entity to take a “cut” of transactions or change the terms in an attempt to monetize the product. It’s like a new extension of the internet for delivering all media – films, ebooks, songs, and apps – from creators directly to consumers with radical efficiency. + +**About Pillar** + +Pillar invests in exceptional entrepreneurs pursuing transformative technologies that leverage machine intelligence. Founded in 2016, the firm partners with entrepreneurs by providing access to critical resources companies need to scale, including enterprise relationships, PR/marketing, talent and leadership development. Pillar is headquartered in Boston. For more information, please visit [www.pillar.vc](http://pillar.vc/). + +**Contact:** +Mike Vine, Evangelist, [mike@lbry.io](mailto:mike@lbry.io), 888-723-7679 + +**Press Kit:** [http://www.lbry.io/press-kit](http://www.lbry.io/press-kit) diff --git a/controller/Actions.class.php b/controller/Actions.class.php index 7f54a6fa..44959bd6 100644 --- a/controller/Actions.class.php +++ b/controller/Actions.class.php @@ -1,39 +1,5 @@ "frame-ancestors 'none'", - 'X-Frame-Options' => 'DENY', - 'X-XSS-Protection'=> '1', - ]; - - if (IS_PRODUCTION) + if (IS_PRODUCTION && function_exists('newrelic_name_transaction')) { - $defaultHeaders['Strict-Transport-Security'] = 'max-age=31536000'; + newrelic_name_transaction(Request::getMethod() . ' ' . strtolower($uri)); } - static::sendHeaders(array_merge($defaultHeaders, $headers)); + $viewAndParams = static::execute(Request::getMethod(), $uri); + $viewTemplate = $viewAndParams[0]; + $viewParameters = $viewAndParams[1] ?? []; + if (!IS_PRODUCTION && isset($viewAndParams[2])) + { + throw new Exception('use response::setheader instead of returning headers'); + } if ($viewTemplate === null) { - return ''; + return; } if (!$viewTemplate) { - throw new LogicException('All execute methods must return a template.'); + throw new LogicException('All execute methods must return a template or NULL.'); } $layout = !(isset($viewParameters['_no_layout']) && $viewParameters['_no_layout']); unset($viewParameters['_no_layout']); - $layoutParams = isset($viewParameters[View::LAYOUT_PARAMS]) ? $viewParameters[View::LAYOUT_PARAMS] : []; + $layoutParams = $viewParameters[View::LAYOUT_PARAMS] ?? []; unset($viewParameters[View::LAYOUT_PARAMS]); $content = View::render($viewTemplate, $viewParameters + ['fullPage' => true]); - echo $layout ? View::render('layout/basic', ['content' => $content] + $layoutParams) : $content; + Response::setContent($layout ? View::render('layout/basic', ['content' => $content] + $layoutParams) : $content); + Response::setDefaultSecurityHeaders(); + if (Request::isGzipAccepted()) + { + Response::gzipContentIfNotDisabled(); + } + Response::send(); } catch (StopException $e) { @@ -52,93 +53,101 @@ class Controller } } - public static function execute($uri) + public static function execute($method, $uri) { - switch($uri) + $router = static::getRouterWithRoutes(); + try { - case '/': - return ContentActions::executeHome(); - case '/get': - case '/windows': - case '/ios': - case '/android': - case '/linux': - case '/osx': - return DownloadActions::executeGet(); - case '/postcommit': - return OpsActions::executePostCommit(); - case '/log-upload': - return OpsActions::executeLogUpload(); - case '/list-subscribe': - return MailActions::executeListSubscribe(); - case '/press-kit.zip': - return ContentActions::executePressKit(); - case '/roadmap': - return ContentActions::executeRoadmap(); - case '/LBRY-deck.pdf': - case '/deck.pdf': - return static::redirect('https://www.dropbox.com/s/0xj4vgucsbi8rtv/lbry-deck.pdf?dl=1'); - case '/pln.pdf': - case '/plan.pdf': - return static::redirect('https://www.dropbox.com/s/uevjrwnyr672clj/lbry-pln.pdf?dl=1'); - case '/lbry-osx-latest.dmg': - case '/lbry-linux-latest.deb': - case '/dl/lbry_setup.sh': - return static::redirect('/get', 301); - case '/get/lbry.dmg': - return static::redirect(Github::getDownloadUrl(Os::OS_OSX) ?: '/get'); - case '/get/lbry.deb': - return static::redirect(Github::getDownloadUrl(Os::OS_LINUX) ?: '/get'); - case '/art': - return static::redirect('/what', 301); - case '/why': - case '/feedback': - return static::redirect('/learn', 301); - case '/faq/when-referral-payouts': - return static::redirect('/faq/referrals', 301); + $dispatcher = new Routing\Dispatcher($router->getData()); + return $dispatcher->dispatch($method, $uri); } - - $newsPattern = '#^' . ContentActions::URL_NEWS . '(/|$)#'; - if (preg_match($newsPattern, $uri)) + catch (\Routing\HttpRouteNotFoundException $e) { - $slug = preg_replace($newsPattern, '', $uri); - if ($slug == ContentActions::RSS_SLUG) + return NavActions::execute404(); + } + catch (\Routing\HttpMethodNotAllowedException $e) + { + Response::setStatus(405); + Response::setHeader('Allow', implode(', ', $e->getAllowedMethods())); + return ['page/405']; + } + } + + protected static function getRouterWithRoutes(): \Routing\RouteCollector + { + $router = new Routing\RouteCollector(); + + $router->get(['/', 'home'], 'ContentActions::executeHome'); + + $router->get(['/get', 'get'], 'DownloadActions::executeGet'); + $router->get(['/windows', 'get-windows'], 'DownloadActions::executeGet'); + $router->get(['/linux', 'get-linux'], 'DownloadActions::executeGet'); + $router->get(['/osx', 'get-osx'], 'DownloadActions::executeGet'); + $router->get(['/android', 'get-android'], 'DownloadActions::executeGet'); + $router->get(['/ios', 'get-ios'], 'DownloadActions::executeGet'); + $router->get('/roadmap', 'ContentActions::executeRoadmap'); + + $router->get(['/press-kit.zip', 'press-kit'], 'ContentActions::executePressKit'); + + $router->post('/postcommit', 'OpsActions::executePostCommit'); + $router->post('/log-upload', 'OpsActions::executeLogUpload'); + + $router->any('/list/subscribe', 'MailActions::executeSubscribe'); + $router->get('/list/confirm/{hash}', 'MailActions::executeConfirm'); + + $router->post('/set-culture', 'i18nActions::setCulture'); + + $permanentRedirects = [ + '/lbry-osx-latest.dmg' => '/get', + '/lbry-linux-latest.deb' => '/get', + '/dl/lbry_setup.sh' => '/get', + '/art' => '/what', + '/why' => '/learn', + '/feedback' => '/learn', + '/faq/when-referral-payouts' => '/faq/referrals', + '/news/meet-the-lbry-founders' => '/team', + '/join-list' => '/list/subscribe', + ]; + + $tempRedirects = [ + '/LBRY-deck.pdf' => 'https://www.dropbox.com/s/0xj4vgucsbi8rtv/lbry-deck.pdf?dl=1', + '/deck.pdf' => 'https://www.dropbox.com/s/0xj4vgucsbi8rtv/lbry-deck.pdf?dl=1', + '/pln.pdf' => 'https://www.dropbox.com/s/uevjrwnyr672clj/lbry-pln.pdf?dl=1', + '/plan.pdf' => 'https://www.dropbox.com/s/uevjrwnyr672clj/lbry-pln.pdf?dl=1', + '/get/lbry.dmg' => GitHub::getDownloadUrl(Os::OS_OSX) ?: '/get', + '/get/lbry.deb' => GitHub::getDownloadUrl(Os::OS_LINUX) ?: '/get', + '/get/lbry.msi' => GitHub::getDownloadUrl(Os::OS_WINDOWS) ?: '/get', + ]; + + foreach ([302 => $tempRedirects, 301 => $permanentRedirects] as $code => $redirects) + { + foreach ($redirects as $src => $target) { - return ContentActions::executeRss(); + $router->any($src, function () use ($target, $code) { return static::redirect($target, $code); }); } - return $slug ? ContentActions::executeNewsPost($uri) : ContentActions::executeNews(); } - $faqPattern = '#^' . ContentActions::URL_FAQ . '(/|$)#'; - if (preg_match($faqPattern, $uri)) - { - $slug = preg_replace($faqPattern, '', $uri); - return $slug ? ContentActions::executeFaqPost($uri) : ContentActions::executeFaq(); - } + $router->get([ContentActions::URL_NEWS . '/{slug:c}?', 'news'], 'ContentActions::executeNews'); + $router->get([ContentActions::URL_FAQ . '/{slug:c}?', 'faq'], 'ContentActions::executeFaq'); + $router->get([ContentActions::URL_BOUNTY . '/{slug:c}?', 'bounty'], 'ContentActions::executeBounty'); + $router->get([ContentActions::URL_PRESS . '/{slug:c}', 'press'], 'ContentActions::executePress'); - $bountyPattern = '#^' . BountyActions::URL_BOUNTY_LIST . '(/|$)#'; - if (preg_match($bountyPattern, $uri)) - { - $slug = preg_replace($bountyPattern, '', $uri); - return $slug ? BountyActions::executeShow($uri) : BountyActions::executeList($uri); - } + $router->any(['/signup{whatever}?', 'signup'], 'DownloadActions::executeSignup'); - $accessPattern = '#^/signup#'; - if (preg_match($accessPattern, $uri)) + $router->get('/{slug}', function (string $slug) { - return DownloadActions::executeSignup(); - } + if (View::exists('page/' . $slug)) + { + Response::enableHttpCache(); + return ['page/' . $slug, []]; + } + else + { + return NavActions::execute404(); + } + }); - - $noSlashUri = ltrim($uri, '/'); - if (View::exists('page/' . $noSlashUri)) - { - return ['page/' . $noSlashUri, []]; - } - else - { - return ['page/404', [], [static::HEADER_STATUS => 404]]; - } + return $router; } public static function redirect($url, $statusCode = 302) @@ -150,88 +159,26 @@ class Controller $url = str_replace('&', '&', $url); - $headers = [static::HEADER_STATUS => $statusCode]; - + Response::setStatus($statusCode); if ($statusCode == 201 || ($statusCode >= 300 && $statusCode < 400)) { - $headers['Location'] = $url; + Response::setHeader(Response::HEADER_LOCATION, $url); } - return ['internal/redirect', ['url' => $url], $headers]; + return ['internal/redirect', ['url' => $url]]; } - protected static function sendHeaders(array $headers) + public static function queueToRunAfterResponse(callable $fn) { - if (isset($headers[static::HEADER_STATUS])) - { - $status = 'HTTP/1.0 ' . $headers[static::HEADER_STATUS] . ' ' . static::getStatusTextForCode($headers[static::HEADER_STATUS]); - header($status); - - if (substr(php_sapi_name(), 0, 3) == 'cgi') - { - // fastcgi servers cannot send this status information because it was sent by them already due to the HTT/1.0 line - // so we can safely unset them. see ticket #3191 - unset($headers[static::HEADER_STATUS]); - } - } - - foreach($headers as $name => $value) - { - header($name . ': ' . $value); - } + static::$queuedFunctions[] = $fn; } - public static function getStatusTextForCode($code) + public static function shutdown() { - $statusTexts = [ - '100' => 'Continue', - '101' => 'Switching Protocols', - '200' => 'OK', - '201' => 'Created', - '202' => 'Accepted', - '203' => 'Non-Authoritative Information', - '204' => 'No Content', - '205' => 'Reset Content', - '206' => 'Partial Content', - '300' => 'Multiple Choices', - '301' => 'Moved Permanently', - '302' => 'Found', - '303' => 'See Other', - '304' => 'Not Modified', - '305' => 'Use Proxy', - '306' => '(Unused)', - '307' => 'Temporary Redirect', - '400' => 'Bad Request', - '401' => 'Unauthorized', - '402' => 'Payment Required', - '403' => 'Forbidden', - '404' => 'Not Found', - '405' => 'Method Not Allowed', - '406' => 'Not Acceptable', - '407' => 'Proxy Authentication Required', - '408' => 'Request Timeout', - '409' => 'Conflict', - '410' => 'Gone', - '411' => 'Length Required', - '412' => 'Precondition Failed', - '413' => 'Request Entity Too Large', - '414' => 'Request-URI Too Long', - '415' => 'Unsupported Media Type', - '416' => 'Requested Range Not Satisfiable', - '417' => 'Expectation Failed', - '419' => 'Authentication Timeout', - '422' => 'Unprocessable Entity', - '426' => 'Upgrade Required', - '429' => 'Too Many Requests', - '500' => 'Internal Server Error', - '501' => 'Not Implemented', - '502' => 'Bad Gateway', - '503' => 'Service Unavailable', - '504' => 'Gateway Timeout', - '505' => 'HTTP Version Not Supported', - ]; - - return isset($statusTexts[$code]) ? $statusTexts[$code] : null; + while ($fn = array_shift(static::$queuedFunctions)) + { + call_user_func($fn); + } } } diff --git a/controller/Request.class.php b/controller/Request.class.php new file mode 100644 index 00000000..ab4b1091 --- /dev/null +++ b/controller/Request.class.php @@ -0,0 +1,108 @@ + ''] + Post::collectMetadata($allBounties, 'category'); - $allStatuses = ['' => ''] + array_merge(Post::collectMetadata($allBounties, 'status'), ['complete' => 'unavailable']); - - $selectedStatus = static::param('status', 'available'); - $selectedCategory = static::param('category'); - - $filters = array_filter([ - 'category' => $selectedCategory && isset($allCategories[$selectedCategory]) ? $selectedCategory : null, - 'status' => $selectedStatus && isset($allStatuses[$selectedStatus]) ? $selectedStatus : null - ]); - - $bounties = $filters ? Post::filter($allBounties, $filters) : $allBounties; - - uasort($bounties, function($postA, $postB) { - $metadataA = $postA->getMetadata(); - $metadataB = $postB->getMetadata(); - $awardA = strpos('-', $metadataA['award']) !== false ? rtrim(explode('-', $metadataA['award'])[0], '+') : $metadataA['award']; - $awardB = strpos('-', $metadataB['award']) !== false ? rtrim(explode('-', $metadataB['award'])[0], '+') : $metadataB['award']; - if ($awardA != $awardB) - { - return $awardA > $awardB ? -1 : 1; - } - return $metadataA['title'] < $metadataB['title'] ? -1 : 1; - }); - - return ['bounty/list', [ - 'bounties' => $bounties, - 'categories' => $allCategories, - 'statuses' => $allStatuses, - 'selectedCategory' => $selectedCategory, - 'selectedStatus' => $selectedStatus - ]]; - } - - public static function executeShow($relativeUri) - { - list($metadata, $postHtml) = View::parseMarkdown(ROOT_DIR . '/posts/' . $relativeUri . '.md'); - if (!$postHtml) - { - return ['page/404', []]; - } - return ['bounty/show', [ - 'postHtml' => $postHtml, - 'metadata' => $metadata - ]]; - } -} \ No newline at end of file diff --git a/controller/action/ContentActions.class.php b/controller/action/ContentActions.class.php index 48b865f6..65966291 100644 --- a/controller/action/ContentActions.class.php +++ b/controller/action/ContentActions.class.php @@ -1,119 +1,196 @@ $posts, + View::LAYOUT_PARAMS => [ + 'showRssLink' => true + ] + ]]; + } + + if ($slug == static::SLUG_RSS) + { + $posts = Post::find(static::VIEW_FOLDER_NEWS, Post::SORT_DATE_DESC); + Response::setHeader(Response::HEADER_CONTENT_TYPE, 'text/xml; charset=utf-8'); + return ['content/rss', [ + 'posts' => array_slice($posts, 0, 10), + '_no_layout' => true + ]]; + } + + try + { + $post = Post::load(static::SLUG_NEWS . '/' . ltrim($slug, '/')); + } + catch (PostNotFoundException $e) + { + return NavActions::execute404(); + } + + return ['content/news-post', [ + 'post' => $post, + View::LAYOUT_PARAMS => [ + 'showRssLink' => true + ] + ]]; + } + + public static function executeFaq(string $slug = null): array + { + Response::enableHttpCache(); + + if (!$slug) + { + $allPosts = Post::find(static::VIEW_FOLDER_FAQ); + + $allCategories = array_merge(['' => ''] + Post::collectMetadata($allPosts, 'category'), [ + 'getstarted' => 'Getting Started', + 'install' => 'Installing LBRY', + 'running' => 'Running LBRY', + 'wallet' => 'The LBRY Wallet', + 'hosting' => 'Hosting Content', + 'mining' => 'Mining LBC', + 'policy' => 'Policies', + 'developer' => 'Developers', + 'other' => 'Other Questions', + ]); + $selectedCategory = Request::getParam('category'); + $filters = array_filter([ + 'category' => $selectedCategory && isset($allCategories[$selectedCategory]) ? $selectedCategory : null, + ]); + + asort($allCategories); + + $posts = $filters ? Post::filter($allPosts, $filters) : $allPosts; + + + $groups = array_fill_keys(array_keys($allCategories), []); + + foreach ($posts as $post) + { + $groups[$post->getCategory()][] = $post; + } + + return ['content/faq', [ + 'categories' => $allCategories, + 'selectedCategory' => $selectedCategory, + 'postGroups' => $groups + ]]; + } + + try + { + $post = Post::load(static::SLUG_FAQ . '/' . ltrim($slug, '/')); + } + catch (PostNotFoundException $e) + { + return NavActions::execute404(); + } + return ['content/faq-post', ['post' => $post]]; + } + + public static function executePress(string $slug = null): array + { + Response::enableHttpCache(); + try + { + $post = Post::load(static::SLUG_PRESS . '/' . ltrim($slug, '/')); + } + catch (PostNotFoundException $e) + { + return NavActions::execute404(); + } + return ['content/press-post', ['post' => $post]]; + } + + public static function executeBounty(string $slug = null): array + { + Response::enableHttpCache(); + + if ($slug) + { + list($metadata, $postHtml) = View::parseMarkdown(ContentActions::VIEW_FOLDER_BOUNTY . '/' . $slug . '.md'); + if (!$postHtml) + { + return NavActions::execute404(); + } + + return ['bounty/show', [ + 'postHtml' => $postHtml, + 'metadata' => $metadata + ]]; + } + + $allBounties = Post::find(static::CONTENT_DIR . '/bounty'); + + $allCategories = ['' => ''] + Post::collectMetadata($allBounties, 'category'); + $allStatuses = ['' => ''] + array_merge(Post::collectMetadata($allBounties, 'status'), ['complete' => 'unavailable']); + + $selectedStatus = Request::getParam('status', 'available'); + $selectedCategory = Request::getParam('category'); - $allCategories = array_merge(['' => ''] + Post::collectMetadata($allPosts, 'category'), [ - 'getstarted' => 'Getting Started', - 'install' => 'Installing LBRY', - 'running' => 'Running LBRY', - 'wallet' => 'The LBRY Wallet', - 'hosting' => 'Hosting Content', - 'mining' => 'Mining LBC', - 'policy' => 'Policies', - 'developer' => 'Developers', - 'other' => 'Other Questions', - ]); - $selectedCategory = static::param('category'); $filters = array_filter([ 'category' => $selectedCategory && isset($allCategories[$selectedCategory]) ? $selectedCategory : null, + 'status' => $selectedStatus && isset($allStatuses[$selectedStatus]) ? $selectedStatus : null ]); - asort($allCategories); + $bounties = $filters ? Post::filter($allBounties, $filters) : $allBounties; - $posts = $filters ? Post::filter($allPosts, $filters) : $allPosts ; + uasort($bounties, function($postA, $postB) { + $metadataA = $postA->getMetadata(); + $metadataB = $postB->getMetadata(); + $awardA = strpos('-', $metadataA['award']) !== false ? rtrim(explode('-', $metadataA['award'])[0], '+') : $metadataA['award']; + $awardB = strpos('-', $metadataB['award']) !== false ? rtrim(explode('-', $metadataB['award'])[0], '+') : $metadataB['award']; + if ($awardA != $awardB) + { + return $awardA > $awardB ? -1 : 1; + } + return $metadataA['title'] < $metadataB['title'] ? -1 : 1; + }); - - $groups = array_fill_keys(array_keys($allCategories), []); - - foreach($posts as $post) - { - $groups[$post->getCategory()][] = $post; - } - - return ['content/faq', [ + return ['bounty/list', [ + 'bounties' => $bounties, 'categories' => $allCategories, + 'statuses' => $allStatuses, 'selectedCategory' => $selectedCategory, - 'postGroups' => $groups + 'selectedStatus' => $selectedStatus ]]; } - public static function executeNews() - { - $posts = Post::find(static::VIEW_FOLDER_NEWS, Post::SORT_DATE_DESC); - return ['content/news', [ - 'posts' => $posts, - View::LAYOUT_PARAMS => [ - 'showRssLink' => true - ] - ]]; - } - - - public static function executeRss() - { - $posts = Post::find(static::VIEW_FOLDER_NEWS, Post::SORT_DATE_DESC); - return ['content/rss', [ - 'posts' => array_slice($posts, 0, 10), - '_no_layout' => true - ], [ - 'Content-Type' => 'text/xml; charset=utf-8' - ]]; - } - - public static function executeNewsPost($relativeUri) - { - try - { - $post = Post::load(ltrim($relativeUri, '/')); - } - catch (PostNotFoundException $e) - { - return ['page/404', []]; - } - return ['content/news-post', [ - 'post' => $post, - View::LAYOUT_PARAMS => [ - 'showRssLink' => true - ] - ]]; - } - - public static function executeFaqPost($relativeUri) - { - try - { - $post = Post::load(ltrim($relativeUri, '/')); - } - catch (PostNotFoundException $e) - { - return ['page/404', []]; - } - return ['content/faq-post', [ - 'post' => $post, - ]]; - } - - public static function executeRoadmap() { $cache = !isset($_GET['nocache']); @@ -141,10 +218,10 @@ class ContentActions extends Actions ]]; } - public static function executePressKit() + public static function executePressKit(): array { $zipFileName = 'lbry-press-kit-' . date('Y-m-d') . '.zip'; - $zipPath = tempnam('/tmp', $zipFileName); + $zipPath = tempnam('/tmp', $zipFileName); $zip = new ZipArchive(); $zip->open($zipPath, ZipArchive::OVERWRITE); @@ -166,63 +243,70 @@ class ContentActions extends Actions // // $zip->addFromString('press.html', $html); - foreach(glob(ROOT_DIR . '/web/img/press/*') as $productImgPath) + foreach (glob(ROOT_DIR . '/web/img/press/*') as $productImgPath) { $imgPathTokens = explode('/', $productImgPath); - $imgName = $imgPathTokens[count($imgPathTokens) - 1]; + $imgName = $imgPathTokens[count($imgPathTokens) - 1]; $zip->addFile($productImgPath, '/logo_and_product/' . $imgName); } - foreach(glob(ROOT_DIR . '/posts/bio/*.md') as $bioPath) + foreach (glob(ContentActions::CONTENT_DIR . '/bio/*.md') as $bioPath) { list($metadata, $bioHtml) = View::parseMarkdown($bioPath); $zip->addFile($bioPath, '/team_bios/' . $metadata['name'] . ' - ' . $metadata['role'] . '.txt'); } - foreach(array_filter(glob(ROOT_DIR . '/web/img/team/*.jpg'), function($path) { + foreach (array_filter(glob(ROOT_DIR . '/web/img/team/*.jpg'), function ($path) + { return strpos($path, 'spooner') === false; }) as $bioImgPath) { $imgPathTokens = explode('/', $bioImgPath); - $imgName = str_replace('644x450', 'lbry', $imgPathTokens[count($imgPathTokens) - 1]); + $imgName = str_replace('644x450', 'lbry', $imgPathTokens[count($imgPathTokens) - 1]); $zip->addFile($bioImgPath, '/team_photos/' . $imgName); } $zip->close(); + Response::enableHttpCache(); + Response::setDownloadHttpHeaders($zipFileName, 'application/zip', filesize($zipPath)); + return ['internal/zip', [ '_no_layout' => true, - 'zipPath' => $zipPath - ], [ - 'Content-Disposition' => 'attachment;filename=' . $zipFileName, - 'X-Content-Type-Options' => 'nosniff', - 'Content-Type' => 'application/zip', - 'Content-Length' => filesize($zipPath), + 'zipPath' => $zipPath ]]; } - public static function prepareBioPartial(array $vars) + public static function prepareBioPartial(array $vars): array { $person = $vars['person']; - $path = 'bio/' . $person . '.md'; + $path = 'bio/' . $person . '.md'; list($metadata, $bioHtml) = View::parseMarkdown($path); $relativeImgSrc = '/img/team/' . $person . '-644x450.jpg'; - $imgSrc = file_exists(ROOT_DIR . '/web' . $relativeImgSrc) ? $relativeImgSrc : '/img/team/spooner-644x450.jpg'; + $imgSrc = file_exists(ROOT_DIR . '/web' . $relativeImgSrc) ? $relativeImgSrc : '/img/team/spooner-644x450.jpg'; return $vars + $metadata + [ - 'imgSrc' => $imgSrc, - 'bioHtml' => $bioHtml, + 'imgSrc' => $imgSrc, + 'bioHtml' => $bioHtml, 'orientation' => 'vertical' ]; } - public static function preparePostAuthorPartial(array $vars) + public static function preparePostAuthorPartial(array $vars): array { $post = $vars['post']; return [ - 'authorName' => $post->getAuthorName(), - 'photoImgSrc' => $post->getAuthorPhoto(), + 'authorName' => $post->getAuthorName(), + 'photoImgSrc' => $post->getAuthorPhoto(), 'authorBioHtml' => $post->getAuthorBioHtml() ]; } + + public static function preparePostListPartial(array $vars): array + { + $count = $vars['count'] ?? 3; + return [ + 'posts' => array_slice(Post::find(static::VIEW_FOLDER_NEWS, Post::SORT_DATE_DESC), 0, $count) + ]; + } } diff --git a/controller/action/DownloadActions.class.php b/controller/action/DownloadActions.class.php index f74fffa3..e432f76d 100644 --- a/controller/action/DownloadActions.class.php +++ b/controller/action/DownloadActions.class.php @@ -4,7 +4,7 @@ class DownloadActions extends Actions { public static function executeGet() { - $email = static::param('e'); + $email = Request::getParam('e'); $user = []; if ($email) @@ -32,7 +32,7 @@ class DownloadActions extends Actions if (!Session::get(Session::KEY_DOWNLOAD_ALLOWED)) { - return ['download/get', ['os' => static::guessOs()]]; + return ['download/get']; } $osChoices = Os::getAll(); @@ -58,8 +58,8 @@ class DownloadActions extends Actions public static function executeSignup() { - $email = static::param('email'); - $code = static::param('code'); + $email = Request::getParam('email'); + $code = Request::getParam('code'); if (!$email || !filter_var($email, FILTER_VALIDATE_EMAIL)) { @@ -67,15 +67,9 @@ class DownloadActions extends Actions } else { - $referrerId = static::param('referrer_id'); + $referrerId = Request::getParam('referrer_id'); $failure = false; - try - { - MailActions::subscribeToMailchimp($email, Mailchimp::LIST_GENERAL_ID); - } - catch (MailchimpSubscribeException $e) - { - } + Mailgun::sendSubscriptionConfirmation($email); try { @@ -119,9 +113,9 @@ class DownloadActions extends Actions public static function prepareSignupPartial(array $vars) { return $vars + [ - 'defaultEmail' => static::param('e'), + 'defaultEmail' => Request::getParam('e'), 'allowInviteCode' => true, - 'referralCode' => static::param('r', '') + 'referralCode' => Request::getParam('r', '') ]; } @@ -152,7 +146,7 @@ class DownloadActions extends Actions protected static function guessOs() { //if exact OS is requested, use that - $uri = strtok($_SERVER['REQUEST_URI'], '?'); + $uri = Request::getRelativeUri(); foreach (Os::getAll() as $os => $osChoice) { if ($osChoice[0] == $uri) @@ -161,13 +155,13 @@ class DownloadActions extends Actions } } - if (static::isForRobot()) + if (Request::isRobot()) { return null; } //otherwise guess from UA - $ua = $_SERVER['HTTP_USER_AGENT']; + $ua = Request::getUserAgent(); if (stripos($ua, 'OS X') !== false) { return strpos($ua, 'iPhone') !== false || stripos($ua, 'iPad') !== false ? Os::OS_IOS : Os::OS_OSX; @@ -180,6 +174,5 @@ class DownloadActions extends Actions { return Os::OS_WINDOWS; } - return null; } } diff --git a/controller/action/MailActions.class.php b/controller/action/MailActions.class.php index 6897eea9..b8ac3b2c 100644 --- a/controller/action/MailActions.class.php +++ b/controller/action/MailActions.class.php @@ -1,84 +1,63 @@ __('email.subscribe_send_failed')]]; + } + + return ['mail/subscribe', ['subscribeSuccess' => true, 'nextUrl' => $nextUrl]]; } - public static function subscribeToMailchimp($email, $listId, array $mergeFields = []) + public static function executeConfirm(string $hash) { - $mcApi = new Mailchimp(); - $success = $mcApi->listSubscribe($listId, $email, $mergeFields, 'html', false); - if (!$success) + $email = Mailgun::checkConfirmHashAndGetEmail($hash); + if ($email === null) { - throw new MailchimpSubscribeException($mcApi->errorMessage ?: __('Something went wrong adding you to the list.')); + return ['mail/subscribe', ['error' => __('email.invalid_confirm_hash')]]; } - return true; + + $outcome = Mailgun::addToMailingList(Mailgun::LIST_GENERAL, $email); + if ($outcome !== true) + { + return ['mail/subscribe', ['error' => $outcome]]; + } + + return ['mail/subscribe', ['confirmSuccess' => true, 'learnFooter' => true]]; } - public static function prepareJoinListPartial(array $vars) + + + public static function prepareSubscribeFormPartial(array $vars) { - $vars['listSig'] = md5(serialize($vars)); - $vars += ['btnClass' => 'btn-primary', 'returnUrl' => $_SERVER['REQUEST_URI']]; + $vars += ['btnClass' => 'btn-primary', 'returnUrl' => Request::getRelativeUri()]; - if (Session::get(Session::KEY_LIST_SUB_SIGNATURE) == $vars['listSig']) - { - $vars['error'] = Session::get(Session::KEY_LIST_SUB_ERROR); - Session::unsetKey(Session::KEY_LIST_SUB_ERROR); - - $vars['success'] = Session::get(Session::KEY_LIST_SUB_SUCCESS) ? __('Great success! Welcome to LBRY.') : false; - $vars['fbEvent'] = Session::get(Session::KEY_LIST_SUB_FB_EVENT) ?: 'Lead'; - Session::unsetKey(Session::KEY_LIST_SUB_SUCCESS); - Session::unsetKey(Session::KEY_LIST_SUB_FB_EVENT); - Session::unsetKey(Session::KEY_LIST_SUB_SIGNATURE); - } - else - { - $vars['success'] = false; - } + $vars['error'] = Session::get(Session::KEY_LIST_SUB_ERROR); + Session::unsetKey(Session::KEY_LIST_SUB_ERROR); return $vars; } diff --git a/controller/action/NavActions.class.php b/controller/action/NavActions.class.php index 7e84b5e0..8f91f5d1 100644 --- a/controller/action/NavActions.class.php +++ b/controller/action/NavActions.class.php @@ -1,10 +1,5 @@ false, 'showLearnFooter' => false ]; } public static function prepareGlobalItemsPartial(array $vars) { - $vars += ['selectedItem' => static::getNavUri()]; - return $vars; - } - - public static function prepareLearnFooterPartial(array $vars) - { - return $vars + [ - 'isDark' => true + return $vars += [ + 'selectedItem' => static::getNavUri(), + 'selectedCulture' => i18n::getLanguage() . '_' . i18n::getCountry(), + 'cultures' => i18n::getAllCultures() ]; } -} \ No newline at end of file + + public static function execute400(array $vars) + { + Response::setStatus(400); + return ['page/400', ['error' => $vars['error'] ?? null]]; + } + + public static function execute404() + { +// $uri = Request::getRelativeUri(); +// Controller::queueToRunAfterResponse(function() use($uri) { +// Slack::sendErrorIfProd('404 for url ' . $uri, false); +// }); + Response::setStatus(404); + return ['page/404']; + } +} diff --git a/controller/action/OpsActions.class.php b/controller/action/OpsActions.class.php index 22ab35af..b3508bbf 100644 --- a/controller/action/OpsActions.class.php +++ b/controller/action/OpsActions.class.php @@ -1,25 +1,36 @@ 'No payload']); + } + + $payload = json_decode($payload, true); if ($payload['ref'] === 'refs/heads/master') { - Actions::returnErrorIf(!isset($_SERVER['HTTP_X_HUB_SIGNATURE']), "HTTP header 'X-Hub-Signature' is missing."); + $sig = Request::getHttpHeader('X-Hub-Signature'); + if (!$sig) + { + return NavActions::execute400(['error' => "HTTP header 'X-Hub-Signature' is missing."]); + } - list($algo, $hash) = explode('=', $_SERVER['HTTP_X_HUB_SIGNATURE'], 2) + array('', ''); - Actions::returnErrorIf(!in_array($algo, hash_algos(), TRUE), 'Invalid hash algorithm "' . $algo . '"'); + list($algo, $hash) = explode('=', Request::getHttpHeader('X-Hub-Signature'), 2) + ['', '']; + if (!in_array($algo, hash_algos(), true)) + { + return NavActions::execute400(['error' => 'Invalid hash algorithm "' . htmlspecialchars($algo) . '"']); + } $rawPost = file_get_contents('php://input'); - $secret = Config::get('github_key'); - Actions::returnErrorIf($hash !== hash_hmac($algo, $rawPost, $secret), 'Hash does not match. "' . $secret . '"' . ' algo: ' . $algo . '$'); + $secret = Config::get('github_key'); + if ($hash !== hash_hmac($algo, $rawPost, $secret)) + { + return NavActions::execute400(['error' => 'Hash does not match.']); + } file_put_contents(ROOT_DIR . '/data/writeable/NEEDS_UPDATE', ''); } @@ -27,19 +38,19 @@ class OpsActions extends Actions return [null, []]; } - public static function executeLogUpload() + public static function executeLogUpload(): array { - $log = isset($_POST['log']) ? urldecode($_POST['log']) : null; - if (isset($_POST['name'])) + $log = Request::getPostParam('log') ? urldecode(Request::getPostParam('log')) : null; + if (Request::getPostParam('name')) { - $name = substr(trim(urldecode($_POST['name'])),0,50); + $name = substr(trim(urldecode(Request::getPostParam('name'))), 0, 50); } - elseif (isset($_POST['date'])) + elseif (Request::getPostParam('date')) { - $name = substr(trim(urldecode($_POST['date'])),0,20) . '_' . - substr(trim(urldecode($_POST['hash'])),0,20) . '_' . - substr(trim(urldecode($_POST['sys'])),0,50) . '_' . - substr(trim(urldecode($_POST['type'])),0,20); + $name = substr(trim(urldecode(Request::getPostParam('date'))), 0, 20) . '_' . + substr(trim(urldecode(Request::getPostParam('hash'))), 0, 20) . '_' . + substr(trim(urldecode(Request::getPostParam('sys'))), 0, 50) . '_' . + substr(trim(urldecode(Request::getPostParam('type'))), 0, 20); } else { @@ -48,17 +59,26 @@ class OpsActions extends Actions $name = preg_replace('/[^A-Za-z0-9_-]+/', '', $name); - Actions::returnErrorIf(!$log || !$name, "Required params: log, name"); + if (!$log || !$name) + { + return NavActions::execute400(['error' => "Required params: log, name"]); + } - $awsKey = Config::get('aws_log_access_key'); + $awsKey = Config::get('aws_log_access_key'); $awsSecret = Config::get('aws_log_secret_key'); - Actions::returnErrorIf(!$awsKey || !$awsSecret, "Missing AWS credentials"); + if (!$log || !$name) + { + throw new RuntimeException('Missing AWS credentials'); + } $tmpFile = tempnam(sys_get_temp_dir(), 'lbryinstalllog'); file_put_contents($tmpFile, $log); - Actions::returnErrorIf(filesize($tmpFile) > 1024*1024*2, "File is too large"); + if (filesize($tmpFile) > 1024 * 1024 * 2) + { + return NavActions::execute400(['error' => 'Log file is too large']); + } S3::$useExceptions = true; S3::setAuth($awsKey, $awsSecret); diff --git a/controller/action/i18nActions.class.php b/controller/action/i18nActions.class.php new file mode 100644 index 00000000..014fea47 --- /dev/null +++ b/controller/action/i18nActions.class.php @@ -0,0 +1,29 @@ +apt ordpkg
."
email:
address: Email
code: Invite Code
- disclaimer: You will receive 1-2 messages a month, only from LBRY, Inc. and only about LBRY. You can easily unsubscribe at any time.
+ confirm_email_body: Please confirm your subscription to LBRY updates by clicking the link below.
+ confirm_email_button: Confirm Subscription
+ confirm_email_subject: Confirm Your Subscription to LBRY
+ confirm_success: Great success! Welcome to LBRY.
+ disclaimer: You will receive 1-2 messages a month, only from LBRY Inc. and only about LBRY. You can easily unsubscribe at any time.
go: Go
+ invalid_confirm_hash: This link is expired or invalid. Please enter your email address again.
nocode: None, but I want in as soon as possible!
placeholder: someone@somewhere.com
+ return: Return to LBRY
subs: Subscribe
subscribe: Subscribe to our email list
+ subscribe_needs_confirm: We sent you an email with a confirmation link. Click the link to complete your subscription.
+ subscribe_send_failed: Something went wrong. Please enter your email address again.
+ unsubs: unsubscribe
+ unsubscribe: Don't want to stay on the bleeding edge of a content distribution revolution?
updates: Get Updates
yescode: "Yes"
global:
@@ -72,8 +82,7 @@ learn:
100: LBRY in 100 Seconds
art: Art in the Internet Age
essay: Read the Essay
- exchange: Bittrex Exchange
- exchange2: Poloniex Exchange
+ exchange_faq: Buy/Sell LBRY Credits
explore: Explore
explorer: Block Explorer
how: Learn how LBRY will forever improve how we create and share with one another.
@@ -94,6 +103,11 @@ news:
next: Next
prev: Previous
page:
+ badmethod: HTTP Method Not Allowed
+ badmethod_details: Did you GET when you should have POSTed? Or vice versa?
+ badrequest: Bad Request - Your browser sent a request that this server could not understand
+ badrequest_details: The client should not repeat the request without modifications.
+ error_contact_us: If this problem persists or requires immediate attention, please contact %email%.
faq:
back: Back to FAQ
header: Frequently Asked Questions
diff --git a/dev.sh b/dev.sh
index 415ebe73..318acd8a 100755
--- a/dev.sh
+++ b/dev.sh
@@ -1,3 +1,3 @@
#!/bin/bash
-php --server localhost:8000 --docroot web/ web/index.php
\ No newline at end of file
+php7.0 --server localhost:8000 --docroot web/ web/index.php
diff --git a/lib/exception/StopException.class.php b/lib/exception/StopException.class.php
index 81e2d9d1..ad3dc43d 100644
--- a/lib/exception/StopException.class.php
+++ b/lib/exception/StopException.class.php
@@ -1,10 +1,5 @@
' . money_format('%.2n', $amount) . '';
@@ -61,7 +49,7 @@ class i18n
public static function formatCredits($amount)
{
- return '' . (is_numeric($amount) ? number_format($amount, 1) : $amount) . ' LBC';
+ return '' . (is_numeric($amount) ? number_format($amount, 1) : $amount) . ' LBC';
}
public static function translate($token, $language = null)
@@ -70,10 +58,11 @@ class i18n
if (!isset(static::$translations[$language]))
{
$path = ROOT_DIR . '/data/i18n/' . $language . '.yaml';
+
static::$translations[$language] = file_exists($path) ? Spyc::YAMLLoadString(file_get_contents($path)) : [];
}
$scope = static::$translations[$language];
- foreach(explode('.', $token) as $level)
+ foreach (explode('.', $token) as $level)
{
if (isset($scope[$level]))
{
@@ -90,4 +79,60 @@ class i18n
}
return $scope ?: $token;
}
+
+ protected static function deduceCulture()
+ {
+ $candidates = [];
+
+ //url trumps everything
+ $urlTokens = Request::getHost() ? explode('.', Request::getHost()) : [];
+ $code = $urlTokens ? reset($urlTokens) : null;
+ if ($code !== 'www')
+ {
+ $candidates[] = $code;
+ }
+
+ //then session
+ $candidates[] = Session::get(Session::KEY_USER_CULTURE);
+
+ // then headers
+ // http://www.thefutureoftheweb.com/blog/use-accept-language-header
+ if (Request::getHttpHeader('Accept-Language'))
+ {
+ // break up string into pieces (languages and q factors)
+ preg_match_all('/([a-z]{1,8}(-[a-z]{1,8})?)\s*(;\s*q\s*=\s*(1|0\.[0-9]+))?/i', Request::getHttpHeader('Accept-Language'), $languages);
+
+ if (isset($languages[1]) && count($languages[1]))
+ {
+ // create a list like "en" => 0.8
+ $langs = array_combine($languages[1], $languages[4]);
+
+ // set default to 1 for any without q factor
+ foreach ($langs as $lang => $val)
+ {
+ if ($val === '')
+ {
+ $langs[$lang] = 1;
+ }
+ }
+
+ arsort($langs, SORT_NUMERIC);
+
+ $candidates = array_merge($candidates, array_keys($langs));
+ }
+ }
+
+ foreach ($candidates as $candidate)
+ {
+ foreach (static::getAllCultures() as $culture)
+ {
+ if ($candidate === $culture || substr($culture, 0, 2) === $candidate)
+ {
+ return $culture;
+ }
+ }
+ }
+
+ return 'en_US';
+ }
}
diff --git a/lib/routing/BadRouteException.class.php b/lib/routing/BadRouteException.class.php
new file mode 100644
index 00000000..33d07f1e
--- /dev/null
+++ b/lib/routing/BadRouteException.class.php
@@ -0,0 +1,5 @@
+staticRouteMap = $data->getStaticRoutes();
+
+ $this->variableRouteData = $data->getVariableRoutes();
+
+ $this->filters = $data->getFilters();
+
+ if ($resolver === null)
+ {
+ $this->handlerResolver = new HandlerResolver();
+ }
+ else
+ {
+ $this->handlerResolver = $resolver;
+ }
+ }
+
+ /**
+ * Dispatch a route for the given HTTP Method / URI.
+ *
+ * @param $httpMethod
+ * @param $uri
+ *
+ * @return mixed|null
+ */
+ public function dispatch($httpMethod, $uri)
+ {
+ list($handler, $filters, $vars) = $this->dispatchRoute($httpMethod, trim($uri, '/'));
+
+ list($beforeFilter, $afterFilter) = $this->parseFilters($filters);
+
+ if (($response = $this->dispatchFilters($beforeFilter)) !== null)
+ {
+ return $response;
+ }
+
+ $resolvedHandler = $this->handlerResolver->resolve($handler);
+
+ $response = call_user_func_array($resolvedHandler, $vars);
+
+ return $this->dispatchFilters($afterFilter, $response);
+ }
+
+ /**
+ * Dispatch a route filter.
+ *
+ * @param $filters
+ * @param null $response
+ *
+ * @return mixed|null
+ */
+ private function dispatchFilters($filters, $response = null)
+ {
+ while ($filter = array_shift($filters))
+ {
+ $handler = $this->handlerResolver->resolve($filter);
+
+ if (($filteredResponse = call_user_func($handler, $response)) !== null)
+ {
+ return $filteredResponse;
+ }
+ }
+
+ return $response;
+ }
+
+ /**
+ * Normalise the array filters attached to the route and merge with any global filters.
+ *
+ * @param $filters
+ *
+ * @return array
+ */
+ private function parseFilters($filters)
+ {
+ $beforeFilter = [];
+ $afterFilter = [];
+
+ if (isset($filters[Route::BEFORE]))
+ {
+ $beforeFilter = array_intersect_key($this->filters, array_flip((array)$filters[Route::BEFORE]));
+ }
+
+ if (isset($filters[Route::AFTER]))
+ {
+ $afterFilter = array_intersect_key($this->filters, array_flip((array)$filters[Route::AFTER]));
+ }
+
+ return [$beforeFilter, $afterFilter];
+ }
+
+ /**
+ * Perform the route dispatching. Check static routes first followed by variable routes.
+ *
+ * @param $httpMethod
+ * @param $uri
+ *
+ * @throws Exception\HttpRouteNotFoundException
+ */
+ private function dispatchRoute($httpMethod, $uri)
+ {
+ if (isset($this->staticRouteMap[$uri]))
+ {
+ return $this->dispatchStaticRoute($httpMethod, $uri);
+ }
+
+ return $this->dispatchVariableRoute($httpMethod, $uri);
+ }
+
+ /**
+ * Handle the dispatching of static routes.
+ *
+ * @param $httpMethod
+ * @param $uri
+ *
+ * @return mixed
+ * @throws Exception\HttpMethodNotAllowedException
+ */
+ private function dispatchStaticRoute($httpMethod, $uri)
+ {
+ $routes = $this->staticRouteMap[$uri];
+
+ if (!isset($routes[$httpMethod]))
+ {
+ $httpMethod = $this->checkFallbacks($routes, $httpMethod);
+ }
+
+ return $routes[$httpMethod];
+ }
+
+ /**
+ * Check fallback routes: HEAD for GET requests followed by the ANY attachment.
+ *
+ * @param $routes
+ * @param $httpMethod
+ *
+ * @throws Exception\HttpMethodNotAllowedException
+ */
+ private function checkFallbacks($routes, $httpMethod)
+ {
+ $additional = [Route::ANY];
+
+ if ($httpMethod === Route::HEAD)
+ {
+ $additional[] = Route::GET;
+ }
+
+ foreach ($additional as $method)
+ {
+ if (isset($routes[$method]))
+ {
+ return $method;
+ }
+ }
+
+ $this->matchedRoute = $routes;
+
+ throw new HttpMethodNotAllowedException(array_keys($routes), 'Method not allowed');
+ }
+
+ /**
+ * Handle the dispatching of variable routes.
+ *
+ * @param $httpMethod
+ * @param $uri
+ *
+ * @throws Exception\HttpMethodNotAllowedException
+ * @throws Exception\HttpRouteNotFoundException
+ */
+ private function dispatchVariableRoute($httpMethod, $uri)
+ {
+ foreach ($this->variableRouteData as $data)
+ {
+ if (!preg_match($data['regex'], $uri, $matches))
+ {
+ continue;
+ }
+
+ $count = count($matches);
+
+ while (!isset($data['routeMap'][$count++]))
+ {
+ ;
+ }
+
+ $routes = $data['routeMap'][$count - 1];
+
+ if (!isset($routes[$httpMethod]))
+ {
+ $httpMethod = $this->checkFallbacks($routes, $httpMethod);
+ }
+
+ foreach (array_values($routes[$httpMethod][2]) as $i => $varName)
+ {
+ if (!isset($matches[$i + 1]) || $matches[$i + 1] === '')
+ {
+ unset($routes[$httpMethod][2][$varName]);
+ }
+ else
+ {
+ $routes[$httpMethod][2][$varName] = $matches[$i + 1];
+ }
+ }
+
+ return $routes[$httpMethod];
+ }
+
+ throw new HttpRouteNotFoundException('Route ' . $uri . ' does not exist');
+ }
+}
diff --git a/lib/routing/HandlerResolver.class.php b/lib/routing/HandlerResolver.class.php
new file mode 100644
index 00000000..9918d2f7
--- /dev/null
+++ b/lib/routing/HandlerResolver.class.php
@@ -0,0 +1,16 @@
+allowedMethods = $allowedMethods;
+ parent::__construct($message, $code, $previous);
+ }
+
+ public function getAllowedMethods()
+ {
+ return $this->allowedMethods;
+ }
+}
diff --git a/lib/routing/HttpRouteNotFoundException.class.php b/lib/routing/HttpRouteNotFoundException.class.php
new file mode 100644
index 00000000..b7dc994a
--- /dev/null
+++ b/lib/routing/HttpRouteNotFoundException.class.php
@@ -0,0 +1,6 @@
+routeParser = $routeParser ?: new RouteParser();
+ }
+
+ public function hasRoute(string $name): bool
+ {
+ return isset($this->reverse[$name]);
+ }
+
+ public function route(string $name, array $args = null): string
+ {
+ $url = [];
+
+ $replacements = is_null($args) ? [] : array_values($args);
+
+ $variable = 0;
+
+ foreach ($this->reverse[$name] as $part)
+ {
+ if (!$part['variable'])
+ {
+ $url[] = $part['value'];
+ }
+ elseif (isset($replacements[$variable]))
+ {
+ if ($part['optional'])
+ {
+ $url[] = '/';
+ }
+
+ $url[] = $replacements[$variable++];
+ }
+ elseif (!$part['optional'])
+ {
+ throw new BadRouteException("Expecting route variable '{$part['name']}'");
+ }
+ }
+
+ return implode('', $url);
+ }
+
+ public function addRoute(string $httpMethod, $route, $handler, array $filters = []): RouteCollector
+ {
+
+ if (is_array($route))
+ {
+ list($route, $name) = $route;
+ }
+
+ $route = $this->addPrefix($this->trim($route));
+
+ list($routeData, $reverseData) = $this->routeParser->parse($route);
+
+ if (isset($name))
+ {
+ $this->reverse[$name] = $reverseData;
+ }
+
+ $filters = array_merge_recursive($this->globalFilters, $filters);
+
+ isset($routeData[1]) ?
+ $this->addVariableRoute($httpMethod, $routeData, $handler, $filters) :
+ $this->addStaticRoute($httpMethod, $routeData, $handler, $filters);
+
+ return $this;
+ }
+
+ private function addStaticRoute(string $httpMethod, $routeData, $handler, $filters)
+ {
+ $routeStr = $routeData[0];
+
+ if (isset($this->staticRoutes[$routeStr][$httpMethod]))
+ {
+ throw new BadRouteException("Cannot register two routes matching '$routeStr' for method '$httpMethod'");
+ }
+
+ foreach ($this->regexToRoutesMap as $regex => $routes)
+ {
+ if (isset($routes[$httpMethod]) && preg_match('~^' . $regex . '$~', $routeStr))
+ {
+ throw new BadRouteException("Static route '$routeStr' is shadowed by previously defined variable route '$regex' for method '$httpMethod'");
+ }
+ }
+
+ $this->staticRoutes[$routeStr][$httpMethod] = [$handler, $filters, []];
+ }
+
+ private function addVariableRoute(string $httpMethod, $routeData, $handler, $filters)
+ {
+ list($regex, $variables) = $routeData;
+
+ if (isset($this->regexToRoutesMap[$regex][$httpMethod]))
+ {
+ throw new BadRouteException("Cannot register two routes matching '$regex' for method '$httpMethod'");
+ }
+
+ $this->regexToRoutesMap[$regex][$httpMethod] = [$handler, $filters, $variables];
+ }
+
+ public function group(array $filters, \Closure $callback)
+ {
+ $oldGlobalFilters = $this->globalFilters;
+
+ $oldGlobalPrefix = $this->globalRoutePrefix;
+
+ $this->globalFilters =
+ array_merge_recursive($this->globalFilters, array_intersect_key($filters, [Route::AFTER => 1, Route::BEFORE => 1]));
+
+ $newPrefix = isset($filters[Route::PREFIX]) ? $this->trim($filters[Route::PREFIX]) : null;
+
+ $this->globalRoutePrefix = $this->addPrefix($newPrefix);
+
+ $callback($this);
+
+ $this->globalFilters = $oldGlobalFilters;
+
+ $this->globalRoutePrefix = $oldGlobalPrefix;
+ }
+
+ private function addPrefix(string $route)
+ {
+ return $this->trim($this->trim($this->globalRoutePrefix) . '/' . $route);
+ }
+
+ public function filter(string $name, $handler)
+ {
+ $this->filters[$name] = $handler;
+ }
+
+
+ public function get($route, $handler, array $filters = [])
+ {
+ return $this->addRoute(Route::GET, $route, $handler, $filters);
+ }
+
+ public function head($route, $handler, array $filters = [])
+ {
+ return $this->addRoute(Route::HEAD, $route, $handler, $filters);
+ }
+
+ public function post($route, $handler, array $filters = [])
+ {
+ return $this->addRoute(Route::POST, $route, $handler, $filters);
+ }
+
+ public function put($route, $handler, array $filters = [])
+ {
+ return $this->addRoute(Route::PUT, $route, $handler, $filters);
+ }
+
+ public function patch($route, $handler, array $filters = [])
+ {
+ return $this->addRoute(Route::PATCH, $route, $handler, $filters);
+ }
+
+ public function delete($route, $handler, array $filters = [])
+ {
+ return $this->addRoute(Route::DELETE, $route, $handler, $filters);
+ }
+
+ public function options($route, $handler, array $filters = [])
+ {
+ return $this->addRoute(Route::OPTIONS, $route, $handler, $filters);
+ }
+
+ public function any($route, $handler, array $filters = [])
+ {
+ return $this->addRoute(Route::ANY, $route, $handler, $filters);
+ }
+
+
+ public function controller(string $route, string $classname, array $filters = []): RouteCollector
+ {
+ $reflection = new ReflectionClass($classname);
+
+ $validMethods = $this->getValidMethods();
+
+ $sep = $route === '/' ? '' : '/';
+
+ foreach ($reflection->getMethods(ReflectionMethod::IS_PUBLIC) as $method)
+ {
+ foreach ($validMethods as $valid)
+ {
+ if (stripos($method->name, $valid) === 0)
+ {
+ $methodName = $this->camelCaseToDashed(substr($method->name, strlen($valid)));
+
+ $params = $this->buildControllerParameters($method);
+
+ if ($methodName === self::DEFAULT_CONTROLLER_ROUTE)
+ {
+ $this->addRoute($valid, $route . $params, [$classname, $method->name], $filters);
+ }
+
+ $this->addRoute($valid, $route . $sep . $methodName . $params, [$classname, $method->name], $filters);
+
+ break;
+ }
+ }
+ }
+
+ return $this;
+ }
+
+ private function buildControllerParameters(ReflectionMethod $method): string
+ {
+ $params = '';
+
+ foreach ($method->getParameters() as $param)
+ {
+ $params .= "/{" . $param->getName() . "}" . ($param->isOptional() ? '?' : '');
+ }
+
+ return $params;
+ }
+
+ private function camelCaseToDashed(string $string): string
+ {
+ return strtolower(preg_replace('/([A-Z])/', '-$1', lcfirst($string)));
+ }
+
+ public function getValidMethods(): array
+ {
+ return [
+ Route::ANY,
+ Route::GET,
+ Route::POST,
+ Route::PUT,
+ Route::PATCH,
+ Route::DELETE,
+ Route::HEAD,
+ Route::OPTIONS,
+ ];
+ }
+
+ public function getData(): RouteData
+ {
+ return new RouteData($this->staticRoutes, $this->regexToRoutesMap ? $this->generateVariableRouteData() : [], $this->filters);
+ }
+
+ private function trim(string $route): string
+ {
+ return trim($route, '/');
+ }
+
+ private function generateVariableRouteData(): array
+ {
+ $chunkSize = $this->computeChunkSize(count($this->regexToRoutesMap));
+ $chunks = array_chunk($this->regexToRoutesMap, $chunkSize, true);
+ return array_map([$this, 'processChunk'], $chunks);
+ }
+
+ private function computeChunkSize(int $count): float
+ {
+ $numParts = max(1, round($count / self::APPROX_CHUNK_SIZE));
+ return ceil($count / $numParts);
+ }
+
+ private function processChunk($regexToRoutesMap): array
+ {
+ $routeMap = [];
+ $regexes = [];
+ $numGroups = 0;
+ foreach ($regexToRoutesMap as $regex => $routes)
+ {
+ $firstRoute = reset($routes);
+ $numVariables = count($firstRoute[2]);
+ $numGroups = max($numGroups, $numVariables);
+
+ $regexes[] = $regex . str_repeat('()', $numGroups - $numVariables);
+
+ foreach ($routes as $httpMethod => $route)
+ {
+ $routeMap[$numGroups + 1][$httpMethod] = $route;
+ }
+
+ $numGroups++;
+ }
+
+ $regex = '~^(?|' . implode('|', $regexes) . ')$~';
+ return ['regex' => $regex, 'routeMap' => $routeMap];
+ }
+}
diff --git a/lib/routing/RouteData.class.php b/lib/routing/RouteData.class.php
new file mode 100644
index 00000000..1a53709f
--- /dev/null
+++ b/lib/routing/RouteData.class.php
@@ -0,0 +1,59 @@
+staticRoutes = $staticRoutes;
+
+ $this->variableRoutes = $variableRoutes;
+
+ $this->filters = $filters;
+ }
+
+ /**
+ * @return array
+ */
+ public function getStaticRoutes()
+ {
+ return $this->staticRoutes;
+ }
+
+ /**
+ * @return array
+ */
+ public function getVariableRoutes()
+ {
+ return $this->variableRoutes;
+ }
+
+ /**
+ * @return mixed
+ */
+ public function getFilters()
+ {
+ return $this->filters;
+ }
+}
diff --git a/lib/routing/RouteParser.class.php b/lib/routing/RouteParser.class.php
new file mode 100644
index 00000000..c6d9ad17
--- /dev/null
+++ b/lib/routing/RouteParser.class.php
@@ -0,0 +1,214 @@
+ ':[0-9]+}',
+ ':a}' => ':[0-9A-Za-z]+}',
+ ':h}' => ':[0-9A-Fa-f]+}',
+ ':c}' => ':[a-zA-Z0-9+_\-\.]+}'
+ ];
+
+ /**
+ * Parse a route returning the correct data format to pass to the dispatch engine.
+ *
+ * @param $route
+ *
+ * @return array
+ */
+ public function parse($route)
+ {
+ $this->reset();
+
+ $route = strtr($route, $this->regexShortcuts);
+
+ if (!$matches = $this->extractVariableRouteParts($route))
+ {
+ $reverse = [
+ 'variable' => false,
+ 'value' => $route
+ ];
+
+ return [[$route], [$reverse]];
+ }
+
+ foreach ($matches as $set)
+ {
+
+ $this->staticParts($route, $set[0][1]);
+
+ $this->validateVariable($set[1][0]);
+
+ $regexPart = (isset($set[2]) ? trim($set[2][0]) : self::DEFAULT_DISPATCH_REGEX);
+
+ $this->regexOffset = $set[0][1] + strlen($set[0][0]);
+
+ $match = '(' . $regexPart . ')';
+
+ $isOptional = substr($set[0][0], -1) === '?';
+
+ if ($isOptional)
+ {
+ $match = $this->makeOptional($match);
+ }
+
+ $this->reverseParts[$this->partsCounter] = [
+ 'variable' => true,
+ 'optional' => $isOptional,
+ 'name' => $set[1][0]
+ ];
+
+ $this->parts[$this->partsCounter++] = $match;
+ }
+
+ $this->staticParts($route, strlen($route));
+
+ return [[implode('', $this->parts), $this->variables], array_values($this->reverseParts)];
+ }
+
+ /**
+ * Reset the parser ready for the next route.
+ */
+ private function reset()
+ {
+ $this->parts = [];
+
+ $this->reverseParts = [];
+
+ $this->partsCounter = 0;
+
+ $this->variables = [];
+
+ $this->regexOffset = 0;
+ }
+
+ /**
+ * Return any variable route portions from the given route.
+ *
+ * @param $route
+ *
+ * @return mixed
+ */
+ private function extractVariableRouteParts($route)
+ {
+ if (preg_match_all(self::VARIABLE_REGEX, $route, $matches, PREG_OFFSET_CAPTURE | PREG_SET_ORDER))
+ {
+ return $matches;
+ }
+ }
+
+ /**
+ * @param $route
+ * @param $nextOffset
+ */
+ private function staticParts($route, $nextOffset)
+ {
+ $static = preg_split('~(/)~u', substr($route, $this->regexOffset, $nextOffset - $this->regexOffset), 0, PREG_SPLIT_DELIM_CAPTURE);
+
+ foreach ($static as $staticPart)
+ {
+ if ($staticPart)
+ {
+ $quotedPart = $this->quote($staticPart);
+
+ $this->parts[$this->partsCounter] = $quotedPart;
+
+ $this->reverseParts[$this->partsCounter] = [
+ 'variable' => false,
+ 'value' => $staticPart
+ ];
+
+ $this->partsCounter++;
+ }
+ }
+ }
+
+ /**
+ * @param $varName
+ */
+ private function validateVariable($varName)
+ {
+ if (isset($this->variables[$varName]))
+ {
+ throw new BadRouteException("Cannot use the same placeholder '$varName' twice");
+ }
+
+ $this->variables[$varName] = $varName;
+ }
+
+ /**
+ * @param $match
+ *
+ * @return string
+ */
+ private function makeOptional($match)
+ {
+ $previous = $this->partsCounter - 1;
+
+ if (isset($this->parts[$previous]) && $this->parts[$previous] === '/')
+ {
+ $this->partsCounter--;
+ $match = '(?:/' . $match . ')';
+ }
+
+ return $match . '?';
+ }
+
+ /**
+ * @param $part
+ *
+ * @return string
+ */
+ private function quote($part)
+ {
+ return preg_quote($part, '~');
+ }
+}
diff --git a/model/api/Asana.class.php b/lib/thirdparty/Asana.class.php
similarity index 100%
rename from model/api/Asana.class.php
rename to lib/thirdparty/Asana.class.php
diff --git a/model/api/Github.class.php b/lib/thirdparty/Github.class.php
similarity index 96%
rename from model/api/Github.class.php
rename to lib/thirdparty/Github.class.php
index 1ff2da84..76a35cb4 100644
--- a/model/api/Github.class.php
+++ b/lib/thirdparty/Github.class.php
@@ -16,7 +16,8 @@ class Github
{
if (
($os == Os::OS_LINUX && in_array($asset['content_type'], ['application/x-debian-package', 'application/x-deb'])) ||
- ($os == Os::OS_OSX && in_array($asset['content_type'], ['application/x-diskcopy', 'application/x-apple-diskimage']))
+ ($os == Os::OS_OSX && in_array($asset['content_type'], ['application/x-diskcopy', 'application/x-apple-diskimage'])) ||
+ ($os == Os::OS_WINDOWS && substr($asset['name'], -4) == '.msi')
)
{
return $asset['browser_download_url'];
diff --git a/lib/thirdparty/Mailgun.class.php b/lib/thirdparty/Mailgun.class.php
new file mode 100644
index 00000000..6f6a5a19
--- /dev/null
+++ b/lib/thirdparty/Mailgun.class.php
@@ -0,0 +1,91 @@
+ 'LBRY + |
+
+
+
|
+