From ac1e937dfd56aa8950da7ff19b4f38edf93e7618 Mon Sep 17 00:00:00 2001 From: Skylar Ittner Date: Mon, 28 Jan 2019 03:06:16 -0700 Subject: [PATCH] Add basic news reader functionality --- .gitignore | 3 +- .gitmodules | 3 + action.php | 4 ++ composer.json | 4 +- composer.lock | 77 ++++++++++++++++----- database.mwb | Bin 0 -> 6342 bytes langs/en/categories.json | 9 +++ lib/ApiFetcher.lib.php | 85 +++++++++++++++++++++++ lib/News.lib.php | 37 ++++++++++ lib/NewsCategory.lib.php | 123 +++++++++++++++++++++++++++++++++ lib/NewsItem.lib.php | 61 ++++++++++++++++ lib/NewsSource.lib.php | 27 ++++++++ lib/NewsSource_NewsAPI.lib.php | 66 ++++++++++++++++++ lib/NewsSource_Reddit.lib.php | 72 +++++++++++++++++++ lib/Thumbnail.lib.php | 60 ++++++++++++++++ lib/Weather.lib.php | 11 +++ nbproject/project.xml | 2 +- pages.php | 23 +++--- pages/home.php | 54 ++++++++++++++- pages/news.php | 41 +++++++++++ settings.template.php | 23 ++++-- static/Shuffle | 1 + static/js/news.js | 19 +++++ 23 files changed, 767 insertions(+), 38 deletions(-) create mode 100644 database.mwb create mode 100644 langs/en/categories.json create mode 100644 lib/ApiFetcher.lib.php create mode 100644 lib/News.lib.php create mode 100644 lib/NewsCategory.lib.php create mode 100644 lib/NewsItem.lib.php create mode 100644 lib/NewsSource.lib.php create mode 100644 lib/NewsSource_NewsAPI.lib.php create mode 100644 lib/NewsSource_Reddit.lib.php create mode 100644 lib/Thumbnail.lib.php create mode 100644 lib/Weather.lib.php create mode 100644 pages/news.php create mode 160000 static/Shuffle create mode 100644 static/js/news.js diff --git a/.gitignore b/.gitignore index 07fe371..207052b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ vendor settings.php nbproject/private -*.sync-conflict* \ No newline at end of file +*.sync-conflict* +/database.mwb.bak \ No newline at end of file diff --git a/.gitmodules b/.gitmodules index 7cba5c4..ea9ed74 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "static/css/material-color"] path = static/css/material-color url = https://source.netsyms.com/Netsyms/Material-Color +[submodule "static/Shuffle"] + path = static/Shuffle + url = https://source.netsyms.com/Mirrors/Vestride_Shuffle.git diff --git a/action.php b/action.php index 67b230b..28f6029 100644 --- a/action.php +++ b/action.php @@ -35,4 +35,8 @@ switch ($VARS['action']) { session_destroy(); header('Location: index.php?logout=1'); die("Logged out."); + case "thumbnail": + header("Content-Type: image/jpeg"); + // TODO + break; } \ No newline at end of file diff --git a/composer.json b/composer.json index fb37c32..9655a01 100644 --- a/composer.json +++ b/composer.json @@ -1,6 +1,6 @@ { - "name": "netsyms/business-app-template", - "description": "Template for a webapp integrated with an AccountHub server.", + "name": "netsyms/todaystream", + "description": "News and weather app.", "type": "project", "require": { "catfan/medoo": "^1.5", diff --git a/composer.lock b/composer.lock index 8d36028..5f8a417 100644 --- a/composer.lock +++ b/composer.lock @@ -4,21 +4,20 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "hash": "5c7439c6e041764f2f6b0270a95ab3ae", - "content-hash": "e4e700119f47d2f68b0ed82abaf8c5c6", + "content-hash": "5642e5d15688c9510f4b0d1b4ceed67e", "packages": [ { "name": "catfan/medoo", - "version": "v1.5.7", + "version": "v1.6.1", "source": { "type": "git", "url": "https://github.com/catfan/Medoo.git", - "reference": "8d90cba0e8ff176028847527d0ea76fe41a06ecf" + "reference": "53a02b300d673f716cb06bf0e24fd774ec53939f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/catfan/Medoo/zipball/8d90cba0e8ff176028847527d0ea76fe41a06ecf", - "reference": "8d90cba0e8ff176028847527d0ea76fe41a06ecf", + "url": "https://api.github.com/repos/catfan/Medoo/zipball/53a02b300d673f716cb06bf0e24fd774ec53939f", + "reference": "53a02b300d673f716cb06bf0e24fd774ec53939f", "shasum": "" }, "require": { @@ -64,7 +63,7 @@ "sql", "sqlite" ], - "time": "2018-06-14 18:59:08" + "time": "2018-12-08T20:24:23+00:00" }, { "name": "guzzlehttp/guzzle", @@ -129,7 +128,7 @@ "rest", "web service" ], - "time": "2018-04-22 15:46:56" + "time": "2018-04-22T15:46:56+00:00" }, { "name": "guzzlehttp/promises", @@ -180,36 +179,37 @@ "keywords": [ "promise" ], - "time": "2016-12-20 10:07:11" + "time": "2016-12-20T10:07:11+00:00" }, { "name": "guzzlehttp/psr7", - "version": "1.4.2", + "version": "1.5.2", "source": { "type": "git", "url": "https://github.com/guzzle/psr7.git", - "reference": "f5b8a8512e2b58b0071a7280e39f14f72e05d87c" + "reference": "9f83dded91781a01c63574e387eaa769be769115" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/psr7/zipball/f5b8a8512e2b58b0071a7280e39f14f72e05d87c", - "reference": "f5b8a8512e2b58b0071a7280e39f14f72e05d87c", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/9f83dded91781a01c63574e387eaa769be769115", + "reference": "9f83dded91781a01c63574e387eaa769be769115", "shasum": "" }, "require": { "php": ">=5.4.0", - "psr/http-message": "~1.0" + "psr/http-message": "~1.0", + "ralouphie/getallheaders": "^2.0.5" }, "provide": { "psr/http-message-implementation": "1.0" }, "require-dev": { - "phpunit/phpunit": "~4.0" + "phpunit/phpunit": "~4.8.36 || ^5.7.27 || ^6.5.8" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.4-dev" + "dev-master": "1.5-dev" } }, "autoload": { @@ -239,13 +239,14 @@ "keywords": [ "http", "message", + "psr-7", "request", "response", "stream", "uri", "url" ], - "time": "2017-03-20 17:10:46" + "time": "2018-12-04T20:46:45+00:00" }, { "name": "psr/http-message", @@ -295,7 +296,47 @@ "request", "response" ], - "time": "2016-08-06 14:39:51" + "time": "2016-08-06T14:39:51+00:00" + }, + { + "name": "ralouphie/getallheaders", + "version": "2.0.5", + "source": { + "type": "git", + "url": "https://github.com/ralouphie/getallheaders.git", + "reference": "5601c8a83fbba7ef674a7369456d12f1e0d0eafa" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/5601c8a83fbba7ef674a7369456d12f1e0d0eafa", + "reference": "5601c8a83fbba7ef674a7369456d12f1e0d0eafa", + "shasum": "" + }, + "require": { + "php": ">=5.3" + }, + "require-dev": { + "phpunit/phpunit": "~3.7.0", + "satooshi/php-coveralls": ">=1.0" + }, + "type": "library", + "autoload": { + "files": [ + "src/getallheaders.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ralph Khattar", + "email": "ralph.khattar@gmail.com" + } + ], + "description": "A polyfill for getallheaders.", + "time": "2016-02-11T07:05:27+00:00" } ], "packages-dev": [], diff --git a/database.mwb b/database.mwb new file mode 100644 index 0000000000000000000000000000000000000000..26402ce9e5aefea6320b946557a2254de9bc6eb4 GIT binary patch literal 6342 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 Xy$uxNAqMhvv4;40c)Hs_pg8{lOBd!y literal 0 HcmV?d00001 diff --git a/langs/en/categories.json b/langs/en/categories.json new file mode 100644 index 0000000..5395e91 --- /dev/null +++ b/langs/en/categories.json @@ -0,0 +1,9 @@ +{ + "business": "Business", + "entertainment": "Entertainment", + "general": "General", + "health": "Health", + "science": "Science", + "sports": "Sports", + "technology": "Technology" +} diff --git a/lib/ApiFetcher.lib.php b/lib/ApiFetcher.lib.php new file mode 100644 index 0000000..37f9189 --- /dev/null +++ b/lib/ApiFetcher.lib.php @@ -0,0 +1,85 @@ +value array of URL parameters + * @param array $extraParams key=>value array of URL parameters that will be ignored when checking the cache + * @param string $expires When the fetched resource should expire from the cache. A string parsable by strtotime() + * @return string The content of the requested URL. + */ + static function get(string $url, array $params = [], array $extraParams = [], string $expires = "+15 minutes"): string { + global $database; + + // Delete stale records + $database->delete("requestcache", ["expires[<=]" => date("Y-m-d H:i:s")]); + + // Make sure the params are in the same order every time + ksort($params); + + $urlparams = []; + foreach ($params as $k => $v) { + $urlparams[] = urlencode($k) . "=" . urlencode($v); + } + + // Make the URL that will be cached + $cacheurl = $url . "?" . implode("&", $urlparams); + + foreach ($extraParams as $k => $v) { + $urlparams[] = urlencode($k) . "=" . urlencode($v); + } + + if ($database->has("requestcache", ["AND" => ["url" => $cacheurl, "expires[>]" => date("Y-m-d H:i:s")]])) { + return $database->get("requestcache", "content", ["AND" => ["url" => $cacheurl, "expires[>]" => date("Y-m-d H:i:s")]]); + } + + // Make the actual URL that will be requested + $requesturl = $url . "?" . implode("&", $urlparams); + + $content = file_get_contents($requesturl); + + // Only insert into db if it didn't fail horribly + if ($content !== FALSE) { + $database->insert("requestcache", ["url" => $cacheurl, "expires" => date("Y-m-d H:i:s", strtotime($expires)), "content" => $content]); + } + + return $content; + } + + /** + * Clear the given URL from the cache. + * + * @global Medoo $database + * @param string $url + * @param array $params key=>value array of URL parameters + */ + static function removeFromCache(string $url = "", array $params = []) { + global $database; + + // Make sure the params are in the same order every time + ksort($params); + + $urlparams = []; + foreach ($params as $k => $v) { + $urlparams[] = urlencode($k) . "=" . urlencode($v); + } + + $cacheurl = $url . "?" . implode("&", $urlparams); + + $database->delete("requestcache", ["url" => $cacheurl]); + } + +} diff --git a/lib/News.lib.php b/lib/News.lib.php new file mode 100644 index 0000000..5a1bca7 --- /dev/null +++ b/lib/News.lib.php @@ -0,0 +1,37 @@ +loadItems(); + News::$items = array_merge(News::$items, $source->getItems()); + } + } + + /** + * Get all news items from all sources loaded. + * @return array + */ + static function getItems() { + return News::$items; + } + +} \ No newline at end of file diff --git a/lib/NewsCategory.lib.php b/lib/NewsCategory.lib.php new file mode 100644 index 0000000..0deb6ef --- /dev/null +++ b/lib/NewsCategory.lib.php @@ -0,0 +1,123 @@ + "Business", + self::ENTERTAINMENT => "Entertainment", + self::GENERAL => "General", + self::HEALTH => "Health", + self::SCIENCE => "Science", + self::SPORTS => "Sports", + self::TECHNOLOGY => "Technology" + ]; + + public function __construct(int $category) { + $this->category = $category; + } + + /** + * Get the category as an int corresponding to one of the constants. + * @return int + */ + public function get(): int { + return $this->category; + } + + /** + * Get a string representation of the category. + * @return string + */ + public function toString(): string { + switch ($this->category) { + case self::BUSINESS: + return "business"; + case self::ENTERTAINMENT: + return "entertainment"; + case self::GENERAL: + return "general"; + case self::HEALTH: + return "health"; + case self::SCIENCE: + return "science"; + case self::SPORTS: + return "sports"; + case self::TECHNOLOGY: + return "technology"; + default: + return ""; + } + } + + public static function fromString(string $category): NewsCategory { + $cat = self::NONE_ALL; + switch (strtolower($category)) { + case "business": + $cat = self::BUSINESS; + break; + case "entertainment": + $cat = self::ENTERTAINMENT; + break; + case "general": + $cat = self::GENERAL; + break; + case "health": + $cat = self::HEALTH; + break; + case "science": + $cat = self::SCIENCE; + break; + case "sports": + $cat = self::SPORTS; + break; + case "technology": + case "tech": + $cat = self::TECHNOLOGY; + break; + } + return new NewsCategory($cat); + } + + /** + * Get a suitable FontAwesome 5 icon for the category. + * @return string CSS classes, such as "fas fa-icon". + */ + public function getIcon(): string { + switch ($this->category) { + case self::BUSINESS: + return "fas fa-briefcase"; + case self::ENTERTAINMENT: + return "fas fa-tv"; + case self::GENERAL: + return "fas fa-info-circle"; + case self::HEALTH: + return "fas fa-heartbeat"; + case self::SCIENCE: + return "fas fa-atom"; + case self::SPORTS: + return "fas fa-futbol"; + case self::TECHNOLOGY: + return "fas fa-laptop"; + default: + return "fas fa-newspaper"; + } + } + +} diff --git a/lib/NewsItem.lib.php b/lib/NewsItem.lib.php new file mode 100644 index 0000000..ba39699 --- /dev/null +++ b/lib/NewsItem.lib.php @@ -0,0 +1,61 @@ +headline = $headline; + $this->img = $img; + $this->url = $url; + $this->source = $source; + $this->via = $via; + $this->timestamp = $timestamp; + if (is_null($category)) { + $this->category = new NewsCategory(NewsCategory::GENERAL); + } else { + $this->category = $category; + } + } + + function getImage(): string { + return $this->img; + } + + function getURL(): string { + return $this->url; + } + + function getHeadline(): string { + return $this->headline; + } + + function getSource(): string { + return $this->source; + } + + function getVia(): string { + return $this->via; + } + + function getTimestamp(): int { + return $this->timestamp; + } + + function getCategory(): \NewsCategory { + return $this->category; + } + +} diff --git a/lib/NewsSource.lib.php b/lib/NewsSource.lib.php new file mode 100644 index 0000000..4883fd8 --- /dev/null +++ b/lib/NewsSource.lib.php @@ -0,0 +1,27 @@ +items; + } + + /** + * Fetch news items from this source. + */ + function loadItems() { + // Do nothing, because this is a dummy source + } + +} \ No newline at end of file diff --git a/lib/NewsSource_NewsAPI.lib.php b/lib/NewsSource_NewsAPI.lib.php new file mode 100644 index 0000000..b8ae4d0 --- /dev/null +++ b/lib/NewsSource_NewsAPI.lib.php @@ -0,0 +1,66 @@ +items; + } + + function loadItems() { + $this->loadHeadlines((new NewsCategory(NewsCategory::BUSINESS))); + $this->loadHeadlines((new NewsCategory(NewsCategory::ENTERTAINMENT))); + $this->loadHeadlines((new NewsCategory(NewsCategory::GENERAL))); + $this->loadHeadlines((new NewsCategory(NewsCategory::HEALTH))); + $this->loadHeadlines((new NewsCategory(NewsCategory::SCIENCE))); + $this->loadHeadlines((new NewsCategory(NewsCategory::SPORTS))); + $this->loadHeadlines((new NewsCategory(NewsCategory::TECHNOLOGY))); + } + + private function loadHeadlines($category = null, string $country = "us", $apikey = null) { + global $SETTINGS; + if (is_null($category)) { + $category = new NewsCategory(NewsCategory::GENERAL); + } + if (is_null($apikey)) { + $apikey = $SETTINGS['apikeys']['newsapi.org']; + } + $url = "https://newsapi.org/v2/top-headlines"; + $params = []; + if (!empty($country)) { + $params['country'] = $country; + } + if (!empty($category->toString())) { + $params['category'] = $category->toString(); + } + $items = []; + $json = ApiFetcher::get($url, $params, ["apiKey" => $apikey]); + $data = json_decode($json, TRUE); + if ($data['status'] != "ok") { + return []; + } + $articles = $data['articles']; + foreach ($articles as $n) { + $title = $n['title']; + $image = $n['urlToImage']; + if (is_null($image)) { + continue; + } + $url = $n['url']; + $source = $n['source']['name']; + $timestamp = strtotime($n['publishedAt']); + + $items[] = new NewsItem($title, $image, $url, $source, "NewsAPI.org", $timestamp, $category); + } + + $this->items = array_merge($this->items, $items); + } + +} diff --git a/lib/NewsSource_Reddit.lib.php b/lib/NewsSource_Reddit.lib.php new file mode 100644 index 0000000..e6d4a59 --- /dev/null +++ b/lib/NewsSource_Reddit.lib.php @@ -0,0 +1,72 @@ +items; + } + + function loadItems() { + $this->loadSubreddit("news+worldnews+UpliftingNews", new NewsCategory(NewsCategory::GENERAL)); + //$this->loadSubreddit("technology", new NewsCategory(NewsCategory::TECHNOLOGY)); + } + + private function loadSubreddit(string $subreddit, NewsCategory $category) { + $items = []; + $json = ApiFetcher::get("https://www.reddit.com/r/$subreddit.json", ["limit" => "50"]); + $news = json_decode($json, TRUE)['data']['children']; + foreach ($news as $d) { + $n = $d['data']; + + // Ignore non-linky or NSFW posts + if ($n['is_self']) { + continue; + } + if ($n['is_video']) { + continue; + } + if ($n['over_18']) { + continue; + } + if (empty($n['url'])) { + continue; + } + + // Thumbnail image + $image = ""; + if (isset($n['thumbnail']) && $n['thumbnail'] != "default") { + $image = $n['thumbnail']; + } + if (isset($n['preview']['images'][0]['resolutions'])) { + if (isset($n['preview']['images'][0]['resolutions'][2]['url'])) { + $image = $n['preview']['images'][0]['resolutions'][2]['url']; + } else if (isset($n['preview']['images'][0]['resolutions'][1]['url'])) { + $image = $n['preview']['images'][0]['resolutions'][1]['url']; + } + } + + $source = "reddit.com"; + if (!empty($n['domain'])) { + $source = $n['domain']; + } + + $timestamp = time(); + if (!empty($n['created'])) { + $timestamp = $n['created']; + } + + $items[] = new NewsItem($n['title'], $image, $n['url'], $source, "reddit", $timestamp, $category); + } + + $this->items = array_merge($this->items, $items); + } + +} diff --git a/lib/Thumbnail.lib.php b/lib/Thumbnail.lib.php new file mode 100644 index 0000000..2f0addf --- /dev/null +++ b/lib/Thumbnail.lib.php @@ -0,0 +1,60 @@ +org.netbeans.modules.php.project - BusinessAppTemplate + TodayStream diff --git a/pages.php b/pages.php index fe7cc1c..23cf0d4 100644 --- a/pages.php +++ b/pages.php @@ -7,19 +7,24 @@ // List of pages and metadata define("PAGES", [ "home" => [ - "title" => "Home", + "title" => "Overview", "navbar" => true, - "icon" => "fas fa-home" - ], - "404" => [ - "title" => "404 error" + "icon" => "fas fa-home", + "scripts" => [ + "static/Shuffle/dist/shuffle.min.js", + "static/js/home.js" + ] ], - "form" => [ - "title" => "Form", + "news" => [ + "title" => "News", "navbar" => true, - "icon" => "fas fa-file-alt", + "icon" => "fas fa-newspaper", "scripts" => [ - "static/js/form.js" + "static/Shuffle/dist/shuffle.min.js", + "static/js/news.js" ] + ], + "404" => [ + "title" => "404 error" ] ]); \ No newline at end of file diff --git a/pages/home.php b/pages/home.php index 1d44fbf..d101aa9 100644 --- a/pages/home.php +++ b/pages/home.php @@ -1,6 +1,54 @@ getCategory()->toString()][] = $item; +} ?> -

