aboutsummarylogtreecommitdiffstats
diff options
context:
space:
mode:
authorAntony Kellermann2018-01-31 15:57:42 -0500
committerAntony Kellermann2018-01-31 15:57:42 -0500
commitae4dc32ef58d61a76a9591b47e0a4fe721d78fd7 (patch)
treecf2d7df6906b1d39853816d3152021e09eaaa740
parent3fd8913733bbb57c38683fdb80aaf12d4719a734 (diff)
downloadaur-ae4dc32ef58d61a76a9591b47e0a4fe721d78fd7.tar.gz
Added support for news
-rw-r--r--.SRCINFO3
-rw-r--r--LICENSE6
-rw-r--r--Makefile2
-rw-r--r--PKGBUILD6
-rw-r--r--README.md20
-rw-r--r--api.c125
-rw-r--r--api.h28
-rw-r--r--main.c8
-rw-r--r--tick.119
9 files changed, 187 insertions, 30 deletions
diff --git a/.SRCINFO b/.SRCINFO
index 6b54836d9e52..22cd99b47455 100644
--- a/.SRCINFO
+++ b/.SRCINFO
@@ -1,11 +1,12 @@
pkgbase = tick
pkgdesc = Command line stock and cryptocurrency portfolio tracker.
- pkgver = 1.4.0
+ pkgver = 1.5.0
pkgrel = 1
url = https://github.com/aokellermann/tick
arch = x86_64
license = MIT
depends = curl
+ depends = json-c
provides = tick
conflicts = tick
source = git://github.com/aokellermann/tick
diff --git a/LICENSE b/LICENSE
index 7d8375fd42af..f8ce84d34b76 100644
--- a/LICENSE
+++ b/LICENSE
@@ -10,4 +10,8 @@ Data provided for free by IEX. Please read IEX's license: https://iextrading.com
Data provided for free by Alpha Vantage. https://www.alphavantage.co
-Data provided for free by Coinmarketcap. https://coinmarketcap.com/ \ No newline at end of file
+Data provided for free by Coinmarketcap. https://coinmarketcap.com
+
+Data provided for free by News API. https://newsapi.org
+
+Shortened links provided for free by Google. https://developers.google.com/url-shortener/ \ No newline at end of file
diff --git a/Makefile b/Makefile
index b1eba2bb84f5..71fc12efe82c 100644
--- a/Makefile
+++ b/Makefile
@@ -1,7 +1,7 @@
CC = gcc
CFLAGS = -g -Wall --std=c99
OBJECTS = main.o api.o portfolio.o
-LIBS = -lcurl
+LIBS = -lcurl -ljson-c
BIN = tick
DESTDIR = /usr
diff --git a/PKGBUILD b/PKGBUILD
index a7308bc3317f..4e4aea6a4821 100644
--- a/PKGBUILD
+++ b/PKGBUILD
@@ -1,13 +1,13 @@
# Maintainer: Antony Kellermann <aokellermann@gmail.com>
pkgname=tick
-pkgver=1.4.0
+pkgver=1.5.0
pkgrel=1
pkgdesc="Command line stock and cryptocurrency portfolio tracker."
-arch=("x86_64")
+arch=('x86_64')
url="https://github.com/aokellermann/${pkgname}"
license=('MIT')
-depends=("curl")
+depends=('curl' 'json-c')
provides=("${pkgname}")
conflicts=("${pkgname}")
source=("git://github.com/aokellermann/${pkgname}")
diff --git a/README.md b/README.md
index 19c2d38824fb..cc0c06b62cea 100644
--- a/README.md
+++ b/README.md
@@ -40,15 +40,27 @@ securities may take up to 10 seconds each. This is due to Alpha Vantage's
relatively slow API. NYSE and NASDAQ listed stocks and ETFs, as well as cryptocurrencies
should take less than one second to print each.
+To get the top three news articles on a specific stock or cryptocurrency,
+you can run
+
+```bash
+$ tick news [symbol]
+```
+
Once installed, you may read the man page for more information.
#### License
MIT License
-Stock information is taken from IEX's free API. Mutual fund and over-the-counter information is taken from Alpha Vantage's
-free API. Cryptocurrency information is taken from Coinmarketcap's free API. Please do not abuse the APIs by repeatedly requesting
-information. Read the provided license for more information.
+Stock information is taken from IEX's free API. Mutual fund and over-the-counter
+information is taken from Alpha Vantage's free API. Cryptocurrency information
+is taken from Coinmarketcap's free API. News information is taken from News
+API. Shortened links are provided by Google's URL Shortener API. Please do
+not abuse the APIs by repeatedly requesting information. Read the provided
+license for more information.
#### Future Ideas
* Command to get info about a security
* More robust information in "check"
-* Historical support -- 24h/7d profits \ No newline at end of file
+* Historical support -- 7d/28d profits
+* Reduce API calls
+* Find a new API to hand all stock calls (quandl?). Alpha Vantage is unreliable \ No newline at end of file
diff --git a/api.c b/api.c
index 7c6afbd8a559..ab98ef1285c1 100644
--- a/api.c
+++ b/api.c
@@ -31,7 +31,7 @@ size_t api_string_writefunc(void* ptr, size_t size, size_t nmemb, String* hStrin
return size * nmemb;
}
-String* api_curl_data(char* url) {
+String* api_curl_data(char* url, char* post_field) {
String* pString = api_string_init();
CURL* curl = curl_easy_init();
CURLcode res;
@@ -41,7 +41,16 @@ String* api_curl_data(char* url) {
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, api_string_writefunc);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &pString->data);
+ struct curl_slist* list = NULL;
+ if (url[12] == 'g') {
+ list = curl_slist_append(list, "Content-Type: application/json");
+ curl_easy_setopt(curl, CURLOPT_HTTPHEADER, list);
+ curl_easy_setopt(curl, CURLOPT_POSTFIELDS, post_field);
+ curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, strlen(post_field));
+ }
res = curl_easy_perform(curl);
+ if (url[12] == 'g')
+ curl_slist_free_all(list);
curl_easy_cleanup(curl);
if (res != CURLE_OK)
return NULL;
@@ -62,7 +71,7 @@ double api_get_current_price(char* ticker_name_string) {
return -1;
}
-double api_get_1d_price(char* ticker_name_string){
+double api_get_1d_price(char* ticker_name_string) {
double val = iex_get_1d_price(ticker_name_string);
if (val != -1)
return val;
@@ -82,7 +91,7 @@ double iex_get_current_price(char* ticker_name_string) {
memcpy(&iex_api_string[37], ticker_name_string, ticker_name_len);
memcpy(&iex_api_string[37 + ticker_name_len], "/quote/latestPrice", 18);
- String* pString = api_curl_data(iex_api_string);
+ String* pString = api_curl_data(iex_api_string, NULL);
free(iex_api_string);
if (strcmp(pString->data, "Unknown symbol") == 0) {
api_string_destroy(&pString);
@@ -99,7 +108,7 @@ double alphavantage_get_current_price(char* ticker_name_string) {
memcpy(alphavantage_api_string, av_str, 160);
size_t av_len = strlen(alphavantage_api_string);
memcpy(&alphavantage_api_string[av_len], ticker_name_string, 10);
- String* pString = api_curl_data(alphavantage_api_string);
+ String* pString = api_curl_data(alphavantage_api_string, NULL);
if (pString->data[0] == '{') {
api_string_destroy(&pString);
memset(alphavantage_api_string, '\0', 160);
@@ -107,7 +116,7 @@ double alphavantage_get_current_price(char* ticker_name_string) {
memcpy(alphavantage_api_string, av_str, 160);
av_len = strlen(alphavantage_api_string);
memcpy(&alphavantage_api_string[av_len], ticker_name_string, 10);
- pString = api_curl_data(alphavantage_api_string);
+ pString = api_curl_data(alphavantage_api_string, NULL);
}
if (pString->data[0] == '{') {
free(alphavantage_api_string);
@@ -133,7 +142,7 @@ double coinmarketcap_get_current_price(char* ticker_name_string) {
char* coinmarketcap_api_string = calloc(64, sizeof(char));
memcpy(coinmarketcap_api_string, cmc_str, 40);
memcpy(&coinmarketcap_api_string[40], ticker_name_string, 20);
- String* pString = api_curl_data(coinmarketcap_api_string);
+ String* pString = api_curl_data(coinmarketcap_api_string, NULL);
if (pString->data[0] == '{') {
free(coinmarketcap_api_string);
api_string_destroy(&pString);
@@ -160,7 +169,7 @@ double iex_get_1d_price(char* ticker_name_string) {
memcpy(&iex_api_string[37], ticker_name_string, ticker_name_len);
memcpy(&iex_api_string[37 + ticker_name_len], "/previous?format=csv", 21);
- String* pString = api_curl_data(iex_api_string);
+ String* pString = api_curl_data(iex_api_string, NULL);
free(iex_api_string);
if (strcmp(pString->data, "Unknown symbol") == 0) {
@@ -180,13 +189,15 @@ double iex_get_1d_price(char* ticker_name_string) {
return ret;
}
-double alphavantage_get_1d_price(char* ticker_name_string){
+double alphavantage_get_1d_price(char* ticker_name_string) {
size_t ticker_name_len = strlen(ticker_name_string);
char* alphavantage_api_string = calloc(128, sizeof(char));
- memcpy(alphavantage_api_string, "https://www.alphavantage.co/query?function=TIME_SERIES_DAILY&apikey=DFUMLJ1ILOM2G7IH&datatype=csv&symbol=", 128);
+ memcpy(alphavantage_api_string,
+ "https://www.alphavantage.co/query?function=TIME_SERIES_DAILY&apikey=DFUMLJ1ILOM2G7IH&datatype=csv&symbol=",
+ 128);
size_t prefix = strlen(alphavantage_api_string);
memcpy(&alphavantage_api_string[prefix], ticker_name_string, ticker_name_len);
- String* pString = api_curl_data(alphavantage_api_string);
+ String* pString = api_curl_data(alphavantage_api_string, NULL);
if (pString->data[0] == '{') {
free(alphavantage_api_string);
api_string_destroy(&pString);
@@ -206,12 +217,12 @@ double alphavantage_get_1d_price(char* ticker_name_string){
return ret;
}
-double coinmarketcap_get_1d_price(char* ticker_name_string){
+double coinmarketcap_get_1d_price(char* ticker_name_string) {
char* cmc_str = "https://api.coinmarketcap.com/v1/ticker/";
char* coinmarketcap_api_string = calloc(64, sizeof(char));
memcpy(coinmarketcap_api_string, cmc_str, 40);
memcpy(&coinmarketcap_api_string[40], ticker_name_string, 20);
- String* pString = api_curl_data(coinmarketcap_api_string);
+ String* pString = api_curl_data(coinmarketcap_api_string, NULL);
if (pString->data[0] == '{') {
free(coinmarketcap_api_string);
api_string_destroy(&pString);
@@ -237,7 +248,95 @@ double coinmarketcap_get_1d_price(char* ticker_name_string){
free(price_string);
free(percent_string);
api_string_destroy(&pString);
- return current_price - (current_price * (percent_change/100));
+ return current_price - (current_price * (percent_change / 100));
+}
+
+void news_print_top_three(char* ticker_name_string) {
+ char* qchar = calloc(64, 1);
+ sprintf(qchar, "&q=%s", ticker_name_string);
+ time_t now = time(NULL);
+ struct tm* ts;
+ char* yearchar = calloc(64, 1);
+ ts = localtime(&now);
+ ts->tm_mday -= 14;
+ mktime(ts);
+ strftime(yearchar, 64, "%Y-%m-%d", ts);
+ char* news_api_string = calloc(256, sizeof(char));
+ sprintf(news_api_string, "%s%s&from=%s",
+ "https://newsapi.org/v2/everything?sortBy=popularity&pageSize=3&language=en&apiKey=1163c352d041460381f0a8273e60a9d1",
+ qchar, yearchar);
+ free(yearchar);
+ free(qchar);
+ String* pString = api_curl_data(news_api_string, NULL);
+ json_print_news(pString->data);
+ free(news_api_string);
+ api_string_destroy(&pString);
+}
+
+void json_print_news(char* data) {
+ Json* jobj = json_tokener_parse(data);
+ Json* articles = json_object_object_get(jobj, "articles");
+ const char* author_string, * title_string, * source_string, * url_string;
+ char* date_string;
+ Json* article, * source, * author, * title, * source_name, * date, * url;
+ for (int i = 0; i < 3; i++) {
+ article = json_object_array_get_idx(articles, (size_t) i);
+ author = json_object_object_get(article, "author");
+ author_string = json_object_to_json_string(author);
+ title = json_object_object_get(article, "title");
+ title_string = json_object_to_json_string(title);
+ source = json_object_object_get(article, "source");
+ source_name = json_object_object_get(source, "name");
+ source_string = json_object_to_json_string(source_name);
+ date = json_object_object_get(article, "publishedAt");
+ date_string = (char*)json_object_to_json_string(date);
+ date_string[11] = '\"';
+ date_string[12] = '\0';
+ url = json_object_object_get(article, "url");
+ url_string = json_object_to_json_string(url);
+ char* url_final = calloc(strlen(url_string + 1), 1);
+ for (int k = 0, j = 0; j < strlen(url_string); k++, j++) {
+ if (url_string[j] == '\\' || url_string[j] == '\"')
+ j++;
+ url_final[k] = url_string[j];
+ }
+
+ const char* shorten = google_shorten_link(url_final);
+ if (author_string != NULL)
+ printf("Title: %s Source: %s Author: %s Date: %s Url: %s\n", title_string, source_string, author_string,
+ date_string, shorten);
+ else printf("Title: %s Source: %s Date: %s Url: %s\n", title_string, source_string, date_string, shorten);
+ free(url_final);
+ free((void*) shorten);
+ }
+ json_object_put(jobj);
+}
+
+const char* google_shorten_link(char* url_string) {
+ char* google_api_string = calloc(256, sizeof(char));
+ memcpy(google_api_string,
+ "https://www.googleapis.com/urlshortener/v1/url?key=AIzaSyAoMAvMPpc7U8lfrnGMk2ZKl966tU2pppU",
+ 256);
+ char* post_string = calloc(1024, 1);
+ sprintf(post_string, "{\"longUrl\": \"%s\"}", url_string);
+ String* pString = api_curl_data(google_api_string, post_string);
+ free(post_string);
+ free(google_api_string);
+ Json* jobj = json_tokener_parse(pString->data);
+ Json* short_url = json_object_object_get(jobj, "id");
+ const char* short_url_string = json_object_to_json_string(short_url);
+ char* copy = calloc(1024, 1);
+ strcpy(copy, short_url_string);
+ char* final = calloc(strlen(copy) + 1, 1);
+ for (int i = 0, j = 0; j < strlen(copy); i++, j++) {
+ if (copy[j] == '\\' || copy[j] == '\"')
+ j++;
+ final[i] = copy[j];
+ }
+ json_object_put(jobj);
+ free(copy);
+ api_string_destroy(&pString);
+ return final;
}
void api_string_destroy(String** phString) {
diff --git a/api.h b/api.h
index 006fae482486..d93888e082a7 100644
--- a/api.h
+++ b/api.h
@@ -1,8 +1,9 @@
/**
- * API data is taken from IEX Trading, Alpha Vantage, and Coinmarketcap.
+ * API data is taken from IEX Trading, Alpha Vantage, Coinmarketcap, and News API.
* https://iextrading.com/developer/docs/
* https://www.alphavantage.co/documentation/
* https://coinmarketcap.com/api/
+ * https://newsapi.org/docs
*/
#ifndef IEX_H
@@ -13,6 +14,7 @@
#include <string.h>
#include <curl/curl.h>
#include <stddef.h>
+#include <json-c/json_tokener.h>
struct string {
char* data;
@@ -21,6 +23,8 @@ struct string {
typedef struct string String;
+typedef struct json_object Json;
+
/**
* Creates and returns a STRING
* object with size 1 and no data
@@ -31,9 +35,10 @@ String* api_string_init(void);
/**
* GETs data from API server and returns it in a String
* @param url API url to GET
+ * @param post_field data needed for POST
* @return NULL if no response from server. Otherwise, String containing data.
*/
-String* api_curl_data(char* url);
+String* api_curl_data(char* url, char* post_field);
/**
* Returns current price of a stock or cryptocurrency.
@@ -112,6 +117,25 @@ double alphavantage_get_1d_price(char* ticker_name_string);
double coinmarketcap_get_1d_price(char* ticker_name_string);
/**
+ * Prints top three news articles in the past week based on the given string
+ * @param ticker_name_string the string
+ */
+void news_print_top_three(char* ticker_name_string);
+
+/**
+ * Given a JSON formatted string, print title, source, author, and url of articles
+ * @param data the json formatted data
+ */
+void json_print_news(char* data);
+
+/**
+ * Given a url, returns a shorter link using goo.gl
+ * @param url_string the link to shorten
+ * @return the shortened link
+ */
+const char* google_shorten_link(char* url_string);
+
+/**
* Destroys String object and frees memory
* @param phString the String to destroy
*/
diff --git a/main.c b/main.c
index 1c469e585d51..d61ada13239d 100644
--- a/main.c
+++ b/main.c
@@ -3,7 +3,13 @@
#include "portfolio.h"
int main(int argc, char* argv[]) {
- if (argc < 3 || argc == 4 || argc > 5) {
+ if (argc == 3 && strcmp(argv[1], "news") == 0) {
+ if (strlen(argv[2]) <= 16 && strlen(argv[2]) > 1)
+ news_print_top_three(argv[2]);
+ else printf("Input invalid length.\n");
+ return 0;
+ }
+ if (argc == 1 || argc == 4 || argc > 5) {
printf("Invalid arguments. Type \"man tick\" for help.\n");
return 0;
}
diff --git a/tick.1 b/tick.1
index f21e8ea014e7..28a67ff20fa1 100644
--- a/tick.1
+++ b/tick.1
@@ -1,4 +1,4 @@
-.TH TICK "1" "January 2018" "Tick 1.4.0" "User Commands"
+.TH TICK "1" "January 2018" "Tick 1.5.0" "User Commands"
.SH NAME
Tick - Command line stock and cryptocurrency portfolio tracker.
@@ -28,6 +28,14 @@ are not reinvested into a specific security, you should update your portfolio wi
Prints information about your current portfolio holdings. Either a symbol or the keyword 'all' can be used. The keyword "all"
will print information about all your current holdings, as well as a grand total.
+.SS
+News:
+
+.TP
+[news] [symbol]
+Prints information on the top three news articles in the past 14 days. The article titles, sources, authors, dates, and URLs
+will be displayed. Technically, the input may be something completely unrelated to investing. This may be amended in the future.
+
.SH FILES
.I ~/.tick_portfolio
.RS
@@ -42,6 +50,9 @@ Antony Kellermann -- https://github.com/aokellermann
.SH LICENSE
MIT License
-Stock information is taken from IEX's free API. Mutual fund and over-the-counter information is taken from Alpha Vantage's
-free API. Cryptocurrency information is taken from Coinmarketcap's free API. Please do not abuse the APIs by repeatedly requesting
-information. Read the provided license for more information. \ No newline at end of file
+Stock information is taken from IEX's free API. Mutual fund and over-the-counter
+information is taken from Alpha Vantage's free API. Cryptocurrency information
+is taken from Coinmarketcap's free API. News information is taken from News
+API. Shortened links are provided by Google's URL Shortener API. Please do
+not abuse the APIs by repeatedly requesting information. Read the provided
+license for more information. \ No newline at end of file