Skip to content

Conversation

FineFindus
Copy link
Contributor

  • I carefully read the contribution guidelines and agree to them.
  • I have tested the API against NewPipe.
  • I agree to create a pull request for NewPipe as soon as possible to make it compatible with the changed API.

YouTube inserts members-only videos (i.e., videos that require channel membership to watch) into the Videos tab. Because the extractor unable to distinguish between them and "normal" videos, they may appear in subscriptions feeds.
This enables the extractor to check if videos require membership, allowing clients to filter them.

Ref: TeamNewPipe/NewPipe#12040
Ref: TeamNewPipe/NewPipe#12011

@ShareASmile ShareASmile added enhancement New feature or request youtube service, https://www.youtube.com/ labels Feb 21, 2025
Copy link
Contributor Author

@FineFindus FineFindus left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thinking about it a bit more, maybe it makes sense to rename requiresMembership to isPaidContent (or something similar)? That would keep the naming consistent with the PaidContentException that is already thrown for member only content, and it would be generic enough for other services to use. But I'm not sure if that might conflict with something else, e.g. premium videos?

FineFindus added a commit to FineFindus/NewPipe that referenced this pull request Mar 2, 2025
YouTube has started to mix members-only videos into the normal videos in
the Videos tab. This filters them, as they cannot be watched without an
account, leading to clutter in the feed.
Requires TeamNewPipe/NewPipeExtractor#1280.

Closes: TeamNewPipe#12011
Closes: TeamNewPipe#12040
FineFindus added a commit to FineFindus/NewPipe that referenced this pull request Mar 2, 2025
YouTube has started to mix members-only videos into the normal videos in
the Videos tab. This filters them, as they cannot be watched without an
account, leading to clutter in the feed.
Requires TeamNewPipe/NewPipeExtractor#1280.

Closes: TeamNewPipe#12011
Closes: TeamNewPipe#12040
@TobiGr
Copy link
Contributor

TobiGr commented Mar 2, 2025

What about tests? Is it possible to create tests for the new extractions?

@FineFindus
Copy link
Contributor Author

I'm honestly a bit overwhelmed with how to create tests for ChannelTabExtractor, and failing to create even this basic one.

    static class MembersOnlyVideos extends DefaultListExtractorTest<ChannelTabExtractor> {
        private static YoutubeChannelTabExtractor extractor;

        @BeforeAll
        static void setUp() throws IOException, ExtractionException {
            YoutubeTestsUtils.ensureStateless();
            NewPipe.init(DownloaderFactory.getDownloader(RESOURCE_PATH + "membersOnlyVideos"));
            extractor = (YoutubeChannelTabExtractor) YouTube.getChannelTabExtractorFromId(
                    "@TheLinuxEXP", ChannelTabs.VIDEOS);
            extractor.fetchPage();
        }

        @Override public ChannelTabExtractor extractor() throws Exception { return extractor; }
        @Override public StreamingService expectedService() throws Exception { return YouTube; }
        @Override public String expectedName() throws Exception { return ChannelTabs.VIDEOS; }
        @Override public String expectedId() throws Exception { return "UC5UAwBUum7CPN5buc-_N1Fw"; }
        @Override public String expectedUrlContains() throws Exception { return "https://www.youtube.com/channel/UC5UAwBUum7CPN5buc-_N1Fw/videos"; }
        @Override public String expectedOriginalUrlContains() throws Exception { return "https://www.youtube.com/@TheLinuxEXP/videos"; }
        @Override public boolean expectedHasMoreItems() { return true; }

        @Test
        @Override
        public void testRelatedItems() throws Exception {
            final List<StreamInfoItem> allItems = extractor.getInitialPage().getItems()
                    .stream()
                    .filter(StreamInfoItem.class::isInstance)
                    .map(StreamInfoItem.class::cast)
                    .collect(Collectors.toUnmodifiableList());
            final List<StreamInfoItem> membershipVideos = allItems.stream()
                    .filter(item -> !item.requiresMembership()) 
                    .collect(Collectors.toUnmodifiableList());

            assertTrue(membershipVideos.size() <= allItems.size());
        }
    }
