[AB-89] adding command to retrieve weather data

This commit is contained in:
Sheldan
2023-03-17 01:49:39 +01:00
parent 735816f5dd
commit 54976ed1d4
28 changed files with 675 additions and 2 deletions

View File

@@ -0,0 +1,176 @@
package dev.sheldan.abstracto.webservices.openeweathermap.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.CommandContext;
import dev.sheldan.abstracto.core.command.execution.CommandResult;
import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.exception.AbstractoRunTimeException;
import dev.sheldan.abstracto.core.interaction.InteractionService;
import dev.sheldan.abstracto.core.interaction.slash.SlashCommandConfig;
import dev.sheldan.abstracto.core.interaction.slash.parameter.SlashCommandParameterService;
import dev.sheldan.abstracto.core.service.ChannelService;
import dev.sheldan.abstracto.core.service.ConfigService;
import dev.sheldan.abstracto.core.templating.model.MessageToSend;
import dev.sheldan.abstracto.core.templating.service.TemplateService;
import dev.sheldan.abstracto.core.utils.FutureUtils;
import dev.sheldan.abstracto.webservices.config.WebServicesSlashCommandNames;
import dev.sheldan.abstracto.webservices.config.WebserviceFeatureDefinition;
import dev.sheldan.abstracto.webservices.openweathermap.config.OpenWeatherMapConfig;
import dev.sheldan.abstracto.webservices.openweathermap.exception.LocationNotFoundException;
import dev.sheldan.abstracto.webservices.openweathermap.model.*;
import dev.sheldan.abstracto.webservices.openweathermap.service.OpenWeatherMapService;
import dev.sheldan.abstracto.webservices.openweathermap.service.WeatherService;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
import org.checkerframework.checker.index.qual.SameLen;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.awt.*;
import java.io.IOException;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
@Component
@Slf4j
public class OpenWeatherMap extends AbstractConditionableCommand {
private static final String OPEN_WEATHER_MAP_COMMAND = "openWeatherMap";
private static final String SEARCH_QUERY_PARAMETER = "searchQuery";
private static final String OPEN_WEATHER_MAP_RESPONSE_TEMPLATE_KEY = "openWeatherMap_command_response";
@Autowired
private SlashCommandParameterService slashCommandParameterService;
@Autowired
private InteractionService interactionService;
@Autowired
private ChannelService channelService;
@Autowired
private OpenWeatherMapService openWeatherMapService;
@Autowired
private TemplateService templateService;
@Autowired
private ConfigService configService;
@Autowired
private WeatherService weatherService;
@Override
public CompletableFuture<CommandResult> executeAsync(CommandContext commandContext) {
String parameter = (String) commandContext.getParameters().getParameters().get(0);
MessageToSend message = getMessageToSend(commandContext.getGuild().getIdLong(), parameter);
return FutureUtils.toSingleFutureGeneric(channelService.sendMessageToSendToChannel(message, commandContext.getChannel()))
.thenApply(unused -> CommandResult.fromSuccess());
}
@Override
public CompletableFuture<CommandResult> executeSlash(SlashCommandInteractionEvent event) {
String query = slashCommandParameterService.getCommandOption(SEARCH_QUERY_PARAMETER, event, String.class);
MessageToSend messageToSend = getMessageToSend(event.getGuild().getIdLong(), query);
return interactionService.replyMessageToSend(messageToSend, event)
.thenApply(interactionHook -> CommandResult.fromSuccess());
}
private MessageToSend getMessageToSend(Long serverId, String parameter) {
try {
GeoCodingResult geoCodingResult = openWeatherMapService.searchForLocation(parameter);
if(geoCodingResult.getResults().isEmpty()) {
throw new LocationNotFoundException();
}
String languageKey = configService.getStringValueOrConfigDefault(OpenWeatherMapConfig.OPEN_WEATHER_MAP_LANGUAGE_KEY_SYSTEM_CONFIG_KEY, serverId);
GeoCodingLocation chosenLocation = geoCodingResult.getResults().get(0);
WeatherResult weatherResult = openWeatherMapService.retrieveWeatherForLocation(chosenLocation, languageKey);
WeatherResponseModel.WeatherResponseModelBuilder builder = WeatherResponseModel
.builder()
.description(weatherResult.getWeathers() != null && !weatherResult.getWeathers().isEmpty()
? weatherResult.getWeathers().get(0).getDescription() : null)
.mainWeather(weatherResult.getWeathers() != null && !weatherResult.getWeathers().isEmpty()
? weatherResult.getWeathers().get(0).getMain() : null)
.clouds(weatherResult.getCloudInfo() != null ? weatherResult.getCloudInfo().getAll() : null)
.rain1H(weatherResult.getRainInfo() != null ? weatherResult.getRainInfo().getRain1H() : null)
.rain3H(weatherResult.getRainInfo() != null ? weatherResult.getRainInfo().getRain3H() : null)
.snow1H(weatherResult.getSnowInfo() != null ? weatherResult.getSnowInfo().getSnow1H() : null)
.snow3H(weatherResult.getSnowInfo() != null ? weatherResult.getSnowInfo().getSnow3H() : null)
.visibility(weatherResult.getVisibility())
.locationName(chosenLocation.getName())
.countryKey(chosenLocation.getCountryKey())
.dataCalculationTime(weatherResult.getDayTime() != null ? Instant.ofEpochSecond(weatherResult.getDayTime()) : null);
Color embedColor = null;
WeatherResultMain mainWeather = weatherResult.getMainWeather();
if(mainWeather != null) {
builder.feelsLikeTemperature(mainWeather.getFeelsLikeTemperature())
.temperature(mainWeather.getTemperature())
.maxTemperature(mainWeather.getMaxTemperature())
.minTemperature(mainWeather.getMinTemperature())
.pressure(mainWeather.getPressure())
.seaLevelPressure(mainWeather.getSeaLevelPressure())
.groundLevelPressure(mainWeather.getGroundLevelPressure())
.humidity(mainWeather.getHumidity());
embedColor = weatherService.getColorForTemperature(mainWeather.getFeelsLikeTemperature());
}
builder.embedColor(embedColor);
WeatherResultSystem systemInfo = weatherResult.getSystemInfo();
if(systemInfo != null) {
builder.sunset(systemInfo.getSunset() != null ? Instant.ofEpochSecond(systemInfo.getSunset()) : null);
builder.sunset(systemInfo.getSunrise() != null ? Instant.ofEpochSecond(systemInfo.getSunrise()) : null);
}
return templateService.renderEmbedTemplate(OPEN_WEATHER_MAP_RESPONSE_TEMPLATE_KEY, builder.build(), serverId);
} catch (IOException e) {
log.warn("Failed to load weather in server {}", serverId, e);
throw new AbstractoRunTimeException(e);
}
}
@Override
public FeatureDefinition getFeature() {
return WebserviceFeatureDefinition.OPEN_WEATHER_MAP;
}
@Override
public CommandConfiguration getConfiguration() {
List<Parameter> parameters = new ArrayList<>();
Parameter searchQueryParameter = Parameter
.builder()
.name(SEARCH_QUERY_PARAMETER)
.type(String.class)
.remainder(true)
.templated(true)
.build();
parameters.add(searchQueryParameter);
HelpInfo helpInfo = HelpInfo
.builder()
.templated(true)
.build();
SlashCommandConfig slashCommandConfig = SlashCommandConfig
.builder()
.enabled(true)
.rootCommandName(WebServicesSlashCommandNames.WEATHER)
.commandName("search")
.build();
return CommandConfiguration.builder()
.name(OPEN_WEATHER_MAP_COMMAND)
.module(UtilityModuleDefinition.UTILITY)
.templated(true)
.slashCommandConfig(slashCommandConfig)
.async(true)
.supportsEmbedException(true)
.causesReaction(false)
.parameters(parameters)
.help(helpInfo)
.build();
}
}

