@@ -123,6 +123,7 @@ else {
123123# Configuration dir/file
124124my $config_dir = catdir($xdg_config_home , $execname );
125125my $config_file = catfile($config_dir , " $execname .conf" );
126+ my $youtube_users_file = catfile($config_dir , ' youtube_users.txt' );
126127my $authentication_file = catfile($config_dir , ' reg.dat' );
127128my $history_file = catfile($config_dir , ' cli-history.txt' );
128129my $watched_file = catfile($config_dir , ' watched.txt' );
@@ -246,6 +247,7 @@ my %CONFIG = (
246247 skip_watched => 0,
247248 remember_watched => 0,
248249 watched_file => $watched_file ,
250+ youtube_users_file => $youtube_users_file ,
249251 highlight_watched => 1,
250252 highlight_color => ' bold' ,
251253 remove_played_file => 0,
@@ -360,6 +362,7 @@ $action_options
360362:pv=i :popular=i : display author's popular uploads
361363:A(ctivity)=i : display author's recent activity
362364:p(laylists)=i : display author's playlists
365+ :s=i :save=i : save author's channel ID to file (see -fc)
363366:ps=i :s2p=i,i : save videos to a post-selected playlist
364367:subscribe=i : subscribe to author's channel
365368:(dis)like=i : like or dislike a video
@@ -722,7 +725,8 @@ usage: $execname [options] ([url] | [keywords])
722725 use "--region=XX" to change the region
723726
724727 * Channels
725- -sc --channels : search for YouTube channels
728+ -sc --channels : search for YouTube channels
729+ -fc --file-channels : show saved channels from file
726730
727731* Comments
728732 --comments=s : display comments for a video by ID or URL
@@ -1314,6 +1318,10 @@ sub apply_configuration {
13141318 print_categories($yv_obj -> video_categories);
13151319 }
13161320
1321+ if (delete $opt -> {channels_from_file }) {
1322+ print_channels_from_file();
1323+ }
1324+
13171325 if (defined $opt -> {uploads }) {
13181326 my $str = delete $opt -> {uploads };
13191327
@@ -1541,6 +1549,8 @@ sub parse_arguments {
15411549 ' c|categories' => \$opt {categories },
15421550 ' video-ids|videoids|id|ids=s' => \$opt {play_video_ids },
15431551
1552+ ' fc|cf|file-channels' => \$opt {channels_from_file },
1553+
15441554 ' search-videos|search|sv!' => \$opt {search_videos },
15451555 ' search-channels|channels|sc!' => \$opt {search_channels },
15461556 ' search-playlists|sp|p!' => \$opt {search_playlists },
@@ -2793,6 +2803,177 @@ sub print_comments {
27932803 __SUB__ -> (@_ );
27942804}
27952805
2806+ sub save_channel_to_file {
2807+ my ($channel_id , $channel_title ) = @_ ;
2808+
2809+ $yv_utils -> is_channelID($channel_id ) || return ;
2810+
2811+ open (my $fh , ' >>' , $opt {youtube_users_file }) or do {
2812+ warn " Can't open file <<$opt {youtube_users_file}>> for appending: $! \n " ;
2813+ return ;
2814+ };
2815+
2816+ print $fh " $channel_id $channel_title \n " ;
2817+
2818+ close $fh ;
2819+ }
2820+
2821+ sub update_channel_file {
2822+ my (%channels ) = @_ ;
2823+
2824+ open (my $fh , ' >' , $opt {youtube_users_file }) or do {
2825+ warn " Can't open file <<$opt {youtube_users_file}>> for writing: $! \n " ;
2826+ return ;
2827+ };
2828+
2829+ foreach my $key (sort { CORE::fc($channels {$a }) cmp CORE::fc($channels {$b }) } keys %channels ) {
2830+ say $fh " $key $channels {$key }" ;
2831+ }
2832+
2833+ close $fh ;
2834+ }
2835+
2836+ sub print_channels_from_file {
2837+
2838+ open (my $fh , ' <' , $opt {youtube_users_file }) or do {
2839+ warn " Can't open file <<$opt {youtube_users_file}>> for reading: $! \n " ;
2840+ return ;
2841+ };
2842+
2843+ my %channels ;
2844+
2845+ while (defined (my $line = <$fh >)) {
2846+ if ($line =~ / ^([a-zA-Z0-9_-]+) (.+)/ ) {
2847+ my ($channel_id , $channel_title ) = ($1 , $2 );
2848+
2849+ if ($yv_utils -> is_channelID($channel_id )) {
2850+ $channels {$channel_id } = $channel_title ;
2851+ }
2852+ else {
2853+ warn " Invalid channel ID: $channel_id \n " ;
2854+ }
2855+ }
2856+ }
2857+
2858+ close $fh ;
2859+
2860+ my $display_channels = sub {
2861+ my (@channels ) = @_ ;
2862+
2863+ if (not @channels ) {
2864+ warn_no_results(" channel" );
2865+ return ;
2866+ }
2867+
2868+ print " \n " ;
2869+ foreach my $i (0 .. $#channels ) {
2870+ my $channel = $channels [$i ];
2871+ printf (" %s . %-40s (id: %s )\n " , colored(sprintf (' %2d' , $i + 1), ' bold' ), $channel -> {title }, $channel -> {id });
2872+ }
2873+
2874+ my @keywords = get_input_for_channels();
2875+
2876+ foreach my $key (@keywords ) {
2877+ if ($key =~ / $valid_opt_re / ) {
2878+
2879+ my $opt = $1 ;
2880+
2881+ if (
2882+ general_options(opt => $opt ,
2883+ sub => __SUB__ ,)
2884+ ) {
2885+ # # ok
2886+ }
2887+ elsif ($opt =~ / ^(?:h|help)\z / ) {
2888+ print <<"EOT" ;
2889+
2890+ # Search for a channel
2891+ <keyword> : search for a channel
2892+
2893+ # Select a channel
2894+ <number> : latest uploads from channel
2895+ :pv=i :popular=i : popular uploads from channel
2896+
2897+ # Remove channels
2898+ :rm=i :remove=i,i : remove the selected channels from file
2899+ EOT
2900+ press_enter_to_continue();
2901+ }
2902+ elsif ($opt =~ / ^(?:pv|popular)${digit_or_equal_re} (.*)/ ) {
2903+ if (my @nums = get_valid_numbers($#channels , $1 )) {
2904+
2905+ foreach my $id (@nums ) {
2906+
2907+ my $channel_id = $channels [$id ]{id };
2908+ my $request = $yv_obj -> popular_videos($channel_id );
2909+
2910+ if ($yv_utils -> has_entries($request )) {
2911+ print_videos($request );
2912+ }
2913+ else {
2914+ warn_no_results(' popular video' );
2915+ }
2916+ }
2917+ }
2918+ else {
2919+ warn_no_thing_selected(' channel' );
2920+ }
2921+ }
2922+ elsif ($opt =~ / ^(?:r|rm|remove)${digit_or_equal_re} (.*)/ ) {
2923+ if (my @nums = get_valid_numbers($#channels , $1 )) {
2924+
2925+ my %removed ;
2926+
2927+ foreach my $id (@nums ) {
2928+
2929+ my $channel_id = $channels [$id ]{id };
2930+ my $channel_title = $channels [$id ]{title };
2931+
2932+ say " :: Removing <<$channel_title >> (id: $channel_id )" ;
2933+
2934+ delete $channels {$channel_id };
2935+ $removed {$channel_id } = 1;
2936+ }
2937+
2938+ @channels = grep { not exists $removed {$_ -> {id }} } @channels ;
2939+ update_channel_file(%channels );
2940+ }
2941+ else {
2942+ warn_no_thing_selected(' channel' );
2943+ }
2944+ }
2945+ elsif ($opt =~ / ^(?:r|return)\z / ) {
2946+ return ;
2947+ }
2948+ else {
2949+ warn_invalid(' option' , $opt );
2950+ }
2951+ }
2952+ elsif (youtube_urls($key )) {
2953+ # # ok
2954+ }
2955+ elsif (valid_num($key , \@channels )) {
2956+ my $channel = $channels [$key - 1];
2957+ my $channel_id = $channel -> {id };
2958+ my $videos = $yv_obj -> uploads($channel_id );
2959+ print_videos($videos );
2960+ }
2961+ else {
2962+ my $re = qr /\Q $key \E / i ;
2963+ __SUB__ -> (grep { $_ -> {title } =~ / $re / } @channels );
2964+ }
2965+ }
2966+
2967+ __SUB__ -> (@channels );
2968+ };
2969+
2970+ my @channels =
2971+ sort { $a -> {key } cmp $b -> {key } }
2972+ map { {id => $_ , title => $channels {$_ }, key => CORE::fc($channels {$_ })} } keys %channels ;
2973+
2974+ $display_channels -> (@channels );
2975+ }
2976+
27962977sub print_categories {
27972978 my ($results ) = @_ ;
27982979
@@ -3993,6 +4174,23 @@ sub print_videos {
39934174 warn_no_thing_selected(' video' );
39944175 }
39954176 }
4177+ elsif ($opt =~ / ^(?:s|save)${digit_or_equal_re} (.*)/ ) {
4178+ if (my @nums = get_valid_numbers($# {$videos }, $1 )) {
4179+ foreach my $id (@nums ) {
4180+ my $channel_id = $yv_utils -> get_channel_id($videos -> [$id ]);
4181+ my $channel_title = $yv_utils -> get_channel_title($videos -> [$id ]);
4182+ if (save_channel_to_file($channel_id , $channel_title )) {
4183+ say " \n :: Saved channel <<$channel_title >> (id: $channel_id ) to file." ;
4184+ }
4185+ else {
4186+ say " \n [!] Unable to save channel <<$channel_title >> to file..." ;
4187+ }
4188+ }
4189+ }
4190+ else {
4191+ warn_no_thing_selected(' video' );
4192+ }
4193+ }
39964194 elsif ($opt =~ / ^(?:pv|popular)${digit_or_equal_re} (.*)/ ) {
39974195 if (my @nums = get_valid_numbers($# {$videos }, $1 )) {
39984196 foreach my $id (@nums ) {
@@ -4681,9 +4879,17 @@ File where to save the video IDs of watched/downloaded videos when C<remember_wa
46814879
46824880Command for C<wget > when C<download_with_wget > is set to a true value.
46834881
4882+ =head2 youtube_users_file
4883+
4884+ Path to a list of YouTube channels, using the following format:
4885+
4886+ ChannelID NAME
4887+
4888+ The channels can be listed with the command-line option: C<-fc > .
4889+
46844890=head2 youtube_video_url
46854891
4686- Sprintf format for an YouTube video URL, given the video ID.
4892+ Format for C< sprintf() > for constructing an YouTube video URL given the video ID.
46874893
46884894=head2 ytdl
46894895
0 commit comments