log
YoutubeChannelTabExtractorTest$MembersOnlyVideos > testUrl() FAILED
    org.opentest4j.AssertionFailedError: 'https://www.youtube.com/channel/UC5UAwBUum7CPN5buc-_N1Fw/videos' should be contained inside 'https://www.youtube.com/channel/UC5UAwBUum7CPN5buc-_N1Fw' ==> expected: <true> but was: <false>
        at app//org.junit.jupiter.api.AssertionFailureBuilder.build(AssertionFailureBuilder.java:151)
        at app//org.junit.jupiter.api.AssertionFailureBuilder.buildAndThrow(AssertionFailureBuilder.java:132)
        at app//org.junit.jupiter.api.AssertTrue.failNotTrue(AssertTrue.java:63)
        at app//org.junit.jupiter.api.AssertTrue.assertTrue(AssertTrue.java:36)
        at app//org.junit.jupiter.api.Assertions.assertTrue(Assertions.java:214)
        at app//org.schabi.newpipe.extractor.ExtractorAsserts.assertContains(ExtractorAsserts.java:167)
        at app//org.schabi.newpipe.extractor.services.DefaultExtractorTest.testUrl(DefaultExtractorTest.java:43)
        at [email protected]/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
        at [email protected]/java.lang.reflect.Method.invoke(Method.java:580)
        at app//org.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:767)
        at app//org.junit.jupiter.engine.execution.MethodInvocation.proceed(MethodInvocation.java:60)
        at app//org.junit.jupiter.engine.execution.InvocationInterceptorChain$ValidatingInvocation.proceed(InvocationInterceptorChain.java:131)
        at app//org.junit.jupiter.engine.extension.TimeoutExtension.intercept(TimeoutExtension.java:156)
        at app//org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestableMethod(TimeoutExtension.java:147)
        at app//org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestMethod(TimeoutExtension.java:86)
        at app//org.junit.jupiter.engine.execution.InterceptingExecutableInvoker$ReflectiveInterceptorCall.lambda$ofVoidMethod$0(InterceptingExecutableInvoker.java:103)
        at app//org.junit.jupiter.engine.execution.InterceptingExecutableInvoker.lambda$invoke$0(InterceptingExecutableInvoker.java:93)
        at app//org.junit.jupiter.engine.execution.InvocationInterceptorChain$InterceptedInvocation.proceed(InvocationInterceptorChain.java:106)
        at app//org.junit.jupiter.engine.execution.InvocationInterceptorChain.proceed(InvocationInterceptorChain.java:64)
        at app//org.junit.jupiter.engine.execution.InvocationInterceptorChain.chainAndInvoke(InvocationInterceptorChain.java:45)
        at app//org.junit.jupiter.engine.execution.InvocationInterceptorChain.invoke(InvocationInterceptorChain.java:37)
        at app//org.junit.jupiter.engine.execution.InterceptingExecutableInvoker.invoke(InterceptingExecutableInvoker.java:92)
        at app//org.junit.jupiter.engine.execution.InterceptingExecutableInvoker.invoke(InterceptingExecutableInvoker.java:86)
        at app//org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeTestMethod$8(TestMethodTestDescriptor.java:217)
        at app//org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
        at app//org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeTestMethod(TestMethodTestDescriptor.java:213)
        at app//org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:138)
        at app//org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:68)
        at app//org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:156)
        at app//org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
        at app//org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:146)
        at app//org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
        at app//org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:144)
        at app//org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
        at app//org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:143)
        at app//org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:100)
        at [email protected]/java.util.ArrayList.forEach(ArrayList.java:1596)
        at app//org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:41)
        at app//org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:160)
        at app//org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
        at app//org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:146)
        at app//org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
        at app//org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:144)
        at app//org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
        at app//org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:143)
        at app//org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:100)
        at [email protected]/java.util.ArrayList.forEach(ArrayList.java:1596)
        at app//org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:41)
        at app//org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:160)
        at app//org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
        at app//org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:146)
        at app//org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
        at app//org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:144)
        at app//org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
        at app//org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:143)
        at app//org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:100)
        at app//org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.submit(SameThreadHierarchicalTestExecutorService.java:35)
        at app//org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:57)
        at app//org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:54)
        at app//org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:107)
        at app//org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:88)
        at app//org.junit.platform.launcher.core.EngineExecutionOrchestrator.lambda$execute$0(EngineExecutionOrchestrator.java:54)
        at app//org.junit.platform.launcher.core.EngineExecutionOrchestrator.withInterceptedStreams(EngineExecutionOrchestrator.java:67)
        at app//org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:52)
        at app//org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:114)
        at app//org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:86)
        at app//org.junit.platform.launcher.core.DefaultLauncherSession$DelegatingLauncher.execute(DefaultLauncherSession.java:86)
        at org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestClassProcessor$CollectAllTestClassesExecutor.processAllTestClasses(JUnitPlatformTestClassProcessor.java:124)
        at org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestClassProcessor$CollectAllTestClassesExecutor.access$000(JUnitPlatformTestClassProcessor.java:99)
        at org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestClassProcessor.stop(JUnitPlatformTestClassProcessor.java:94)
        at org.gradle.api.internal.tasks.testing.SuiteTestClassProcessor.stop(SuiteTestClassProcessor.java:63)
        at [email protected]/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
        at [email protected]/java.lang.reflect.Method.invoke(Method.java:580)
        at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:36)
        at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24)
        at org.gradle.internal.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:33)
        at org.gradle.internal.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:92)
        at jdk.proxy1/jdk.proxy1.$Proxy4.stop(Unknown Source)
        at org.gradle.api.internal.tasks.testing.worker.TestWorker$3.run(TestWorker.java:200)
        at org.gradle.api.internal.tasks.testing.worker.TestWorker.executeAndMaintainThreadName(TestWorker.java:132)
        at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:103)
        at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:63)
        at org.gradle.process.internal.worker.child.ActionExecutionWorker.execute(ActionExecutionWorker.java:56)
        at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:121)
        at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:71)
        at app//worker.org.gradle.process.internal.worker.GradleWorkerMain.run(GradleWorkerMain.java:69)
        at app//worker.org.gradle.process.internal.worker.GradleWorkerMain.main(GradleWorkerMain.java:74)

