run.sh 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507
  1. #!/bin/bash
  2. menu="fzf"
  3. videoplayer="mpv --no-terminal"
  4. art="./art.txt"
  5. if [ ! -f $art ]; then
  6. art="/usr/share/ytui/art.txt"
  7. fi
  8. instance=https://invidious.namazso.eu
  9. RSSinstance=https://invidious.namazso.eu
  10. videoInstance=https://yewtu.be
  11. cachedir=/tmp/ytui
  12. if [ ! -d $cachedir ]; then
  13. mkdir $cachedir
  14. fi
  15. configdir=~/.config/ytui
  16. instances=$(cat $cachedir/instances)
  17. subsfeeds=$configdir/rssfeedupdate
  18. subsfeedsdir=$configdir/rssfeeds
  19. if [ ! -d $configdir ]; then
  20. mkdir $configdir
  21. fi
  22. freetubesubs=~/.config/FreeTube/profiles.db
  23. subsfile=$configdir/subscriptions.csv
  24. playimmediate=true
  25. green='\e[32m'
  26. blue='\e[34m'
  27. red='\e[31m'
  28. white='\e[0m'
  29. clear='\e[H\e[J'
  30. bold='\e[1m'
  31. PS3="> "
  32. settingsMenu () {
  33. heading 'Settings'
  34. echo -ne "
  35. f) get instances
  36. i) data instance [$instance]
  37. r) RSS instance [$RSSinstance]
  38. v) video instance [$videoInstance]
  39. s) import subscriptions from FreeTube
  40. y) import subscriptions from YouTube export
  41. q) Back
  42. > "
  43. read -n 1 option
  44. case $option in
  45. f) getInstances ; settingsMenu ;;
  46. i) changeInstance ; settingsMenu ;;
  47. r) changeRSSInstance ; settingsMenu ;;
  48. v) changeVideoInstance ; settingsMenu ;;
  49. s) importFreetube ;;
  50. y) importYoutube ;;
  51. $'\e'|'q') return ;;
  52. *) settingsMenu ;;
  53. esac
  54. }
  55. getInstances() {
  56. heading "Finding Instances"
  57. all_instances="$(echo "$(cat $configdir/instances)
  58. $(get 'https://api.invidious.io/instances.json' | \
  59. jq -r '.[] | .[1].uri' | \
  60. sed 's#/$##' | sed '/\.onion$/d')" | sort | uniq)"
  61. while read uri ; do
  62. testSite $uri
  63. done <<< $all_instances
  64. echo "$instances" > $cachedir/instances
  65. }
  66. testSite() {
  67. echo -ne "$white$uri\t"
  68. wget --spider --timeout=0.3 -t 3 -q $uri
  69. if [ $? -eq 0 ]; then
  70. echo -e "$green Online"
  71. instances="$instances
  72. $uri"
  73. else
  74. echo -e "$red Offline"
  75. fi;
  76. }
  77. changeInstance () {
  78. heading "Choose an instance"
  79. instance_tmp=$instance
  80. instance=$($menu <<< "$instances")
  81. if [ -z "$instance" ]; then
  82. instance=$instance_tmp
  83. fi
  84. }
  85. changeRSSInstance () {
  86. heading "Choose an instance"
  87. instance_tmp=$RSSinstance
  88. RSSinstance=$($menu <<< "$instances")
  89. if [ -z "$RSSinstance" ]; then
  90. RSSinstance=$instance_tmp
  91. fi
  92. }
  93. changeVideoInstance () {
  94. heading "Choose an instance"
  95. videoInstance_tmp=$videoInstance
  96. videoInstance=$($menu <<< "$instances")
  97. if [ -z "$videoInstance" ]; then
  98. videoInstance=$videoInstance_tmp
  99. fi
  100. }
  101. importFreetube () {
  102. if [ ! -f $freetubesubs ]; then
  103. echo "Cannot find freetube Subscriptions in $freetubesubs"
  104. echo -ne "Enter a different file to try >"
  105. read freetubesubs
  106. importFreetube
  107. return;
  108. fi
  109. echo "$(jq -r '.subscriptions[] | .id + "," + .name' $freetubesubs)" >> $subsfile
  110. echo -e "\nImported" $(jq -r '.subscriptions[] | .id' $freetubesubs | wc -l) items
  111. read -n 1 -t 1
  112. }
  113. importYoutube () {
  114. echo -ne "path to youtube subs >"
  115. read youtubesubs
  116. while [ ! -f "$youtubesubs" ]; do
  117. echo "Cannot find file $youtubesubs"
  118. echo -ne "Enter a different file to try >"
  119. read youtubesubs
  120. done
  121. jq -r '.[] | .snippet.resourceId.channelId + "," + .snippet.title' $youtubesubs >> $subsfile
  122. echo -e "\nImported" $(jq -r '.[] | .snippet.channelId' $youtubesubs | wc -l) items
  123. read -n 1 -t 1
  124. }
  125. invidiousGet () {
  126. get $instance/api/v1/$1
  127. return $?
  128. }
  129. get () {
  130. hash=$(echo $1 | md5sum | cut -c 1-32)
  131. if [ ! -f "$cachedir/$hash.json" ]; then
  132. wget $1 -O $cachedir/$hash.json -a $cachedir/logs
  133. error=$?
  134. if [ ! $error -eq 0 ]; then
  135. rm $cachedir/$hash.json
  136. return $error
  137. fi;
  138. fi
  139. cat $cachedir/$hash.json
  140. }
  141. invidiousRefresh () {
  142. refresh $instance/api/v1/$1
  143. }
  144. refresh () {
  145. hash=$(echo $1 | md5sum | cut -c 1-32)
  146. filename="$cachedir/$hash.json"
  147. if [ -f $filename ]; then
  148. rm $filename
  149. fi
  150. }
  151. searchChannel () {
  152. echo -ne "\nSearching for a channel. Blank to cancel.\e[1F? "
  153. read channelSearch
  154. channelSearch=$(echo $channelSearch | sed 's/ /+/g')
  155. if [ ! -z $channelSearch ]; then
  156. pickChannel "search?q=$channelSearch&type=channel" 0
  157. fi
  158. }
  159. searchVideo () {
  160. echo -ne "\nSearching for a video. Blank to cancel.\e[1F/ "
  161. read search
  162. search=$(echo $search | sed 's/ /+/g')
  163. if [ ! -z $search ]; then
  164. pickVideo "search?q=$search&type=video" 0
  165. fi
  166. }
  167. pickChannel () {
  168. heading "Loading $1"
  169. channelsData=$(invidiousGet $1)
  170. error=$?
  171. if [ ! $error -eq 0 ]; then
  172. echo "wget error $error. Maybe try another instance."
  173. read -n 1
  174. return $error
  175. fi
  176. pickChannelJSON "$(jq -r '.' <<< $channelsData)" ||
  177. invidiousRefresh $1
  178. }
  179. pickChannelJSON () {
  180. heading "Pick a channel"
  181. options=$(jq -r '.[] | .author + "\t" + (.subCount | tostring) + "\t" + .authorId' <<< $1 |
  182. column -t -s$'\t')
  183. while true; do
  184. id=$(echo "$options" | $menu | sed 's/^\(.*\) \(.*\)$/\2/')
  185. if [ -z "$id" ]; then
  186. break
  187. fi
  188. channelMenu $id
  189. done
  190. }
  191. heading () {
  192. echo -e $clear$blue$bold$1$white
  193. }
  194. channelMenu () {
  195. heading "Loading... channels/$1"
  196. channelData=$(invidiousGet channels/$1)
  197. error=$?
  198. if [ ! $error -eq 0 ]; then
  199. echo "wget error $error. Maybe try another instance."
  200. read -n 1
  201. return $error
  202. fi
  203. channelName=$(echo $channelData | jq -r '.author')
  204. heading "$channelName Channel"
  205. echo $1
  206. grep -Fq "$1" "$subsfile" && echo "Subscribed" || echo "Not Subscribed"
  207. echo -ne $white
  208. echo $channelData | jq -r '.description'
  209. echo -ne "
  210. What would you like to see?
  211. l) Latest Videos
  212. v) Popular Videos
  213. p) Playlists
  214. w) Subscribe
  215. q) Back
  216. > "
  217. read -n 1 option
  218. case $option in
  219. ,) settingsMenu ;
  220. channelMenu $1 ;;
  221. /) searchVideo ; channelMenu $1 ;;
  222. '?') searchChannel ; channelMenu $1 ;;
  223. l) pickVideo "channels/videos/$1?sort_by=newest"
  224. channelMenu $1
  225. ;;
  226. v) pickVideo "channels/videos/$1?sort_by=popular"
  227. channelMenu $1
  228. ;;
  229. p) playlists $1 ; channelmenu $1 ;;
  230. w) echo "$1,$channelName" >> $subsfile
  231. channelMenu $1
  232. ;;
  233. r) invidiousRefresh channels/$1 ""
  234. channelMenu $1
  235. ;;
  236. $'\e'|'q') return ;;
  237. *) channelMenu $1 ;;
  238. esac
  239. }
  240. playlists () {
  241. heading Loading "channels/$1/playlists"
  242. playlistData=$(invidiousGet "channels/$1/playlists")
  243. error=$?
  244. if [ ! $error -eq 0 ]; then
  245. echo "wget error $error. Maybe try another instance."
  246. read -n 1
  247. return $error
  248. fi
  249. choice=$( echo $playlistData | jq -r '.playlists[] | .title + "\t" + .playlistId' | column -t -s$'\t' | fzf | awk '{ print $NF }' )
  250. playlistMenu $choice
  251. }
  252. playlistMenu () {
  253. heading Loading "playlists/$1"
  254. playlistData=$(invidiousGet "playlists/$1")
  255. error=$?
  256. if [ ! $error -eq 0 ]; then
  257. echo "wget error $error. Maybe try another instance."
  258. read -n 1
  259. return $error
  260. fi
  261. while true; do
  262. choice=$( echo $playlistData | jq -r '.videos[] | .title + "\t" + .videoId' | column -t -s$'\t' | fzf | awk '{ print $NF }' )
  263. if [ -z "$choice" ]; then
  264. break
  265. fi
  266. watchVideo $choice
  267. done
  268. }
  269. # TODO: multiple pages of videos
  270. pickVideo () {
  271. heading Loading $1
  272. data=$(invidiousGet $1)
  273. error=$?
  274. if [ ! $error -eq 0 ]; then
  275. echo "wget error $error. Maybe try another instance."
  276. read -n 1
  277. return $error
  278. fi
  279. pickVideoJSON "$(jq -r '.' <<< $data)"
  280. }
  281. pickVideoJSON () {
  282. heading "Pick a video"
  283. options=$(jq -r '.[] | (.title) + "\t" +
  284. (.author) + "\t" +
  285. (if (.lengthSeconds >= 3600) then
  286. (.lengthSeconds/3600 | floor | tostring) + ":" + (if (.lengthSeconds%3600/60<=10) then "0" else "" end) + (.lengthSeconds%3600/60 | floor | tostring) + ":" + (if (.lengthSeconds%60<=10) then "0" else "" end) + (.lengthSeconds%60 | tostring)
  287. else
  288. (.lengthSeconds/60 | floor | tostring) + ":" + (if (.lengthSeconds%60<=10) then "0" else "" end) + (.lengthSeconds%60 | tostring)
  289. end) + "\t" +
  290. (if .viewCount > 1000000 then
  291. (.viewCount / 1000000 | floor | tostring) + "M"
  292. elif .viewCount > 1000 then
  293. (.viewCount / 1000 | floor | tostring) + "K"
  294. else (.viewCount | tostring)
  295. end + "\t" + .videoId)' <<< $1 |
  296. column -t -s$'\t' -R3,4)
  297. while true; do
  298. id=$(echo "$options" | $menu | sed 's/^\(.*\) \(.*\)$/\2/')
  299. if [ -z "$id" ]; then
  300. break
  301. fi
  302. watchVideo $id
  303. done
  304. }
  305. watchVideo () {
  306. if [ -z "$1" ]; then
  307. return
  308. fi
  309. url="$videoInstance/watch?v=$1"
  310. if [ $playimmediate = true ]; then
  311. $videoplayer $url &
  312. fi
  313. if [ -z "$2" ]; then
  314. heading "Loading $instance/api/v1/videos/$1 ..."
  315. videoData=$(invidiousGet "videos/$1")
  316. error=$?
  317. if [ ! $error -eq 0 ]; then
  318. echo "wget error $error. Maybe try another instance."
  319. read -n 1
  320. return $error
  321. fi
  322. else
  323. videoData=$2
  324. fi
  325. while true; do
  326. heading "$(echo $videoData | jq -r '.title')"
  327. echo $url
  328. echo -ne "
  329. p) Play Video
  330. d) description
  331. c) View Channel
  332. n) Related Videos
  333. r) Refresh page
  334. q) Back
  335. > "
  336. read -n 1 vidNum
  337. case $vidNum in
  338. ,) settingsMenu ;;
  339. /) searchVideo;;
  340. '?') searchChannel ;;
  341. c)
  342. channelMenu $(jq -r '.authorId' <<< $videoData)
  343. ;;
  344. n)
  345. pickVideoJSON "$(jq -r '.recommendedVideos' <<<$videoData)"
  346. ;;
  347. d)
  348. echo $videoData | jq -r '.description' | less
  349. ;;
  350. p)
  351. echo -e "\nStarting Video"
  352. $videoplayer $url &
  353. ;;
  354. r)
  355. invidiousRefresh videos/$1
  356. watchVideo $1 $2
  357. break
  358. ;;
  359. $'\e'|'q') break ;;
  360. *) echo 'try again' ;;
  361. esac
  362. done
  363. }
  364. subscriptionsMenu () {
  365. heading Subscriptions
  366. echo -ne "
  367. What do you want to do?
  368. c) View Subscribed Channels
  369. l) Latest Videos
  370. r) Reload Videos
  371. q) Exit
  372. > "
  373. read -n 1 option
  374. case $option in
  375. ,) settingsMenu ; subscriptionsMenu ;;
  376. /) searchVideo ; subscriptionsMenu ;;
  377. '?') searchChannel ; subscriptionsMenu ;;
  378. c) subscribedChannels ; subscriptionsMenu ;;
  379. l) subscriptionsLatest ; subscriptionsMenu ;;
  380. r) subscriptionsReload ; subscriptionsMenu ;;
  381. $'\e'|'q') echo -ne $clear ;;
  382. esac
  383. }
  384. subscriptionsReload () {
  385. heading Reloading
  386. echo -e "sfeedpath=$subsfeedsdir \n feeds() {" > $subsfeeds
  387. cat $configdir/subscriptions.csv | \
  388. awk -F ',' '{print "feed \""$1"\"" " \"'$RSSinstance'/feed/channel/"$1"\""}' \
  389. >> $subsfeeds
  390. echo "}" >> $subsfeeds
  391. sfeed_update $subsfeeds
  392. }
  393. subscriptionsLatest () {
  394. while true; do
  395. if [ -z "$1" ]; then
  396. choice=$(awk -F'\t' '!seen[$6]++ {OFS="\t|\t";
  397. $1=strftime("%Y-%m-%d", $1);
  398. sub(".*/", "", FILENAME);
  399. channelid = FILENAME;
  400. print $1, $7, $2, $3, channelid}' "$subsfeedsdir/"* | sort -r | column -t -s$'\t' -T4 | $menu)
  401. else
  402. choice=$(awk -F'\t' '!seen[$6]++ {OFS="\t|\t";
  403. $1=strftime("%Y-%m-%d", $1);
  404. sub(".*/", "", FILENAME);
  405. channelid = FILENAME;
  406. print $1, $7, $2, $3, channelid}' "$subsfeedsdir/$1" | sort -r | column -t -s$'\t' -T4 | $menu)
  407. fi
  408. if [ ! -z "$choice" ]; then
  409. watchVideo "$(sed 's@^.*/watch?v=\(.*\)@\1@' <<< $choice)" \
  410. "{ \"title\":\"$(awk -F'|' '{ print $3 }' <<< $choice)\",
  411. \"authorId\":\"$(awk -F'|' '{ print $5 }' <<< $choice)\"}"
  412. else
  413. break
  414. fi
  415. done
  416. }
  417. subscribedChannels () {
  418. while true; do
  419. heading Subscriptions
  420. subs=$(cat $configdir/subscriptions.csv)
  421. channel=$(awk -F, '{OFS="\t"; print $1, $2 }' <<< $subs | column -t -s$'\t' | $menu | awk '{print $1}')
  422. if [ ! -z "$channel" ]; then
  423. subscriptionsLatest "$channel" || channelMenu "$channel"
  424. else
  425. break
  426. fi
  427. done
  428. }
  429. menu () {
  430. echo -ne $clear$bold$red
  431. cat $art
  432. echo -ne $white
  433. echo -ne "
  434. What do you want to do?
  435. ,) Settings
  436. ?) Search for channel
  437. /) Search for video
  438. s) Subscriptions
  439. q) Exit
  440. > "
  441. read -n 1 option
  442. case $option in
  443. ,) settingsMenu ; menu ;;
  444. '?') searchChannel ; menu ;;
  445. /) searchVideo ; menu ;;
  446. s) subscriptionsMenu ; menu ;;
  447. r) sfeed_update $subsfeeds ;;
  448. $'\e'|'q') echo -ne $clear ; exit ;;
  449. *) menu ;;
  450. esac
  451. }
  452. if [ ! -z $1 ]; then
  453. if [[ $1 =~ [-_a-zA-Z0-9]{11} ]]; then
  454. watchVideo $BASH_REMATCH
  455. menu
  456. exit
  457. fi
  458. echo "could not find video ID in $1"
  459. read -n 1
  460. exit
  461. else
  462. menu
  463. fi