Skip to content

Commit 1e93b1d

Browse files
authored
Merge pull request #1135 from Stypox/yt-emergency-info
[YouTube] Implement emergency meta info
2 parents 1f8a044 + 5b59a1a commit 1e93b1d

5 files changed

Lines changed: 226 additions & 123 deletions

File tree

Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
package org.schabi.newpipe.extractor.services.youtube;
2+
3+
import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.extractCachedUrlIfNeeded;
4+
import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.getTextFromObject;
5+
import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.getTextFromObjectOrThrow;
6+
import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.getUrlFromNavigationEndpoint;
7+
import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.isGoogleURL;
8+
import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty;
9+
import static org.schabi.newpipe.extractor.utils.Utils.replaceHttpWithHttps;
10+
11+
import com.grack.nanojson.JsonArray;
12+
import com.grack.nanojson.JsonObject;
13+
14+
import org.schabi.newpipe.extractor.MetaInfo;
15+
import org.schabi.newpipe.extractor.exceptions.ParsingException;
16+
import org.schabi.newpipe.extractor.stream.Description;
17+
18+
import java.net.MalformedURLException;
19+
import java.net.URL;
20+
import java.util.ArrayList;
21+
import java.util.List;
22+
import java.util.Objects;
23+
import java.util.function.Consumer;
24+
import java.util.stream.Collectors;
25+
26+
import javax.annotation.Nonnull;
27+
28+
public final class YoutubeMetaInfoHelper {
29+
30+
private YoutubeMetaInfoHelper() {
31+
}
32+
33+
34+
@Nonnull
35+
public static List<MetaInfo> getMetaInfo(@Nonnull final JsonArray contents)
36+
throws ParsingException {
37+
final List<MetaInfo> metaInfo = new ArrayList<>();
38+
for (final Object content : contents) {
39+
final JsonObject resultObject = (JsonObject) content;
40+
if (resultObject.has("itemSectionRenderer")) {
41+
for (final Object sectionContentObject
42+
: resultObject.getObject("itemSectionRenderer").getArray("contents")) {
43+
44+
final JsonObject sectionContent = (JsonObject) sectionContentObject;
45+
if (sectionContent.has("infoPanelContentRenderer")) {
46+
metaInfo.add(getInfoPanelContent(sectionContent
47+
.getObject("infoPanelContentRenderer")));
48+
}
49+
if (sectionContent.has("clarificationRenderer")) {
50+
metaInfo.add(getClarificationRenderer(sectionContent
51+
.getObject("clarificationRenderer")
52+
));
53+
}
54+
if (sectionContent.has("emergencyOneboxRenderer")) {
55+
getEmergencyOneboxRenderer(
56+
sectionContent.getObject("emergencyOneboxRenderer"),
57+
metaInfo::add
58+
);
59+
}
60+
}
61+
}
62+
}
63+
return metaInfo;
64+
}
65+
66+
@Nonnull
67+
private static MetaInfo getInfoPanelContent(@Nonnull final JsonObject infoPanelContentRenderer)
68+
throws ParsingException {
69+
final MetaInfo metaInfo = new MetaInfo();
70+
final StringBuilder sb = new StringBuilder();
71+
for (final Object paragraph : infoPanelContentRenderer.getArray("paragraphs")) {
72+
if (sb.length() != 0) {
73+
sb.append("<br>");
74+
}
75+
sb.append(getTextFromObject((JsonObject) paragraph));
76+
}
77+
metaInfo.setContent(new Description(sb.toString(), Description.HTML));
78+
if (infoPanelContentRenderer.has("sourceEndpoint")) {
79+
final String metaInfoLinkUrl = getUrlFromNavigationEndpoint(
80+
infoPanelContentRenderer.getObject("sourceEndpoint"));
81+
try {
82+
metaInfo.addUrl(new URL(Objects.requireNonNull(extractCachedUrlIfNeeded(
83+
metaInfoLinkUrl))));
84+
} catch (final NullPointerException | MalformedURLException e) {
85+
throw new ParsingException("Could not get metadata info URL", e);
86+
}
87+
88+
final String metaInfoLinkText = getTextFromObject(
89+
infoPanelContentRenderer.getObject("inlineSource"));
90+
if (isNullOrEmpty(metaInfoLinkText)) {
91+
throw new ParsingException("Could not get metadata info link text.");
92+
}
93+
metaInfo.addUrlText(metaInfoLinkText);
94+
}
95+
96+
return metaInfo;
97+
}
98+
99+
@Nonnull
100+
private static MetaInfo getClarificationRenderer(
101+
@Nonnull final JsonObject clarificationRenderer) throws ParsingException {
102+
final MetaInfo metaInfo = new MetaInfo();
103+
104+
final String title = getTextFromObject(clarificationRenderer
105+
.getObject("contentTitle"));
106+
final String text = getTextFromObject(clarificationRenderer
107+
.getObject("text"));
108+
if (title == null || text == null) {
109+
throw new ParsingException("Could not extract clarification renderer content");
110+
}
111+
metaInfo.setTitle(title);
112+
metaInfo.setContent(new Description(text, Description.PLAIN_TEXT));
113+
114+
if (clarificationRenderer.has("actionButton")) {
115+
final JsonObject actionButton = clarificationRenderer.getObject("actionButton")
116+
.getObject("buttonRenderer");
117+
try {
118+
final String url = getUrlFromNavigationEndpoint(actionButton
119+
.getObject("command"));
120+
metaInfo.addUrl(new URL(Objects.requireNonNull(extractCachedUrlIfNeeded(url))));
121+
} catch (final NullPointerException | MalformedURLException e) {
122+
throw new ParsingException("Could not get metadata info URL", e);
123+
}
124+
125+
final String metaInfoLinkText = getTextFromObject(
126+
actionButton.getObject("text"));
127+
if (isNullOrEmpty(metaInfoLinkText)) {
128+
throw new ParsingException("Could not get metadata info link text.");
129+
}
130+
metaInfo.addUrlText(metaInfoLinkText);
131+
}
132+
133+
if (clarificationRenderer.has("secondaryEndpoint") && clarificationRenderer
134+
.has("secondarySource")) {
135+
final String url = getUrlFromNavigationEndpoint(clarificationRenderer
136+
.getObject("secondaryEndpoint"));
137+
// Ignore Google URLs, because those point to a Google search about "Covid-19"
138+
if (url != null && !isGoogleURL(url)) {
139+
try {
140+
metaInfo.addUrl(new URL(url));
141+
final String description = getTextFromObject(clarificationRenderer
142+
.getObject("secondarySource"));
143+
metaInfo.addUrlText(description == null ? url : description);
144+
} catch (final MalformedURLException e) {
145+
throw new ParsingException("Could not get metadata info secondary URL", e);
146+
}
147+
}
148+
}
149+
150+
return metaInfo;
151+
}
152+
153+
private static void getEmergencyOneboxRenderer(
154+
@Nonnull final JsonObject emergencyOneboxRenderer,
155+
final Consumer<MetaInfo> addMetaInfo
156+
) throws ParsingException {
157+
final List<JsonObject> supportRenderers = emergencyOneboxRenderer.values()
158+
.stream()
159+
.filter(o -> o instanceof JsonObject
160+
&& ((JsonObject) o).has("singleActionEmergencySupportRenderer"))
161+
.map(o -> ((JsonObject) o).getObject("singleActionEmergencySupportRenderer"))
162+
.collect(Collectors.toList());
163+
164+
if (supportRenderers.isEmpty()) {
165+
throw new ParsingException("Could not extract any meta info from emergency renderer");
166+
}
167+
168+
for (final JsonObject r : supportRenderers) {
169+
final MetaInfo metaInfo = new MetaInfo();
170+
171+
// usually an encouragement like "We are with you"
172+
final String title = getTextFromObjectOrThrow(r.getObject("title"), "title");
173+
// usually a phone number
174+
final String action = getTextFromObjectOrThrow(r.getObject("actionText"), "action");
175+
// usually details about the phone number
176+
final String details = getTextFromObjectOrThrow(r.getObject("detailsText"), "details");
177+
// usually the name of an association
178+
final String urlText = getTextFromObjectOrThrow(r.getObject("navigationText"),
179+
"urlText");
180+
181+
metaInfo.setTitle(title);
182+
metaInfo.setContent(new Description(details + "\n" + action, Description.PLAIN_TEXT));
183+
metaInfo.addUrlText(urlText);
184+
185+
// usually the webpage of the association
186+
final String url = getUrlFromNavigationEndpoint(r.getObject("navigationEndpoint"));
187+
if (url == null) {
188+
throw new ParsingException("Could not extract emergency renderer url");
189+
}
190+
191+
try {
192+
metaInfo.addUrl(new URL(replaceHttpWithHttps(url)));
193+
} catch (final MalformedURLException e) {
194+
throw new ParsingException("Could not parse emergency renderer url", e);
195+
}
196+
197+
addMetaInfo.accept(metaInfo);
198+
}
199+
}
200+
}

extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/YoutubeParsingHelper.java

Lines changed: 12 additions & 120 deletions
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,10 @@
3232
import com.grack.nanojson.JsonParser;
3333
import com.grack.nanojson.JsonParserException;
3434
import com.grack.nanojson.JsonWriter;
35-
import org.jsoup.nodes.Entities;
3635

36+
import org.jsoup.nodes.Entities;
3737
import org.schabi.newpipe.extractor.Image;
3838
import org.schabi.newpipe.extractor.Image.ResolutionLevel;
39-
import org.schabi.newpipe.extractor.MetaInfo;
4039
import org.schabi.newpipe.extractor.downloader.Response;
4140
import org.schabi.newpipe.extractor.exceptions.AccountTerminatedException;
4241
import org.schabi.newpipe.extractor.exceptions.ContentNotAvailableException;
@@ -47,7 +46,6 @@
4746
import org.schabi.newpipe.extractor.localization.Localization;
4847
import org.schabi.newpipe.extractor.playlist.PlaylistInfo;
4948
import org.schabi.newpipe.extractor.stream.AudioTrackType;
50-
import org.schabi.newpipe.extractor.stream.Description;
5149
import org.schabi.newpipe.extractor.utils.JsonUtils;
5250
import org.schabi.newpipe.extractor.utils.Parser;
5351
import org.schabi.newpipe.extractor.utils.RandomStringFromAlphabetGenerator;
@@ -62,12 +60,10 @@
6260
import java.time.OffsetDateTime;
6361
import java.time.ZoneOffset;
6462
import java.time.format.DateTimeParseException;
65-
import java.util.ArrayList;
6663
import java.util.HashMap;
6764
import java.util.List;
6865
import java.util.Locale;
6966
import java.util.Map;
70-
import java.util.Objects;
7167
import java.util.Optional;
7268
import java.util.Random;
7369
import java.util.Set;
@@ -262,7 +258,7 @@ private YoutubeParsingHelper() {
262258

263259
private static boolean consentAccepted = false;
264260

265-
private static boolean isGoogleURL(final String url) {
261+
public static boolean isGoogleURL(final String url) {
266262
final String cachedUrl = extractCachedUrlIfNeeded(url);
267263
try {
268264
final URL u = new URL(cachedUrl);
@@ -1080,6 +1076,16 @@ public static String getAttributedDescription(
10801076
.replaceAll(" {2}", " &nbsp;");
10811077
}
10821078

1079+
@Nonnull
1080+
public static String getTextFromObjectOrThrow(final JsonObject textObject, final String error)
1081+
throws ParsingException {
1082+
final String result = getTextFromObject(textObject);
1083+
if (result == null) {
1084+
throw new ParsingException("Could not extract text: " + error);
1085+
}
1086+
return result;
1087+
}
1088+
10831089
@Nullable
10841090
public static String getTextFromObject(final JsonObject textObject) {
10851091
return getTextFromObject(textObject, false);
@@ -1648,120 +1654,6 @@ public static void defaultAlertsCheck(@Nonnull final JsonObject initialData)
16481654
}
16491655
}
16501656

1651-
@Nonnull
1652-
public static List<MetaInfo> getMetaInfo(@Nonnull final JsonArray contents)
1653-
throws ParsingException {
1654-
final List<MetaInfo> metaInfo = new ArrayList<>();
1655-
for (final Object content : contents) {
1656-
final JsonObject resultObject = (JsonObject) content;
1657-
if (resultObject.has("itemSectionRenderer")) {
1658-
for (final Object sectionContentObject
1659-
: resultObject.getObject("itemSectionRenderer").getArray("contents")) {
1660-
1661-
final JsonObject sectionContent = (JsonObject) sectionContentObject;
1662-
if (sectionContent.has("infoPanelContentRenderer")) {
1663-
metaInfo.add(getInfoPanelContent(sectionContent
1664-
.getObject("infoPanelContentRenderer")));
1665-
}
1666-
if (sectionContent.has("clarificationRenderer")) {
1667-
metaInfo.add(getClarificationRendererContent(sectionContent
1668-
.getObject("clarificationRenderer")
1669-
));
1670-
}
1671-
1672-
}
1673-
}
1674-
}
1675-
return metaInfo;
1676-
}
1677-
1678-
@Nonnull
1679-
private static MetaInfo getInfoPanelContent(@Nonnull final JsonObject infoPanelContentRenderer)
1680-
throws ParsingException {
1681-
final MetaInfo metaInfo = new MetaInfo();
1682-
final StringBuilder sb = new StringBuilder();
1683-
for (final Object paragraph : infoPanelContentRenderer.getArray("paragraphs")) {
1684-
if (sb.length() != 0) {
1685-
sb.append("<br>");
1686-
}
1687-
sb.append(YoutubeParsingHelper.getTextFromObject((JsonObject) paragraph));
1688-
}
1689-
metaInfo.setContent(new Description(sb.toString(), Description.HTML));
1690-
if (infoPanelContentRenderer.has("sourceEndpoint")) {
1691-
final String metaInfoLinkUrl = YoutubeParsingHelper.getUrlFromNavigationEndpoint(
1692-
infoPanelContentRenderer.getObject("sourceEndpoint"));
1693-
try {
1694-
metaInfo.addUrl(new URL(Objects.requireNonNull(extractCachedUrlIfNeeded(
1695-
metaInfoLinkUrl))));
1696-
} catch (final NullPointerException | MalformedURLException e) {
1697-
throw new ParsingException("Could not get metadata info URL", e);
1698-
}
1699-
1700-
final String metaInfoLinkText = YoutubeParsingHelper.getTextFromObject(
1701-
infoPanelContentRenderer.getObject("inlineSource"));
1702-
if (isNullOrEmpty(metaInfoLinkText)) {
1703-
throw new ParsingException("Could not get metadata info link text.");
1704-
}
1705-
metaInfo.addUrlText(metaInfoLinkText);
1706-
}
1707-
1708-
return metaInfo;
1709-
}
1710-
1711-
@Nonnull
1712-
private static MetaInfo getClarificationRendererContent(
1713-
@Nonnull final JsonObject clarificationRenderer) throws ParsingException {
1714-
final MetaInfo metaInfo = new MetaInfo();
1715-
1716-
final String title = YoutubeParsingHelper.getTextFromObject(clarificationRenderer
1717-
.getObject("contentTitle"));
1718-
final String text = YoutubeParsingHelper.getTextFromObject(clarificationRenderer
1719-
.getObject("text"));
1720-
if (title == null || text == null) {
1721-
throw new ParsingException("Could not extract clarification renderer content");
1722-
}
1723-
metaInfo.setTitle(title);
1724-
metaInfo.setContent(new Description(text, Description.PLAIN_TEXT));
1725-
1726-
if (clarificationRenderer.has("actionButton")) {
1727-
final JsonObject actionButton = clarificationRenderer.getObject("actionButton")
1728-
.getObject("buttonRenderer");
1729-
try {
1730-
final String url = YoutubeParsingHelper.getUrlFromNavigationEndpoint(actionButton
1731-
.getObject("command"));
1732-
metaInfo.addUrl(new URL(Objects.requireNonNull(extractCachedUrlIfNeeded(url))));
1733-
} catch (final NullPointerException | MalformedURLException e) {
1734-
throw new ParsingException("Could not get metadata info URL", e);
1735-
}
1736-
1737-
final String metaInfoLinkText = YoutubeParsingHelper.getTextFromObject(
1738-
actionButton.getObject("text"));
1739-
if (isNullOrEmpty(metaInfoLinkText)) {
1740-
throw new ParsingException("Could not get metadata info link text.");
1741-
}
1742-
metaInfo.addUrlText(metaInfoLinkText);
1743-
}
1744-
1745-
if (clarificationRenderer.has("secondaryEndpoint") && clarificationRenderer
1746-
.has("secondarySource")) {
1747-
final String url = getUrlFromNavigationEndpoint(clarificationRenderer
1748-
.getObject("secondaryEndpoint"));
1749-
// Ignore Google URLs, because those point to a Google search about "Covid-19"
1750-
if (url != null && !isGoogleURL(url)) {
1751-
try {
1752-
metaInfo.addUrl(new URL(url));
1753-
final String description = getTextFromObject(clarificationRenderer
1754-
.getObject("secondarySource"));
1755-
metaInfo.addUrlText(description == null ? url : description);
1756-
} catch (final MalformedURLException e) {
1757-
throw new ParsingException("Could not get metadata info secondary URL", e);
1758-
}
1759-
}
1760-
}
1761-
1762-
return metaInfo;
1763-
}
1764-
17651657
/**
17661658
* Sometimes, YouTube provides URLs which use Google's cache. They look like
17671659
* {@code https://webcache.googleusercontent.com/search?q=cache:CACHED_URL}

extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/extractors/YoutubeSearchExtractor.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
import org.schabi.newpipe.extractor.localization.Localization;
3131
import org.schabi.newpipe.extractor.localization.TimeAgoParser;
3232
import org.schabi.newpipe.extractor.search.SearchExtractor;
33-
import org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper;
33+
import org.schabi.newpipe.extractor.services.youtube.YoutubeMetaInfoHelper;
3434
import org.schabi.newpipe.extractor.utils.JsonUtils;
3535

3636
import java.io.IOException;
@@ -151,7 +151,7 @@ public boolean isCorrectedSearch() {
151151
@Nonnull
152152
@Override
153153
public List<MetaInfo> getMetaInfo() throws ParsingException {
154-
return YoutubeParsingHelper.getMetaInfo(
154+
return YoutubeMetaInfoHelper.getMetaInfo(
155155
initialData.getObject("contents")
156156
.getObject("twoColumnSearchResultsRenderer")
157157
.getObject("primaryContents")

0 commit comments

Comments
 (0)