YoutubeChannelTabExtractorTest$MembersOnlyVideos > testOriginalUrl() FAILED
    java.lang.AssertionError: Invalid url: "/@TheLinuxEXP/videos"
        at org.schabi.newpipe.extractor.ExtractorAsserts.urlFromString(ExtractorAsserts.java:42)
        at org.schabi.newpipe.extractor.ExtractorAsserts.assertIsSecureUrl(ExtractorAsserts.java:51)
        at org.schabi.newpipe.extractor.services.DefaultExtractorTest.testOriginalUrl(DefaultExtractorTest.java:50)
        at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
        at java.base/java.lang.reflect.Method.invoke(Method.java:580)
        at org.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:767)
        at org.junit.jupiter.engine.execution.MethodInvocation.proceed(MethodInvocation.java:60)
        at org.junit.jupiter.engine.execution.InvocationInterceptorChain$ValidatingInvocation.proceed(InvocationInterceptorChain.java:131)
        at org.junit.jupiter.engine.extension.TimeoutExtension.intercept(TimeoutExtension.java:156)
        at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestableMethod(TimeoutExtension.java:147)
        at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestMethod(TimeoutExtension.java:86)
        at org.junit.jupiter.engine.execution.InterceptingExecutableInvoker$ReflectiveInterceptorCall.lambda$ofVoidMethod$0(InterceptingExecutableInvoker.java:103)
        at org.junit.jupiter.engine.execution.InterceptingExecutableInvoker.lambda$invoke$0(InterceptingExecutableInvoker.java:93)
        at org.junit.jupiter.engine.execution.InvocationInterceptorChain$InterceptedInvocation.proceed(InvocationInterceptorChain.java:106)
        at org.junit.jupiter.engine.execution.InvocationInterceptorChain.proceed(InvocationInterceptorChain.java:64)
        at org.junit.jupiter.engine.execution.InvocationInterceptorChain.chainAndInvoke(InvocationInterceptorChain.java:45)
        at org.junit.jupiter.engine.execution.InvocationInterceptorChain.invoke(InvocationInterceptorChain.java:37)
        at org.junit.jupiter.engine.execution.InterceptingExecutableInvoker.invoke(InterceptingExecutableInvoker.java:92)
        at org.junit.jupiter.engine.execution.InterceptingExecutableInvoker.invoke(InterceptingExecutableInvoker.java:86)
        at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeTestMethod$8(TestMethodTestDescriptor.java:217)
        at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
        at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeTestMethod(TestMethodTestDescriptor.java:213)
        at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:138)
        at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:68)
        at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:156)
        at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
        at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:146)
        at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
        at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:144)
        at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
        at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:143)
        at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:100)
        at java.base/java.util.ArrayList.forEach(ArrayList.java:1596)
        at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:41)
        at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:160)
        at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
        at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:146)
        at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
        at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:144)
        at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
        at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:143)
        at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:100)
        at java.base/java.util.ArrayList.forEach(ArrayList.java:1596)
        at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:41)
        at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:160)
        at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
        at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:146)
        at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
        at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:144)
        at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
        at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:143)
        at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:100)
        at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.submit(SameThreadHierarchicalTestExecutorService.java:35)
        at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:57)
        at org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:54)
        at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:107)
        at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:88)
        at org.junit.platform.launcher.core.EngineExecutionOrchestrator.lambda$execute$0(EngineExecutionOrchestrator.java:54)
        at org.junit.platform.launcher.core.EngineExecutionOrchestrator.withInterceptedStreams(EngineExecutionOrchestrator.java:67)
        at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:52)
        at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:114)
        at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:86)
        at org.junit.platform.launcher.core.DefaultLauncherSession$DelegatingLauncher.execute(DefaultLauncherSession.java:86)
        at org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestClassProcessor$CollectAllTestClassesExecutor.processAllTestClasses(JUnitPlatformTestClassProcessor.java:124)
        at org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestClassProcessor$CollectAllTestClassesExecutor.access$000(JUnitPlatformTestClassProcessor.java:99)
        at org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestClassProcessor.stop(JUnitPlatformTestClassProcessor.java:94)
        at org.gradle.api.internal.tasks.testing.SuiteTestClassProcessor.stop(SuiteTestClassProcessor.java:63)
        at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
        at java.base/java.lang.reflect.Method.invoke(Method.java:580)
        at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:36)
        at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24)
        at org.gradle.internal.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:33)
        at org.gradle.internal.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:92)
        at jdk.proxy1/jdk.proxy1.$Proxy4.stop(Unknown Source)
        at org.gradle.api.internal.tasks.testing.worker.TestWorker$3.run(TestWorker.java:200)
        at org.gradle.api.internal.tasks.testing.worker.TestWorker.executeAndMaintainThreadName(TestWorker.java:132)
        at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:103)
        at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:63)
        at org.gradle.process.internal.worker.child.ActionExecutionWorker.execute(ActionExecutionWorker.java:56)
        at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:121)
        at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:71)
        at worker.org.gradle.process.internal.worker.GradleWorkerMain.run(GradleWorkerMain.java:69)
        at worker.org.gradle.process.internal.worker.GradleWorkerMain.main(GradleWorkerMain.java:74)

        Caused by:
        java.net.MalformedURLException: no protocol: /@TheLinuxEXP/videos
            at java.base/java.net.URL.<init>(URL.java:772)
            at java.base/java.net.URL.<init>(URL.java:654)
            at java.base/java.net.URL.<init>(URL.java:590)
            at org.schabi.newpipe.extractor.ExtractorAsserts.urlFromString(ExtractorAsserts.java:40)
            ... 82 more

