From 46cedb2b465e91591a30b809c3df61ebddc04cbf Mon Sep 17 00:00:00 2001 From: JR Cologne Date: Sat, 20 Jan 2018 14:10:40 +0100 Subject: [PATCH] Init commit --- .editorconfig | 7 + .gitattributes | 17 ++ .gitignore | 50 +++++ LICENSE | 21 ++ README.md | 4 + app/app.php | 34 +++ app/classes/CryptoClient.php | 109 +++++++++ app/classes/CryptoStatus.php | 206 ++++++++++++++++++ app/classes/CurlClient.php | 106 +++++++++ .../Exceptions/CryptoClientException.php | 30 +++ .../Exceptions/CryptoStatusException.php | 30 +++ .../Exceptions/CurlClientException.php | 30 +++ .../Exceptions/TwitterClientException.php | 30 +++ app/classes/TwitterClient.php | 138 ++++++++++++ app/config/config.php | 33 +++ composer.json | 14 ++ composer.lock | 199 +++++++++++++++++ 17 files changed, 1058 insertions(+) create mode 100644 .editorconfig create mode 100644 .gitattributes create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md create mode 100644 app/app.php create mode 100644 app/classes/CryptoClient.php create mode 100644 app/classes/CryptoStatus.php create mode 100644 app/classes/CurlClient.php create mode 100644 app/classes/Exceptions/CryptoClientException.php create mode 100644 app/classes/Exceptions/CryptoStatusException.php create mode 100644 app/classes/Exceptions/CurlClientException.php create mode 100644 app/classes/Exceptions/TwitterClientException.php create mode 100644 app/classes/TwitterClient.php create mode 100644 app/config/config.php create mode 100644 composer.json create mode 100644 composer.lock diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..be6bd50 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,7 @@ +root = true + +[*] +charset = utf-8 +indent_style = space +indent_size = 2 +insert_final_newline = true \ No newline at end of file diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..bdb0cab --- /dev/null +++ b/.gitattributes @@ -0,0 +1,17 @@ +# Auto detect text files and perform LF normalization +* text=auto + +# Custom for Visual Studio +*.cs diff=csharp + +# Standard to msysgit +*.doc diff=astextplain +*.DOC diff=astextplain +*.docx diff=astextplain +*.DOCX diff=astextplain +*.dot diff=astextplain +*.DOT diff=astextplain +*.pdf diff=astextplain +*.PDF diff=astextplain +*.rtf diff=astextplain +*.RTF diff=astextplain diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..50620dd --- /dev/null +++ b/.gitignore @@ -0,0 +1,50 @@ +# Windows image file caches +Thumbs.db +ehthumbs.db + +# Folder config file +Desktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msm +*.msp + +# Windows shortcuts +*.lnk + +# ========================= +# Operating System Files +# ========================= + +# OSX +# ========================= + +.DS_Store +.AppleDouble +.LSOverride + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +# Exclude Composer stuff +vendor diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..23e7361 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 JR Cologne + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..2aaba62 --- /dev/null +++ b/README.md @@ -0,0 +1,4 @@ +# CryptoStatus +A simple Twitter bot application which posts hourly status updates for the top 10 cryptocurrencies. + +Twitter Account: [@status_crypto](https://twitter.com/status_crypto) diff --git a/app/app.php b/app/app.php new file mode 100644 index 0000000..49a6e42 --- /dev/null +++ b/app/app.php @@ -0,0 +1,34 @@ += 7.0 + * + * LICENSE: MIT, see LICENSE file for more information + * + * @author JR Cologne + * @copyright 2018 JR Cologne + * @license https://github.com/jr-cologne/CryptoStatus/blob/master/LICENSE MIT + * @version v0.1 + * @link https://github.com/jr-cologne/CryptoStatus GitHub Repository + * + * ________________________________________________________________________________ + * + * app.php + * + * The main application file + * + */ + +require_once 'vendor/autoload.php'; + +use CryptoStatus\CryptoStatus; + +$app = new CryptoStatus; + +// initialize app +$app->init(); + +// run app +$app->run(); diff --git a/app/classes/CryptoClient.php b/app/classes/CryptoClient.php new file mode 100644 index 0000000..17b07d1 --- /dev/null +++ b/app/classes/CryptoClient.php @@ -0,0 +1,109 @@ += 7.0 + * + * LICENSE: MIT, see LICENSE file for more information + * + * @author JR Cologne + * @copyright 2018 JR Cologne + * @license https://github.com/jr-cologne/CryptoStatus/blob/master/LICENSE MIT + * @version v0.1 + * @link https://github.com/jr-cologne/CryptoStatus GitHub Repository + * + * ________________________________________________________________________________ + * + * CryptoClient.php + * + * The client for retrieving the Crypto data from an API. + * + */ + +namespace CryptoStatus; + +use CryptoStatus\CurlClient; +use CryptoStatus\Exceptions\CryptoClientException; + +class CryptoClient { + + /** + * The cURL client instance + * + * @var CurlClient $curl_client + */ + protected $curl_client; + + /** + * The Crypto client options + * + * @var array $options + */ + protected $options = [ + 'api' => null, + 'endpoint' => null, + 'params' => [] + ]; + + /** + * Constructor, initialization + * + * @param CurlClient $curl_client + * @param array $options + * @throws CryptoClientException if no API and/or API Endpoint is specified in options + */ + public function __construct(CurlClient $curl_client, array $options = []) { + $this->curl_client = $curl_client; + + $this->options = array_merge($this->options, $options); + + if (empty($this->options['api']) || !isset($this->options['endpoint'])) { + throw new CryptoClientException('No API and/or API Endpoint specified', 1); + } + } + + /** + * Get Crypto data + * + * @return array + */ + public function getData() : array { + return $this->curl_client->get($this->getRequestUrl())->json(); + } + + /** + * Get request URL for Crypto API call + * + * @return string + * @throws CryptoClientException if no API and/or API Endpoint is specified + */ + protected function getRequestUrl() : string { + if (empty($this->options['api']) || !isset($this->options['endpoint'])) { + throw new CryptoClientException('No API and/or API Endpoint specified', 1); + } + + $url = $this->options['api'] . $this->options['endpoint']; + + if (empty($this->options['params'])) { + return $url; + } + + $first_param = true; + + foreach ($this->options['params'] as $key => $value) { + if (isset($this->options['params'][$key])) { + if ($first_param) { + $first_param = false; + } else { + $url .= urlencode('&'); + } + + $url .= "?{$key}={$value}"; + } + } + + return $url; + } + +} diff --git a/app/classes/CryptoStatus.php b/app/classes/CryptoStatus.php new file mode 100644 index 0000000..544ce83 --- /dev/null +++ b/app/classes/CryptoStatus.php @@ -0,0 +1,206 @@ += 7.0 + * + * LICENSE: MIT, see LICENSE file for more information + * + * @author JR Cologne + * @copyright 2018 JR Cologne + * @license https://github.com/jr-cologne/CryptoStatus/blob/master/LICENSE MIT + * @version v0.1 + * @link https://github.com/jr-cologne/CryptoStatus GitHub Repository + * + * ________________________________________________________________________________ + * + * CryptoStatus.php + * + * The main class of the application. + * + */ + +namespace CryptoStatus; + +use CryptoStatus\Exceptions\CryptoStatusException; + +use CryptoStatus\TwitterClient; +use CryptoStatus\CurlClient; +use CryptoStatus\CryptoClient; + +use Codebird\Codebird; + +class CryptoStatus { + + /** + * The Twitter client instance + * + * @var TwitterClient $twitter_client + */ + protected $twitter_client; + + /** + * The Crypto client instance + * + * @var CryptoClient $crypto_client + */ + protected $crypto_client; + + /** + * The Crypto data + * + * @var array $dataset + */ + protected $dataset; + + /** + * The IDs of the Tweets which need to be deleted because of an error + * + * @var array $failed_tweets + */ + protected $failed_tweets = []; + + /** + * Initialize application + */ + public function init() { + $this->twitter_client = new TwitterClient(Codebird::getInstance()); + + $this->crypto_client = new CryptoClient(new CurlClient, [ + 'api' => CRYPTO_API, + 'endpoint' => CRYPTO_API_ENDPOINT, + 'params' => [ + 'limit' => CRYPTO_API_LIMIT + ] + ]); + } + + /** + * Run the application + */ + public function run() { + $this->dataset = $this->getDataset(); + + $this->formatData(); + + $tweets = $this->createTweets(); + + if (!$this->postTweets($tweets)) { + $this->deleteTweets($this->failed_tweets); + } + } + + /** + * Get the Crypto data + * + * @return array + */ + protected function getDataset() : array { + return $this->crypto_client->getData(); + } + + /** + * Format the Crypto data to an array of strings + * + * @throws CryptoStatusException if Crypto data is missing + */ + protected function formatData() { + $this->dataset = array_map(function (array $data) { + if (isset($data['rank'], $data['symbol'], $data['name'], $data['price_usd'], $data['price_btc'], $data['percent_change_1h'])) { + $data['price_usd'] = number_format($data['price_usd'], 2); + $data['price_btc'] = number_format($data['price_btc'], 6); + return "#{$data['rank']} {$data['symbol']} ({$data['name']}): {$data['price_usd']} USD | {$data['price_btc']} BTC | {$data['percent_change_1h']}% 1h"; + } + + throw new CryptoStatusException('Crypto data is missing', 1); + }, $this->dataset); + } + + /** + * Create the Tweets with Crypto data and return them as an array + * + * @return array + */ + protected function createTweets() : array { + $tweets = []; + $start_rank = 1; + $end_rank = 3; + $length = 3; + + for ($i = 0; $i < 3; $i++) { + $tweets[$i] = "#HourlyCryptoStatus (#{$start_rank} to #{$end_rank}):\n\n"; + $tweets[$i] .= implode("\n\n", array_slice($this->dataset, $start_rank - 1, $length)); + + $start_rank += 3; + $end_rank += 3; + + if ($i == 1) { + $end_rank++; + $length++; + } + } + + return $tweets; + } + + /** + * Post the specified Tweets + * + * @param array $tweets The Tweets to post + * @return bool + */ + protected function postTweets(array $tweets) : bool { + $last_tweet_id = null; + + for ($i = 0; $i < 3; $i++) { + if ($last_tweet_id) { + $tweet = $this->twitter_client->postTweet([ + 'status' => '@' . TWITTER_SCREENNAME . ' ' . $tweets[$i], + 'in_reply_to_status_id' => $last_tweet_id + ], [ 'id' ]); + } else { + $tweet = $this->twitter_client->postTweet([ + 'status' => $tweets[$i] + ], [ 'id' ]); + } + + if (isset($tweet['id'])) { + $tweet_ids[] = $last_tweet_id = $tweet['id']; + } else { + break; + } + } + + if (count($tweet_ids) == 3) { + return true; + } else { + $this->failed_tweets = $tweet_ids; + + return false; + } + } + + /** + * Delete the specified Tweets + * + * @param array $tweet_ids The IDs of the Tweets to delete + * @throws CryptoStatusException if Tweets could not be deleted + */ + protected function deleteTweets(array $tweet_ids) { + $deleted_counter = 0; + + foreach ($tweet_ids as $tweet_id) { + $deleted = $this->twitter_client->deleteTweet($tweet_id); + + if ($deleted) { + $deleted_counter++; + } + } + + if ($deleted_counter != count($tweet_ids)) { + throw new CryptoStatusException('Deleting Tweets failed', 2); + } + } + +} diff --git a/app/classes/CurlClient.php b/app/classes/CurlClient.php new file mode 100644 index 0000000..34f55a0 --- /dev/null +++ b/app/classes/CurlClient.php @@ -0,0 +1,106 @@ += 7.0 + * + * LICENSE: MIT, see LICENSE file for more information + * + * @author JR Cologne + * @copyright 2018 JR Cologne + * @license https://github.com/jr-cologne/CryptoStatus/blob/master/LICENSE MIT + * @version v0.1 + * @link https://github.com/jr-cologne/CryptoStatus GitHub Repository + * + * ________________________________________________________________________________ + * + * CurlClient.php + * + * The cURL client for making simple requests to an API. + * + */ + +namespace CryptoStatus; + +use CryptoStatus\Exceptions\CurlClientException; + +class CurlClient { + + /** + * The cURL handle of the current cURL session + * + * @var resource $ch + */ + protected $ch; + + /** + * The result of a performed cURL request + * + * @var mixed $result + */ + protected $result; + + /** + * Constructor, initialize cURL + */ + public function __construct() { + $this->ch = curl_init(); + } + + /** + * Perform a cURL request and retrieve the result + * + * @param string $url The URL for the cURL request + * @return self + * @throws CurlClientException if cURL request failed + */ + public function get(string $url) : self { + curl_setopt($this->ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($this->ch, CURLOPT_URL, $this->sanitizeUrl($url)); + + $this->result = curl_exec($this->ch); + curl_close($this->ch); + + if ($this->result === false) { + throw new CurlClientException('cURL request failed', 1); + } + + return $this; + } + + /** + * Json-decode result/return data of cURL request + * + * @param bool $array = true Return json-decoded data as array + * @return array (default) or object + * @throws CurlClientException if cURL return data could not be json-decoded + */ + public function json(bool $array = true) { + $json = json_decode($this->result, $array); + + if ($json === null) { + throw new CurlClientException('cURL return data could not be json-decoded', 3); + } + + return $json; + } + + /** + * Sanitize and validate URL for cURL request + * + * @param string $url The URL for the cURL request + * @return string + * @throws CurlClientException if an invalid URL is given + */ + protected function sanitizeUrl(string $url) : string { + $url = filter_var($url, FILTER_SANITIZE_URL); + + if (!filter_var($url, FILTER_VALIDATE_URL)) { + throw new CurlClientException('Invalid URL', 2); + } + + return $url; + } + +} diff --git a/app/classes/Exceptions/CryptoClientException.php b/app/classes/Exceptions/CryptoClientException.php new file mode 100644 index 0000000..0e42938 --- /dev/null +++ b/app/classes/Exceptions/CryptoClientException.php @@ -0,0 +1,30 @@ += 7.0 + * + * LICENSE: MIT, see LICENSE file for more information + * + * @author JR Cologne + * @copyright 2018 JR Cologne + * @license https://github.com/jr-cologne/CryptoStatus/blob/master/LICENSE MIT + * @version v0.1 + * @link https://github.com/jr-cologne/CryptoStatus GitHub Repository + * + * ________________________________________________________________________________ + * + * CryptoClientException.php + * + * The Exception of the Crypto client + * + */ + +namespace CryptoStatus\Exceptions; + +use \Exception; + +class CryptoClientException extends Exception { + +} diff --git a/app/classes/Exceptions/CryptoStatusException.php b/app/classes/Exceptions/CryptoStatusException.php new file mode 100644 index 0000000..669db7f --- /dev/null +++ b/app/classes/Exceptions/CryptoStatusException.php @@ -0,0 +1,30 @@ += 7.0 + * + * LICENSE: MIT, see LICENSE file for more information + * + * @author JR Cologne + * @copyright 2018 JR Cologne + * @license https://github.com/jr-cologne/CryptoStatus/blob/master/LICENSE MIT + * @version v0.1 + * @link https://github.com/jr-cologne/CryptoStatus GitHub Repository + * + * ________________________________________________________________________________ + * + * CryptoStatusException.php + * + * The Exception of the CryptoStatus class + * + */ + +namespace CryptoStatus\Exceptions; + +use \Exception; + +class CryptoStatusException extends Exception { + +} diff --git a/app/classes/Exceptions/CurlClientException.php b/app/classes/Exceptions/CurlClientException.php new file mode 100644 index 0000000..651b24e --- /dev/null +++ b/app/classes/Exceptions/CurlClientException.php @@ -0,0 +1,30 @@ += 7.0 + * + * LICENSE: MIT, see LICENSE file for more information + * + * @author JR Cologne + * @copyright 2018 JR Cologne + * @license https://github.com/jr-cologne/CryptoStatus/blob/master/LICENSE MIT + * @version v0.1 + * @link https://github.com/jr-cologne/CryptoStatus GitHub Repository + * + * ________________________________________________________________________________ + * + * CurlClientException.php + * + * The Exception of the cURL client + * + */ + +namespace CryptoStatus\Exceptions; + +use \Exception; + +class CurlClientException extends Exception { + +} diff --git a/app/classes/Exceptions/TwitterClientException.php b/app/classes/Exceptions/TwitterClientException.php new file mode 100644 index 0000000..a17a9e1 --- /dev/null +++ b/app/classes/Exceptions/TwitterClientException.php @@ -0,0 +1,30 @@ += 7.0 + * + * LICENSE: MIT, see LICENSE file for more information + * + * @author JR Cologne + * @copyright 2018 JR Cologne + * @license https://github.com/jr-cologne/CryptoStatus/blob/master/LICENSE MIT + * @version v0.1 + * @link https://github.com/jr-cologne/CryptoStatus GitHub Repository + * + * ________________________________________________________________________________ + * + * TwitterClientException.php + * + * The Exception of the Twitter client + * + */ + +namespace CryptoStatus\Exceptions; + +use \Exception; + +class TwitterClientException extends Exception { + +} diff --git a/app/classes/TwitterClient.php b/app/classes/TwitterClient.php new file mode 100644 index 0000000..2b85355 --- /dev/null +++ b/app/classes/TwitterClient.php @@ -0,0 +1,138 @@ += 7.0 + * + * LICENSE: MIT, see LICENSE file for more information + * + * @author JR Cologne + * @copyright 2018 JR Cologne + * @license https://github.com/jr-cologne/CryptoStatus/blob/master/LICENSE MIT + * @version v0.1 + * @link https://github.com/jr-cologne/CryptoStatus GitHub Repository + * + * ________________________________________________________________________________ + * + * TwitterClient.php + * + * The client for interacting with the Twitter API. + * + */ + +namespace CryptoStatus; + +use CryptoStatus\Exceptions\TwitterClientException; + +use Codebird\Codebird; + +class TwitterClient { + + /** + * A Codebird instance (a Twitter client library for PHP) + * + * @var Codebird $client + */ + protected $client; + + /** + * The Twitter API keys + * + * @var array $api_keys + */ + protected $api_keys; + + /** + * Constructor, initialization and authentication with Twitter API + * + * @param Codebird $twitter_client A Cordbird instance + * @param string $twitter_api_credentials_file The Twitter API credentials file + * @throws TwitterClientException if authentication with Twitter API failed + */ + public function __construct(Codebird $twitter_client) { + $this->client = $twitter_client; + + $this->api_keys = $this->getApiKeys(); + + if (!$this->authenticate()) { + throw new TwitterClientException("Authentication with Twitter API failed", 2); + } + } + + /** + * Post a Tweet + * + * @param array $params Parameters for Twitter API method statuses/update + * @param array $return Data to return from Twitter API reply + * @return boolean (default) or array (when $return is specified) + */ + public function postTweet(array $params, array $return = []) { + $reply = $this->client->statuses_update($params); + + if ($reply->httpstatus == 200) { + if (!empty($return)) { + foreach ($return as $value) { + $return_data[$value] = $reply->{$value}; + } + + return $return_data; + } else { + return true; + } + } + + return false; + } + + /** + * Delete a Tweet + * + * @param string $id ID of the Tweet to delete + * @return bool + */ + public function deleteTweet(string $id) : bool { + $reply = $this->client->statuses_destroy_ID([ 'id' => $id ]); + + if ($reply->httpstatus == 200) { + return true; + } + + return false; + } + + /** + * Get API keys from environment variables + * + * @return array + * @throws TwitterClientException if Twitter API keys could not be retrieved + */ + protected function getApiKeys() : array { + $api_keys = [ + 'consumer_key' => getenv(TWITTER_API_CONSUMER_KEY), + 'consumer_secret' => getenv(TWITTER_API_CONSUMER_SECRET), + 'access_token' => getenv(TWITTER_API_ACCESS_TOKEN), + 'access_token_secret' => getenv(TWITTER_API_ACCESS_TOKEN_SECRET) + ]; + + if ( empty($api_keys['consumer_key']) || empty($api_keys['consumer_secret']) || empty($api_keys['access_token']) || empty($api_keys['access_token_secret']) ) { + throw new TwitterClientException("Could not get Twitter API Keys", 1); + } + + return $api_keys; + } + + /** + * Authenticate with Twitter API + * + * @return bool + */ + protected function authenticate() : bool { + $this->client::setConsumerKey($this->api_keys['consumer_key'], $this->api_keys['consumer_secret']); + + $this->client->setToken($this->api_keys['access_token'], $this->api_keys['access_token_secret']); + + return true; + } + +} diff --git a/app/config/config.php b/app/config/config.php new file mode 100644 index 0000000..e3052d5 --- /dev/null +++ b/app/config/config.php @@ -0,0 +1,33 @@ += 7.0 + * + * LICENSE: MIT, see LICENSE file for more information + * + * @author JR Cologne + * @copyright 2018 JR Cologne + * @license https://github.com/jr-cologne/CryptoStatus/blob/master/LICENSE MIT + * @version v0.1 + * @link https://github.com/jr-cologne/CryptoStatus GitHub Repository + * + * ________________________________________________________________________________ + * + * config.php + * + * The Crypto Status config file + * + */ + +const TWITTER_API_CONSUMER_KEY = 'TWITTER_API_CONSUMER_KEY'; +const TWITTER_API_CONSUMER_SECRET = 'TWITTER_API_CONSUMER_SECRET'; +const TWITTER_API_ACCESS_TOKEN = 'TWITTER_API_ACCESS_TOKEN'; +const TWITTER_API_ACCESS_TOKEN_SECRET = 'TWITTER_API_ACCESS_TOKEN_SECRET'; + +const TWITTER_SCREENNAME = 'status_crypto'; + +const CRYPTO_API = 'https://api.coinmarketcap.com/v1/'; +const CRYPTO_API_ENDPOINT = 'ticker/'; +const CRYPTO_API_LIMIT = 10; diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..5a2898a --- /dev/null +++ b/composer.json @@ -0,0 +1,14 @@ +{ + "require": { + "php": ">=7.0", + "jublonet/codebird-php": "^3.1" + }, + "autoload": { + "psr-4": { + "CryptoStatus\\": "app/classes/" + }, + "files": [ + "app/config/config.php" + ] + } +} diff --git a/composer.lock b/composer.lock new file mode 100644 index 0000000..27e740d --- /dev/null +++ b/composer.lock @@ -0,0 +1,199 @@ +{ + "_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" + ], + "content-hash": "739315aa4ec025431319e2aefc21fb67", + "packages": [ + { + "name": "composer/installers", + "version": "v1.5.0", + "source": { + "type": "git", + "url": "https://github.com/composer/installers.git", + "reference": "049797d727261bf27f2690430d935067710049c2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/installers/zipball/049797d727261bf27f2690430d935067710049c2", + "reference": "049797d727261bf27f2690430d935067710049c2", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.0" + }, + "replace": { + "roundcube/plugin-installer": "*", + "shama/baton": "*" + }, + "require-dev": { + "composer/composer": "1.0.*@dev", + "phpunit/phpunit": "^4.8.36" + }, + "type": "composer-plugin", + "extra": { + "class": "Composer\\Installers\\Plugin", + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\Installers\\": "src/Composer/Installers" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Kyle Robinson Young", + "email": "kyle@dontkry.com", + "homepage": "https://github.com/shama" + } + ], + "description": "A multi-framework Composer library installer", + "homepage": "https://composer.github.io/installers/", + "keywords": [ + "Craft", + "Dolibarr", + "Eliasis", + "Hurad", + "ImageCMS", + "Kanboard", + "Lan Management System", + "MODX Evo", + "Mautic", + "Maya", + "OXID", + "Plentymarkets", + "Porto", + "RadPHP", + "SMF", + "Thelia", + "WolfCMS", + "agl", + "aimeos", + "annotatecms", + "attogram", + "bitrix", + "cakephp", + "chef", + "cockpit", + "codeigniter", + "concrete5", + "croogo", + "dokuwiki", + "drupal", + "eZ Platform", + "elgg", + "expressionengine", + "fuelphp", + "grav", + "installer", + "itop", + "joomla", + "kohana", + "laravel", + "lavalite", + "lithium", + "magento", + "majima", + "mako", + "mediawiki", + "modulework", + "modx", + "moodle", + "osclass", + "phpbb", + "piwik", + "ppi", + "puppet", + "pxcms", + "reindex", + "roundcube", + "shopware", + "silverstripe", + "sydes", + "symfony", + "typo3", + "wordpress", + "yawik", + "zend", + "zikula" + ], + "time": "2017-12-29T09:13:20+00:00" + }, + { + "name": "jublonet/codebird-php", + "version": "3.1.0", + "source": { + "type": "git", + "url": "https://github.com/jublonet/codebird-php.git", + "reference": "100a8e8f1928a5738b4476f0caf83f2c2ba6da5b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/jublonet/codebird-php/zipball/100a8e8f1928a5738b4476f0caf83f2c2ba6da5b", + "reference": "100a8e8f1928a5738b4476f0caf83f2c2ba6da5b", + "shasum": "" + }, + "require": { + "composer/installers": "~1.0", + "ext-hash": "*", + "ext-json": "*", + "lib-openssl": "*", + "php": ">=5.5.0" + }, + "require-dev": { + "phpunit/phpunit": ">=3.7", + "satooshi/php-coveralls": ">=0.6", + "squizlabs/php_codesniffer": "2.*" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "GPL-3.0+" + ], + "authors": [ + { + "name": "Joshua Atkins", + "email": "joshua.atkins@jublo.net", + "homepage": "http://atkins.im/", + "role": "Developer" + }, + { + "name": "J.M.", + "email": "jm@jublo.net", + "homepage": "http://mynetx.net/", + "role": "Developer" + } + ], + "description": "Easy access to the Twitter REST API, Collections API, Streaming API, TON (Object Nest) API and Twitter Ads API — all from one PHP library.", + "homepage": "https://www.jublo.net/projects/codebird/php", + "keywords": [ + "api", + "networking", + "twitter" + ], + "time": "2016-02-15T18:38:55+00:00" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": ">=7.0" + }, + "platform-dev": [] +}