View File

@@ -0,0 +1,63 @@
package dev.sheldan.abstracto.webservices.openeweathermap.service;
import com.google.common.reflect.TypeToken;
import com.google.gson.Gson;
import dev.sheldan.abstracto.webservices.openweathermap.model.GeoCodingLocation;
import dev.sheldan.abstracto.webservices.openweathermap.model.GeoCodingResult;
import dev.sheldan.abstracto.webservices.openweathermap.model.WeatherResult;
import dev.sheldan.abstracto.webservices.openweathermap.service.OpenWeatherMapService;
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.stereotype.Component;
import java.io.IOException;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
@Component
public class OpenWeatherMapServiceBean implements OpenWeatherMapService {
@Autowired
private OkHttpClient okHttpClient;
@Value("${abstracto.feature.webservices.openweatherMap.apiKey}")
private String apiKey;
@Value("${abstracto.feature.webservices.openweathermap.geocodingURL}")
private String geoCodingURL;
@Value("${abstracto.feature.webservices.openweathermap.weatherDataURL}")
private String weatherDataURL;
@Autowired
private Gson gson;
@Override
public GeoCodingResult searchForLocation(String query) throws IOException {
Type geoCodingType = new TypeToken<ArrayList<GeoCodingLocation>>() {}.getType();
Request request = new Request.Builder()
.url(String.format(geoCodingURL, query, 5, apiKey))
.get()
.build();
Response response = okHttpClient.newCall(request).execute();
List<GeoCodingLocation> result = gson.fromJson(response.body().string(), geoCodingType);
return GeoCodingResult
.builder()
.results(result)
.build();
}
@Override
public WeatherResult retrieveWeatherForLocation(GeoCodingLocation location, String languageKey) throws IOException {
Request request = new Request.Builder()
.url(String.format(weatherDataURL, location.getLatitude(), location.getLongitude(), apiKey, languageKey))
.get()
.build();
Response response = okHttpClient.newCall(request).execute();
return gson.fromJson(response.body().string(), WeatherResult.class);
}
}