And just checking if there are <= items after filtering doesn't feel like a great test, since that test will always pass.

@AudricV AudricV self-requested a review March 2, 2025 15:09
@litetex
Copy link
Member

litetex commented Mar 5, 2025

Note about tests:

When writing a channel extractor test the channel should be chosen very carefully.
If new, not member-only videos get uploaded this might cause test failures as the member-only videos get "pushed out".

I think it's best to chose a channel here that either

  • Uploads only member-only videos or
  • doesn't upload new videos anymore and has member-only videos

Maybe as an alternative a playlist can also be considered.

@FineFindus
Copy link
Contributor Author

Added a test using a playlist with only members only videos.

Copy link
Contributor

@TobiGr TobiGr left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, please add the two jdoc comments

@AudricV
Copy link
Member

AudricV commented Mar 15, 2025

For #1280 (review), you're right, a better approach would be to create an enum that would return availability type (creator membership required, service paid content, upcoming content, available content, geo-restricted, ...), it could be applied to info items and items.

This would solve several issues of detecting some content unavailability type in a good way in the extractor.

@AudricV AudricV changed the title [Youtube] Mark members-only videos [YouTube] Mark members-only videos Mar 17, 2025
@FineFindus
Copy link
Contributor Author

As suggested in #1280 (comment), I rewrote the code to return an enum with different availabilities instead. I left out the test for the other types for now.

