[AB-82] adding youtube video search

This commit is contained in:
Sheldan
2021-03-28 23:29:51 +02:00
parent 9541f907b8
commit c2b413e4b9
31 changed files with 1979 additions and 1 deletions

View File

@@ -23,6 +23,7 @@
<module>remind</module>
<module>suggestion</module>
<module>repost-detection</module>
<module>webservices</module>
</modules>

View File

@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>abstracto-modules</artifactId>
<version>1.2.5-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>webservices</artifactId>
<packaging>pom</packaging>
<modules>
<module>webservices-int</module>
<module>webservices-impl</module>
</modules>
<dependencies>
<dependency>
<groupId>dev.sheldan.abstracto.core</groupId>
<artifactId>core-int</artifactId>
<version>${project.version}</version>
<scope>compile</scope>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,60 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>webservices</artifactId>
<version>1.2.5-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>webservices-impl</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<build>
<plugins>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
<descriptors>
<descriptor>src/main/assembly/liquibase.xml</descriptor>
</descriptors>
</configuration>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>webservices-int</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.google.api-client</groupId>
<artifactId>google-api-client</artifactId>
</dependency>
<dependency>
<groupId>com.google.apis</groupId>
<artifactId>google-api-services-youtube</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,18 @@
<assembly xmlns="http://maven.apache.org/ASSEMBLY/2.1.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/ASSEMBLY/2.1.0 http://maven.apache.org/xsd/assembly-2.1.0.xsd">
<id>liquibase</id>
<formats>
<format>zip</format>
</formats>
<includeBaseDirectory>false</includeBaseDirectory>
<fileSets>
<fileSet>
<outputDirectory>.</outputDirectory>
<directory>${project.basedir}/src/main/resources/migrations</directory>
<includes>
<include>**/*</include>
</includes>
</fileSet>
</fileSets>
</assembly>

View File

@@ -0,0 +1,10 @@
package dev.sheldan.abstracto.webservices.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
@Configuration
@PropertySource("classpath:webservices-config.properties")
public class WebServicesConfig {
}

View File

@@ -0,0 +1,77 @@
package dev.sheldan.abstracto.webservices.youtube.commands;
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.command.execution.ContextConverter;
import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.service.ChannelService;
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.WebserviceFeatureDefinition;
import dev.sheldan.abstracto.webservices.youtube.model.YoutubeVideo;
import dev.sheldan.abstracto.webservices.youtube.model.command.YoutubeVideoSearchCommandModel;
import dev.sheldan.abstracto.webservices.youtube.service.YoutubeSearchService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CompletableFuture;
@Component
public class YoutubeVideoSearch extends AbstractConditionableCommand {
@Autowired
private YoutubeSearchService youtubeSearchService;
@Autowired
private TemplateService templateService;
@Autowired
private ChannelService channelService;
@Override
public CompletableFuture<CommandResult> executeAsync(CommandContext commandContext) {
String query = (String) commandContext.getParameters().getParameters().get(0);
YoutubeVideo foundVideo = youtubeSearchService.searchOneVideoForQuery(query);
YoutubeVideoSearchCommandModel model = (YoutubeVideoSearchCommandModel) ContextConverter.slimFromCommandContext(commandContext, YoutubeVideoSearchCommandModel.class);
model.setVideo(foundVideo);
MessageToSend message = templateService.renderEmbedTemplate("youtube_search_command_response", model);
MessageToSend linkEmbed = templateService.renderEmbedTemplate("youtube_search_command_response_link", model);
CompletableFuture<Void> infoEmbedFuture = FutureUtils.toSingleFutureGeneric(channelService.sendMessageToSendToChannel(message, commandContext.getChannel()));
CompletableFuture<Void> linkEmbedFuture = FutureUtils.toSingleFutureGeneric(channelService.sendMessageToSendToChannel(linkEmbed, commandContext.getChannel()));
return CompletableFuture.allOf(infoEmbedFuture, linkEmbedFuture)
.thenApply(unused -> CommandResult.fromSuccess());
}
@Override
public CommandConfiguration getConfiguration() {
List<Parameter> parameters = new ArrayList<>();
parameters.add(Parameter.builder().name("searchQuery").type(String.class).remainder(true).templated(true).build());
HelpInfo helpInfo = HelpInfo.builder().templated(true).build();
List<String> aliases = Arrays.asList("yt");
return CommandConfiguration.builder()
.name("youtubeSearch")
.module(UtilityModuleDefinition.UTILITY)
.templated(true)
.async(true)
.aliases(aliases)
.supportsEmbedException(true)
.causesReaction(false)
.parameters(parameters)
.help(helpInfo)
.build();
}
@Override
public FeatureDefinition getFeature() {
return WebserviceFeatureDefinition.YOUTUBE;
}
}

View File

@@ -0,0 +1,39 @@
package dev.sheldan.abstracto.webservices.youtube.config;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.http.javanet.NetHttpTransport;
import com.google.api.client.json.JsonFactory;
import com.google.api.client.json.gson.GsonFactory;
import com.google.api.services.youtube.YouTube;
import com.google.api.services.youtube.YouTubeRequestInitializer;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class YoutubeServiceBeans {
@Value("${abstracto.feature.youtube.apiKey}")
private String youtubeApiKey;
@Value("${spring.application.name}")
private String applicationName;
@Bean
public YouTube getYouTubeBean() {
return new YouTube.Builder(getHttpTransport(), getJsonFactory(), request -> {}).setApplicationName(applicationName)
.setYouTubeRequestInitializer(new YouTubeRequestInitializer(youtubeApiKey)).build();
}
@Bean
public HttpTransport getHttpTransport() {
return new NetHttpTransport();
}
@Bean
public JsonFactory getJsonFactory() {
return new GsonFactory();
}
}

View File

@@ -0,0 +1,41 @@
package dev.sheldan.abstracto.webservices.youtube.service;
import com.google.api.services.youtube.YouTube;
import com.google.api.services.youtube.model.SearchListResponse;
import com.google.api.services.youtube.model.SearchResult;
import dev.sheldan.abstracto.webservices.youtube.exception.YoutubeAPIException;
import dev.sheldan.abstracto.webservices.youtube.exception.YoutubeVideoNotFoundException;
import dev.sheldan.abstracto.webservices.youtube.model.YoutubeVideo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.util.List;
@Component
public class YoutubeSearchServiceBean implements YoutubeSearchService {
@Autowired
private YouTube youTube;
@Autowired
private YoutubeVideoService youtubeVideoService;
@Override
public YoutubeVideo searchOneVideoForQuery(String query) {
try {
YouTube.Search.List search = youTube.search().list("id,snippet");
search.setQ(query);
search.setType("video");
search.setMaxResults(1L);
SearchListResponse execute = search.execute();
List<SearchResult> items = execute.getItems();
if(items.isEmpty()) {
throw new YoutubeVideoNotFoundException();
}
return youtubeVideoService.getVideoInfo(items.get(0).getId().getVideoId());
} catch (IOException e) {
throw new YoutubeAPIException(e);
}
}
}

View File

@@ -0,0 +1,53 @@
package dev.sheldan.abstracto.webservices.youtube.service;
import com.google.api.services.youtube.YouTube;
import com.google.api.services.youtube.model.*;
import dev.sheldan.abstracto.webservices.youtube.exception.YoutubeAPIException;
import dev.sheldan.abstracto.webservices.youtube.exception.YoutubeVideoNotFoundException;
import dev.sheldan.abstracto.webservices.youtube.model.YoutubeVideo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.time.Duration;
import java.time.Instant;
import java.util.List;
@Component
public class YoutubeVideoServiceBean implements YoutubeVideoService {
@Autowired
private YouTube youTube;
@Override
public YoutubeVideo getVideoInfo(String id) {
try {
VideoListResponse videoListResponse = youTube
.videos()
.list("statistics,contentDetails,snippet")
.setId(id)
.execute();
List<Video> items = videoListResponse.getItems();
if(items.isEmpty()) {
throw new YoutubeVideoNotFoundException();
}
Video videoInfo = items.get(0);
VideoStatistics statistics = videoInfo.getStatistics();
VideoSnippet snipped = videoInfo.getSnippet();
VideoContentDetails contentDetails = videoInfo.getContentDetails();
return YoutubeVideo
.builder()
.channelTitle(snipped.getChannelTitle())
.commentCount(statistics.getCommentCount())
.dislikes(statistics.getDislikeCount())
.likes(statistics.getLikeCount())
.views(statistics.getViewCount())
.duration(Duration.parse(contentDetails.getDuration()))
.publishedAt(Instant.ofEpochMilli(snipped.getPublishedAt().getValue()))
.url(String.format("https://youtu.be/%s", videoInfo.getId()))
.build();
} catch (IOException e) {
throw new YoutubeAPIException(e);
}
}
}

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-3.8.xsd
http://www.liquibase.org/xml/ns/dbchangelog-ext ../dbchangelog-3.8.xsd
http://www.liquibase.org/xml/ns/pro ../dbchangelog-3.8.xsd" >
<include file="webservices-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-3.8.xsd
http://www.liquibase.org/xml/ns/dbchangelog-ext ../../dbchangelog-3.8.xsd
http://www.liquibase.org/xml/ns/pro ../../dbchangelog-3.8.xsd" >
<property name="utilityModule" value="(SELECT id FROM module WHERE name = 'utility')"/>
<property name="youtubeFeature" value="(SELECT id FROM feature WHERE key = 'youtube')"/>
<changeSet author="Sheldan" id="webservices_youtube-commands">
<insert tableName="command">
<column name="name" value="youtubeSearch"/>
<column name="module_id" valueComputed="${utilityModule}"/>
<column name="feature_id" valueComputed="${youtubeFeature}"/>
</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-3.8.xsd
http://www.liquibase.org/xml/ns/dbchangelog-ext ../../dbchangelog-3.8.xsd
http://www.liquibase.org/xml/ns/pro ../../dbchangelog-3.8.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-3.8.xsd
http://www.liquibase.org/xml/ns/dbchangelog-ext ../../dbchangelog-3.8.xsd
http://www.liquibase.org/xml/ns/pro ../../dbchangelog-3.8.xsd" >
<changeSet author="Sheldan" id="webservices_feature-insertion">
<insert tableName="feature">
<column name="key" value="youtube"/>
</insert>
</changeSet>
</databaseChangeLog>

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-3.8.xsd
http://www.liquibase.org/xml/ns/dbchangelog-ext dbchangelog-3.8.xsd
http://www.liquibase.org/xml/ns/pro dbchangelog-3.8.xsd" >
<include file="1.0-webservices/collection.xml" relativeToChangelogFile="true"/>
</databaseChangeLog>

View File

@@ -0,0 +1,4 @@
abstracto.featureFlags.youtube.featureName=youtube
abstracto.featureFlags.youtube.enabled=false
abstracto.feature.youtube.apiKey=${YOUTUBE_API_KEY}

View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>webservices</artifactId>
<version>1.2.5-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>webservices-int</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
</project>

View File

@@ -0,0 +1,15 @@
package dev.sheldan.abstracto.webservices.config;
import dev.sheldan.abstracto.core.config.FeatureDefinition;
import lombok.Getter;
@Getter
public enum WebserviceFeatureDefinition implements FeatureDefinition {
YOUTUBE("youtube");
private String key;
WebserviceFeatureDefinition(String key) {
this.key = key;
}
}

View File

@@ -0,0 +1,14 @@
package dev.sheldan.abstracto.webservices.youtube.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 YoutubeFeatureConfig implements FeatureConfig {
@Override
public FeatureDefinition getFeature() {
return WebserviceFeatureDefinition.YOUTUBE;
}
}

View File

@@ -0,0 +1,27 @@
package dev.sheldan.abstracto.webservices.youtube.exception;
import dev.sheldan.abstracto.core.exception.AbstractoRunTimeException;
import dev.sheldan.abstracto.core.templating.Templatable;
import dev.sheldan.abstracto.webservices.youtube.model.exception.YoutubeAPIExceptionModel;
public class YoutubeAPIException extends AbstractoRunTimeException implements Templatable {
private final YoutubeAPIExceptionModel model;
public YoutubeAPIException(Throwable throwable) {
this.model = YoutubeAPIExceptionModel
.builder()
.exception(throwable)
.build();
}
@Override
public String getTemplateName() {
return "webservices_youtube_api_exception";
}
@Override
public Object getTemplateModel() {
return model;
}
}

View File

@@ -0,0 +1,17 @@
package dev.sheldan.abstracto.webservices.youtube.exception;
import dev.sheldan.abstracto.core.exception.AbstractoRunTimeException;
import dev.sheldan.abstracto.core.templating.Templatable;
public class YoutubeVideoNotFoundException extends AbstractoRunTimeException implements Templatable {
@Override
public String getTemplateName() {
return "webservices_youtube_video_not_found_exception";
}
@Override
public Object getTemplateModel() {
return new Object();
}
}

View File

@@ -0,0 +1,23 @@
package dev.sheldan.abstracto.webservices.youtube.model;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
import java.math.BigInteger;
import java.time.Duration;
import java.time.Instant;
@Getter
@Setter
@Builder
public class YoutubeVideo {
private String url;
private BigInteger views;
private Instant publishedAt;
private Duration duration;
private String channelTitle;
private BigInteger likes;
private BigInteger dislikes;
private BigInteger commentCount;
}

View File

@@ -0,0 +1,14 @@
package dev.sheldan.abstracto.webservices.youtube.model.command;
import dev.sheldan.abstracto.core.models.context.SlimUserInitiatedServerContext;
import dev.sheldan.abstracto.webservices.youtube.model.YoutubeVideo;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.SuperBuilder;
@Getter
@SuperBuilder
@Setter
public class YoutubeVideoSearchCommandModel extends SlimUserInitiatedServerContext {
private YoutubeVideo video;
}

View File

@@ -0,0 +1,14 @@
package dev.sheldan.abstracto.webservices.youtube.model.exception;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
import java.io.Serializable;
@Getter
@Setter
@Builder
public class YoutubeAPIExceptionModel implements Serializable {
private Throwable exception;
}

View File

@@ -0,0 +1,7 @@
package dev.sheldan.abstracto.webservices.youtube.service;
import dev.sheldan.abstracto.webservices.youtube.model.YoutubeVideo;
public interface YoutubeSearchService {
YoutubeVideo searchOneVideoForQuery(String query);
}

View File

@@ -0,0 +1,7 @@
package dev.sheldan.abstracto.webservices.youtube.service;
import dev.sheldan.abstracto.webservices.youtube.model.YoutubeVideo;
public interface YoutubeVideoService {
YoutubeVideo getVideoInfo(String id);
}

View File

@@ -191,6 +191,18 @@
<artifactId>suggestion-int</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>webservices-int</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>webservices-impl</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
</dependencyManagement>

View File

@@ -209,6 +209,12 @@
<artifactId>suggestion-int</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>webservices-impl</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
<build>

View File

@@ -48,3 +48,5 @@ include::modules/assignableRoles.adoc[]
include::modules/statistic.adoc[]
include::modules/utility.adoc[]
include::modules/webservices.adoc[]

View File

@@ -0,0 +1,13 @@
=== Webservices
Integrates different web APIs to be used via the bot.
=== Youtube
Feature key: `youtube`
==== Command
Search for a youtube video::
* Usage: `youtubeSearch <query>`
* Aliases: `yt`
* Description: Searches youtube for a video with this query, and returns the link with additional information.

View File

@@ -56,7 +56,7 @@
<jruby.version>9.2.11.1</jruby.version>
<maven.compiler.target>1.8</maven.compiler.target>
<maven.compiler.source>1.8</maven.compiler.source>
<guava.version>29.0-jre</guava.version>
<guava.version>30.0-jre</guava.version>
<gson.version>2.8.6</gson.version>
<commons-lang3.version>3.9</commons-lang3.version>
<commons-io.version>2.6</commons-io.version>
@@ -68,6 +68,8 @@
<junit.version>4.13.1</junit.version>
<micrometer.prometheus.version>1.6.3</micrometer.prometheus.version>
<code.coverage.overall.data>${basedir}/../target/jacoco.exec</code.coverage.overall.data>
<google.api.client.version>1.31.3</google.api.client.version>
<google.api.youtube.version>v3-rev222-1.25.0</google.api.youtube.version>
</properties>
<build>
@@ -232,6 +234,18 @@
<version>${micrometer.prometheus.version}</version>
</dependency>
<dependency>
<groupId>com.google.api-client</groupId>
<artifactId>google-api-client</artifactId>
<version>${google.api.client.version}</version>
</dependency>
<dependency>
<groupId>com.google.apis</groupId>
<artifactId>google-api-services-youtube</artifactId>
<version>${google.api.youtube.version}</version>
</dependency>
</dependencies>
</dependencyManagement>