Skip to content

Commit

Permalink
Merge pull request #26 from fedy97/feature/add-coinmarketcap-data-pro…
Browse files Browse the repository at this point in the history
…vider

feat: add coinmarketcap data provider
  • Loading branch information
fedy97 authored Feb 10, 2024
2 parents f4732b1 + 979c363 commit 128d01a
Show file tree
Hide file tree
Showing 10 changed files with 182 additions and 69 deletions.
3 changes: 2 additions & 1 deletion src/main/java/org/bot/MyBot.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import org.bot.commands.*;
import org.bot.commands.base.AuthorizedCommandDecorator;
import org.bot.commands.base.CommandHandler;
import org.bot.commands.base.CommandProcessor;
import org.bot.operations.KucoinOperations;
import org.bot.operations.OperationsDispatcher;
import org.bot.utils.EnvVars;
Expand Down Expand Up @@ -35,7 +36,7 @@ private MyBot() {
commandHandler.register(new AuthorizedCommandDecorator(new BalanceCommand()));
commandHandler.register(new AuthorizedCommandDecorator(new TradeCommand()));

// CommandProcessor.getInstance().registerPortfolioCommands();
CommandProcessor.getInstance().registerPortfolioCommands();

OperationsDispatcher.getInstance().register(new KucoinOperations());
}
Expand Down
7 changes: 4 additions & 3 deletions src/main/java/org/bot/commands/PortfolioCommand.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
import org.bot.commands.base.Command;
import org.bot.models.Portfolio;
import org.bot.models.PortfolioLink;
import org.bot.utils.CoingeckoFacade;
import org.bot.providers.CoinMarketCapProvider;
import org.bot.providers.DataProvider;
import org.telegram.telegrambots.meta.api.objects.Update;
import org.telegram.telegrambots.meta.exceptions.TelegramApiException;