View File

@@ -0,0 +1,67 @@
package dev.sheldan.abstracto.webservices.openeweathermap.service;
import dev.sheldan.abstracto.webservices.openweathermap.service.WeatherService;
import org.apache.commons.lang3.tuple.Pair;
import org.springframework.stereotype.Component;
import java.awt.*;
import java.util.HashMap;
import java.util.Map;
@Component
public class WeatherServiceBean implements WeatherService {
private static final Map<Pair<Integer, Integer>, String> TEMPERATURE_COLOR_MAP = new HashMap<>();
// source for colors: https://www.esri.com/arcgis-blog/products/arcgis-pro/mapping/a-meaningful-temperature-palette/
static {
TEMPERATURE_COLOR_MAP.put(Pair.of(-100, -48), "#E4EFFF");
TEMPERATURE_COLOR_MAP.put(Pair.of(-48, -45), "#DCE9FA");
TEMPERATURE_COLOR_MAP.put(Pair.of(-45, -42), "#D3E2F7");
TEMPERATURE_COLOR_MAP.put(Pair.of(-42, -40), "#CBDBF4");
TEMPERATURE_COLOR_MAP.put(Pair.of(-40, -37), "#C0D4ED");
TEMPERATURE_COLOR_MAP.put(Pair.of(-37, -34), "#B8CDEA");
TEMPERATURE_COLOR_MAP.put(Pair.of(-34, -31), "#AFC6E6");
TEMPERATURE_COLOR_MAP.put(Pair.of(-31, -28), "#A7BFE3");
TEMPERATURE_COLOR_MAP.put(Pair.of(-28, -26), "#9CB8DF");
TEMPERATURE_COLOR_MAP.put(Pair.of(-26, -23), "#93B1D6");
TEMPERATURE_COLOR_MAP.put(Pair.of(-23, -20), "#89A4CD");
TEMPERATURE_COLOR_MAP.put(Pair.of(-20, -17), "#7F9BC3");
TEMPERATURE_COLOR_MAP.put(Pair.of(-17, -15), "#7590B9");
TEMPERATURE_COLOR_MAP.put(Pair.of(-15, -12), "#617AA8");
TEMPERATURE_COLOR_MAP.put(Pair.of(-12, -9), "#56719C");
TEMPERATURE_COLOR_MAP.put(Pair.of(-9, -6), "#4D6591");
TEMPERATURE_COLOR_MAP.put(Pair.of(-6, -3), "#415C87");
TEMPERATURE_COLOR_MAP.put(Pair.of(-3, -1), "#39517F");
TEMPERATURE_COLOR_MAP.put(Pair.of(-1, 1), "#2F4577");
TEMPERATURE_COLOR_MAP.put(Pair.of(1, 4), "#26436F");
TEMPERATURE_COLOR_MAP.put(Pair.of(4, 7), "#254F77");
TEMPERATURE_COLOR_MAP.put(Pair.of(7, 10), "#275B80");
TEMPERATURE_COLOR_MAP.put(Pair.of(10, 12), "#27678A");
TEMPERATURE_COLOR_MAP.put(Pair.of(12, 15), "#287593");
TEMPERATURE_COLOR_MAP.put(Pair.of(15, 18), "#438190");
TEMPERATURE_COLOR_MAP.put(Pair.of(18, 21), "#648C89");
TEMPERATURE_COLOR_MAP.put(Pair.of(21, 23), "#879A84");
TEMPERATURE_COLOR_MAP.put(Pair.of(23, 26), "#ABA87D");
TEMPERATURE_COLOR_MAP.put(Pair.of(26, 29), "#C2A875");
TEMPERATURE_COLOR_MAP.put(Pair.of(29, 32), "#C19D61");
TEMPERATURE_COLOR_MAP.put(Pair.of(32, 35), "#C38A53");
TEMPERATURE_COLOR_MAP.put(Pair.of(35, 37), "#BE704C");
TEMPERATURE_COLOR_MAP.put(Pair.of(37, 40), "#AF4D4C");
TEMPERATURE_COLOR_MAP.put(Pair.of(40, 43), "#9F294C");
TEMPERATURE_COLOR_MAP.put(Pair.of(43, 46), "#87203E");
TEMPERATURE_COLOR_MAP.put(Pair.of(46, 48), "#631531");
TEMPERATURE_COLOR_MAP.put(Pair.of(48, 65), "#560C25");
TEMPERATURE_COLOR_MAP.put(Pair.of(65, 100), "#3D0216");
}
@Override
public Color getColorForTemperature(Float temperature) {
return Color.decode(TEMPERATURE_COLOR_MAP.get(TEMPERATURE_COLOR_MAP
.keySet()
.stream()
.filter(pair -> pair.getLeft() < temperature && pair.getRight() > temperature)
.findFirst().orElse(TEMPERATURE_COLOR_MAP.keySet().iterator().next())));
}
}