Hello World

\ No newline at end of file + + $items) { + $cat = NewsCategory::fromString($category); + ?> +

+ get($cat->toString()); ?> +

+
+ = 5) { + break; + } + $count++; + ?> + + +
+ \ No newline at end of file diff --git a/pages/news.php b/pages/news.php new file mode 100644 index 0000000..7a71fa5 --- /dev/null +++ b/pages/news.php @@ -0,0 +1,41 @@ + + + + +
+ + +
+ +
+ + +
+ +
diff --git a/settings.template.php b/settings.template.php index 94686c0..36543b0 100644 --- a/settings.template.php +++ b/settings.template.php @@ -19,14 +19,14 @@ $SETTINGS = [ // See http://medoo.in/api/new for info "database" => [ "type" => "mysql", - "name" => "app", + "name" => "todaystream", "server" => "localhost", - "user" => "app", + "user" => "", "password" => "", "charset" => "utf8" ], // Name of the app. - "site_title" => "Web App Template", + "site_title" => "TodayStream", // Settings for connecting to the AccountHub server. "accounthub" => [ // URL for the API endpoint @@ -36,6 +36,21 @@ $SETTINGS = [ // API key "key" => "123" ], + // API keys for data sources + "apikeys" => [ + "newsapi.org" => "", + "darksky.net" => "", + ], + // Which data sources to use + "sources" => [ + "news" => [ + "NewsAPI", + "Reddit" + ], + "weather" => [ + "DarkSky" + ] + ], // List of required user permissions to access this app. "permissions" => [ ], @@ -44,7 +59,7 @@ $SETTINGS = [ // Language to use for localization. See langs folder to add a language. "language" => "en", // Shown in the footer of all the pages. - "footer_text" => "", + "footer_text" => "Data sources: NewsAPI.org, Reddit, Dark Sky", // Also shown in the footer, but with "Copyright " in front. "copyright" => "Netsyms Technologies", // Base URL for building links relative to the location of the app. diff --git a/static/Shuffle b/static/Shuffle new file mode 160000 index 0000000..c742bfa --- /dev/null +++ b/static/Shuffle @@ -0,0 +1 @@ +Subproject commit c742bfaefc8908170825227a7385f20e0ba5da37 diff --git a/static/js/news.js b/static/js/news.js new file mode 100644 index 0000000..f51f399 --- /dev/null +++ b/static/js/news.js @@ -0,0 +1,19 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +window.shuffleInstance = new window.Shuffle(document.getElementById('news-grid'), { + itemSelector: '.grid__brick', + sizer: '.sizer-element' +}); + +$("input[name=newscategory]").on("change", function () { + window.shuffleInstance.filter($(this).val()); + $(this).button('toggle'); +}); + +setInterval(function () { + window.shuffleInstance.layout(); +}, 500); \ No newline at end of file