HandieBot/src/main/java/handiebot/utils/YoutubeSearch.java

215 lines
9.3 KiB
Java

package handiebot.utils;
import com.google.api.client.auth.oauth2.Credential;
import com.google.api.client.extensions.java6.auth.oauth2.AuthorizationCodeInstalledApp;
import com.google.api.client.extensions.jetty.auth.oauth2.LocalServerReceiver;
import com.google.api.client.googleapis.auth.oauth2.GoogleAuthorizationCodeFlow;
import com.google.api.client.googleapis.auth.oauth2.GoogleClientSecrets;
import com.google.api.client.googleapis.javanet.GoogleNetHttpTransport;
import com.google.api.client.googleapis.json.GoogleJsonResponseException;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.json.JsonFactory;
import com.google.api.client.json.jackson2.JacksonFactory;
import com.google.api.client.util.store.FileDataStoreFactory;
import com.google.api.services.youtube.YouTube;
import com.google.api.services.youtube.YouTubeScopes;
import com.google.api.services.youtube.model.SearchListResponse;
import com.google.api.services.youtube.model.SearchResult;
import com.google.api.services.youtube.model.Video;
import com.google.api.services.youtube.model.VideoListResponse;
import com.google.common.base.Joiner;
import sx.blah.discord.api.internal.json.objects.EmbedObject;
import sx.blah.discord.handle.obj.IChannel;
import sx.blah.discord.handle.obj.IMessage;
import sx.blah.discord.util.EmbedBuilder;
import java.awt.*;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.security.GeneralSecurityException;
import java.text.MessageFormat;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import static handiebot.HandieBot.APPLICATION_NAME;
import static handiebot.HandieBot.resourceBundle;
import static handiebot.utils.MessageUtils.addReaction;
import static handiebot.utils.MessageUtils.sendMessage;
/**
* @author Andrew Lalis
* Class to query Youtube Data API for results to searches, and return these in a nice list.
*/
public class YoutubeSearch {
private static final String KEY = "AIzaSyAjYuxCYBCuZCNvW4w573LQ-jw5UKL64G8";
private static final int NUMBER_OF_VIDEOS_RETURNED = 5;
public static final String WATCH_URL = "https://www.youtube.com/watch?v=";
private static final File DATA_STORE_DIR = new File(FileUtil.getDataDirectory()+"googleData/");
private static FileDataStoreFactory DATA_STORE_FACTORY;
private static final JsonFactory JSON_FACTORY = JacksonFactory.getDefaultInstance();
private static HttpTransport HTTP_TRANSPORT;
private static final List<String> SCOPES = Arrays.asList(YouTubeScopes.YOUTUBE_READONLY);
static {
try {
HTTP_TRANSPORT = GoogleNetHttpTransport.newTrustedTransport();
DATA_STORE_FACTORY = new FileDataStoreFactory(DATA_STORE_DIR);
} catch (GeneralSecurityException | IOException e) {
e.printStackTrace();
HTTP_TRANSPORT = null;
}
}
/**
* Create an authorized Credential object.
* @return an authorized Credential object.
* @throws IOException
*/
public static Credential authorize() throws IOException {
// Load client secrets.
GoogleClientSecrets clientSecrets = GoogleClientSecrets.load(JSON_FACTORY,
new InputStreamReader(new FileInputStream(FileUtil.getDataDirectory()+"googleData/client_secret.json")));
// Build flow and trigger user authorization request.
GoogleAuthorizationCodeFlow flow = new GoogleAuthorizationCodeFlow.Builder(HTTP_TRANSPORT, JSON_FACTORY, clientSecrets, SCOPES)
.setDataStoreFactory(DATA_STORE_FACTORY)
.setAccessType("offline")
.build();
return new AuthorizationCodeInstalledApp(flow, new LocalServerReceiver()).authorize("user");
}
/**
* Build and return an authorized API client service, such as a YouTube
* Data API client service.
* @return an authorized API client service
* @throws IOException
*/
public static YouTube getYouTubeService() throws IOException {
Credential credential = authorize();
return new YouTube.Builder(HTTP_TRANSPORT, JSON_FACTORY, credential)
.setApplicationName(APPLICATION_NAME)
.build();
}
/**
* Query Youtube Data API for a list of videos matching a given string.
* @param searchString The string to use for the search.
* @return A List of SearchResult objects which contain data about each video.
*/
public static List<Video> query(String searchString){
try {
YouTube youtube = getYouTubeService();
YouTube.Search.List search = youtube.search().list("id,snippet");
search.setKey(KEY);
search.setQ(searchString);
search.setType("video");
search.setFields("items(id/videoId)");
search.setMaxResults((long)NUMBER_OF_VIDEOS_RETURNED);
SearchListResponse searchResponse = search.execute();
List<SearchResult> results = searchResponse.getItems();
List<String> videoIds = new ArrayList<>();
if (results != null){
for (SearchResult searchResult : results){
videoIds.add(searchResult.getId().getVideoId());
}
Joiner stringJoiner = Joiner.on(',');
String videosId = stringJoiner.join(videoIds);
YouTube.Videos.List listVideosRequest = youtube.videos().list("snippet,statistics,contentDetails").setId(videosId);
VideoListResponse listResponse = listVideosRequest.execute();
return listResponse.getItems();
}
} catch (GoogleJsonResponseException e) {
System.err.println("There was a service error: " + e.getDetails().getCode() + " : "
+ e.getDetails().getMessage());
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
/**
* Creates an embed object to display.
* @param results The list of videos to use to generate an embed object.
* @return A fully assembled embedded object.
*/
public static EmbedObject createEmbed(List<Video> results){
EmbedBuilder builder = new EmbedBuilder();
if (results != null) {
builder.withTitle(MessageFormat.format(resourceBundle.getString("commands.youtube.title"), NUMBER_OF_VIDEOS_RETURNED));
builder.withColor(Color.red);
for (int i = 0; i < results.size(); i++) {
Video video = results.get(i);
String views = NumberFormat.getNumberInstance(Locale.US).format(video.getStatistics().getViewCount());
String duration = video.getContentDetails().getDuration()
.replace("PT", "")
.replace("H", ":")
.replace("M", ":")
.replace("S", "");
String[] components = duration.split(":");
int hours, minutes, seconds;
String formattedTime = "Unknown";
if (components.length == 2){
minutes = Integer.parseInt(components[0]);
seconds = Integer.parseInt(components[1]);
formattedTime = String.format("%d:%02d", minutes, seconds);
} else if (components.length == 3){
hours = Integer.parseInt(components[0]);
minutes = Integer.parseInt(components[1]);
seconds = Integer.parseInt(components[2]);
formattedTime = String.format("%d:%02d:%02d", hours, minutes, seconds);
}
String channelName = video.getSnippet().getChannelTitle();
double likeDislikeRatio = (double)video.getStatistics().getLikeCount().longValue() / (double)video.getStatistics().getDislikeCount().longValue();
builder.appendField(MessageUtils.getNumberEmoji(i+1) + video.getSnippet().getTitle(),
":signal_strength: " + views +
"\t:watch: " + formattedTime +
"\t:copyright: "+ channelName +
"\t:arrow_up_down: "+ String.format("%.2f", likeDislikeRatio) +
"\n"+ WATCH_URL + video.getId(),
false);
}
builder.withFooterText(resourceBundle.getString("commands.youtube.footerInstruction"));
}
return builder.build();
}
/**
* Displays the first five results from a youtube search, and adds reactions so that a user may choose an option.
* @param videos The list of videos, returned from calling {@code query}.
* @param channel The channel to display the dialog in.
*/
public static IMessage displayChoicesDialog(List<Video> videos, IChannel channel){
EmbedObject e = YoutubeSearch.createEmbed(videos);
IMessage message = sendMessage(e, channel);
List<String> urls = new ArrayList<>(videos.size());
videos.forEach((video) -> urls.add(WATCH_URL + video.getId()));
addReaction(message, ":one:");
addReaction(message, ":two:");
addReaction(message, ":three:");
addReaction(message, ":four:");
addReaction(message, ":five:");
addReaction(message, ":x:");
return message;
}
}