Also, if I remember correctly, for upcoming videos an exception is thrown, so it wouldn't be possible to use the enum, but I would gladly leave this to someone with more experience in the code base :)

@TobiGr
Copy link
Contributor

TobiGr commented Mar 17, 2025

@AudricV One general question: should the Privacy enum be merged into ContentAvailability? They indicate similar things and the term Privacy was always a bit off in my opinion.

AudricV
AudricV previously requested changes Jul 9, 2025
Copy link
Member

@AudricV AudricV left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks almost good, thank you!

@FineFindus FineFindus requested a review from AudricV July 10, 2025 16:22
@FineFindus FineFindus force-pushed the feat/member-channeltab-info branch from 2041b3c to 32cdee8 Compare July 11, 2025 06:53
@FineFindus FineFindus requested a review from litetex July 11, 2025 06:54
Copy link
Member

@litetex litetex left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code LGTM
however regarding design I'd like to have some backing from the others (devs) before merging this.

@AudricV

@AudricV
Copy link
Member

AudricV commented Jul 11, 2025

One general question: should the Privacy enum be merged into ContentAvailability? They indicate similar things and the term Privacy was always a bit off in my opinion.

I don't think so. For me, availability and privacy (or better for this term visibility), should be different values.

however regarding design I'd like to have some backing from the others (devs) before merging this.

Except my very nitpicking change request which has been not applied, it could make sense to add a new value describing we don't have any info for the availability like UNKNOWN. This may be the default value in stream items and stream info.

Changes LGTM otherwise (unless we want better docs), we need to apply these changes to Soundcloud (and Bandcamp too I think) tracks :)

@FineFindus FineFindus force-pushed the feat/member-channeltab-info branch 2 times, most recently from 9a0f4a8 to 291946d Compare July 21, 2025 12:35
@TobiGr
Copy link
Contributor

TobiGr commented Jul 21, 2025

We changed how mocks are stored and used in #1332. You need to drop 9014e79 and record the mocks for the new tests again.

@FineFindus FineFindus force-pushed the feat/member-channeltab-info branch from 291946d to 7684059 Compare July 26, 2025 08:57
@FineFindus
Copy link
Contributor Author

Regenerated the mocks and squashed them into ef0da2b.

FineFindus and others added 9 commits October 4, 2025 15:05
YouTube inserts members-only videos (i.e., videos that require channel
membership to watch) into the Videos tab. Because the extractor unable
to distinguish between them and "normal" videos, they may appear in
subscriptions feeds.
This enables the extractor to check if videos require membership,
allowing clients to filter them.

Ref: TeamNewPipe/NewPipe#12040
Ref: TeamNewPipe/NewPipe#12011
Adds a new field to `StreamInfoItem` to denote the availability of the
stream. A stream may be restricted to only certain user groups or times.

This allows users to ignore the stream based on their availability, e.g.
a app may choose to hide all streams that require payment.
@Stypox Stypox force-pushed the feat/member-channeltab-info branch from 7684059 to 9b98978 Compare October 4, 2025 14:03
Copy link
Member

@Stypox Stypox left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me, thanks! I pushed two commit, one to add the UNKNOWN field as suggested by Audric, and another to add a base test for StreamInfo that currently does nothing useful (since StreamInfo.getContentAvailability() always returns UNKNOWN), but that will force any future contributors that want to implement overrides for StreamInfo.getContentAvailability() to also implement tests.

@Stypox Stypox merged commit 606bb93 into TeamNewPipe:dev Oct 4, 2025
4 checks passed
@FineFindus FineFindus deleted the feat/member-channeltab-info branch October 4, 2025 14:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request youtube service, https://www.youtube.com/

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants