diff --git a/abstracto-application/abstracto-modules/webservices/webservices-impl/pom.xml b/abstracto-application/abstracto-modules/webservices/webservices-impl/pom.xml
index 56169d9ac..ebf5e144c 100644
--- a/abstracto-application/abstracto-modules/webservices/webservices-impl/pom.xml
+++ b/abstracto-application/abstracto-modules/webservices/webservices-impl/pom.xml
@@ -48,6 +48,16 @@
com.google.apis
google-api-services-youtube
+
+
+ org.ehcache
+ ehcache
+ jakarta
+
+
+ org.springframework
+ spring-context-support
+
\ No newline at end of file
diff --git a/abstracto-application/abstracto-modules/webservices/webservices-impl/src/main/java/dev/sheldan/abstracto/webservices/currencyconversion/command/ConvertCurrency.java b/abstracto-application/abstracto-modules/webservices/webservices-impl/src/main/java/dev/sheldan/abstracto/webservices/currencyconversion/command/ConvertCurrency.java
new file mode 100644
index 000000000..791d78ffa
--- /dev/null
+++ b/abstracto-application/abstracto-modules/webservices/webservices-impl/src/main/java/dev/sheldan/abstracto/webservices/currencyconversion/command/ConvertCurrency.java
@@ -0,0 +1,150 @@
+package dev.sheldan.abstracto.webservices.currencyconversion.command;
+
+import dev.sheldan.abstracto.core.command.UtilityModuleDefinition;
+import dev.sheldan.abstracto.core.command.condition.AbstractConditionableCommand;
+import dev.sheldan.abstracto.core.command.config.CommandConfiguration;
+import dev.sheldan.abstracto.core.command.config.HelpInfo;
+import dev.sheldan.abstracto.core.command.config.Parameter;
+import dev.sheldan.abstracto.core.command.execution.CommandResult;
+import dev.sheldan.abstracto.core.config.FeatureDefinition;
+import dev.sheldan.abstracto.core.interaction.InteractionService;
+import dev.sheldan.abstracto.core.interaction.slash.SlashCommandConfig;
+import dev.sheldan.abstracto.core.interaction.slash.parameter.SlashCommandAutoCompleteService;
+import dev.sheldan.abstracto.core.interaction.slash.parameter.SlashCommandParameterService;
+import dev.sheldan.abstracto.webservices.config.WebServicesSlashCommandNames;
+import dev.sheldan.abstracto.webservices.config.WebserviceFeatureDefinition;
+import dev.sheldan.abstracto.webservices.currencyconversion.model.ConvertCurrencyResponseModel;
+import dev.sheldan.abstracto.webservices.currencyconversion.model.Currency;
+import dev.sheldan.abstracto.webservices.currencyconversion.service.CurrencyConversionApiService;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+import net.dv8tion.jda.api.events.interaction.command.CommandAutoCompleteInteractionEvent;
+import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+@Component
+public class ConvertCurrency extends AbstractConditionableCommand {
+
+ private static final String SOURCE_CURRENCY_PARAMETER = "sourceCurrency";
+ private static final String TARGET_CURRENCY_PARAMETER = "targetCurrency";
+ private static final String VALUE_PARAMETER = "value";
+ private static final String CONVERT_CURRENCY_RESPONSE_TEMPLATE = "convertCurrency_response";
+
+ @Autowired
+ private InteractionService interactionService;
+
+ @Autowired
+ private SlashCommandParameterService slashCommandParameterService;
+
+ @Autowired
+ private CurrencyConversionApiService currencyConversionApiService;
+
+ @Autowired
+ private SlashCommandAutoCompleteService slashCommandAutoCompleteService;
+
+ @Override
+ public CompletableFuture executeSlash(SlashCommandInteractionEvent event) {
+ String sourceCurrencyText = slashCommandParameterService.getCommandOption(SOURCE_CURRENCY_PARAMETER, event, String.class);
+ String targetCurrencyText = slashCommandParameterService.getCommandOption(TARGET_CURRENCY_PARAMETER, event, String.class);
+ Double value = slashCommandParameterService.getCommandOption(VALUE_PARAMETER, event, Double.class);
+ Currency sourceCurrency = currencyConversionApiService.getCurrencyForString(sourceCurrencyText);
+ Currency targetCurrency = currencyConversionApiService.getCurrencyForString(targetCurrencyText);
+ Double convertedValue = currencyConversionApiService.convertCurrency(sourceCurrency, targetCurrency, value);
+ ConvertCurrencyResponseModel responseModel = ConvertCurrencyResponseModel
+ .builder()
+ .sourceCurrency(sourceCurrency)
+ .targetCurrency(targetCurrency)
+ .sourceValue(value)
+ .targetValue(convertedValue)
+ .build();
+ return interactionService.replyEmbed(CONVERT_CURRENCY_RESPONSE_TEMPLATE, responseModel, event)
+ .thenApply(interactionHook -> CommandResult.fromSuccess());
+ }
+
+ @Override
+ public List performAutoComplete(CommandAutoCompleteInteractionEvent event) {
+ if(slashCommandAutoCompleteService.matchesParameter(event.getFocusedOption(), SOURCE_CURRENCY_PARAMETER)
+ || slashCommandAutoCompleteService.matchesParameter(event.getFocusedOption(), TARGET_CURRENCY_PARAMETER)) {
+ String input = event.getFocusedOption().getValue().toLowerCase();
+ List supportedCurrencies = currencyConversionApiService.getSupportedCurrencies();
+ Set currencies = new HashSet<>();
+ supportedCurrencies.forEach(currency -> {
+ currencies.add(currency.getCode().toLowerCase());
+ currencies.add(currency.getName().toLowerCase());
+ currencies.add(currency.getSymbol().toLowerCase());
+ if(currency.getSymbolNative() != null) {
+ currencies.add(currency.getSymbolNative().toLowerCase());
+ }
+ });
+ if(!input.isEmpty()) {
+ return currencies.stream().filter(s -> s.contains(input)).toList();
+ } else {
+ return currencies.stream().toList();
+ }
+ } else {
+ return new ArrayList<>();
+ }
+ }
+
+ @Override
+ public CommandConfiguration getConfiguration() {
+ List parameters = new ArrayList<>();
+ Parameter sourceCurrencyParameter = Parameter
+ .builder()
+ .name(SOURCE_CURRENCY_PARAMETER)
+ .type(String.class)
+ .supportsAutoComplete(true)
+ .templated(true)
+ .build();
+ parameters.add(sourceCurrencyParameter);
+ Parameter targetCurrencyParameter = Parameter
+ .builder()
+ .name(TARGET_CURRENCY_PARAMETER)
+ .type(String.class)
+ .supportsAutoComplete(true)
+ .templated(true)
+ .build();
+ parameters.add(targetCurrencyParameter);
+ Parameter valueParameter = Parameter
+ .builder()
+ .name(VALUE_PARAMETER)
+ .type(Double.class)
+ .templated(true)
+ .build();
+ parameters.add(valueParameter);
+ HelpInfo helpInfo = HelpInfo
+ .builder()
+ .templated(true)
+ .build();
+
+
+ SlashCommandConfig slashCommandConfig = SlashCommandConfig
+ .builder()
+ .enabled(true)
+ .rootCommandName(WebServicesSlashCommandNames.CONVERSION)
+ .commandName("currency")
+ .build();
+
+ return CommandConfiguration.builder()
+ .name("convertCurrency")
+ .module(UtilityModuleDefinition.UTILITY)
+ .templated(true)
+ .slashCommandConfig(slashCommandConfig)
+ .async(true)
+ .supportsEmbedException(true)
+ .slashCommandOnly(true)
+ .causesReaction(false)
+ .parameters(parameters)
+ .help(helpInfo)
+ .build();
+ }
+
+ @Override
+ public FeatureDefinition getFeature() {
+ return WebserviceFeatureDefinition.CURRENCY_CONVERSION;
+ }
+}
diff --git a/abstracto-application/abstracto-modules/webservices/webservices-impl/src/main/java/dev/sheldan/abstracto/webservices/currencyconversion/config/CurrencyCacheConfig.java b/abstracto-application/abstracto-modules/webservices/webservices-impl/src/main/java/dev/sheldan/abstracto/webservices/currencyconversion/config/CurrencyCacheConfig.java
new file mode 100644
index 000000000..ae0b944cf
--- /dev/null
+++ b/abstracto-application/abstracto-modules/webservices/webservices-impl/src/main/java/dev/sheldan/abstracto/webservices/currencyconversion/config/CurrencyCacheConfig.java
@@ -0,0 +1,28 @@
+package dev.sheldan.abstracto.webservices.currencyconversion.config;
+
+import java.net.URL;
+import lombok.extern.slf4j.Slf4j;
+import org.ehcache.config.builders.CacheManagerBuilder;
+import org.ehcache.jsr107.Eh107Configuration;
+import org.ehcache.xml.XmlConfiguration;
+import org.springframework.boot.autoconfigure.cache.JCacheManagerCustomizer;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+@Slf4j
+public class CurrencyCacheConfig {
+ @Bean
+ public JCacheManagerCustomizer currencyCacheManagerCustomizer() {
+ URL myUrl = getClass().getResource("/currency-cache-config.xml");
+ XmlConfiguration xmlConfig = new XmlConfiguration(myUrl);
+ org.ehcache.CacheManager myCacheManager = CacheManagerBuilder.newCacheManager(xmlConfig);
+ return cm -> {
+ myCacheManager.getRuntimeConfiguration().getCacheConfigurations().entrySet().forEach(cacheConfiguration -> {
+ javax.cache.configuration.Configuration, ?> jConfiguration = Eh107Configuration.fromEhcacheCacheConfiguration(cacheConfiguration.getValue());
+ log.info("Creating custom cache: " + cacheConfiguration.getKey());
+ cm.createCache(cacheConfiguration.getKey(), jConfiguration);
+ });
+ };
+ }
+}
diff --git a/abstracto-application/abstracto-modules/webservices/webservices-impl/src/main/java/dev/sheldan/abstracto/webservices/currencyconversion/model/api/CurrencyLatestExchangeResponse.java b/abstracto-application/abstracto-modules/webservices/webservices-impl/src/main/java/dev/sheldan/abstracto/webservices/currencyconversion/model/api/CurrencyLatestExchangeResponse.java
new file mode 100644
index 000000000..52cd0723e
--- /dev/null
+++ b/abstracto-application/abstracto-modules/webservices/webservices-impl/src/main/java/dev/sheldan/abstracto/webservices/currencyconversion/model/api/CurrencyLatestExchangeResponse.java
@@ -0,0 +1,11 @@
+package dev.sheldan.abstracto.webservices.currencyconversion.model.api;
+
+import java.util.Map;
+import lombok.Builder;
+import lombok.Getter;
+
+@Builder
+@Getter
+public class CurrencyLatestExchangeResponse {
+ private Map data;
+}
diff --git a/abstracto-application/abstracto-modules/webservices/webservices-impl/src/main/java/dev/sheldan/abstracto/webservices/currencyconversion/model/api/CurrencyListCurrency.java b/abstracto-application/abstracto-modules/webservices/webservices-impl/src/main/java/dev/sheldan/abstracto/webservices/currencyconversion/model/api/CurrencyListCurrency.java
new file mode 100644
index 000000000..5f6defd19
--- /dev/null
+++ b/abstracto-application/abstracto-modules/webservices/webservices-impl/src/main/java/dev/sheldan/abstracto/webservices/currencyconversion/model/api/CurrencyListCurrency.java
@@ -0,0 +1,20 @@
+package dev.sheldan.abstracto.webservices.currencyconversion.model.api;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Builder;
+import lombok.Getter;
+
+@Builder
+@Getter
+public class CurrencyListCurrency {
+ private String symbol;
+ private String name;
+ @SerializedName("symbol_native")
+ private String symbolNative;
+ @SerializedName("decimal_digits")
+ private String decimalDigits;
+ private String rounding;
+ private String code;
+ @SerializedName("name_plural")
+ private String namePlural;
+}
diff --git a/abstracto-application/abstracto-modules/webservices/webservices-impl/src/main/java/dev/sheldan/abstracto/webservices/currencyconversion/model/api/CurrencyListResponse.java b/abstracto-application/abstracto-modules/webservices/webservices-impl/src/main/java/dev/sheldan/abstracto/webservices/currencyconversion/model/api/CurrencyListResponse.java
new file mode 100644
index 000000000..5a2f910cd
--- /dev/null
+++ b/abstracto-application/abstracto-modules/webservices/webservices-impl/src/main/java/dev/sheldan/abstracto/webservices/currencyconversion/model/api/CurrencyListResponse.java
@@ -0,0 +1,11 @@
+package dev.sheldan.abstracto.webservices.currencyconversion.model.api;
+
+import java.util.Map;
+import lombok.Builder;
+import lombok.Getter;
+
+@Builder
+@Getter
+public class CurrencyListResponse {
+ private Map data;
+}
diff --git a/abstracto-application/abstracto-modules/webservices/webservices-impl/src/main/java/dev/sheldan/abstracto/webservices/currencyconversion/service/CurrencyConversionApiServiceBean.java b/abstracto-application/abstracto-modules/webservices/webservices-impl/src/main/java/dev/sheldan/abstracto/webservices/currencyconversion/service/CurrencyConversionApiServiceBean.java
new file mode 100644
index 000000000..b8f7a3f14
--- /dev/null
+++ b/abstracto-application/abstracto-modules/webservices/webservices-impl/src/main/java/dev/sheldan/abstracto/webservices/currencyconversion/service/CurrencyConversionApiServiceBean.java
@@ -0,0 +1,120 @@
+package dev.sheldan.abstracto.webservices.currencyconversion.service;
+
+import com.google.gson.Gson;
+import dev.sheldan.abstracto.core.exception.AbstractoRunTimeException;
+import dev.sheldan.abstracto.webservices.currencyconversion.exception.CurrencyNotFoundException;
+import dev.sheldan.abstracto.webservices.currencyconversion.model.Currency;
+import dev.sheldan.abstracto.webservices.currencyconversion.model.api.CurrencyLatestExchangeResponse;
+import dev.sheldan.abstracto.webservices.currencyconversion.model.api.CurrencyListResponse;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import lombok.extern.slf4j.Slf4j;
+import okhttp3.OkHttpClient;
+import okhttp3.Request;
+import okhttp3.Response;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.cache.annotation.Cacheable;
+import org.springframework.stereotype.Component;
+
+@Component
+@Slf4j
+public class CurrencyConversionApiServiceBean implements CurrencyConversionApiService {
+
+ @Autowired
+ private OkHttpClient okHttpClient;
+
+ @Value("${abstracto.feature.webservices.currencyConversion.currencyURL}")
+ private String currencyUrl;
+
+ @Value("${abstracto.feature.webservices.currencyConversion.conversionURL}")
+ private String conversionUrl;
+
+ @Value("${abstracto.feature.webservices.currencyConversion.apiKey}")
+ private String apiKey;
+
+ @Autowired
+ private Gson gson;
+
+ @Autowired
+ private CurrencyConversionApiService self;
+
+ @Autowired
+ private CurrencyConverter currencyConverter;
+
+ @Override
+ @Cacheable(value = "currency-cache")
+ public List getSupportedCurrencies() {
+ String formattedUrl = currencyUrl;
+ Request request = new Request.Builder()
+ .url(formattedUrl)
+ .header("apikey", apiKey)
+ .get()
+ .build();
+ Response response = null;
+ log.info("Loading available currencies.");
+ try {
+ response = okHttpClient.newCall(request).execute();
+ CurrencyListResponse currencyListResponse = gson.fromJson(response.body().string(), CurrencyListResponse.class);
+ List currencies = new ArrayList<>();
+ currencyListResponse.getData().forEach((s, currencyListCurrency) -> currencies.add(currencyConverter.fromResponseObject(currencyListCurrency)));
+ return currencies;
+ } catch (IOException e) {
+ log.error("Failed to load currencies.", e);
+ throw new AbstractoRunTimeException(e);
+ }
+ }
+
+ @Override
+ public Currency getCurrencyForString(String input) {
+ List supportedCurrencies = self.getSupportedCurrencies();
+ String lowerInput = input.toLowerCase();
+ return supportedCurrencies
+ .stream()
+ .filter(currency -> currency.getCode().toLowerCase().equals(lowerInput)
+ || currency.getName().toLowerCase().equals(lowerInput)
+ || currency.getSymbol().toLowerCase().equals(lowerInput)
+ || currency.getSymbolNative().toLowerCase().equals(lowerInput))
+ .findFirst()
+ .orElseThrow(CurrencyNotFoundException::new);
+ }
+
+ @Override
+ public Double convertCurrency(String sourceCurrencyString, String targetCurrencyString, Double amount) {
+ Currency sourceCurrency = getCurrencyForString(sourceCurrencyString);
+ Currency targetCurrency = getCurrencyForString(targetCurrencyString);
+ return convertCurrency(sourceCurrency, targetCurrency, amount);
+ }
+
+ @Override
+ @Cacheable(value = "currency-conversion-cache")
+ public Map getExchangeRates(Currency sourceCurrency) {
+ String formattedUrl = String.format(conversionUrl, sourceCurrency.getCode());
+ Request request = new Request.Builder()
+ .url(formattedUrl)
+ .header("apikey", apiKey)
+ .get()
+ .build();
+ log.info("Loading exchange rate for {}.", sourceCurrency);
+ try {
+ Response response = okHttpClient.newCall(request).execute();
+ CurrencyLatestExchangeResponse currencyLatestExchangeResponse = gson.fromJson(response.body().string(), CurrencyLatestExchangeResponse.class);
+ return currencyLatestExchangeResponse.getData();
+ } catch (IOException e) {
+ log.error("Failed to load currency exchange rate for {}.", sourceCurrency, e);
+ throw new AbstractoRunTimeException(e);
+ }
+ }
+
+ @Override
+ public Double convertCurrency(Currency sourceCurrency, Currency targetCurrency, Double amount) {
+ Map receivedCurrencies = self.getExchangeRates(sourceCurrency);
+ if(!receivedCurrencies.containsKey(targetCurrency.getCode())) {
+ throw new CurrencyNotFoundException();
+ }
+ Double targetCurrencyValue = receivedCurrencies.get(targetCurrency.getCode());
+ return amount * targetCurrencyValue;
+ }
+}
diff --git a/abstracto-application/abstracto-modules/webservices/webservices-impl/src/main/java/dev/sheldan/abstracto/webservices/currencyconversion/service/CurrencyConverter.java b/abstracto-application/abstracto-modules/webservices/webservices-impl/src/main/java/dev/sheldan/abstracto/webservices/currencyconversion/service/CurrencyConverter.java
new file mode 100644
index 000000000..98f7acdf6
--- /dev/null
+++ b/abstracto-application/abstracto-modules/webservices/webservices-impl/src/main/java/dev/sheldan/abstracto/webservices/currencyconversion/service/CurrencyConverter.java
@@ -0,0 +1,18 @@
+package dev.sheldan.abstracto.webservices.currencyconversion.service;
+
+import dev.sheldan.abstracto.webservices.currencyconversion.model.Currency;
+import dev.sheldan.abstracto.webservices.currencyconversion.model.api.CurrencyListCurrency;
+import org.springframework.stereotype.Component;
+
+@Component
+public class CurrencyConverter {
+ public Currency fromResponseObject(CurrencyListCurrency currencyListCurrency) {
+ return Currency
+ .builder()
+ .code(currencyListCurrency.getCode())
+ .name(currencyListCurrency.getName())
+ .symbol(currencyListCurrency.getSymbol())
+ .symbolNative(currencyListCurrency.getSymbolNative())
+ .build();
+ }
+}
diff --git a/abstracto-application/abstracto-modules/webservices/webservices-impl/src/main/resources/currency-cache-config.xml b/abstracto-application/abstracto-modules/webservices/webservices-impl/src/main/resources/currency-cache-config.xml
new file mode 100644
index 000000000..6b0f25c8c
--- /dev/null
+++ b/abstracto-application/abstracto-modules/webservices/webservices-impl/src/main/resources/currency-cache-config.xml
@@ -0,0 +1,33 @@
+
+
+
+ 43200
+
+
+
+ 5
+
+
+
+
+ 43200
+
+
+
+ 5
+
+
+
+
+ 43200
+
+
+ 50
+
+
+
\ No newline at end of file
diff --git a/abstracto-application/abstracto-modules/webservices/webservices-impl/src/main/resources/migrations/1.5.57/collection.xml b/abstracto-application/abstracto-modules/webservices/webservices-impl/src/main/resources/migrations/1.5.57/collection.xml
new file mode 100644
index 000000000..b14512a81
--- /dev/null
+++ b/abstracto-application/abstracto-modules/webservices/webservices-impl/src/main/resources/migrations/1.5.57/collection.xml
@@ -0,0 +1,6 @@
+
+
+
+
\ No newline at end of file
diff --git a/abstracto-application/abstracto-modules/webservices/webservices-impl/src/main/resources/migrations/1.5.57/seedData/command.xml b/abstracto-application/abstracto-modules/webservices/webservices-impl/src/main/resources/migrations/1.5.57/seedData/command.xml
new file mode 100644
index 000000000..8f2b02995
--- /dev/null
+++ b/abstracto-application/abstracto-modules/webservices/webservices-impl/src/main/resources/migrations/1.5.57/seedData/command.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/abstracto-application/abstracto-modules/webservices/webservices-impl/src/main/resources/migrations/1.5.57/seedData/data.xml b/abstracto-application/abstracto-modules/webservices/webservices-impl/src/main/resources/migrations/1.5.57/seedData/data.xml
new file mode 100644
index 000000000..b7dcd429c
--- /dev/null
+++ b/abstracto-application/abstracto-modules/webservices/webservices-impl/src/main/resources/migrations/1.5.57/seedData/data.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/abstracto-application/abstracto-modules/webservices/webservices-impl/src/main/resources/migrations/1.5.57/seedData/feature.xml b/abstracto-application/abstracto-modules/webservices/webservices-impl/src/main/resources/migrations/1.5.57/seedData/feature.xml
new file mode 100644
index 000000000..da2de52ac
--- /dev/null
+++ b/abstracto-application/abstracto-modules/webservices/webservices-impl/src/main/resources/migrations/1.5.57/seedData/feature.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/abstracto-application/abstracto-modules/webservices/webservices-impl/src/main/resources/migrations/webservices-changeLog.xml b/abstracto-application/abstracto-modules/webservices/webservices-impl/src/main/resources/migrations/webservices-changeLog.xml
index cb5ef07bf..bf358a4b9 100644
--- a/abstracto-application/abstracto-modules/webservices/webservices-impl/src/main/resources/migrations/webservices-changeLog.xml
+++ b/abstracto-application/abstracto-modules/webservices/webservices-impl/src/main/resources/migrations/webservices-changeLog.xml
@@ -6,4 +6,5 @@
+
\ No newline at end of file
diff --git a/abstracto-application/abstracto-modules/webservices/webservices-impl/src/main/resources/webservices-config.properties b/abstracto-application/abstracto-modules/webservices/webservices-impl/src/main/resources/webservices-config.properties
index 0299ed368..85704d2dc 100644
--- a/abstracto-application/abstracto-modules/webservices/webservices-impl/src/main/resources/webservices-config.properties
+++ b/abstracto-application/abstracto-modules/webservices/webservices-impl/src/main/resources/webservices-config.properties
@@ -40,4 +40,10 @@ abstracto.systemConfigs.wikipediaLanguageKey.stringValue=en
abstracto.featureFlags.dictionary.featureName=dictionary
abstracto.featureFlags.dictionary.enabled=false
-abstracto.feature.webservices.dictionaryapi.definitionURL=https://api.dictionaryapi.dev/api/v2/entries/en/{1}
\ No newline at end of file
+abstracto.featureFlags.currencyConversion.featureName=currencyConversion
+abstracto.featureFlags.currencyConversion.enabled=false
+
+abstracto.feature.webservices.dictionaryapi.definitionURL=https://api.dictionaryapi.dev/api/v2/entries/en/{1}
+abstracto.feature.webservices.currencyConversion.currencyURL=https://api.freecurrencyapi.com/v1/currencies
+abstracto.feature.webservices.currencyConversion.conversionURL=https://api.freecurrencyapi.com/v1/latest?base_currency=%s
+abstracto.feature.webservices.currencyConversion.apiKey=${FREE_CURRENCY_API_API_KEY}
\ No newline at end of file
diff --git a/abstracto-application/abstracto-modules/webservices/webservices-int/src/main/java/dev/sheldan/abstracto/webservices/config/WebServicesSlashCommandNames.java b/abstracto-application/abstracto-modules/webservices/webservices-int/src/main/java/dev/sheldan/abstracto/webservices/config/WebServicesSlashCommandNames.java
index 9f5195e71..3f7502c86 100644
--- a/abstracto-application/abstracto-modules/webservices/webservices-int/src/main/java/dev/sheldan/abstracto/webservices/config/WebServicesSlashCommandNames.java
+++ b/abstracto-application/abstracto-modules/webservices/webservices-int/src/main/java/dev/sheldan/abstracto/webservices/config/WebServicesSlashCommandNames.java
@@ -5,5 +5,6 @@ public class WebServicesSlashCommandNames {
public static final String URBAN = "urban";
public static final String WEATHER = "weather";
public static final String WIKIPEDIA = "wikipedia";
+ public static final String CONVERSION = "conversion";
public static final String DICTIONARY = "dictionary";
}
diff --git a/abstracto-application/abstracto-modules/webservices/webservices-int/src/main/java/dev/sheldan/abstracto/webservices/config/WebserviceFeatureDefinition.java b/abstracto-application/abstracto-modules/webservices/webservices-int/src/main/java/dev/sheldan/abstracto/webservices/config/WebserviceFeatureDefinition.java
index fa9edb685..e284a95a0 100644
--- a/abstracto-application/abstracto-modules/webservices/webservices-int/src/main/java/dev/sheldan/abstracto/webservices/config/WebserviceFeatureDefinition.java
+++ b/abstracto-application/abstracto-modules/webservices/webservices-int/src/main/java/dev/sheldan/abstracto/webservices/config/WebserviceFeatureDefinition.java
@@ -10,7 +10,8 @@ public enum WebserviceFeatureDefinition implements FeatureDefinition {
THREAD_READER("threadReader"),
OPEN_WEATHER_MAP("openWeatherMap"),
WIKIPEDIA("wikipedia"),
- DICTIONARY("dictionary");
+ DICTIONARY("dictionary"),
+ CURRENCY_CONVERSION("currencyConversion");
private String key;
diff --git a/abstracto-application/abstracto-modules/webservices/webservices-int/src/main/java/dev/sheldan/abstracto/webservices/currencyconversion/config/CurrencyConversionApiFeatureConfig.java b/abstracto-application/abstracto-modules/webservices/webservices-int/src/main/java/dev/sheldan/abstracto/webservices/currencyconversion/config/CurrencyConversionApiFeatureConfig.java
new file mode 100644
index 000000000..e4619f266
--- /dev/null
+++ b/abstracto-application/abstracto-modules/webservices/webservices-int/src/main/java/dev/sheldan/abstracto/webservices/currencyconversion/config/CurrencyConversionApiFeatureConfig.java
@@ -0,0 +1,16 @@
+package dev.sheldan.abstracto.webservices.currencyconversion.config;
+
+import dev.sheldan.abstracto.core.config.FeatureConfig;
+import dev.sheldan.abstracto.core.config.FeatureDefinition;
+import dev.sheldan.abstracto.webservices.config.WebserviceFeatureDefinition;
+import org.springframework.stereotype.Component;
+
+@Component
+public class CurrencyConversionApiFeatureConfig implements FeatureConfig {
+
+ @Override
+ public FeatureDefinition getFeature() {
+ return WebserviceFeatureDefinition.CURRENCY_CONVERSION;
+ }
+
+}
diff --git a/abstracto-application/abstracto-modules/webservices/webservices-int/src/main/java/dev/sheldan/abstracto/webservices/currencyconversion/exception/CurrencyNotFoundException.java b/abstracto-application/abstracto-modules/webservices/webservices-int/src/main/java/dev/sheldan/abstracto/webservices/currencyconversion/exception/CurrencyNotFoundException.java
new file mode 100644
index 000000000..148851a60
--- /dev/null
+++ b/abstracto-application/abstracto-modules/webservices/webservices-int/src/main/java/dev/sheldan/abstracto/webservices/currencyconversion/exception/CurrencyNotFoundException.java
@@ -0,0 +1,9 @@
+package dev.sheldan.abstracto.webservices.currencyconversion.exception;
+
+import dev.sheldan.abstracto.core.command.exception.AbstractoTemplatedException;
+
+public class CurrencyNotFoundException extends AbstractoTemplatedException {
+ public CurrencyNotFoundException() {
+ super("Currency not found", "currency_conversion_currency_not_found_exception");
+ }
+}
diff --git a/abstracto-application/abstracto-modules/webservices/webservices-int/src/main/java/dev/sheldan/abstracto/webservices/currencyconversion/model/ConvertCurrencyResponseModel.java b/abstracto-application/abstracto-modules/webservices/webservices-int/src/main/java/dev/sheldan/abstracto/webservices/currencyconversion/model/ConvertCurrencyResponseModel.java
new file mode 100644
index 000000000..a01672487
--- /dev/null
+++ b/abstracto-application/abstracto-modules/webservices/webservices-int/src/main/java/dev/sheldan/abstracto/webservices/currencyconversion/model/ConvertCurrencyResponseModel.java
@@ -0,0 +1,13 @@
+package dev.sheldan.abstracto.webservices.currencyconversion.model;
+
+import lombok.Builder;
+import lombok.Getter;
+
+@Builder
+@Getter
+public class ConvertCurrencyResponseModel {
+ private Currency sourceCurrency;
+ private Currency targetCurrency;
+ private Double sourceValue;
+ private Double targetValue;
+}
diff --git a/abstracto-application/abstracto-modules/webservices/webservices-int/src/main/java/dev/sheldan/abstracto/webservices/currencyconversion/model/Currency.java b/abstracto-application/abstracto-modules/webservices/webservices-int/src/main/java/dev/sheldan/abstracto/webservices/currencyconversion/model/Currency.java
new file mode 100644
index 000000000..ed7ffba5b
--- /dev/null
+++ b/abstracto-application/abstracto-modules/webservices/webservices-int/src/main/java/dev/sheldan/abstracto/webservices/currencyconversion/model/Currency.java
@@ -0,0 +1,19 @@
+package dev.sheldan.abstracto.webservices.currencyconversion.model;
+
+import java.util.Map;
+import lombok.Builder;
+import lombok.EqualsAndHashCode;
+import lombok.Getter;
+import lombok.ToString;
+
+@Builder
+@Getter
+@EqualsAndHashCode
+@ToString
+public class Currency {
+ private String symbol;
+ private String symbolNative;
+ private String name;
+ private String code;
+ private Map exchangeRates;
+}
diff --git a/abstracto-application/abstracto-modules/webservices/webservices-int/src/main/java/dev/sheldan/abstracto/webservices/currencyconversion/service/CurrencyConversionApiService.java b/abstracto-application/abstracto-modules/webservices/webservices-int/src/main/java/dev/sheldan/abstracto/webservices/currencyconversion/service/CurrencyConversionApiService.java
new file mode 100644
index 000000000..1f55990e1
--- /dev/null
+++ b/abstracto-application/abstracto-modules/webservices/webservices-int/src/main/java/dev/sheldan/abstracto/webservices/currencyconversion/service/CurrencyConversionApiService.java
@@ -0,0 +1,13 @@
+package dev.sheldan.abstracto.webservices.currencyconversion.service;
+
+import dev.sheldan.abstracto.webservices.currencyconversion.model.Currency;
+import java.util.List;
+import java.util.Map;
+
+public interface CurrencyConversionApiService {
+ List getSupportedCurrencies();
+ Currency getCurrencyForString(String input);
+ Double convertCurrency(String sourceCurrency, String targetCurrency, Double amount);
+ Map getExchangeRates(Currency sourceCurrency);
+ Double convertCurrency(Currency sourceCurrency, Currency targetCurrency, Double amount);
+}