Expand Down Expand Up @@ -40,8 +41,8 @@ public String getDescription() {

private String buildPortfolioResponse() {
try {
CoingeckoFacade coingeckoFacade = CoingeckoFacade.getInstance();
Portfolio portfolio = coingeckoFacade.getCoingeckoPortfolio(getPortfolioLink().getLink());
DataProvider provider = CoinMarketCapProvider.getInstance();
Portfolio portfolio = provider.getPortfolio(getPortfolioLink().getLink());
portfolio.sort();
return portfolio.toString();
} catch (Exception e) {
Expand Down
20 changes: 15 additions & 5 deletions src/main/java/org/bot/commands/TrendingCommand.java
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
package org.bot.commands;

import lombok.extern.slf4j.Slf4j;
import org.bot.commands.base.Command;
import org.bot.models.Trending;
import org.bot.utils.CoingeckoFacade;
import org.bot.providers.CoingeckoProvider;
import org.bot.providers.DataProvider;
import org.telegram.telegrambots.meta.api.objects.Update;
import org.telegram.telegrambots.meta.exceptions.TelegramApiException;

import java.util.Arrays;

@Slf4j
public class TrendingCommand implements Command {

public TrendingCommand() {
Expand All @@ -26,12 +31,17 @@ public String getName() {

@Override
public String getDescription() {
return "show trending coins on Coingecko";
return "show trending coins";
}

private String buildTrendingResponse() {
CoingeckoFacade coingeckoFacade = CoingeckoFacade.getInstance();
Trending trending = coingeckoFacade.getTrendingCoins();
return trending.toString();
try {
DataProvider provider = CoingeckoProvider.getInstance();
Trending trending = provider.getTrendingCoins();
return trending.toString();
} catch (Exception e) {
log.error(Arrays.toString(e.getStackTrace()));
return e.getMessage();
}
}
}
26 changes: 0 additions & 26 deletions src/main/java/org/bot/models/factory/CoinFactory.java

This file was deleted.

97 changes: 97 additions & 0 deletions src/main/java/org/bot/providers/CoinMarketCapProvider.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package org.bot.providers;

import lombok.extern.slf4j.Slf4j;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import org.apache.http.client.HttpResponseException;
import org.bot.models.Coin;
import org.bot.models.Portfolio;
import org.bot.models.Trending;
import org.bot.utils.exceptions.CoingeckoException;
import org.bot.utils.exceptions.NotImplementedException;

import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.Map;

@Slf4j
public class CoinMarketCapProvider implements DataProvider {
private static CoinMarketCapProvider instance;

private CoinMarketCapProvider() {
// Private constructor to prevent instantiation
}

public static CoinMarketCapProvider getInstance() {
if (instance == null) {
instance = new CoinMarketCapProvider();
}
return instance;
}

@Override
public Portfolio getPortfolio(String url) {
Map<String, Coin> coins = new LinkedHashMap<>();
Response response = null;
OkHttpClient client = new OkHttpClient();
try {
Request request = this.buildGetRequest(url);
response = client.newCall(request).execute();
if (!response.isSuccessful())
throw new HttpResponseException(response.code(), response.toString());
assert response.body() != null;
String responseBody = response.body().string();
String[] coinsRaw = responseBody.split("class=\"sc-4984dd93-0 kKpPOn\">");
for (int i = 1; i < coinsRaw.length; i = i + 1) {
Coin coin = this.fromRawCoin(coinsRaw[i]);
if (coin.getTicker() != null) coins.put(coin.getTicker().toUpperCase(), coin);
}
} catch (Exception e) {
log.error(Arrays.toString(e.getStackTrace()));
log.error(e.toString());
throw new CoingeckoException();
} finally {
if (response != null) {
response.close();
}
}
return new Portfolio(coins);
}

@Override
public Trending getTrendingCoins() {
throw new NotImplementedException();
}

@Override
public Coin fromRawCoin(String raw) {
Coin coin = new Coin();
try {
coin.setCoinName(raw.split("<")[0]);
} catch (Exception e) {
log.warn("name for token ignored: " + e);
}
try {
coin.setTicker(raw.split("click=\"true\">")[1].split("<")[0]);
} catch (Exception e) {
log.warn("ticker for token ignored: " + e);
}
try {
coin.setLink("https://coinmarketcap.com" + raw.split("<a href=\"")[1].split("\"")[0]);
} catch (Exception e) {
log.warn("link for token ignored: " + e);
}
try {
coin.setPrice(Double.parseDouble(raw.split("\"cmc-link\"><span>\\$")[1].split("<")[0].replace(",", "")));
} catch (Exception e) {
log.warn("price for token ignored: " + e);
}
try {
coin.setChange24(raw.split("display:inline-block\"></span>")[2].split("<")[0]);
} catch (Exception e) {
log.warn("change24 for token ignored: " + e);
}
return coin;
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package org.bot.utils;
package org.bot.providers;

import lombok.extern.slf4j.Slf4j;
import okhttp3.OkHttpClient;
Expand All @@ -9,41 +9,40 @@
import org.bot.models.Coin;
import org.bot.models.Portfolio;
import org.bot.models.Trending;
import org.bot.models.factory.CoinFactory;
import org.bot.utils.exceptions.CoingeckoException;

import java.util.*;

@Slf4j
public class CoingeckoFacade {
public class CoingeckoProvider implements DataProvider {

private static CoingeckoFacade instance;
private static CoingeckoProvider instance;

private CoingeckoFacade() {
private CoingeckoProvider() {
// Private constructor to prevent instantiation
}

public static CoingeckoFacade getInstance() {
public static CoingeckoProvider getInstance() {
if (instance == null) {
instance = new CoingeckoFacade();
instance = new CoingeckoProvider();
}
return instance;
}

public Portfolio getCoingeckoPortfolio(String url) {
public Portfolio getPortfolio(String url) {
Map<String, Coin> coins = new LinkedHashMap<>();
Response response = null;
OkHttpClient client = new OkHttpClient();
try {
Request request = Helpers.buildRequestCoingecko(url);
Request request = this.buildGetRequest(url);
response = client.newCall(request).execute();
if (!response.isSuccessful())
throw new HttpResponseException(response.code(), response.toString());
assert response.body() != null;
String responseBody = response.body().string();
String[] coinsRaw = responseBody.split("<img loading=\"lazy\" alt=\"");
for (int i = 1; i < coinsRaw.length - 2; i = i + 2) {
Coin coin = CoinFactory.fromRawCoin(coinsRaw[i]);
Coin coin = this.fromRawCoin(coinsRaw[i]);
if (coin.getTicker() != null) coins.put(coin.getTicker().toUpperCase(), coin);
}
} catch (Exception e) {
Expand All @@ -63,11 +62,7 @@ public Trending getTrendingCoins() {
OkHttpClient client = new OkHttpClient();
Response response = null;
try {
Request request = new Request.Builder()
.url(url)
.get()
.build();

Request request = this.buildGetRequest(url);
response = client.newCall(request).execute();
if (response.isSuccessful()) {
ResponseBody responseBody = response.body();
Expand All @@ -86,4 +81,31 @@ public Trending getTrendingCoins() {
}
}
}

@Override
public Request buildGetRequest(String url) {
return new Request.Builder()
.url(url)
.header("Upgrade-Insecure-Requests", "1")
.header("Sec-Fetch-Dest", "document")
.header("Sec-Fetch-Mode", "navigate")
.header("Sec-Fetch-Site", "cross-site")
.get()
.build();
}

@Override
public Coin fromRawCoin(String raw) {
Coin coin = new Coin();
try {
coin.setCoinName(raw.split(" \\(")[0]);
coin.setTicker(raw.split("\\(")[1].split("\\)")[0]);
coin.setLink("https://www.coingecko.com" + raw.split("\"width: 115px;\" href=\"")[1].split("\"")[0]);
coin.setPrice(Double.parseDouble(raw.split("<td data-sort=\"")[1].split("\"")[0]));
coin.setChange24(raw.split("data-formatted=\"false\">")[2].split("<")[0]);
} catch (Exception e) {
log.warn("some info for token ignored: " + e.getMessage());
}
return coin;
}
}
21 changes: 21 additions & 0 deletions src/main/java/org/bot/providers/DataProvider.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package org.bot.providers;

import okhttp3.Request;
import org.bot.models.Coin;
import org.bot.models.Portfolio;
import org.bot.models.Trending;

public interface DataProvider {
Portfolio getPortfolio(String url);

Trending getTrendingCoins();

default Request buildGetRequest(String url) {
return new Request.Builder()
.url(url)
.get()
.build();
}

Coin fromRawCoin(String raw);
}
8 changes: 4 additions & 4 deletions src/main/java/org/bot/tasks/NotifyPercentageChangeTask.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
import org.bot.repositories.CoinNotifyRepository;
import org.bot.repositories.PortfolioLinkRepository;
import org.bot.tasks.base.Task;
import org.bot.utils.CoingeckoFacade;
import org.bot.providers.CoingeckoProvider;
import org.bot.utils.formatters.ToBoldDecorator;

import java.util.List;
Expand All @@ -31,9 +31,9 @@ public void run() {
if (coinsToCheck.isEmpty())
return;
List<PortfolioLink> portfolios = CacheFlyWeight.getInstance(PortfolioLink.class, PortfolioLinkRepository.getInstance()).findAll();
Portfolio portfolio = null;
Portfolio portfolio;
try {
portfolio = CoingeckoFacade.getInstance().getCoingeckoPortfolio(portfolios.get(0).getLink());
portfolio = CoingeckoProvider.getInstance().getPortfolio(portfolios.get(0).getLink());
} catch (Exception e) {
throw new RuntimeException(e);
}
Expand All @@ -56,7 +56,7 @@ public void run() {
}
i++;
}
}catch (Exception e) {
} catch (Exception e) {
e.printStackTrace();
}

Expand Down
13 changes: 0 additions & 13 deletions src/main/java/org/bot/utils/Helpers.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
package org.bot.utils;

import okhttp3.Request;

import java.util.Map;

public class Helpers {
Expand All @@ -11,15 +9,4 @@ private Helpers() {
public static void addToMapOrSum(Map<String, Double> map, String key, Double value) {
map.compute(key, (k, v) -> (v == null) ? value : v + value);
}

public static Request buildRequestCoingecko(String url) {
return new Request.Builder()
.url(url)
.header("Upgrade-Insecure-Requests", "1")
.header("Sec-Fetch-Dest", "document")
.header("Sec-Fetch-Mode", "navigate")
.header("Sec-Fetch-Site", "cross-site")
.get()
.build();
}
}
4 changes: 2 additions & 2 deletions src/main/java/org/bot/visitor/ValidatingCommandVisitor.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ public void visitSavePortofolioLinkCommand() {
if (parts.length != 3)
throw new InvalidCommandException();
String link = parts[2];
if (!link.contains("coingecko.com") || !link.contains("portfolios/public/"))
throw new InvalidCommandException("Coingecko link not valid");
if (!link.contains("coinmarketcap.com/watchlist"))
throw new InvalidCommandException("Portfolio link not valid");
}

@Override
Expand Down

0 comments on commit 128d01a

Please sign in to comment.