View File

@@ -0,0 +1,10 @@
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext"
xmlns:pro="http://www.liquibase.org/xml/ns/pro"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog dbchangelog.xsd
http://www.liquibase.org/xml/ns/dbchangelog-ext dbchangelog.xsd
http://www.liquibase.org/xml/ns/pro dbchangelog.xsd">
<include file="seedData/data.xml" relativeToChangelogFile="true"/>
</databaseChangeLog>

View File

@@ -0,0 +1,20 @@
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext"
xmlns:pro="http://www.liquibase.org/xml/ns/pro"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog dbchangelog.xsd
http://www.liquibase.org/xml/ns/dbchangelog-ext dbchangelog.xsd
http://www.liquibase.org/xml/ns/pro dbchangelog.xsd" >
<property name="utilityModule" value="(SELECT id FROM module WHERE name = 'utility')"/>
<property name="openWeatherMapFeature" value="(SELECT id FROM feature WHERE key = 'openWeatherMap')"/>
<changeSet author="Sheldan" id="openWeatherMap-command">
<insert tableName="command">
<column name="name" value="openWeatherMap"/>
<column name="module_id" valueComputed="${utilityModule}"/>
<column name="feature_id" valueComputed="${openWeatherMapFeature}"/>
</insert>
</changeSet>
</databaseChangeLog>

View File

@@ -0,0 +1,11 @@
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext"
xmlns:pro="http://www.liquibase.org/xml/ns/pro"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog dbchangelog.xsd
http://www.liquibase.org/xml/ns/dbchangelog-ext dbchangelog.xsd
http://www.liquibase.org/xml/ns/pro dbchangelog.xsd" >
<include file="feature.xml" relativeToChangelogFile="true"/>
<include file="command.xml" relativeToChangelogFile="true"/>
</databaseChangeLog>

View File

@@ -0,0 +1,14 @@
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext"
xmlns:pro="http://www.liquibase.org/xml/ns/pro"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog dbchangelog.xsd
http://www.liquibase.org/xml/ns/dbchangelog-ext dbchangelog.xsd
http://www.liquibase.org/xml/ns/pro dbchangelog.xsd" >
<changeSet author="Sheldan" id="openWeatherMap_feature-insertion">
<insert tableName="feature">
<column name="key" value="openWeatherMap"/>
</insert>
</changeSet>
</databaseChangeLog>

View File

@@ -8,4 +8,5 @@
http://www.liquibase.org/xml/ns/pro dbchangelog.xsd" >
<include file="1.2.5-webservices/collection.xml" relativeToChangelogFile="true"/>
<include file="1.4.7/collection.xml" relativeToChangelogFile="true"/>
<include file="1.4.22/collection.xml" relativeToChangelogFile="true"/>
</databaseChangeLog>

View File

@@ -15,4 +15,16 @@ abstracto.featureModes.videoDetails.mode=videoDetails
abstracto.featureModes.videoDetails.enabled=false
abstracto.featureFlags.threadReader.featureName=threadReader
abstracto.featureFlags.threadReader.enabled=false
abstracto.featureFlags.threadReader.enabled=false
abstracto.feature.webservices.openweathermap.geocodingURL=http://api.openweathermap.org/geo/1.0/direct?q=%s&limit=%s&appid=%s
abstracto.feature.webservices.openweathermap.weatherDataURL=https://api.openweathermap.org/data/2.5/weather?lat=%s&lon=%s&appid=%s&units=metric&lang=%s
abstracto.feature.webservices.openweatherMap.apiKey=${OPEN_WEATHER_MAP_API_KEY}
abstracto.featureFlags.openWeatherMap.featureName=openWeatherMap
abstracto.featureFlags.openWeatherMap.enabled=false
abstracto.systemConfigs.openWeatherMapLanguageKey.name=openWeatherMapLanguageKey
abstracto.systemConfigs.openWeatherMapLanguageKey.stringValue=en