Compare commits
613 Commits
ac1c22d81c
...
fbe9e6223a
Author | SHA1 | Date |
---|---|---|
Stypox | fbe9e6223a | |
Stypox | 4e9e7cb29c | |
Stypox | 9d0dd36034 | |
Stypox | d4e6d22e64 | |
AudricV | 74bf000473 | |
AudricV | f9792cf3a9 | |
AudricV | f40fc0aa4f | |
AudricV | 2a3c6f80d2 | |
AudricV | 657b4377aa | |
AudricV | 7bf50bf1cb | |
AudricV | 27dc1b1f50 | |
AudricV | e380bb4bc3 | |
Audric V | 6c3c2e25d7 | |
Stypox | 02274d5395 | |
Stypox | 3f7b2653e3 | |
Stypox | a90237816a | |
Stypox | b80c3f5d51 | |
Stypox | 09732d6785 | |
AudricV | 293c3e9e47 | |
Stypox | e5b30ae8c3 | |
Stypox | 23fc7aa209 | |
Stypox | fb468a23f4 | |
Stypox | 6589e2c15d | |
Tobi | ad71864b23 | |
Stypox | c57016b79b | |
Tobi | adcc1f17ee | |
TobiGr | 51ddacc81d | |
TobiGr | 8392d50ba6 | |
TobiGr | aaccfecda8 | |
TobiGr | 73f0c63a9d | |
Tobi | 896a55e319 | |
dependabot[bot] | e58fc652e0 | |
Tobi | e3f2c9aec7 | |
Tobi | 6b0fc14c04 | |
dependabot[bot] | d579b608e5 | |
TobiGr | fe47a4311f | |
TobiGr | 15e0e74b48 | |
dependabot[bot] | da04eded5d | |
Profpatsch | 7408173246 | |
Stypox | aaf3231fc7 | |
Stypox | 137e924035 | |
Stypox | cc9ade962e | |
Stypox | 3402cdb666 | |
petlyh | 6dc25f7b97 | |
petlyh | 4408e2d0ac | |
TobiGr | 9ab932e394 | |
Stypox | 9d66debf3c | |
Tobi | 038ebdedc4 | |
TobiGr | 61d237de02 | |
petlyh | 2b2c1546d1 | |
Tobi | 1e93b1dc20 | |
dependabot[bot] | 3400af99b3 | |
Tobi | 1f8a044462 | |
dependabot[bot] | 1470aa7303 | |
TobiGr | 8f9ebdcb77 | |
Stypox | 1553931027 | |
Stypox | b2ec1b15fb | |
dependabot[bot] | 151ee99da3 | |
dragfyre | 65e7bc5b95 | |
Stypox | f276caf54a | |
Stypox | fc54fb2fdb | |
Stypox | 0518487d26 | |
Stypox | 5b59a1a8c5 | |
Stypox | b8e12dd76c | |
Stypox | 83c1737f70 | |
Stypox | 2938067c2c | |
Stypox | 91419ec6e8 | |
Audric V | 678c98f24c | |
AudricV | ec0194cfbf | |
AudricV | 00a0f1a103 | |
AudricV | 06838d7245 | |
AudricV | 642bb01388 | |
AudricV | bedc9e5bc0 | |
AudricV | 5fa22ae25b | |
AudricV | 29dc7625f2 | |
AudricV | 56ab35423e | |
AudricV | 9dc1eab28c | |
AudricV | ad3d187ac7 | |
AudricV | e111814401 | |
AudricV | fc45941ead | |
AudricV | 0bcb241c38 | |
AudricV | 6ba8251be1 | |
AudricV | 7dea2d0d27 | |
AudricV | 3782d9a02a | |
AudricV | b71ce1123f | |
AudricV | ff8ed7247f | |
AudricV | ec838d7421 | |
AudricV | 2c941794c0 | |
AudricV | d97c9e0db1 | |
AudricV | 8a9ebcc373 | |
Tobi | eac850ca10 | |
Tobi | 5ab1f784e8 | |
dependabot[bot] | 9d7bcba050 | |
dependabot[bot] | e26065148a | |
FineFindus | 34b05a0dda | |
TobiGr | 0821f09114 | |
FineFindus | c1784a4bdb | |
TobiGr | f9846352ea | |
Tobi | d6f5cba6e2 | |
TobiGr | 9d63c75623 | |
TobiGr | d49f8411d7 | |
Stypox | bb132167d5 | |
AudricV | c98695fcea | |
AudricV | ac00459c1a | |
FineFindus | dd7b2d9798 | |
Youssif Shaaban Alsager | 917554acc4 | |
Tobi | 8b0068f8f4 | |
Christian | fc67d49f59 | |
Stypox | 289db1178a | |
AudricV | 6ed22099a2 | |
AudricV | 714b141ecb | |
AudricV | 588c6a8422 | |
AudricV | 1fa85ec6ca | |
AudricV | a04bc320de | |
AudricV | 7de3753a81 | |
AudricV | 6884d191cd | |
Tobi | 3be76a6406 | |
TobiGr | 17790328cd | |
Isira Seneviratne | 4bc8ae7812 | |
Tobi | 90aed06a63 | |
TobiGr | cf49f4a31c | |
Tobi | 7c7ceaceab | |
dependabot[bot] | 72c475d944 | |
Stypox | 1f08d28ae5 | |
AudricV | e8bfd20170 | |
AudricV | 0292c4f3e8 | |
AudricV | 2578f22054 | |
AudricV | ba5315c72d | |
AudricV | 1d72bac53d | |
AudricV | 93a210394d | |
AudricV | 2c436d428c | |
AudricV | d381f3b70b | |
AudricV | 434e885708 | |
AudricV | 5158472852 | |
AudricV | 70fb3aa38e | |
AudricV | e16d521b7b | |
AudricV | 306068a63b | |
AudricV | 2f40861428 | |
AudricV | 71cda03c4c | |
AudricV | 7e01eaac33 | |
AudricV | 4b80d737a4 | |
AudricV | 4e6fb368bc | |
AudricV | 0a6011a50e | |
AudricV | 6f8331524b | |
AudricV | 81c0d80a54 | |
AudricV | 31da5beb51 | |
AudricV | a3a74cd566 | |
AudricV | 7f818217d2 | |
AudricV | 266cd1f76b | |
AudricV | c1981ed54f | |
AudricV | 4cc99f9ce1 | |
AudricV | adfad086ac | |
AudricV | d56b880cae | |
AudricV | 9d8098576e | |
AudricV | 0f4a5a8184 | |
AudricV | ca1d4a6fa4 | |
AudricV | 2f3ee8a3f2 | |
AudricV | 78ce65769f | |
AudricV | d85454186a | |
Stypox | 7294675aea | |
Stypox | 93a90b816d | |
Stypox | 44b664af15 | |
AudricV | 2f7bfd3e7f | |
AudricV | b147904571 | |
AudricV | 1852031a0b | |
AudricV | 698c710685 | |
AudricV | 8237052ef5 | |
AudricV | 162c261577 | |
AudricV | e2f4ee47b9 | |
AudricV | e6f371fb94 | |
Stypox | 7936987955 | |
Stypox | 6d2227111f | |
Stypox | ee625c325c | |
Stypox | 276c293889 | |
Stypox | 9d3761a371 | |
Stypox | 95a3cc0a17 | |
Stypox | e34b4f1978 | |
Stypox | ef67c7cd74 | |
Stypox | a104cf3227 | |
Stypox | bb47f05f89 | |
Stypox | 468bcc045d | |
Stypox | 35f3a4ad01 | |
AudricV | e7d64099a7 | |
AudricV | 684101c47d | |
AudricV | eaf2600ce0 | |
AudricV | 0ee2072de5 | |
AudricV | d3801dd0e9 | |
AudricV | 8baec04611 | |
AudricV | e0ba29cd19 | |
AudricV | 18846baba7 | |
ThetaDev | c70a0e3543 | |
AudricV | 7366eab156 | |
AudricV | 4586067934 | |
AudricV | d4bfe791ee | |
AudricV | 6f7d1f079f | |
AudricV | 1e8474b22d | |
AudricV | 652c2c8408 | |
AudricV | de823a6b68 | |
AudricV | 76fb9dcdd7 | |
AudricV | 946eb9bd91 | |
AudricV | 356a888d6c | |
Stypox | 3faaf4301c | |
Stypox | 8fb6ba36fa | |
Stypox | 2947257111 | |
Stypox | 485bfbca9d | |
Stypox | 7c70fef197 | |
Stypox | de0a9bb797 | |
TobiGr | 340095515d | |
TobiGr | fe27d6a0ec | |
Kavin | 25082d78b0 | |
TobiGr | aa6c17dc77 | |
TobiGr | 2fb9922a15 | |
AudricV | a3d160edab | |
Stypox | 5492343b8e | |
Tobi | 9a59afbcf5 | |
AudricV | bb1ab166bf | |
AudricV | f1fa84b4e3 | |
Tobi | 39a911db9f | |
dependabot[bot] | bda3a3fc5d | |
AudricV | 522c78160f | |
AudricV | 7528eb2bd9 | |
AudricV | 164c8e3abb | |
Tobi | 8ebbe1b7e1 | |
FireMasterK | 6db0d116fe | |
Tobi | 986a3f7747 | |
Tobi | d6132a9ed9 | |
Tobi | 87eb29a25d | |
AudricV | 4e22c5ee87 | |
Kavin | d961d349c3 | |
ThetaDev | ad97f08048 | |
Tobi | d294ccb433 | |
Tobi | 5d112aa772 | |
TobiGr | 0e01d90562 | |
TobiGr | 5809904cf7 | |
TobiGr | 53dfd871e2 | |
Stypox | 5eb886243f | |
TobiGr | 19ce06fe00 | |
Audric V | 533121fb81 | |
Audric V | 92a0024424 | |
Tobi | 3c036a9c03 | |
TobiGr | c70bb83801 | |
TobiGr | ca0ce00753 | |
TobiGr | b218bf69bd | |
chunky programmer | 81f29116ba | |
chunky programmer | e147867d41 | |
chunky programmer | 5ab6cd7420 | |
TobiGr | d358ba1c41 | |
TobiGr | aff3e795f8 | |
ThetaDev | 3673d4ae01 | |
ThetaDev | 0addb98cd7 | |
ThetaDev | 24eba62305 | |
Kavin | a9ca5c49e4 | |
Stypox | 00408db959 | |
dependabot[bot] | 108f8a7a17 | |
Tobi | 11565db17f | |
AudricV | cf6040ddb3 | |
AudricV | 593122342f | |
AudricV | e923fca440 | |
AudricV | 945165a3c0 | |
Stypox | d61dc27406 | |
Stypox | 51cba5a287 | |
Stypox | f819c2fe34 | |
dependabot[bot] | 4b92c5a34d | |
Stypox | 2deb023da4 | |
ThetaDev | 4aada7f91b | |
Tobi | b2b426048f | |
ThetaDev | 47aa9fed40 | |
ThetaDev | 20370395c5 | |
Stypox | 7dba6e3891 | |
petlyh | e6aad117e7 | |
TobiGr | 8495ad619e | |
fynngodau | 69705138e4 | |
Björn Sigurbergsson | 1b6fe5edd6 | |
ThetaDev | 8d1303e18f | |
Tobi | 44ae139d33 | |
Tobi | d5e9df5381 | |
Tobi | 0486ad7a08 | |
AudricV | 80a6fc2c63 | |
petlyh | 5a9b6ed2e3 | |
Stypox | 6bdd698c25 | |
Stypox | 19e4b216c9 | |
Stypox | b1298490c0 | |
petlyh | 9dc1832733 | |
fynngodau | 3fdb6ee476 | |
AudricV | bd79b921e8 | |
AudricV | 51f9b39953 | |
AudricV | 95b3f5e391 | |
AudricV | 30a0f8c510 | |
AudricV | 7f0269c4c7 | |
AudricV | 034f82dae7 | |
AudricV | 05e8cb39f7 | |
AudricV | bf30d70152 | |
AudricV | 76b7c19c5d | |
AudricV | 3bb5eeef30 | |
AudricV | 14bf3fb05b | |
AudricV | f92426560c | |
AudricV | a63f289667 | |
AudricV | 9483dcd9fa | |
AudricV | 1556adbb2d | |
Stypox | 99ab9777ad | |
Stypox | 39c2e1fda0 | |
petlyh | f7a7a236fb | |
dependabot[bot] | f5599ff08d | |
Stypox | 7e793c11ae | |
Stypox | e920ab3f5f | |
TobiGr | 3f7df9536e | |
Stypox | 999fb7f812 | |
Stypox | 3519d4c367 | |
Stypox | 9aca710e86 | |
Stypox | 76eeabac45 | |
AudricV | 676622f6df | |
AudricV | 2a24d407d5 | |
Tobi | 0e4e6a9bac | |
AudricV | ba24976e41 | |
AudricV | 57f850bc2d | |
AudricV | 1f4ed9dce9 | |
Tobi | c589a2c1a2 | |
Tobi | c2b36fd405 | |
TobiGr | 72573932cf | |
TobiGr | f50b7275af | |
Kunal | 9bdad40b06 | |
Stypox | 5945057227 | |
Stypox | 7293991832 | |
Stypox | ff94e9f30b | |
Stypox | c1040bccac | |
Stypox | 6ccc43e57e | |
dependabot[bot] | 1bc44cb949 | |
dependabot[bot] | f43049985e | |
Stypox | 33f9f2cf66 | |
TobiGr | 56aab4d971 | |
Kavin | 22a47da8c7 | |
Kavin | 98a90fd9c8 | |
Kavin | 2974dfaa48 | |
Kavin | 64d24aa09e | |
Kavin | 67ef4f4c30 | |
FireMasterK | 22f71b010c | |
FireMasterK | 656b7c1cd9 | |
FireMasterK | 981aee4092 | |
Stypox | 45636b0d00 | |
Stypox | 219c5c5be5 | |
Stypox | 259de3cba6 | |
Stypox | 991394b53a | |
Stypox | ce15f7cc50 | |
Isira Seneviratne | d8ce08d969 | |
Kavin | 01acf79436 | |
TobiGr | 292e0d8ce7 | |
TobiGr | 2a8729aeb2 | |
TobiGr | d75a997611 | |
TobiGr | dea6d8ce4c | |
Stypox | 95cc6aefbb | |
Stypox | 7b54457789 | |
AudricV | f45966d449 | |
AudricV | d5437e0bc5 | |
Tobi | 88e07e555d | |
AudricV | 0766b1d211 | |
Tobi | 896d7e09eb | |
TobiGr | cd3262745d | |
TobiGr | 4e66b2287e | |
Tobi | 41c8dce452 | |
Isira Seneviratne | 2bca56f0df | |
Isira Seneviratne | 3b80547976 | |
Tobi | a1128ecbdc | |
ThetaDev | 016623131e | |
Kavin | 2e08eaad96 | |
Kavin | abf08e1496 | |
Tobi | 72d5ed3318 | |
Kavin | 57e7a6fb7c | |
Kavin | 1d3d7fa5c3 | |
Kavin | 52fda37915 | |
Kavin | b566084cac | |
Tobi | 40f1ec4a54 | |
Stypox | 5f6101698b | |
Tobi | f8162b049d | |
Tobi | ad7c3c2374 | |
Tobi | 1da0190056 | |
Stypox | 60fb30f835 | |
Kavin | 5abea22225 | |
Kavin | 7874d3c8aa | |
Kavin | faf28f5c11 | |
Kavin | a3ed947b2d | |
Kavin | c043597255 | |
TobiGr | 4680df0bdf | |
TobiGr | 9de8405c9f | |
Stypox | 34d79bd267 | |
AudricV | 2ec296e674 | |
AudricV | 3891542ca1 | |
AudricV | b2862f3cd1 | |
AudricV | e9a0d3bd95 | |
AudricV | b9e463de49 | |
AudricV | 65d6321e3d | |
ThetaDev | 5daabd1793 | |
Kavin | c953e23414 | |
Tobi | 2211a24b69 | |
Tobi | f6eefdc76c | |
Kavin | a1f22be0dd | |
Kavin | 9bdad55508 | |
Kavin | 86f06b333a | |
Kavin | b16e6082e1 | |
Kavin | 30909da1df | |
Kavin | 6d59cdbe3a | |
Tobi | 43d1c1f8b1 | |
Isira Seneviratne | e4d982c7ea | |
Isira Seneviratne | 416089146e | |
Isira Seneviratne | ff5f223d3f | |
Isira Seneviratne | ddbce3b83d | |
Isira Seneviratne | 366f5c1632 | |
Tobi | 0ea16c0b73 | |
dependabot[bot] | 0b82fade51 | |
Luigi Russo | c9635218e2 | |
Isira Seneviratne | 1be270768f | |
Isira Seneviratne | 316d8573fa | |
AudricV | 6a2c680d8f | |
AudricV | e66fed41d6 | |
AudricV | aa9a8ca23c | |
AudricV | eb07d70a2c | |
AudricV | 20cd8e8a4a | |
AudricV | a34f060ba0 | |
AudricV | 724f669ff7 | |
AudricV | 61ce041bda | |
AudricV | ffffb04439 | |
ThetaDev | 592e1d6386 | |
AudricV | 31bf7046b7 | |
ThetaDev | 099b53cc4f | |
AudricV | e4c24d4c36 | |
Theta-Dev | 20e4a35814 | |
AudricV | 4cae66f1f9 | |
Tobi | eb40bb8458 | |
chowder | b1a899fd47 | |
Stypox | a4db106a66 | |
Kavin | b441910257 | |
chowder | 3fdc0e72cc | |
Kavin | 6a256d0631 | |
Tobi | 430504b4b5 | |
Kavin | f9bd08c649 | |
Caleb | 9282c3c13b | |
Caleb | c5216f7c12 | |
Caleb | 04795fe5d2 | |
chowder | 975b38a0b9 | |
chowder | 4cccd33f3d | |
chowder | 09544ceb23 | |
chowder | 644cc6cd76 | |
chowder | daf5674951 | |
Tobi | 3d314169b9 | |
AudricV | 65125a3eb4 | |
AudricV | 7258a53225 | |
AudricV | e37e8b358e | |
AudricV | 60e97cd274 | |
AudricV | 4bc90cd9d8 | |
AudricV | c230d84df1 | |
Tobi | 9ffdd0948b | |
dependabot[bot] | 24137bb15f | |
xz-dev | 0ffcb32d9c | |
Isira Seneviratne | b90a566dd8 | |
Isira Seneviratne | b232c29d22 | |
TobiGr | 4d136599bd | |
Tobi | a822e91909 | |
TobiGr | 02810a7db7 | |
Tobi | 2d50369c77 | |
Tobi | 54092fc3c7 | |
Tobi | d7c23ef118 | |
dependabot[bot] | 08339d6b0c | |
Tobi | 5537e84100 | |
dependabot[bot] | 719664593f | |
TurtleArmyMc | bf70d32eb4 | |
Stypox | 5c710da160 | |
AudricV | 8067c43837 | |
AudricV | abcee87167 | |
ThetaDev | 7244be7627 | |
Isira Seneviratne | 0e31c86aee | |
Stypox | a99af9bb6e | |
Kavin | 4e14644c41 | |
Stypox | 14ef430546 | |
Stypox | 93ef73e2f2 | |
AudricV | d8ddeb549c | |
AudricV | 884da40f65 | |
AudricV | 58b09cf78b | |
AudricV | 119b9129e2 | |
ThetaDev | 4905f74700 | |
Isira Seneviratne | e101d58ae7 | |
Stypox | 6a858368c8 | |
Stypox | fa25a7280f | |
Stypox | 25f0ce7305 | |
Stypox | d8e10dd3ad | |
dependabot[bot] | 1d7a3a90a4 | |
Isira Seneviratne | 41254ae12a | |
Isira Seneviratne | 943b7c033b | |
litetex | 20914a6035 | |
litetex | d6577e5e0b | |
litetex | 0a5ad90724 | |
AudricV | 03d9a4fe9d | |
litetex | 0beb55a232 | |
litetex | 9e93d6b193 | |
litetex | ed0a07af4e | |
litetex | 641a447d9b | |
litetex | 8ff7a90f52 | |
TacoTheDank | d8339d4060 | |
AudricV | cb64a480cd | |
litetex | 5bbea3a8f2 | |
litetex | da06166065 | |
litetex | 2e36ab1578 | |
litetex | 2a8a623643 | |
litetex | 938e69a16a | |
litetex | 844de3e378 | |
litetex | c1a72b8240 | |
litetex | fc27b8a5b8 | |
Stypox | 17bad6cedf | |
litetex | ecfc370685 | |
litetex | 2b6fe294b2 | |
litetex | d6586da614 | |
litetex | bf3ae5e679 | |
litetex | 504f81036e | |
litetex | e7c12258f4 | |
litetex | 9e8724df4d | |
Tobi | 76aad92fa5 | |
AudricV | 472f5d9e9c | |
AudricV | 7bdca33a87 | |
AudricV | c82317e318 | |
AudricV | d0549a5a52 | |
AudricV | d7e678aca2 | |
AudricV | 6a885ef5ab | |
AudricV | 5b548340e8 | |
ThetaDev | 52ded6e3d7 | |
Stypox | d12003651b | |
Isira Seneviratne | 7daca10a06 | |
Stypox | 2906be22af | |
Stypox | 4ddb96a86f | |
Isira Seneviratne | 64771c5712 | |
Stypox | fc8b5ebbc6 | |
Stypox | 4a4939d89c | |
Stypox | c336bd58a5 | |
dependabot[bot] | 325af31e5f | |
dependabot[bot] | d905636021 | |
Isira Seneviratne | 1af6b8eedb | |
Isira Seneviratne | ff60e05c76 | |
Isira Seneviratne | 682a4263e5 | |
Stypox | 8c5f014a6f | |
Stypox | 954a294e27 | |
Mohammed Anas | f57049d2c0 | |
Stypox | 5bd7bf20cc | |
Stypox | 5ab74b3631 | |
dependabot[bot] | 122365005a | |
mhmdanas | 414186cff5 | |
dependabot[bot] | e9b4be3e3c | |
Stypox | 5219a705ba | |
AudricV | 090debd83b | |
litetex | a26bcc55c4 | |
dependabot[bot] | 424eb1c559 | |
litetex | 2712c3d788 | |
dependabot[bot] | 281d2b9f81 | |
litetex | f775155d25 | |
litetex | 9d625dd75f | |
Gábor Lipták | 28c9340d69 | |
Stypox | 1b51eab664 | |
AudricV | 301a795ed3 | |
AudricV | e960a417ec | |
Stypox | c8a77da2ab | |
Kavin | 7635aeed2c | |
TiA4f8R | 287d1dfd63 | |
Stypox | b3c620f0d8 | |
Stypox | d652e05874 | |
Stypox | 044639c32b | |
litetex | c33d392958 | |
TiA4f8R | fffbbee7f3 | |
TiA4f8R | f7b1515290 | |
TiA4f8R | f17f7b9842 | |
TiA4f8R | 301b9fa024 | |
TiA4f8R | 2f3920c648 | |
TiA4f8R | 4158fc46a0 | |
TiA4f8R | 54d323c2ae | |
Stypox | 2321822844 | |
Stypox | cfc13f4a6f | |
Stypox | 00bbe5eb4d | |
Stypox | 4da05afe11 | |
Stypox | 3708ab9ed5 | |
Stypox | 5c83409039 | |
Stypox | 8226fd044f | |
Stypox | ba68b8c014 | |
Stypox | 159d05c91b | |
Stypox | 50272db946 | |
TiA4f8R | 07b045f20d | |
TiA4f8R | 436ddde29f | |
TiA4f8R | d64d7bbd01 | |
TiA4f8R | 2fb1a412a6 | |
TiA4f8R | f61e2092a1 | |
TiA4f8R | aa4c10e751 | |
TiA4f8R | 6985167e63 | |
TiA4f8R | f6ec7f9a61 | |
TiA4f8R | 7477ed0f3d | |
TiA4f8R | a857684442 | |
TiA4f8R | 4330b5f7be | |
TiA4f8R | 881969f1da | |
TiA4f8R | d5f3637fc3 | |
TiA4f8R | 7c67d46e09 | |
TiA4f8R | ad993b920f | |
TiA4f8R | 2f061b8dbd | |
Stypox | 1dc80957d8 | |
TiA4f8R | c34b5e3a8b | |
litetex | 2015eb374a | |
litetex | f69b0ff77b | |
Stypox | 23fa31a1ec | |
TiA4f8R | 3c3cd78676 | |
Stypox | 2e1c5c119d | |
Stypox | 598ebb92ea | |
litetex | 5db4d1faf3 | |
litetex | fe30eb43a9 | |
Stypox | c2b5370517 | |
Stypox | 7c78c39230 | |
Fynn Godau | c38c016de5 | |
Stypox | 8c1041def6 | |
Stypox | adbbdc7a5b | |
Stypox | 24e83997b4 | |
Stypox | 349990fd48 | |
litetex | 3bf7aa3762 |
|
@ -10,21 +10,23 @@ on:
|
|||
- master
|
||||
pull_request:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
build-and-test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: set up JDK 8
|
||||
uses: actions/setup-java@v3
|
||||
- name: set up JDK 11
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
java-version: '8'
|
||||
distribution: 'adopt'
|
||||
|
||||
java-version: '11'
|
||||
distribution: 'temurin'
|
||||
|
||||
- name: Cache Gradle dependencies
|
||||
uses: actions/cache@v3
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.gradle/caches
|
||||
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle') }}
|
||||
|
@ -33,7 +35,7 @@ jobs:
|
|||
# See gradle file for difference between downloaders
|
||||
- name: Build and run Tests
|
||||
run: |
|
||||
if [[ $GITHUB_EVENT_NAME == 'schedule' ]]; then
|
||||
if [[ "$GITHUB_EVENT_NAME" == 'schedule' ]]; then
|
||||
echo running with real downloader
|
||||
./gradlew check --stacktrace -Ddownloader=REAL
|
||||
else
|
||||
|
@ -42,7 +44,7 @@ jobs:
|
|||
fi
|
||||
|
||||
- name: Upload test reports when failure occurs
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
if: failure()
|
||||
with:
|
||||
name: NewPipeExtractor-test-reports
|
||||
|
|
|
@ -5,20 +5,24 @@ on:
|
|||
branches:
|
||||
- master
|
||||
|
||||
permissions:
|
||||
# The generated docs are written to the `gh-pages` branch.
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
build-and-deploy-docs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: set up JDK 8
|
||||
uses: actions/setup-java@v3
|
||||
- name: set up JDK 11
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
java-version: '8'
|
||||
distribution: 'adopt'
|
||||
java-version: '11'
|
||||
distribution: 'temurin'
|
||||
|
||||
- name: Cache Gradle dependencies
|
||||
uses: actions/cache@v3
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.gradle/caches
|
||||
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle') }}
|
||||
|
|
12
README.md
12
README.md
|
@ -12,8 +12,16 @@ If you're using Gradle, you could add NewPipe Extractor as a dependency with the
|
|||
|
||||
1. Add `maven { url 'https://jitpack.io' }` to the `repositories` in your `build.gradle`.
|
||||
2. Add `implementation 'com.github.TeamNewPipe:NewPipeExtractor:INSERT_VERSION_HERE'` to the `dependencies` in your `build.gradle`. Replace `INSERT_VERSION_HERE` with the [latest release](https://github.com/TeamNewPipe/NewPipeExtractor/releases/latest).
|
||||
3. If you are using tools to minimize your project, make sure to keep the files below, by e.g. adding the following lines to your proguard file:
|
||||
```
|
||||
## Rules for NewPipeExtractor
|
||||
-keep class org.schabi.newpipe.extractor.timeago.patterns.** { *; }
|
||||
-keep class org.mozilla.javascript.** { *; }
|
||||
-keep class org.mozilla.classfile.ClassFileWriter
|
||||
-dontwarn org.mozilla.javascript.tools.**
|
||||
```
|
||||
|
||||
**Note:** To use NewPipe Extractor in projects with a `minSdkVersion` below 26, [API desugaring](https://developer.android.com/studio/write/java8-support#library-desugaring) is required.
|
||||
**Note:** To use NewPipe Extractor in Android projects with a `minSdk` below 26, [API desugaring](https://developer.android.com/studio/write/java8-support#library-desugaring) is required. If the `minSdk` is below 19, the `desugar_jdk_libs_nio` artifact is required, which requires Android Gradle Plugin (AGP) version 7.4.0.
|
||||
|
||||
### Testing changes
|
||||
|
||||
|
@ -50,7 +58,7 @@ The following sites are currently supported:
|
|||
|
||||
[![GNU GPLv3 Image](https://www.gnu.org/graphics/gplv3-127x51.png)](https://www.gnu.org/licenses/gpl-3.0.en.html)
|
||||
|
||||
NewPipe is Free Software: You can use, study share and improve it at your
|
||||
NewPipe Extractor is Free Software: You can use, study share and improve it at your
|
||||
will. Specifically you can redistribute and/or modify it under the terms of the
|
||||
[GNU General Public License](https://www.gnu.org/licenses/gpl.html) as
|
||||
published by the Free Software Foundation, either version 3 of the License, or
|
||||
|
|
12
build.gradle
12
build.gradle
|
@ -5,10 +5,10 @@ allprojects {
|
|||
compileJava.options.encoding = 'UTF-8'
|
||||
compileTestJava.options.encoding = 'UTF-8'
|
||||
|
||||
sourceCompatibility = 1.8
|
||||
targetCompatibility = 1.8
|
||||
sourceCompatibility = JavaVersion.VERSION_11
|
||||
targetCompatibility = JavaVersion.VERSION_11
|
||||
|
||||
version 'v0.22.1'
|
||||
version 'v0.23.1'
|
||||
group 'com.github.TeamNewPipe'
|
||||
|
||||
repositories {
|
||||
|
@ -28,9 +28,9 @@ allprojects {
|
|||
|
||||
ext {
|
||||
nanojsonVersion = "1d9e1aea9049fc9f85e68b43ba39fe7be1c1f751"
|
||||
spotbugsVersion = "4.6.0"
|
||||
junitVersion = "5.8.2"
|
||||
checkstyleVersion = "9.3" // do not use latest version (10.0) as it requires compile JDK 11
|
||||
spotbugsVersion = "4.8.3"
|
||||
junitVersion = "5.10.2"
|
||||
checkstyleVersion = "10.4"
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -20,12 +20,6 @@
|
|||
<property name="fileNamePattern" value="module\-info\.java$"/>
|
||||
</module>
|
||||
|
||||
<!-- https://checkstyle.org/config_filters.html#SuppressionFilter -->
|
||||
<module name="SuppressionFilter">
|
||||
<property name="file" value="${config_loc}/suppressions.xml" />
|
||||
<property name="optional" value="true"/>
|
||||
</module>
|
||||
|
||||
<!-- Checks that a package-info.java file exists for each package. -->
|
||||
<!-- See https://checkstyle.org/config_javadoc.html#JavadocPackage -->
|
||||
<!--<module name="JavadocPackage"/>-->
|
||||
|
@ -68,6 +62,8 @@
|
|||
|
||||
<module name="SuppressWarningsFilter" />
|
||||
|
||||
<module name="SuppressWithPlainTextCommentFilter"/>
|
||||
|
||||
<module name="TreeWalker">
|
||||
<!-- Checks for Javadoc comments. -->
|
||||
<!-- See https://checkstyle.org/config_javadoc.html -->
|
||||
|
@ -100,7 +96,15 @@
|
|||
<!-- Checks for imports -->
|
||||
<!-- See https://checkstyle.org/config_import.html -->
|
||||
<module name="AvoidStarImport"/>
|
||||
<module name="IllegalImport"/> <!-- defaults to sun.* packages -->
|
||||
<module name="IllegalImport"> <!-- defaults to sun.* packages -->
|
||||
<property name="illegalClasses" value="
|
||||
org.jetbrains.annotations.Nullable,
|
||||
org.jetbrains.annotations.NotNull,
|
||||
androidx.annotation.Nullable,
|
||||
androidx.annotation.NonNull,
|
||||
io.reactivex.rxjava3.annotations.NonNull,
|
||||
io.reactivex.rxjava3.annotations.Nullable" />
|
||||
</module>
|
||||
<module name="RedundantImport"/>
|
||||
<module name="UnusedImports"/>
|
||||
|
||||
|
|
|
@ -1,15 +0,0 @@
|
|||
<?xml version="1.0"?>
|
||||
<!DOCTYPE suppressions PUBLIC
|
||||
"-//Checkstyle//DTD SuppressionFilter Configuration 1.2//EN"
|
||||
"https://checkstyle.org/dtds/suppressions_1_2.dtd">
|
||||
<suppressions>
|
||||
<!-- Use @SuppressWarnings("...") if it is possible, only use this file if it is not -->
|
||||
|
||||
<suppress checks="LineLength"
|
||||
files="BandcampExtractorHelper.java"
|
||||
lines="54"/>
|
||||
|
||||
<suppress checks="LineLength"
|
||||
files="ItagItem.java"
|
||||
lines="19"/>
|
||||
</suppressions>
|
|
@ -26,10 +26,12 @@ dependencies {
|
|||
implementation project(':timeago-parser')
|
||||
|
||||
implementation "com.github.TeamNewPipe:nanojson:$nanojsonVersion"
|
||||
implementation 'org.jsoup:jsoup:1.14.3'
|
||||
implementation 'org.mozilla:rhino:1.7.14'
|
||||
implementation 'org.jsoup:jsoup:1.17.2'
|
||||
implementation "com.github.spotbugs:spotbugs-annotations:$spotbugsVersion"
|
||||
implementation 'org.nibor.autolink:autolink:0.10.0'
|
||||
|
||||
// do not upgrade to 1.7.14, since in 1.7.14 Rhino uses the `SourceVersion` class, which is not
|
||||
// available on Android (even when using desugaring), and `NoClassDefFoundError` is thrown
|
||||
implementation 'org.mozilla:rhino:1.7.13'
|
||||
|
||||
checkstyle "com.puppycrawl.tools:checkstyle:$checkstyleVersion"
|
||||
|
||||
|
@ -39,5 +41,5 @@ dependencies {
|
|||
testImplementation 'org.junit.jupiter:junit-jupiter-params'
|
||||
|
||||
testImplementation "com.squareup.okhttp3:okhttp:3.12.13"
|
||||
testImplementation 'com.google.code.gson:gson:2.9.0'
|
||||
testImplementation 'com.google.code.gson:gson:2.10.1'
|
||||
}
|
||||
|
|
|
@ -0,0 +1,211 @@
|
|||
package org.schabi.newpipe.extractor;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.io.Serializable;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Class representing images in the extractor.
|
||||
*
|
||||
* <p>
|
||||
* An image has four properties: its URL, its height, its width and its estimated quality level.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* Depending of the services, the height, the width or both properties may be not known.
|
||||
* Implementations <b>must use</b> the relevant unknown constants in this case
|
||||
* ({@link #HEIGHT_UNKNOWN} and {@link #WIDTH_UNKNOWN}), to ensure properly the lack of knowledge
|
||||
* of one or both of these properties to extractor clients.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* They should also respect the ranges defined in the estimated image resolution levels as much as
|
||||
* possible, to ensure consistency to extractor clients.
|
||||
* </p>
|
||||
*/
|
||||
public final class Image implements Serializable {
|
||||
|
||||
/**
|
||||
* Constant representing that the height of an {@link Image} is unknown.
|
||||
*/
|
||||
public static final int HEIGHT_UNKNOWN = -1;
|
||||
|
||||
/**
|
||||
* Constant representing that the width of an {@link Image} is unknown.
|
||||
*/
|
||||
public static final int WIDTH_UNKNOWN = -1;
|
||||
|
||||
@Nonnull
|
||||
private final String url;
|
||||
private final int height;
|
||||
private final int width;
|
||||
@Nonnull
|
||||
private final ResolutionLevel estimatedResolutionLevel;
|
||||
|
||||
/**
|
||||
* Construct an {@link Image} instance.
|
||||
*
|
||||
* @param url the URL to the image, which should be not null or empty
|
||||
* @param height the image's height
|
||||
* @param width the image's width
|
||||
* @param estimatedResolutionLevel the image's estimated resolution level, which must not be
|
||||
* null
|
||||
* @throws NullPointerException if {@code estimatedResolutionLevel} is null
|
||||
*/
|
||||
public Image(@Nonnull final String url,
|
||||
final int height,
|
||||
final int width,
|
||||
@Nonnull final ResolutionLevel estimatedResolutionLevel)
|
||||
throws NullPointerException {
|
||||
this.url = url;
|
||||
this.height = height;
|
||||
this.width = width;
|
||||
this.estimatedResolutionLevel = Objects.requireNonNull(
|
||||
estimatedResolutionLevel, "estimatedResolutionLevel is null");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the URL of this {@link Image}.
|
||||
*
|
||||
* @return the {@link Image}'s URL.
|
||||
*/
|
||||
@Nonnull
|
||||
public String getUrl() {
|
||||
return url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the height of this {@link Image}.
|
||||
*
|
||||
* <p>
|
||||
* If it is unknown, {@link #HEIGHT_UNKNOWN} is returned instead.
|
||||
* </p>
|
||||
*
|
||||
* @return the {@link Image}'s height or {@link #HEIGHT_UNKNOWN}
|
||||
*/
|
||||
public int getHeight() {
|
||||
return height;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the width of this {@link Image}.
|
||||
*
|
||||
* <p>
|
||||
* If it is unknown, {@link #WIDTH_UNKNOWN} is returned instead.
|
||||
* </p>
|
||||
*
|
||||
* @return the {@link Image}'s width or {@link #WIDTH_UNKNOWN}
|
||||
*/
|
||||
public int getWidth() {
|
||||
return width;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the estimated resolution level of this image.
|
||||
*
|
||||
* <p>
|
||||
* If it is unknown, {@link ResolutionLevel#UNKNOWN} is returned instead.
|
||||
* </p>
|
||||
*
|
||||
* @return the estimated resolution level, which is never {@code null}
|
||||
* @see ResolutionLevel
|
||||
*/
|
||||
@Nonnull
|
||||
public ResolutionLevel getEstimatedResolutionLevel() {
|
||||
return estimatedResolutionLevel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a string representation of this {@link Image} instance.
|
||||
*
|
||||
* <p>
|
||||
* The representation will be in the following format, where {@code url}, {@code height},
|
||||
* {@code width} and {@code estimatedResolutionLevel} represent the corresponding properties:
|
||||
* <br>
|
||||
* <br>
|
||||
* {@code Image {url=url, height='height, width=width,
|
||||
* estimatedResolutionLevel=estimatedResolutionLevel}'}
|
||||
* </p>
|
||||
*
|
||||
* @return a string representation of this {@link Image} instance
|
||||
*/
|
||||
@Nonnull
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Image {" + "url=" + url + ", height=" + height + ", width=" + width
|
||||
+ ", estimatedResolutionLevel=" + estimatedResolutionLevel + "}";
|
||||
}
|
||||
|
||||
/**
|
||||
* The estimated resolution level of an {@link Image}.
|
||||
*
|
||||
* <p>
|
||||
* Some services don't return the size of their images, but we may know for a specific image
|
||||
* type that a service returns, according to real data, an approximation of the resolution
|
||||
* level.
|
||||
* </p>
|
||||
*/
|
||||
public enum ResolutionLevel {
|
||||
|
||||
/**
|
||||
* The high resolution level.
|
||||
*
|
||||
* <p>
|
||||
* This level applies to images with a height greater than or equal to 720px.
|
||||
* </p>
|
||||
*/
|
||||
HIGH,
|
||||
|
||||
/**
|
||||
* The medium resolution level.
|
||||
*
|
||||
* <p>
|
||||
* This level applies to images with a height between 175px inclusive and 720px exclusive.
|
||||
* </p>
|
||||
*/
|
||||
MEDIUM,
|
||||
|
||||
/**
|
||||
* The low resolution level.
|
||||
*
|
||||
* <p>
|
||||
* This level applies to images with a height between 1px inclusive and 175px exclusive.
|
||||
* </p>
|
||||
*/
|
||||
LOW,
|
||||
|
||||
/**
|
||||
* The unknown resolution level.
|
||||
*
|
||||
* <p>
|
||||
* This value is returned when the extractor doesn't know what resolution level an image
|
||||
* could have, for example if the extractor loops in an array of images with different
|
||||
* resolution levels without knowing the height.
|
||||
* </p>
|
||||
*/
|
||||
UNKNOWN;
|
||||
|
||||
/**
|
||||
* Get a {@link ResolutionLevel} based from the given height.
|
||||
*
|
||||
* @param heightPx the height from which returning the good {@link ResolutionLevel}
|
||||
* @return the {@link ResolutionLevel} corresponding to the height provided. See the
|
||||
* {@link ResolutionLevel} values for details about what value is returned.
|
||||
*/
|
||||
public static ResolutionLevel fromHeight(final int heightPx) {
|
||||
if (heightPx <= 0) {
|
||||
return UNKNOWN;
|
||||
}
|
||||
|
||||
if (heightPx < 175) {
|
||||
return LOW;
|
||||
}
|
||||
|
||||
if (heightPx < 720) {
|
||||
return MEDIUM;
|
||||
}
|
||||
|
||||
return HIGH;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,33 +1,36 @@
|
|||
package org.schabi.newpipe.extractor;
|
||||
|
||||
/*
|
||||
* Created by Christian Schabesberger on 11.02.17.
|
||||
*
|
||||
* Copyright (C) Christian Schabesberger 2017 <chris.schabesberger@mailbox.org>
|
||||
* InfoItem.java is part of NewPipe.
|
||||
* Copyright (C) 2017 Christian Schabesberger <chris.schabesberger@mailbox.org>
|
||||
* InfoItem.java is part of NewPipe Extractor.
|
||||
*
|
||||
* NewPipe is free software: you can redistribute it and/or modify
|
||||
* NewPipe Extractor is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* NewPipe is distributed in the hope that it will be useful,
|
||||
* NewPipe Extractor is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
|
||||
* along with NewPipe Extractor. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.schabi.newpipe.extractor;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
|
||||
public abstract class InfoItem implements Serializable {
|
||||
private final InfoType infoType;
|
||||
private final int serviceId;
|
||||
private final String url;
|
||||
private final String name;
|
||||
private String thumbnailUrl;
|
||||
@Nonnull
|
||||
private List<Image> thumbnails = List.of();
|
||||
|
||||
public InfoItem(final InfoType infoType,
|
||||
final int serviceId,
|
||||
|
@ -55,12 +58,13 @@ public abstract class InfoItem implements Serializable {
|
|||
return name;
|
||||
}
|
||||
|
||||
public void setThumbnailUrl(final String thumbnailUrl) {
|
||||
this.thumbnailUrl = thumbnailUrl;
|
||||
public void setThumbnails(@Nonnull final List<Image> thumbnails) {
|
||||
this.thumbnails = thumbnails;
|
||||
}
|
||||
|
||||
public String getThumbnailUrl() {
|
||||
return thumbnailUrl;
|
||||
@Nonnull
|
||||
public List<Image> getThumbnails() {
|
||||
return thumbnails;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -2,8 +2,12 @@ package org.schabi.newpipe.extractor;
|
|||
|
||||
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.util.List;
|
||||
|
||||
public interface InfoItemExtractor {
|
||||
String getName() throws ParsingException;
|
||||
String getUrl() throws ParsingException;
|
||||
String getThumbnailUrl() throws ParsingException;
|
||||
@Nonnull
|
||||
List<Image> getThumbnails() throws ParsingException;
|
||||
}
|
||||
|
|
|
@ -12,21 +12,21 @@ import java.util.List;
|
|||
/*
|
||||
* Created by Christian Schabesberger on 12.02.17.
|
||||
*
|
||||
* Copyright (C) Christian Schabesberger 2017 <chris.schabesberger@mailbox.org>
|
||||
* InfoItemsCollector.java is part of NewPipe.
|
||||
* Copyright (C) 2017 Christian Schabesberger <chris.schabesberger@mailbox.org>
|
||||
* InfoItemsCollector.java is part of NewPipe Extractor.
|
||||
*
|
||||
* NewPipe is free software: you can redistribute it and/or modify
|
||||
* NewPipe Extractor is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* NewPipe is distributed in the hope that it will be useful,
|
||||
* NewPipe Extractor is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
|
||||
* along with NewPipe Extractor. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
public abstract class InfoItemsCollector<I extends InfoItem, E extends InfoItemExtractor>
|
||||
|
|
|
@ -3,27 +3,31 @@ package org.schabi.newpipe.extractor;
|
|||
/*
|
||||
* Created by Adam Howard on 08/11/15.
|
||||
*
|
||||
* Copyright (c) Christian Schabesberger <chris.schabesberger@mailbox.org>
|
||||
* and Adam Howard <achdisposable1@gmail.com> 2015
|
||||
* Copyright (c) 2015 Christian Schabesberger <chris.schabesberger@mailbox.org>
|
||||
* and Adam Howard <achdisposable1@gmail.com>
|
||||
*
|
||||
* MediaFormat.java is part of NewPipe.
|
||||
* MediaFormat.java is part of NewPipe Extractor.
|
||||
*
|
||||
* NewPipe is free software: you can redistribute it and/or modify
|
||||
* NewPipe Extractor is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* NewPipe is distributed in the hope that it will be useful,
|
||||
* NewPipe Extractor is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
|
||||
* along with NewPipe Extractor. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Static data about various media formats support by NewPipe, eg mime type, extension
|
||||
|
@ -41,9 +45,18 @@ public enum MediaFormat {
|
|||
M4A (0x100, "m4a", "m4a", "audio/mp4"),
|
||||
WEBMA (0x200, "WebM", "webm", "audio/webm"),
|
||||
MP3 (0x300, "MP3", "mp3", "audio/mpeg"),
|
||||
MP2 (0x310, "MP2", "mp2", "audio/mpeg"),
|
||||
OPUS (0x400, "opus", "opus", "audio/opus"),
|
||||
OGG (0x500, "ogg", "ogg", "audio/ogg"),
|
||||
WEBMA_OPUS(0x200, "WebM Opus", "webm", "audio/webm"),
|
||||
AIFF (0x600, "AIFF", "aiff", "audio/aiff"),
|
||||
/**
|
||||
* Same as {@link MediaFormat.AIFF}, just with the shorter suffix/file extension
|
||||
*/
|
||||
AIF (0x600, "AIFF", "aif", "audio/aiff"),
|
||||
WAV (0x700, "WAV", "wav", "audio/wav"),
|
||||
FLAC (0x800, "FLAC", "flac", "audio/flac"),
|
||||
ALAC (0x900, "ALAC", "alac", "audio/alac"),
|
||||
// subtitles formats
|
||||
VTT (0x1000, "WebVTT", "vtt", "text/vtt"),
|
||||
TTML (0x2000, "Timed Text Markup Language", "ttml", "application/ttml+xml"),
|
||||
|
@ -54,11 +67,15 @@ public enum MediaFormat {
|
|||
// @formatter:on
|
||||
|
||||
public final int id;
|
||||
@Nonnull
|
||||
public final String name;
|
||||
@Nonnull
|
||||
public final String suffix;
|
||||
@Nonnull
|
||||
public final String mimeType;
|
||||
|
||||
MediaFormat(final int id, final String name, final String suffix, final String mimeType) {
|
||||
MediaFormat(final int id, @Nonnull final String name,
|
||||
@Nonnull final String suffix, @Nonnull final String mimeType) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.suffix = suffix;
|
||||
|
@ -82,6 +99,7 @@ public enum MediaFormat {
|
|||
* @return the friendly name of the MediaFormat associated with this ids,
|
||||
* or an empty String if none match it.
|
||||
*/
|
||||
@Nonnull
|
||||
public static String getNameById(final int id) {
|
||||
return getById(id, MediaFormat::getName, "");
|
||||
}
|
||||
|
@ -93,6 +111,7 @@ public enum MediaFormat {
|
|||
* @return the file extension of the MediaFormat associated with this ids,
|
||||
* or an empty String if none match it.
|
||||
*/
|
||||
@Nonnull
|
||||
public static String getSuffixById(final int id) {
|
||||
return getById(id, MediaFormat::getSuffix, "");
|
||||
}
|
||||
|
@ -104,16 +123,20 @@ public enum MediaFormat {
|
|||
* @return the MIME type of the MediaFormat associated with this ids,
|
||||
* or an empty String if none match it.
|
||||
*/
|
||||
@Nullable
|
||||
public static String getMimeById(final int id) {
|
||||
return getById(id, MediaFormat::getMimeType, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the MediaFormat with the supplied mime type
|
||||
* Return the first {@link MediaFormat} with the supplied mime type.
|
||||
* There might be more formats which have the same mime type.
|
||||
* To retrieve those, use {@link #getAllFromMimeType(String)}.
|
||||
*
|
||||
* @return MediaFormat associated with this mime type,
|
||||
* or null if none match it.
|
||||
*/
|
||||
@Nullable
|
||||
public static MediaFormat getFromMimeType(final String mimeType) {
|
||||
return Arrays.stream(MediaFormat.values())
|
||||
.filter(mediaFormat -> mediaFormat.mimeType.equals(mimeType))
|
||||
|
@ -121,16 +144,35 @@ public enum MediaFormat {
|
|||
.orElse(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all media formats which have the given mime type.
|
||||
* @param mimeType the mime type to search for
|
||||
* @return a modifiable {@link List} which contains the {@link MediaFormat}s
|
||||
* that have the given mime type.
|
||||
*/
|
||||
@Nonnull
|
||||
public static List<MediaFormat> getAllFromMimeType(final String mimeType) {
|
||||
return Arrays.stream(MediaFormat.values())
|
||||
.filter(mediaFormat -> mediaFormat.mimeType.equals(mimeType))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the media format by its id.
|
||||
*
|
||||
* @param id the id
|
||||
* @return the id of the media format or null.
|
||||
*/
|
||||
@Nullable
|
||||
public static MediaFormat getFormatById(final int id) {
|
||||
return getById(id, mediaFormat -> mediaFormat, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the first media format that has the given suffix/file extension.
|
||||
* @return the matching {@link MediaFormat} or {@code null} if no associated format is found
|
||||
*/
|
||||
@Nullable
|
||||
public static MediaFormat getFromSuffix(final String suffix) {
|
||||
return Arrays.stream(MediaFormat.values())
|
||||
.filter(mediaFormat -> mediaFormat.suffix.equals(suffix))
|
||||
|
@ -143,6 +185,7 @@ public enum MediaFormat {
|
|||
*
|
||||
* @return the name of the format
|
||||
*/
|
||||
@Nonnull
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
@ -152,6 +195,7 @@ public enum MediaFormat {
|
|||
*
|
||||
* @return the filename extension
|
||||
*/
|
||||
@Nonnull
|
||||
public String getSuffix() {
|
||||
return suffix;
|
||||
}
|
||||
|
@ -161,6 +205,7 @@ public enum MediaFormat {
|
|||
*
|
||||
* @return the mime type
|
||||
*/
|
||||
@Nonnull
|
||||
public String getMimeType() {
|
||||
return mimeType;
|
||||
}
|
||||
|
|
|
@ -15,21 +15,21 @@ import java.util.List;
|
|||
/*
|
||||
* Created by Christian Schabesberger on 12.02.17.
|
||||
*
|
||||
* Copyright (C) Christian Schabesberger 2017 <chris.schabesberger@mailbox.org>
|
||||
* InfoItemsSearchCollector.java is part of NewPipe.
|
||||
* Copyright (C) 2017 Christian Schabesberger <chris.schabesberger@mailbox.org>
|
||||
* InfoItemsSearchCollector.java is part of NewPipe Extractor.
|
||||
*
|
||||
* NewPipe is free software: you can redistribute it and/or modify
|
||||
* NewPipe Extractor is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* NewPipe is distributed in the hope that it will be useful,
|
||||
* NewPipe Extractor is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
|
||||
* along with NewPipe Extractor. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/**
|
||||
|
|
|
@ -3,21 +3,21 @@ package org.schabi.newpipe.extractor;
|
|||
/*
|
||||
* Created by Christian Schabesberger on 23.08.15.
|
||||
*
|
||||
* Copyright (C) Christian Schabesberger 2015 <chris.schabesberger@mailbox.org>
|
||||
* NewPipe.java is part of NewPipe.
|
||||
* Copyright (C) 2015 Christian Schabesberger <chris.schabesberger@mailbox.org>
|
||||
* NewPipe Extractor.java is part of NewPipe Extractor.
|
||||
*
|
||||
* NewPipe is free software: you can redistribute it and/or modify
|
||||
* NewPipe Extractor is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* NewPipe is distributed in the hope that it will be useful,
|
||||
* NewPipe Extractor is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
|
||||
* along with NewPipe Extractor. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import org.schabi.newpipe.extractor.downloader.Downloader;
|
||||
|
@ -25,9 +25,10 @@ import org.schabi.newpipe.extractor.exceptions.ExtractionException;
|
|||
import org.schabi.newpipe.extractor.localization.ContentCountry;
|
||||
import org.schabi.newpipe.extractor.localization.Localization;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Provides access to streaming services supported by NewPipe.
|
||||
|
@ -92,24 +93,6 @@ public final class NewPipe {
|
|||
throw new ExtractionException("No service can handle the url = \"" + url + "\"");
|
||||
}
|
||||
|
||||
public static int getIdOfService(final String serviceName) {
|
||||
try {
|
||||
return getService(serviceName).getServiceId();
|
||||
} catch (final ExtractionException ignored) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
public static String getNameOfService(final int id) {
|
||||
try {
|
||||
return getService(id).getServiceInfo().getName();
|
||||
} catch (final Exception e) {
|
||||
System.err.println("Service id not known");
|
||||
e.printStackTrace();
|
||||
return "<unknown>";
|
||||
}
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Localization
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
|
|
@ -37,6 +37,10 @@ public class Page implements Serializable {
|
|||
this(url, id, null, null, null);
|
||||
}
|
||||
|
||||
public Page(final String url, final String id, final byte[] body) {
|
||||
this(url, id, null, null, body);
|
||||
}
|
||||
|
||||
public Page(final String url, final byte[] body) {
|
||||
this(url, null, null, null, body);
|
||||
}
|
||||
|
|
|
@ -6,26 +6,24 @@ import org.schabi.newpipe.extractor.services.peertube.PeertubeService;
|
|||
import org.schabi.newpipe.extractor.services.soundcloud.SoundcloudService;
|
||||
import org.schabi.newpipe.extractor.services.youtube.YoutubeService;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/*
|
||||
* Copyright (C) Christian Schabesberger 2018 <chris.schabesberger@mailbox.org>
|
||||
* ServiceList.java is part of NewPipe.
|
||||
* Copyright (C) 2018 Christian Schabesberger <chris.schabesberger@mailbox.org>
|
||||
* ServiceList.java is part of NewPipe Extractor.
|
||||
*
|
||||
* NewPipe is free software: you can redistribute it and/or modify
|
||||
* NewPipe Extractor is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* NewPipe is distributed in the hope that it will be useful,
|
||||
* NewPipe Extractor is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
|
||||
* along with NewPipe Extractor. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/**
|
||||
|
@ -34,27 +32,21 @@ import java.util.List;
|
|||
@SuppressWarnings({"ConstantName", "InnerAssignment"}) // keep unusual names and inner assignments
|
||||
public final class ServiceList {
|
||||
private ServiceList() {
|
||||
//no instance
|
||||
// no instance
|
||||
}
|
||||
|
||||
public static final YoutubeService YouTube;
|
||||
public static final SoundcloudService SoundCloud;
|
||||
public static final MediaCCCService MediaCCC;
|
||||
public static final PeertubeService PeerTube;
|
||||
public static final BandcampService Bandcamp;
|
||||
public static final YoutubeService YouTube = new YoutubeService(0);
|
||||
public static final SoundcloudService SoundCloud = new SoundcloudService(1);
|
||||
public static final MediaCCCService MediaCCC = new MediaCCCService(2);
|
||||
public static final PeertubeService PeerTube = new PeertubeService(3);
|
||||
public static final BandcampService Bandcamp = new BandcampService(4);
|
||||
|
||||
/**
|
||||
* When creating a new service, put this service in the end of this list,
|
||||
* and give it the next free id.
|
||||
*/
|
||||
private static final List<StreamingService> SERVICES = Collections.unmodifiableList(
|
||||
Arrays.asList(
|
||||
YouTube = new YoutubeService(0),
|
||||
SoundCloud = new SoundcloudService(1),
|
||||
MediaCCC = new MediaCCCService(2),
|
||||
PeerTube = new PeertubeService(3),
|
||||
Bandcamp = new BandcampService(4)
|
||||
));
|
||||
private static final List<StreamingService> SERVICES = List.of(
|
||||
YouTube, SoundCloud, MediaCCC, PeerTube, Bandcamp);
|
||||
|
||||
/**
|
||||
* Get all the supported services.
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package org.schabi.newpipe.extractor;
|
||||
|
||||
import org.schabi.newpipe.extractor.channel.ChannelExtractor;
|
||||
import org.schabi.newpipe.extractor.channel.tabs.ChannelTabExtractor;
|
||||
import org.schabi.newpipe.extractor.comments.CommentsExtractor;
|
||||
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
|
||||
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
||||
|
@ -28,21 +29,21 @@ import java.util.Collections;
|
|||
import java.util.List;
|
||||
|
||||
/*
|
||||
* Copyright (C) Christian Schabesberger 2018 <chris.schabesberger@mailbox.org>
|
||||
* StreamingService.java is part of NewPipe.
|
||||
* Copyright (C) 2018 Christian Schabesberger <chris.schabesberger@mailbox.org>
|
||||
* StreamingService.java is part of NewPipe Extractor.
|
||||
*
|
||||
* NewPipe is free software: you can redistribute it and/or modify
|
||||
* NewPipe Extractor is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* NewPipe is distributed in the hope that it will be useful,
|
||||
* NewPipe Extractor is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
|
||||
* along with NewPipe Extractor. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
public abstract class StreamingService {
|
||||
|
@ -140,6 +141,14 @@ public abstract class StreamingService {
|
|||
*/
|
||||
public abstract ListLinkHandlerFactory getChannelLHFactory();
|
||||
|
||||
/**
|
||||
* Must return a new instance of an implementation of ListLinkHandlerFactory for channel tabs.
|
||||
* If support for channel tabs is not given null must be returned.
|
||||
*
|
||||
* @return an instance of a ListLinkHandlerFactory for channels or null
|
||||
*/
|
||||
public abstract ListLinkHandlerFactory getChannelTabLHFactory();
|
||||
|
||||
/**
|
||||
* Must return a new instance of an implementation of ListLinkHandlerFactory for playlists.
|
||||
* If support for playlists is not given null must be returned.
|
||||
|
@ -204,6 +213,15 @@ public abstract class StreamingService {
|
|||
public abstract ChannelExtractor getChannelExtractor(ListLinkHandler linkHandler)
|
||||
throws ExtractionException;
|
||||
|
||||
/**
|
||||
* Must create a new instance of a ChannelTabExtractor implementation.
|
||||
*
|
||||
* @param linkHandler is pointing to the channel which should be handled by this new instance.
|
||||
* @return a new ChannelTabExtractor
|
||||
*/
|
||||
public abstract ChannelTabExtractor getChannelTabExtractor(ListLinkHandler linkHandler)
|
||||
throws ExtractionException;
|
||||
|
||||
/**
|
||||
* Must crete a new instance of a PlaylistExtractor implementation.
|
||||
* @param linkHandler is pointing to the playlist which should be handled by this new instance.
|
||||
|
@ -262,6 +280,20 @@ public abstract class StreamingService {
|
|||
return getChannelExtractor(getChannelLHFactory().fromUrl(url));
|
||||
}
|
||||
|
||||
public ChannelTabExtractor getChannelTabExtractorFromId(final String id, final String tab)
|
||||
throws ExtractionException {
|
||||
return getChannelTabExtractor(getChannelTabLHFactory().fromQuery(
|
||||
id, Collections.singletonList(tab), ""));
|
||||
}
|
||||
|
||||
public ChannelTabExtractor getChannelTabExtractorFromIdAndBaseUrl(final String id,
|
||||
final String tab,
|
||||
final String baseUrl)
|
||||
throws ExtractionException {
|
||||
return getChannelTabExtractor(getChannelTabLHFactory().fromQuery(
|
||||
id, Collections.singletonList(tab), "", baseUrl));
|
||||
}
|
||||
|
||||
public PlaylistExtractor getPlaylistExtractor(final String url) throws ExtractionException {
|
||||
return getPlaylistExtractor(getPlaylistLHFactory().fromUrl(url));
|
||||
}
|
||||
|
|
|
@ -1,47 +1,58 @@
|
|||
package org.schabi.newpipe.extractor.channel;
|
||||
|
||||
import org.schabi.newpipe.extractor.ListExtractor;
|
||||
import org.schabi.newpipe.extractor.StreamingService;
|
||||
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
||||
import org.schabi.newpipe.extractor.linkhandler.ListLinkHandler;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
|
||||
|
||||
/*
|
||||
* Created by Christian Schabesberger on 25.07.16.
|
||||
*
|
||||
* Copyright (C) Christian Schabesberger 2016 <chris.schabesberger@mailbox.org>
|
||||
* ChannelExtractor.java is part of NewPipe.
|
||||
* Copyright (C) 2016 Christian Schabesberger <chris.schabesberger@mailbox.org>
|
||||
* ChannelExtractor.java is part of NewPipe Extractor.
|
||||
*
|
||||
* NewPipe is free software: you can redistribute it and/or modify
|
||||
* NewPipe Extractor is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* NewPipe is distributed in the hope that it will be useful,
|
||||
* NewPipe Extractor is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
|
||||
* along with NewPipe Extractor. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
public abstract class ChannelExtractor extends ListExtractor<StreamInfoItem> {
|
||||
package org.schabi.newpipe.extractor.channel;
|
||||
|
||||
import org.schabi.newpipe.extractor.Extractor;
|
||||
import org.schabi.newpipe.extractor.Image;
|
||||
import org.schabi.newpipe.extractor.StreamingService;
|
||||
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
||||
import org.schabi.newpipe.extractor.linkhandler.ListLinkHandler;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.util.List;
|
||||
|
||||
public abstract class ChannelExtractor extends Extractor {
|
||||
|
||||
public static final long UNKNOWN_SUBSCRIBER_COUNT = -1;
|
||||
|
||||
public ChannelExtractor(final StreamingService service, final ListLinkHandler linkHandler) {
|
||||
protected ChannelExtractor(final StreamingService service, final ListLinkHandler linkHandler) {
|
||||
super(service, linkHandler);
|
||||
}
|
||||
|
||||
public abstract String getAvatarUrl() throws ParsingException;
|
||||
public abstract String getBannerUrl() throws ParsingException;
|
||||
@Nonnull
|
||||
public abstract List<Image> getAvatars() throws ParsingException;
|
||||
@Nonnull
|
||||
public abstract List<Image> getBanners() throws ParsingException;
|
||||
public abstract String getFeedUrl() throws ParsingException;
|
||||
public abstract long getSubscriberCount() throws ParsingException;
|
||||
public abstract String getDescription() throws ParsingException;
|
||||
public abstract String getParentChannelName() throws ParsingException;
|
||||
public abstract String getParentChannelUrl() throws ParsingException;
|
||||
public abstract String getParentChannelAvatarUrl() throws ParsingException;
|
||||
@Nonnull
|
||||
public abstract List<Image> getParentChannelAvatars() throws ParsingException;
|
||||
public abstract boolean isVerified() throws ParsingException;
|
||||
|
||||
@Nonnull
|
||||
public abstract List<ListLinkHandler> getTabs() throws ParsingException;
|
||||
@Nonnull
|
||||
public List<String> getTags() throws ParsingException {
|
||||
return List.of();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,47 +1,45 @@
|
|||
package org.schabi.newpipe.extractor.channel;
|
||||
|
||||
import org.schabi.newpipe.extractor.ListExtractor.InfoItemsPage;
|
||||
import org.schabi.newpipe.extractor.ListInfo;
|
||||
import org.schabi.newpipe.extractor.NewPipe;
|
||||
import org.schabi.newpipe.extractor.Page;
|
||||
import org.schabi.newpipe.extractor.StreamingService;
|
||||
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
|
||||
import org.schabi.newpipe.extractor.linkhandler.ListLinkHandler;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
|
||||
import org.schabi.newpipe.extractor.utils.ExtractorHelper;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/*
|
||||
* Created by Christian Schabesberger on 31.07.16.
|
||||
*
|
||||
* Copyright (C) Christian Schabesberger 2016 <chris.schabesberger@mailbox.org>
|
||||
* ChannelInfo.java is part of NewPipe.
|
||||
* Copyright (C) 2016 Christian Schabesberger <chris.schabesberger@mailbox.org>
|
||||
* ChannelInfo.java is part of NewPipe Extractor.
|
||||
*
|
||||
* NewPipe is free software: you can redistribute it and/or modify
|
||||
* NewPipe Extractor is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* NewPipe is distributed in the hope that it will be useful,
|
||||
* NewPipe Extractor is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
|
||||
* along with NewPipe Extractor. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
public class ChannelInfo extends ListInfo<StreamInfoItem> {
|
||||
package org.schabi.newpipe.extractor.channel;
|
||||
|
||||
import org.schabi.newpipe.extractor.Info;
|
||||
import org.schabi.newpipe.extractor.Image;
|
||||
import org.schabi.newpipe.extractor.NewPipe;
|
||||
import org.schabi.newpipe.extractor.StreamingService;
|
||||
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
|
||||
import org.schabi.newpipe.extractor.linkhandler.ListLinkHandler;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
public class ChannelInfo extends Info {
|
||||
|
||||
public ChannelInfo(final int serviceId,
|
||||
final String id,
|
||||
final String url,
|
||||
final String originalUrl,
|
||||
final String name,
|
||||
final ListLinkHandler listLinkHandler) {
|
||||
super(serviceId, id, url, originalUrl, name, listLinkHandler.getContentFilters(),
|
||||
listLinkHandler.getSortFilter());
|
||||
final String name) {
|
||||
super(serviceId, id, url, originalUrl, name);
|
||||
}
|
||||
|
||||
public static ChannelInfo getInfo(final String url) throws IOException, ExtractionException {
|
||||
|
@ -55,13 +53,6 @@ public class ChannelInfo extends ListInfo<StreamInfoItem> {
|
|||
return getInfo(extractor);
|
||||
}
|
||||
|
||||
public static InfoItemsPage<StreamInfoItem> getMoreItems(final StreamingService service,
|
||||
final String url,
|
||||
final Page page)
|
||||
throws IOException, ExtractionException {
|
||||
return service.getChannelExtractor(url).getPage(page);
|
||||
}
|
||||
|
||||
public static ChannelInfo getInfo(final ChannelExtractor extractor)
|
||||
throws IOException, ExtractionException {
|
||||
|
||||
|
@ -71,35 +62,32 @@ public class ChannelInfo extends ListInfo<StreamInfoItem> {
|
|||
final String originalUrl = extractor.getOriginalUrl();
|
||||
final String name = extractor.getName();
|
||||
|
||||
final ChannelInfo info =
|
||||
new ChannelInfo(serviceId, id, url, originalUrl, name, extractor.getLinkHandler());
|
||||
final ChannelInfo info = new ChannelInfo(serviceId, id, url, originalUrl, name);
|
||||
|
||||
try {
|
||||
info.setAvatarUrl(extractor.getAvatarUrl());
|
||||
info.setAvatars(extractor.getAvatars());
|
||||
} catch (final Exception e) {
|
||||
info.addError(e);
|
||||
}
|
||||
|
||||
try {
|
||||
info.setBannerUrl(extractor.getBannerUrl());
|
||||
info.setBanners(extractor.getBanners());
|
||||
} catch (final Exception e) {
|
||||
info.addError(e);
|
||||
}
|
||||
|
||||
try {
|
||||
info.setFeedUrl(extractor.getFeedUrl());
|
||||
} catch (final Exception e) {
|
||||
info.addError(e);
|
||||
}
|
||||
|
||||
final InfoItemsPage<StreamInfoItem> itemsPage =
|
||||
ExtractorHelper.getItemsPageOrLogError(info, extractor);
|
||||
info.setRelatedItems(itemsPage.getItems());
|
||||
info.setNextPage(itemsPage.getNextPage());
|
||||
|
||||
try {
|
||||
info.setSubscriberCount(extractor.getSubscriberCount());
|
||||
} catch (final Exception e) {
|
||||
info.addError(e);
|
||||
}
|
||||
|
||||
try {
|
||||
info.setDescription(extractor.getDescription());
|
||||
} catch (final Exception e) {
|
||||
|
@ -119,7 +107,7 @@ public class ChannelInfo extends ListInfo<StreamInfoItem> {
|
|||
}
|
||||
|
||||
try {
|
||||
info.setParentChannelAvatarUrl(extractor.getParentChannelAvatarUrl());
|
||||
info.setParentChannelAvatars(extractor.getParentChannelAvatars());
|
||||
} catch (final Exception e) {
|
||||
info.addError(e);
|
||||
}
|
||||
|
@ -130,19 +118,36 @@ public class ChannelInfo extends ListInfo<StreamInfoItem> {
|
|||
info.addError(e);
|
||||
}
|
||||
|
||||
try {
|
||||
info.setTabs(extractor.getTabs());
|
||||
} catch (final Exception e) {
|
||||
info.addError(e);
|
||||
}
|
||||
|
||||
try {
|
||||
info.setTags(extractor.getTags());
|
||||
} catch (final Exception e) {
|
||||
info.addError(e);
|
||||
}
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
private String avatarUrl;
|
||||
private String parentChannelName;
|
||||
private String parentChannelUrl;
|
||||
private String parentChannelAvatarUrl;
|
||||
private String bannerUrl;
|
||||
private String feedUrl;
|
||||
private long subscriberCount = -1;
|
||||
private String description;
|
||||
private String[] donationLinks;
|
||||
@Nonnull
|
||||
private List<Image> avatars = List.of();
|
||||
@Nonnull
|
||||
private List<Image> banners = List.of();
|
||||
@Nonnull
|
||||
private List<Image> parentChannelAvatars = List.of();
|
||||
private boolean verified;
|
||||
private List<ListLinkHandler> tabs = List.of();
|
||||
private List<String> tags = List.of();
|
||||
|
||||
public String getParentChannelName() {
|
||||
return parentChannelName;
|
||||
|
@ -160,28 +165,31 @@ public class ChannelInfo extends ListInfo<StreamInfoItem> {
|
|||
this.parentChannelUrl = parentChannelUrl;
|
||||
}
|
||||
|
||||
public String getParentChannelAvatarUrl() {
|
||||
return parentChannelAvatarUrl;
|
||||
@Nonnull
|
||||
public List<Image> getParentChannelAvatars() {
|
||||
return parentChannelAvatars;
|
||||
}
|
||||
|
||||
public void setParentChannelAvatarUrl(final String parentChannelAvatarUrl) {
|
||||
this.parentChannelAvatarUrl = parentChannelAvatarUrl;
|
||||
public void setParentChannelAvatars(@Nonnull final List<Image> parentChannelAvatars) {
|
||||
this.parentChannelAvatars = parentChannelAvatars;
|
||||
}
|
||||
|
||||
public String getAvatarUrl() {
|
||||
return avatarUrl;
|
||||
@Nonnull
|
||||
public List<Image> getAvatars() {
|
||||
return avatars;
|
||||
}
|
||||
|
||||
public void setAvatarUrl(final String avatarUrl) {
|
||||
this.avatarUrl = avatarUrl;
|
||||
public void setAvatars(@Nonnull final List<Image> avatars) {
|
||||
this.avatars = avatars;
|
||||
}
|
||||
|
||||
public String getBannerUrl() {
|
||||
return bannerUrl;
|
||||
@Nonnull
|
||||
public List<Image> getBanners() {
|
||||
return banners;
|
||||
}
|
||||
|
||||
public void setBannerUrl(final String bannerUrl) {
|
||||
this.bannerUrl = bannerUrl;
|
||||
public void setBanners(@Nonnull final List<Image> banners) {
|
||||
this.banners = banners;
|
||||
}
|
||||
|
||||
public String getFeedUrl() {
|
||||
|
@ -223,4 +231,22 @@ public class ChannelInfo extends ListInfo<StreamInfoItem> {
|
|||
public void setVerified(final boolean verified) {
|
||||
this.verified = verified;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public List<ListLinkHandler> getTabs() {
|
||||
return tabs;
|
||||
}
|
||||
|
||||
public void setTabs(@Nonnull final List<ListLinkHandler> tabs) {
|
||||
this.tabs = tabs;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public List<String> getTags() {
|
||||
return tags;
|
||||
}
|
||||
|
||||
public void setTags(@Nonnull final List<String> tags) {
|
||||
this.tags = tags;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,21 +5,21 @@ import org.schabi.newpipe.extractor.InfoItem;
|
|||
/*
|
||||
* Created by Christian Schabesberger on 11.02.17.
|
||||
*
|
||||
* Copyright (C) Christian Schabesberger 2017 <chris.schabesberger@mailbox.org>
|
||||
* ChannelInfoItem.java is part of NewPipe.
|
||||
* Copyright (C) 2017 Christian Schabesberger <chris.schabesberger@mailbox.org>
|
||||
* ChannelInfoItem.java is part of NewPipe Extractor.
|
||||
*
|
||||
* NewPipe is free software: you can redistribute it and/or modify
|
||||
* NewPipe Extractor is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* NewPipe is distributed in the hope that it will be useful,
|
||||
* NewPipe Extractor is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
|
||||
* along with NewPipe Extractor. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
public class ChannelInfoItem extends InfoItem {
|
||||
|
|
|
@ -6,21 +6,21 @@ import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
|||
/*
|
||||
* Created by Christian Schabesberger on 12.02.17.
|
||||
*
|
||||
* Copyright (C) Christian Schabesberger 2017 <chris.schabesberger@mailbox.org>
|
||||
* ChannelInfoItemExtractor.java is part of NewPipe.
|
||||
* Copyright (C) 2017 Christian Schabesberger <chris.schabesberger@mailbox.org>
|
||||
* ChannelInfoItemExtractor.java is part of NewPipe Extractor.
|
||||
*
|
||||
* NewPipe is free software: you can redistribute it and/or modify
|
||||
* NewPipe Extractor is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* NewPipe is distributed in the hope that it will be useful,
|
||||
* NewPipe Extractor is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
|
||||
* along with NewPipe Extractor. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
public interface ChannelInfoItemExtractor extends InfoItemExtractor {
|
||||
|
|
|
@ -1,28 +1,28 @@
|
|||
package org.schabi.newpipe.extractor.channel;
|
||||
|
||||
import org.schabi.newpipe.extractor.InfoItemsCollector;
|
||||
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
||||
|
||||
/*
|
||||
* Created by Christian Schabesberger on 12.02.17.
|
||||
*
|
||||
* Copyright (C) Christian Schabesberger 2017 <chris.schabesberger@mailbox.org>
|
||||
* ChannelInfoItemsCollector.java is part of NewPipe.
|
||||
* Copyright (C) 2017 Christian Schabesberger <chris.schabesberger@mailbox.org>
|
||||
* ChannelInfoItemsCollector.java is part of NewPipe Extractor.
|
||||
*
|
||||
* NewPipe is free software: you can redistribute it and/or modify
|
||||
* NewPipe Extractor is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* NewPipe is distributed in the hope that it will be useful,
|
||||
* NewPipe Extractor is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
|
||||
* along with NewPipe Extractor. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.schabi.newpipe.extractor.channel;
|
||||
|
||||
import org.schabi.newpipe.extractor.InfoItemsCollector;
|
||||
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
||||
|
||||
public final class ChannelInfoItemsCollector
|
||||
extends InfoItemsCollector<ChannelInfoItem, ChannelInfoItemExtractor> {
|
||||
public ChannelInfoItemsCollector(final int serviceId) {
|
||||
|
@ -47,7 +47,7 @@ public final class ChannelInfoItemsCollector
|
|||
addError(e);
|
||||
}
|
||||
try {
|
||||
resultItem.setThumbnailUrl(extractor.getThumbnailUrl());
|
||||
resultItem.setThumbnails(extractor.getThumbnails());
|
||||
} catch (final Exception e) {
|
||||
addError(e);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
package org.schabi.newpipe.extractor.channel.tabs;
|
||||
|
||||
import org.schabi.newpipe.extractor.InfoItem;
|
||||
import org.schabi.newpipe.extractor.ListExtractor;
|
||||
import org.schabi.newpipe.extractor.StreamingService;
|
||||
import org.schabi.newpipe.extractor.linkhandler.ListLinkHandler;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
/**
|
||||
* A {@link ListExtractor} of {@link InfoItem}s for tabs of channels.
|
||||
*/
|
||||
public abstract class ChannelTabExtractor extends ListExtractor<InfoItem> {
|
||||
|
||||
protected ChannelTabExtractor(@Nonnull final StreamingService service,
|
||||
@Nonnull final ListLinkHandler linkHandler) {
|
||||
super(service, linkHandler);
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public String getName() {
|
||||
return getLinkHandler().getContentFilters().get(0);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,70 @@
|
|||
package org.schabi.newpipe.extractor.channel.tabs;
|
||||
|
||||
import org.schabi.newpipe.extractor.InfoItem;
|
||||
import org.schabi.newpipe.extractor.ListExtractor;
|
||||
import org.schabi.newpipe.extractor.ListInfo;
|
||||
import org.schabi.newpipe.extractor.Page;
|
||||
import org.schabi.newpipe.extractor.StreamingService;
|
||||
import org.schabi.newpipe.extractor.channel.ChannelInfo;
|
||||
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
|
||||
import org.schabi.newpipe.extractor.linkhandler.ListLinkHandler;
|
||||
import org.schabi.newpipe.extractor.utils.ExtractorHelper;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.io.IOException;
|
||||
|
||||
public class ChannelTabInfo extends ListInfo<InfoItem> {
|
||||
|
||||
public ChannelTabInfo(final int serviceId,
|
||||
@Nonnull final ListLinkHandler linkHandler) {
|
||||
super(serviceId, linkHandler, linkHandler.getContentFilters().get(0));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a {@link ChannelTabInfo} instance from the given service and tab handler.
|
||||
*
|
||||
* @param service streaming service
|
||||
* @param linkHandler Channel tab handler (from {@link ChannelInfo})
|
||||
* @return the extracted {@link ChannelTabInfo}
|
||||
*/
|
||||
@Nonnull
|
||||
public static ChannelTabInfo getInfo(@Nonnull final StreamingService service,
|
||||
@Nonnull final ListLinkHandler linkHandler)
|
||||
throws ExtractionException, IOException {
|
||||
final ChannelTabExtractor extractor = service.getChannelTabExtractor(linkHandler);
|
||||
extractor.fetchPage();
|
||||
return getInfo(extractor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a {@link ChannelTabInfo} instance from a {@link ChannelTabExtractor}.
|
||||
*
|
||||
* @param extractor an extractor where {@code fetchPage()} was already got called on
|
||||
* @return the extracted {@link ChannelTabInfo}
|
||||
*/
|
||||
@Nonnull
|
||||
public static ChannelTabInfo getInfo(@Nonnull final ChannelTabExtractor extractor) {
|
||||
final ChannelTabInfo info =
|
||||
new ChannelTabInfo(extractor.getServiceId(), extractor.getLinkHandler());
|
||||
|
||||
try {
|
||||
info.setOriginalUrl(extractor.getOriginalUrl());
|
||||
} catch (final Exception e) {
|
||||
info.addError(e);
|
||||
}
|
||||
|
||||
final ListExtractor.InfoItemsPage<InfoItem> page
|
||||
= ExtractorHelper.getItemsPageOrLogError(info, extractor);
|
||||
info.setRelatedItems(page.getItems());
|
||||
info.setNextPage(page.getNextPage());
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
public static ListExtractor.InfoItemsPage<InfoItem> getMoreItems(
|
||||
@Nonnull final StreamingService service,
|
||||
@Nonnull final ListLinkHandler linkHandler,
|
||||
@Nonnull final Page page) throws ExtractionException, IOException {
|
||||
return service.getChannelTabExtractor(linkHandler).getPage(page);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
package org.schabi.newpipe.extractor.channel.tabs;
|
||||
|
||||
/**
|
||||
* Constants of channel tabs supported by the extractor.
|
||||
*/
|
||||
public final class ChannelTabs {
|
||||
public static final String VIDEOS = "videos";
|
||||
public static final String TRACKS = "tracks";
|
||||
public static final String SHORTS = "shorts";
|
||||
public static final String LIVESTREAMS = "livestreams";
|
||||
public static final String CHANNELS = "channels";
|
||||
public static final String PLAYLISTS = "playlists";
|
||||
public static final String ALBUMS = "albums";
|
||||
|
||||
private ChannelTabs() {
|
||||
}
|
||||
}
|
|
@ -22,6 +22,13 @@ public abstract class CommentsExtractor extends ListExtractor<CommentsInfoItem>
|
|||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the total number of comments
|
||||
*/
|
||||
public int getCommentsCount() throws ExtractionException {
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public String getName() throws ParsingException {
|
||||
|
|
|
@ -48,6 +48,11 @@ public final class CommentsInfo extends ListInfo<CommentsInfoItem> {
|
|||
ExtractorHelper.getItemsPageOrLogError(commentsInfo, commentsExtractor);
|
||||
commentsInfo.setCommentsDisabled(commentsExtractor.isCommentsDisabled());
|
||||
commentsInfo.setRelatedItems(initialCommentsPage.getItems());
|
||||
try {
|
||||
commentsInfo.setCommentsCount(commentsExtractor.getCommentsCount());
|
||||
} catch (final Exception e) {
|
||||
commentsInfo.addError(e);
|
||||
}
|
||||
commentsInfo.setNextPage(initialCommentsPage.getNextPage());
|
||||
|
||||
return commentsInfo;
|
||||
|
@ -76,6 +81,7 @@ public final class CommentsInfo extends ListInfo<CommentsInfoItem> {
|
|||
|
||||
private transient CommentsExtractor commentsExtractor;
|
||||
private boolean commentsDisabled = false;
|
||||
private int commentsCount;
|
||||
|
||||
public CommentsExtractor getCommentsExtractor() {
|
||||
return commentsExtractor;
|
||||
|
@ -86,7 +92,6 @@ public final class CommentsInfo extends ListInfo<CommentsInfoItem> {
|
|||
}
|
||||
|
||||
/**
|
||||
* @apiNote Warning: This method is experimental and may get removed in a future release.
|
||||
* @return {@code true} if the comments are disabled otherwise {@code false} (default)
|
||||
* @see CommentsExtractor#isCommentsDisabled()
|
||||
*/
|
||||
|
@ -95,10 +100,27 @@ public final class CommentsInfo extends ListInfo<CommentsInfoItem> {
|
|||
}
|
||||
|
||||
/**
|
||||
* @apiNote Warning: This method is experimental and may get removed in a future release.
|
||||
* @param commentsDisabled {@code true} if the comments are disabled otherwise {@code false}
|
||||
*/
|
||||
public void setCommentsDisabled(final boolean commentsDisabled) {
|
||||
this.commentsDisabled = commentsDisabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the total number of comments.
|
||||
*
|
||||
* @return the total number of comments
|
||||
*/
|
||||
public int getCommentsCount() {
|
||||
return commentsCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the total number of comments.
|
||||
*
|
||||
* @param commentsCount the commentsCount to set.
|
||||
*/
|
||||
public void setCommentsCount(final int commentsCount) {
|
||||
this.commentsCount = commentsCount;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,17 +1,23 @@
|
|||
package org.schabi.newpipe.extractor.comments;
|
||||
|
||||
import org.schabi.newpipe.extractor.Image;
|
||||
import org.schabi.newpipe.extractor.InfoItem;
|
||||
import org.schabi.newpipe.extractor.Page;
|
||||
import org.schabi.newpipe.extractor.localization.DateWrapper;
|
||||
import org.schabi.newpipe.extractor.stream.Description;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.List;
|
||||
|
||||
public class CommentsInfoItem extends InfoItem {
|
||||
|
||||
private String commentId;
|
||||
private String commentText;
|
||||
@Nonnull
|
||||
private Description commentText = Description.EMPTY_DESCRIPTION;
|
||||
private String uploaderName;
|
||||
private String uploaderAvatarUrl;
|
||||
@Nonnull
|
||||
private List<Image> uploaderAvatars = List.of();
|
||||
private String uploaderUrl;
|
||||
private boolean uploaderVerified;
|
||||
private String textualUploadDate;
|
||||
|
@ -22,12 +28,17 @@ public class CommentsInfoItem extends InfoItem {
|
|||
private boolean heartedByUploader;
|
||||
private boolean pinned;
|
||||
private int streamPosition;
|
||||
private int replyCount;
|
||||
@Nullable
|
||||
private Page replies;
|
||||
private boolean isChannelOwner;
|
||||
private boolean creatorReply;
|
||||
|
||||
public static final int NO_LIKE_COUNT = -1;
|
||||
public static final int NO_STREAM_POSITION = -1;
|
||||
|
||||
public static final int UNKNOWN_REPLY_COUNT = -1;
|
||||
|
||||
public CommentsInfoItem(final int serviceId, final String url, final String name) {
|
||||
super(InfoType.COMMENT, serviceId, url, name);
|
||||
}
|
||||
|
@ -40,11 +51,12 @@ public class CommentsInfoItem extends InfoItem {
|
|||
this.commentId = commentId;
|
||||
}
|
||||
|
||||
public String getCommentText() {
|
||||
@Nonnull
|
||||
public Description getCommentText() {
|
||||
return commentText;
|
||||
}
|
||||
|
||||
public void setCommentText(final String commentText) {
|
||||
public void setCommentText(@Nonnull final Description commentText) {
|
||||
this.commentText = commentText;
|
||||
}
|
||||
|
||||
|
@ -56,12 +68,13 @@ public class CommentsInfoItem extends InfoItem {
|
|||
this.uploaderName = uploaderName;
|
||||
}
|
||||
|
||||
public String getUploaderAvatarUrl() {
|
||||
return uploaderAvatarUrl;
|
||||
@Nonnull
|
||||
public List<Image> getUploaderAvatars() {
|
||||
return uploaderAvatars;
|
||||
}
|
||||
|
||||
public void setUploaderAvatarUrl(final String uploaderAvatarUrl) {
|
||||
this.uploaderAvatarUrl = uploaderAvatarUrl;
|
||||
public void setUploaderAvatars(@Nonnull final List<Image> uploaderAvatars) {
|
||||
this.uploaderAvatars = uploaderAvatars;
|
||||
}
|
||||
|
||||
public String getUploaderUrl() {
|
||||
|
@ -90,8 +103,8 @@ public class CommentsInfoItem extends InfoItem {
|
|||
}
|
||||
|
||||
/**
|
||||
* @return the comment's like count
|
||||
* or {@link CommentsInfoItem#NO_LIKE_COUNT} if it is unavailable
|
||||
* @return the comment's like count or {@link CommentsInfoItem#NO_LIKE_COUNT} if it is
|
||||
* unavailable
|
||||
*/
|
||||
public int getLikeCount() {
|
||||
return likeCount;
|
||||
|
@ -140,12 +153,21 @@ public class CommentsInfoItem extends InfoItem {
|
|||
/**
|
||||
* Get the playback position of the stream to which this comment belongs.
|
||||
* This is not supported by all services.
|
||||
*
|
||||
* @return the playback position in seconds or {@link #NO_STREAM_POSITION} if not available
|
||||
*/
|
||||
public int getStreamPosition() {
|
||||
return streamPosition;
|
||||
}
|
||||
|
||||
public void setReplyCount(final int replyCount) {
|
||||
this.replyCount = replyCount;
|
||||
}
|
||||
|
||||
public int getReplyCount() {
|
||||
return replyCount;
|
||||
}
|
||||
|
||||
public void setReplies(@Nullable final Page replies) {
|
||||
this.replies = replies;
|
||||
}
|
||||
|
@ -154,4 +176,22 @@ public class CommentsInfoItem extends InfoItem {
|
|||
public Page getReplies() {
|
||||
return this.replies;
|
||||
}
|
||||
|
||||
public void setChannelOwner(final boolean channelOwner) {
|
||||
this.isChannelOwner = channelOwner;
|
||||
}
|
||||
|
||||
public boolean isChannelOwner() {
|
||||
return isChannelOwner;
|
||||
}
|
||||
|
||||
|
||||
public void setCreatorReply(final boolean creatorReply) {
|
||||
this.creatorReply = creatorReply;
|
||||
}
|
||||
|
||||
public boolean hasCreatorReply() {
|
||||
return creatorReply;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,14 +1,17 @@
|
|||
package org.schabi.newpipe.extractor.comments;
|
||||
|
||||
import org.schabi.newpipe.extractor.Image;
|
||||
import org.schabi.newpipe.extractor.InfoItemExtractor;
|
||||
import org.schabi.newpipe.extractor.Page;
|
||||
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
||||
import org.schabi.newpipe.extractor.localization.DateWrapper;
|
||||
import org.schabi.newpipe.extractor.services.youtube.extractors.YoutubeCommentsInfoItemExtractor;
|
||||
import org.schabi.newpipe.extractor.stream.Description;
|
||||
import org.schabi.newpipe.extractor.stream.StreamExtractor;
|
||||
import org.schabi.newpipe.extractor.utils.Utils;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.List;
|
||||
|
||||
public interface CommentsInfoItemExtractor extends InfoItemExtractor {
|
||||
|
||||
|
@ -17,14 +20,14 @@ public interface CommentsInfoItemExtractor extends InfoItemExtractor {
|
|||
* or {@link CommentsInfoItem#NO_LIKE_COUNT} if it is unavailable.
|
||||
*
|
||||
* <br>
|
||||
*
|
||||
* <p>
|
||||
* NOTE: Currently only implemented for YT {@link
|
||||
* YoutubeCommentsInfoItemExtractor#getLikeCount()}
|
||||
* with limitations (only approximate like count is returned)
|
||||
*
|
||||
* @see StreamExtractor#getLikeCount()
|
||||
* @return the comment's like count
|
||||
* or {@link CommentsInfoItem#NO_LIKE_COUNT} if it is unavailable
|
||||
* @see StreamExtractor#getLikeCount()
|
||||
*/
|
||||
default int getLikeCount() throws ParsingException {
|
||||
return CommentsInfoItem.NO_LIKE_COUNT;
|
||||
|
@ -36,14 +39,15 @@ public interface CommentsInfoItemExtractor extends InfoItemExtractor {
|
|||
* It may be language dependent
|
||||
*/
|
||||
default String getTextualLikeCount() throws ParsingException {
|
||||
return Utils.EMPTY_STRING;
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* The text of the comment
|
||||
*/
|
||||
default String getCommentText() throws ParsingException {
|
||||
return Utils.EMPTY_STRING;
|
||||
@Nonnull
|
||||
default Description getCommentText() throws ParsingException {
|
||||
return Description.EMPTY_DESCRIPTION;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -52,7 +56,7 @@ public interface CommentsInfoItemExtractor extends InfoItemExtractor {
|
|||
* @see StreamExtractor#getTextualUploadDate()
|
||||
*/
|
||||
default String getTextualUploadDate() throws ParsingException {
|
||||
return Utils.EMPTY_STRING;
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -66,19 +70,20 @@ public interface CommentsInfoItemExtractor extends InfoItemExtractor {
|
|||
}
|
||||
|
||||
default String getCommentId() throws ParsingException {
|
||||
return Utils.EMPTY_STRING;
|
||||
return "";
|
||||
}
|
||||
|
||||
default String getUploaderUrl() throws ParsingException {
|
||||
return Utils.EMPTY_STRING;
|
||||
return "";
|
||||
}
|
||||
|
||||
default String getUploaderName() throws ParsingException {
|
||||
return Utils.EMPTY_STRING;
|
||||
return "";
|
||||
}
|
||||
|
||||
default String getUploaderAvatarUrl() throws ParsingException {
|
||||
return Utils.EMPTY_STRING;
|
||||
@Nonnull
|
||||
default List<Image> getUploaderAvatars() throws ParsingException {
|
||||
return List.of();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -104,18 +109,44 @@ public interface CommentsInfoItemExtractor extends InfoItemExtractor {
|
|||
|
||||
/**
|
||||
* The playback position of the stream to which this comment belongs.
|
||||
*
|
||||
* @see CommentsInfoItem#getStreamPosition()
|
||||
*/
|
||||
default int getStreamPosition() throws ParsingException {
|
||||
return CommentsInfoItem.NO_STREAM_POSITION;
|
||||
}
|
||||
|
||||
/**
|
||||
* The count of comment replies.
|
||||
*
|
||||
* @return the count of the replies
|
||||
* or {@link CommentsInfoItem#UNKNOWN_REPLY_COUNT} if replies are not supported
|
||||
*/
|
||||
default int getReplyCount() throws ParsingException {
|
||||
return CommentsInfoItem.UNKNOWN_REPLY_COUNT;
|
||||
}
|
||||
|
||||
/**
|
||||
* The continuation page which is used to get comment replies from.
|
||||
*
|
||||
* @return the continuation Page for the replies, or null if replies are not supported
|
||||
*/
|
||||
@Nullable
|
||||
default Page getReplies() throws ParsingException {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the comment was made by the channel owner.
|
||||
*/
|
||||
default boolean isChannelOwner() throws ParsingException {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the comment was replied to by the creator.
|
||||
*/
|
||||
default boolean hasCreatorReply() throws ParsingException {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -36,7 +36,7 @@ public final class CommentsInfoItemsCollector
|
|||
addError(e);
|
||||
}
|
||||
try {
|
||||
resultItem.setUploaderAvatarUrl(extractor.getUploaderAvatarUrl());
|
||||
resultItem.setUploaderAvatars(extractor.getUploaderAvatars());
|
||||
} catch (final Exception e) {
|
||||
addError(e);
|
||||
}
|
||||
|
@ -66,7 +66,7 @@ public final class CommentsInfoItemsCollector
|
|||
addError(e);
|
||||
}
|
||||
try {
|
||||
resultItem.setThumbnailUrl(extractor.getThumbnailUrl());
|
||||
resultItem.setThumbnails(extractor.getThumbnails());
|
||||
} catch (final Exception e) {
|
||||
addError(e);
|
||||
}
|
||||
|
@ -89,12 +89,32 @@ public final class CommentsInfoItemsCollector
|
|||
addError(e);
|
||||
}
|
||||
|
||||
try {
|
||||
resultItem.setReplyCount(extractor.getReplyCount());
|
||||
} catch (final Exception e) {
|
||||
addError(e);
|
||||
}
|
||||
|
||||
try {
|
||||
resultItem.setReplies(extractor.getReplies());
|
||||
} catch (final Exception e) {
|
||||
addError(e);
|
||||
}
|
||||
|
||||
try {
|
||||
resultItem.setChannelOwner(extractor.isChannelOwner());
|
||||
} catch (final Exception e) {
|
||||
addError(e);
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
resultItem.setCreatorReply(extractor.hasCreatorReply());
|
||||
} catch (final Exception e) {
|
||||
addError(e);
|
||||
}
|
||||
|
||||
|
||||
return resultItem;
|
||||
}
|
||||
|
||||
|
|
|
@ -7,6 +7,8 @@ import org.schabi.newpipe.extractor.localization.Localization;
|
|||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
|
@ -39,7 +41,7 @@ public abstract class Downloader {
|
|||
* @param localization the source of the value of the {@code Accept-Language} header
|
||||
* @return the result of the GET request
|
||||
*/
|
||||
public Response get(final String url, @Nullable final Localization localization)
|
||||
public Response get(final String url, final Localization localization)
|
||||
throws IOException, ReCaptchaException {
|
||||
return get(url, null, localization);
|
||||
}
|
||||
|
@ -70,7 +72,7 @@ public abstract class Downloader {
|
|||
*/
|
||||
public Response get(final String url,
|
||||
@Nullable final Map<String, List<String>> headers,
|
||||
@Nullable final Localization localization)
|
||||
final Localization localization)
|
||||
throws IOException, ReCaptchaException {
|
||||
return execute(Request.newBuilder()
|
||||
.get(url)
|
||||
|
@ -112,7 +114,7 @@ public abstract class Downloader {
|
|||
* @param headers a list of headers that will be used in the request.
|
||||
* Any default headers <b>should</b> be overridden by these.
|
||||
* @param dataToSend byte array that will be sent when doing the request.
|
||||
* @return the result of the GET request
|
||||
* @return the result of the POST request
|
||||
*/
|
||||
public Response post(final String url,
|
||||
@Nullable final Map<String, List<String>> headers,
|
||||
|
@ -131,12 +133,12 @@ public abstract class Downloader {
|
|||
* Any default headers <b>should</b> be overridden by these.
|
||||
* @param dataToSend byte array that will be sent when doing the request.
|
||||
* @param localization the source of the value of the {@code Accept-Language} header
|
||||
* @return the result of the GET request
|
||||
* @return the result of the POST request
|
||||
*/
|
||||
public Response post(final String url,
|
||||
@Nullable final Map<String, List<String>> headers,
|
||||
@Nullable final byte[] dataToSend,
|
||||
@Nullable final Localization localization)
|
||||
final Localization localization)
|
||||
throws IOException, ReCaptchaException {
|
||||
return execute(Request.newBuilder()
|
||||
.post(url, dataToSend)
|
||||
|
@ -145,6 +147,95 @@ public abstract class Downloader {
|
|||
.build());
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenient method to send a POST request using the specified value of the
|
||||
* {@code Content-Type} header with a given {@link Localization}.
|
||||
*
|
||||
* @param url the URL that is pointing to the wanted resource
|
||||
* @param headers a list of headers that will be used in the request.
|
||||
* Any default headers <b>should</b> be overridden by these.
|
||||
* @param dataToSend byte array that will be sent when doing the request.
|
||||
* @param localization the source of the value of the {@code Accept-Language} header
|
||||
* @param contentType the mime type of the body sent, which will be set as the value of the
|
||||
* {@code Content-Type} header
|
||||
* @return the result of the POST request
|
||||
* @see #post(String, Map, byte[], Localization)
|
||||
*/
|
||||
public Response postWithContentType(final String url,
|
||||
@Nullable final Map<String, List<String>> headers,
|
||||
@Nullable final byte[] dataToSend,
|
||||
final Localization localization,
|
||||
final String contentType)
|
||||
throws IOException, ReCaptchaException {
|
||||
final Map<String, List<String>> actualHeaders = new HashMap<>();
|
||||
if (headers != null) {
|
||||
actualHeaders.putAll(headers);
|
||||
}
|
||||
actualHeaders.put("Content-Type", Collections.singletonList(contentType));
|
||||
return post(url, actualHeaders, dataToSend, localization);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenient method to send a POST request using the specified value of the
|
||||
* {@code Content-Type} header.
|
||||
*
|
||||
* @param url the URL that is pointing to the wanted resource
|
||||
* @param headers a list of headers that will be used in the request.
|
||||
* Any default headers <b>should</b> be overridden by these.
|
||||
* @param dataToSend byte array that will be sent when doing the request.
|
||||
* @param contentType the mime type of the body sent, which will be set as the value of the
|
||||
* {@code Content-Type} header
|
||||
* @return the result of the POST request
|
||||
* @see #post(String, Map, byte[], Localization)
|
||||
*/
|
||||
public Response postWithContentType(final String url,
|
||||
@Nullable final Map<String, List<String>> headers,
|
||||
@Nullable final byte[] dataToSend,
|
||||
final String contentType)
|
||||
throws IOException, ReCaptchaException {
|
||||
return postWithContentType(url, headers, dataToSend, NewPipe.getPreferredLocalization(),
|
||||
contentType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenient method to send a POST request the JSON mime type as the value of the
|
||||
* {@code Content-Type} header with a given {@link Localization}.
|
||||
*
|
||||
* @param url the URL that is pointing to the wanted resource
|
||||
* @param headers a list of headers that will be used in the request.
|
||||
* Any default headers <b>should</b> be overridden by these.
|
||||
* @param dataToSend byte array that will be sent when doing the request.
|
||||
* @param localization the source of the value of the {@code Accept-Language} header
|
||||
* @return the result of the POST request
|
||||
* @see #post(String, Map, byte[], Localization)
|
||||
*/
|
||||
public Response postWithContentTypeJson(final String url,
|
||||
@Nullable final Map<String, List<String>> headers,
|
||||
@Nullable final byte[] dataToSend,
|
||||
final Localization localization)
|
||||
throws IOException, ReCaptchaException {
|
||||
return postWithContentType(url, headers, dataToSend, localization, "application/json");
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenient method to send a POST request the JSON mime type as the value of the
|
||||
* {@code Content-Type} header.
|
||||
*
|
||||
* @param url the URL that is pointing to the wanted resource
|
||||
* @param headers a list of headers that will be used in the request.
|
||||
* Any default headers <b>should</b> be overridden by these.
|
||||
* @param dataToSend byte array that will be sent when doing the request.
|
||||
* @return the result of the POST request
|
||||
* @see #post(String, Map, byte[], Localization)
|
||||
*/
|
||||
public Response postWithContentTypeJson(final String url,
|
||||
@Nullable final Map<String, List<String>> headers,
|
||||
@Nullable final byte[] dataToSend)
|
||||
throws IOException, ReCaptchaException {
|
||||
return postWithContentTypeJson(url, headers, dataToSend,
|
||||
NewPipe.getPreferredLocalization());
|
||||
}
|
||||
|
||||
/**
|
||||
* Do a request using the specified {@link Request} object.
|
||||
*
|
||||
|
|
|
@ -32,15 +32,8 @@ public class Request {
|
|||
@Nullable final byte[] dataToSend,
|
||||
@Nullable final Localization localization,
|
||||
final boolean automaticLocalizationHeader) {
|
||||
if (httpMethod == null) {
|
||||
throw new IllegalArgumentException("Request's httpMethod is null");
|
||||
}
|
||||
if (url == null) {
|
||||
throw new IllegalArgumentException("Request's url is null");
|
||||
}
|
||||
|
||||
this.httpMethod = httpMethod;
|
||||
this.url = url;
|
||||
this.httpMethod = Objects.requireNonNull(httpMethod, "Request's httpMethod is null");
|
||||
this.url = Objects.requireNonNull(url, "Request's url is null");
|
||||
this.dataToSend = dataToSend;
|
||||
this.localization = localization;
|
||||
|
||||
|
@ -49,7 +42,7 @@ public class Request {
|
|||
actualHeaders.putAll(headers);
|
||||
}
|
||||
if (automaticLocalizationHeader && localization != null) {
|
||||
actualHeaders.putAll(headersFromLocalization(localization));
|
||||
actualHeaders.putAll(getHeadersFromLocalization(localization));
|
||||
}
|
||||
|
||||
this.headers = Collections.unmodifiableMap(actualHeaders);
|
||||
|
@ -98,7 +91,7 @@ public class Request {
|
|||
* A localization object that should be used when executing a request.<br>
|
||||
* <br>
|
||||
* Usually the {@code Accept-Language} will be set to this value (a helper
|
||||
* method to do this easily: {@link Request#headersFromLocalization(Localization)}).
|
||||
* method to do this easily: {@link Request#getHeadersFromLocalization(Localization)}).
|
||||
*/
|
||||
@Nullable
|
||||
public Localization localization() {
|
||||
|
@ -165,7 +158,7 @@ public class Request {
|
|||
* A localization object that should be used when executing a request.<br>
|
||||
* <br>
|
||||
* Usually the {@code Accept-Language} will be set to this value (a helper
|
||||
* method to do this easily: {@link Request#headersFromLocalization(Localization)}).
|
||||
* method to do this easily: {@link Request#getHeadersFromLocalization(Localization)}).
|
||||
*/
|
||||
public Builder localization(final Localization localizationToSet) {
|
||||
this.localization = localizationToSet;
|
||||
|
@ -245,23 +238,17 @@ public class Request {
|
|||
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
@Nonnull
|
||||
public static Map<String, List<String>> headersFromLocalization(
|
||||
public static Map<String, List<String>> getHeadersFromLocalization(
|
||||
@Nullable final Localization localization) {
|
||||
if (localization == null) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
|
||||
final Map<String, List<String>> headers = new LinkedHashMap<>();
|
||||
if (!localization.getCountryCode().isEmpty()) {
|
||||
headers.put("Accept-Language",
|
||||
Collections.singletonList(localization.getLocalizationCode()
|
||||
+ ", " + localization.getLanguageCode() + ";q=0.9"));
|
||||
} else {
|
||||
headers.put("Accept-Language",
|
||||
Collections.singletonList(localization.getLanguageCode()));
|
||||
}
|
||||
|
||||
return headers;
|
||||
final String languageCode = localization.getLanguageCode();
|
||||
final List<String> languageCodeList = Collections.singletonList(
|
||||
localization.getCountryCode().isEmpty() ? languageCode
|
||||
: localization.getLocalizationCode() + ", " + languageCode + ";q=0.9");
|
||||
return Collections.singletonMap("Accept-Language", languageCodeList);
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
|
|
|
@ -3,21 +3,21 @@ package org.schabi.newpipe.extractor.exceptions;
|
|||
/*
|
||||
* Created by Christian Schabesberger on 30.01.16.
|
||||
*
|
||||
* Copyright (C) Christian Schabesberger 2016 <chris.schabesberger@mailbox.org>
|
||||
* ExtractionException.java is part of NewPipe.
|
||||
* Copyright (C) 2016 Christian Schabesberger <chris.schabesberger@mailbox.org>
|
||||
* ExtractionException.java is part of NewPipe Extractor.
|
||||
*
|
||||
* NewPipe is free software: you can redistribute it and/or modify
|
||||
* NewPipe Extractor is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* NewPipe is distributed in the hope that it will be useful,
|
||||
* NewPipe Extractor is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
|
||||
* along with NewPipe Extractor. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
public class ExtractionException extends Exception {
|
||||
|
|
|
@ -3,21 +3,21 @@ package org.schabi.newpipe.extractor.exceptions;
|
|||
/*
|
||||
* Created by Christian Schabesberger on 12.09.16.
|
||||
*
|
||||
* Copyright (C) Christian Schabesberger 2016 <chris.schabesberger@mailbox.org>
|
||||
* FoundAdException.java is part of NewPipe.
|
||||
* Copyright (C) 2016 Christian Schabesberger <chris.schabesberger@mailbox.org>
|
||||
* FoundAdException.java is part of NewPipe Extractor.
|
||||
*
|
||||
* NewPipe is free software: you can redistribute it and/or modify
|
||||
* NewPipe Extractor is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* NewPipe is distributed in the hope that it will be useful,
|
||||
* NewPipe Extractor is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
|
||||
* along with NewPipe Extractor. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
public class FoundAdException extends ParsingException {
|
||||
|
|
|
@ -3,21 +3,21 @@ package org.schabi.newpipe.extractor.exceptions;
|
|||
/*
|
||||
* Created by Christian Schabesberger on 31.01.16.
|
||||
*
|
||||
* Copyright (C) Christian Schabesberger 2016 <chris.schabesberger@mailbox.org>
|
||||
* ParsingException.java is part of NewPipe.
|
||||
* Copyright (C) 2016 Christian Schabesberger <chris.schabesberger@mailbox.org>
|
||||
* ParsingException.java is part of NewPipe Extractor.
|
||||
*
|
||||
* NewPipe is free software: you can redistribute it and/or modify
|
||||
* NewPipe Extractor is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* NewPipe is distributed in the hope that it will be useful,
|
||||
* NewPipe Extractor is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
|
||||
* along with NewPipe Extractor. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
|
|
|
@ -3,21 +3,21 @@ package org.schabi.newpipe.extractor.exceptions;
|
|||
/*
|
||||
* Created by beneth <bmauduit@beneth.fr> on 07.12.16.
|
||||
*
|
||||
* Copyright (C) Christian Schabesberger 2016 <chris.schabesberger@mailbox.org>
|
||||
* ReCaptchaException.java is part of NewPipe.
|
||||
* Copyright (C) 2016 Christian Schabesberger <chris.schabesberger@mailbox.org>
|
||||
* ReCaptchaException.java is part of NewPipe Extractor.
|
||||
*
|
||||
* NewPipe is free software: you can redistribute it and/or modify
|
||||
* NewPipe Extractor is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* NewPipe is distributed in the hope that it will be useful,
|
||||
* NewPipe Extractor is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
|
||||
* along with NewPipe Extractor. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
public class ReCaptchaException extends ExtractionException {
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
package org.schabi.newpipe.extractor.exceptions;
|
||||
|
||||
public final class UnsupportedTabException extends UnsupportedOperationException {
|
||||
public UnsupportedTabException(final String unsupportedTab) {
|
||||
super("Unsupported tab " + unsupportedTab);
|
||||
}
|
||||
}
|
|
@ -3,21 +3,21 @@ package org.schabi.newpipe.extractor.kiosk;
|
|||
/*
|
||||
* Created by Christian Schabesberger on 12.08.17.
|
||||
*
|
||||
* Copyright (C) Christian Schabesberger 2017 <chris.schabesberger@mailbox.org>
|
||||
* KioskExtractor.java is part of NewPipe.
|
||||
* Copyright (C) 2017 Christian Schabesberger <chris.schabesberger@mailbox.org>
|
||||
* KioskExtractor.java is part of NewPipe Extractor.
|
||||
*
|
||||
* NewPipe is free software: you can redistribute it and/or modify
|
||||
* NewPipe Extractor is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* NewPipe is distributed in the hope that it will be useful,
|
||||
* NewPipe Extractor is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
|
||||
* along with NewPipe Extractor. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import org.schabi.newpipe.extractor.InfoItem;
|
||||
|
|
|
@ -3,21 +3,21 @@ package org.schabi.newpipe.extractor.kiosk;
|
|||
/*
|
||||
* Created by Christian Schabesberger on 12.08.17.
|
||||
*
|
||||
* Copyright (C) Christian Schabesberger 2017 <chris.schabesberger@mailbox.org>
|
||||
* KioskInfo.java is part of NewPipe.
|
||||
* Copyright (C) 2017 Christian Schabesberger <chris.schabesberger@mailbox.org>
|
||||
* KioskInfo.java is part of NewPipe Extractor.
|
||||
*
|
||||
* NewPipe is free software: you can redistribute it and/or modify
|
||||
* NewPipe Extractor is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* NewPipe is distributed in the hope that it will be useful,
|
||||
* NewPipe Extractor is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
|
||||
* along with NewPipe Extractor. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import org.schabi.newpipe.extractor.ListExtractor;
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
package org.schabi.newpipe.extractor.kiosk;
|
||||
|
||||
import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty;
|
||||
|
||||
import org.schabi.newpipe.extractor.NewPipe;
|
||||
import org.schabi.newpipe.extractor.Page;
|
||||
import org.schabi.newpipe.extractor.StreamingService;
|
||||
|
@ -8,13 +10,12 @@ import org.schabi.newpipe.extractor.linkhandler.ListLinkHandlerFactory;
|
|||
import org.schabi.newpipe.extractor.localization.ContentCountry;
|
||||
import org.schabi.newpipe.extractor.localization.Localization;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
public class KioskList {
|
||||
|
||||
|
@ -78,10 +79,10 @@ public class KioskList {
|
|||
if (!isNullOrEmpty(defaultKiosk)) {
|
||||
return getExtractorById(defaultKiosk, nextPage, localization);
|
||||
} else {
|
||||
if (!kioskList.isEmpty()) {
|
||||
final String first = kioskList.keySet().stream().findAny().orElse(null);
|
||||
if (first != null) {
|
||||
// if not set get any entry
|
||||
final Object[] keySet = kioskList.keySet().toArray();
|
||||
return getExtractorById(keySet[0].toString(), nextPage, localization);
|
||||
return getExtractorById(first, nextPage, localization);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -3,24 +3,26 @@ package org.schabi.newpipe.extractor.linkhandler;
|
|||
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
||||
import org.schabi.newpipe.extractor.utils.Utils;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/*
|
||||
* Created by Christian Schabesberger on 26.07.16.
|
||||
*
|
||||
* Copyright (C) Christian Schabesberger 2016 <chris.schabesberger@mailbox.org>
|
||||
* LinkHandlerFactory.java is part of NewPipe.
|
||||
* Copyright (C) 2016 Christian Schabesberger <chris.schabesberger@mailbox.org>
|
||||
* LinkHandlerFactory.java is part of NewPipe Extractor.
|
||||
*
|
||||
* NewPipe is free software: you can redistribute it and/or modify
|
||||
* NewPipe Extractor is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* NewPipe is distributed in the hope that it will be useful,
|
||||
* NewPipe Extractor is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
|
||||
* along with NewPipe Extractor. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
public abstract class LinkHandlerFactory {
|
||||
|
@ -29,13 +31,14 @@ public abstract class LinkHandlerFactory {
|
|||
// To Override
|
||||
///////////////////////////////////
|
||||
|
||||
public abstract String getId(String url) throws ParsingException;
|
||||
public abstract String getId(String url) throws ParsingException, UnsupportedOperationException;
|
||||
|
||||
public abstract String getUrl(String id) throws ParsingException;
|
||||
public abstract String getUrl(String id) throws ParsingException, UnsupportedOperationException;
|
||||
|
||||
public abstract boolean onAcceptUrl(String url) throws ParsingException;
|
||||
|
||||
public String getUrl(final String id, final String baseUrl) throws ParsingException {
|
||||
public String getUrl(final String id, final String baseUrl)
|
||||
throws ParsingException, UnsupportedOperationException {
|
||||
return getUrl(id);
|
||||
}
|
||||
|
||||
|
@ -72,9 +75,7 @@ public abstract class LinkHandlerFactory {
|
|||
* @return a {@link LinkHandler} complete with information
|
||||
*/
|
||||
public LinkHandler fromUrl(final String url, final String baseUrl) throws ParsingException {
|
||||
if (url == null) {
|
||||
throw new IllegalArgumentException("URL cannot be null");
|
||||
}
|
||||
Objects.requireNonNull(url, "URL cannot be null");
|
||||
if (!acceptUrl(url)) {
|
||||
throw new ParsingException("URL not accepted: " + url);
|
||||
}
|
||||
|
@ -84,17 +85,13 @@ public abstract class LinkHandlerFactory {
|
|||
}
|
||||
|
||||
public LinkHandler fromId(final String id) throws ParsingException {
|
||||
if (id == null) {
|
||||
throw new IllegalArgumentException("id can not be null");
|
||||
}
|
||||
Objects.requireNonNull(id, "ID cannot be null");
|
||||
final String url = getUrl(id);
|
||||
return new LinkHandler(url, url, id);
|
||||
}
|
||||
|
||||
public LinkHandler fromId(final String id, final String baseUrl) throws ParsingException {
|
||||
if (id == null) {
|
||||
throw new IllegalArgumentException("id can not be null");
|
||||
}
|
||||
Objects.requireNonNull(id, "ID cannot be null");
|
||||
final String url = getUrl(id, baseUrl);
|
||||
return new LinkHandler(url, url, id);
|
||||
}
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
package org.schabi.newpipe.extractor.linkhandler;
|
||||
|
||||
import static org.schabi.newpipe.extractor.utils.Utils.EMPTY_STRING;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
|
@ -32,7 +30,7 @@ public class ListLinkHandler extends LinkHandler {
|
|||
handler.url,
|
||||
handler.id,
|
||||
Collections.emptyList(),
|
||||
EMPTY_STRING);
|
||||
"");
|
||||
}
|
||||
|
||||
public List<String> getContentFilters() {
|
||||
|
|
|
@ -5,6 +5,7 @@ import org.schabi.newpipe.extractor.utils.Utils;
|
|||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
public abstract class ListLinkHandlerFactory extends LinkHandlerFactory {
|
||||
|
||||
|
@ -13,12 +14,13 @@ public abstract class ListLinkHandlerFactory extends LinkHandlerFactory {
|
|||
///////////////////////////////////
|
||||
|
||||
public abstract String getUrl(String id, List<String> contentFilter, String sortFilter)
|
||||
throws ParsingException;
|
||||
throws ParsingException, UnsupportedOperationException;
|
||||
|
||||
public String getUrl(final String id,
|
||||
final List<String> contentFilter,
|
||||
final String sortFilter,
|
||||
final String baseUrl) throws ParsingException {
|
||||
final String baseUrl)
|
||||
throws ParsingException, UnsupportedOperationException {
|
||||
return getUrl(id, contentFilter, sortFilter);
|
||||
}
|
||||
|
||||
|
@ -35,10 +37,7 @@ public abstract class ListLinkHandlerFactory extends LinkHandlerFactory {
|
|||
|
||||
@Override
|
||||
public ListLinkHandler fromUrl(final String url, final String baseUrl) throws ParsingException {
|
||||
if (url == null) {
|
||||
throw new IllegalArgumentException("url may not be null");
|
||||
}
|
||||
|
||||
Objects.requireNonNull(url, "URL may not be null");
|
||||
return new ListLinkHandler(super.fromUrl(url, baseUrl));
|
||||
}
|
||||
|
||||
|
@ -74,7 +73,7 @@ public abstract class ListLinkHandlerFactory extends LinkHandlerFactory {
|
|||
*
|
||||
* @return the url corresponding to id without any filters applied
|
||||
*/
|
||||
public String getUrl(final String id) throws ParsingException {
|
||||
public String getUrl(final String id) throws ParsingException, UnsupportedOperationException {
|
||||
return getUrl(id, new ArrayList<>(0), "");
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,55 @@
|
|||
package org.schabi.newpipe.extractor.linkhandler;
|
||||
|
||||
import org.schabi.newpipe.extractor.StreamingService;
|
||||
import org.schabi.newpipe.extractor.channel.tabs.ChannelTabExtractor;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A {@link ListLinkHandler} which can be used to be returned from {@link
|
||||
* org.schabi.newpipe.extractor.channel.ChannelInfo#getTabs() ChannelInfo#getTabs()} when a
|
||||
* specific tab's data has already been fetched.
|
||||
*
|
||||
* <p>
|
||||
* This class allows passing a builder for a {@link ChannelTabExtractor} that can hold references
|
||||
* to variables.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* Note: a service that wishes to use this class in one of its {@link
|
||||
* org.schabi.newpipe.extractor.channel.ChannelExtractor ChannelExtractor}s must also add the
|
||||
* following snippet of code in the service's
|
||||
* {@link StreamingService#getChannelTabExtractor(ListLinkHandler)}:
|
||||
* <pre>
|
||||
* if (linkHandler instanceof ReadyChannelTabListLinkHandler) {
|
||||
* return ((ReadyChannelTabListLinkHandler) linkHandler).getChannelTabExtractor(this);
|
||||
* }
|
||||
* </pre>
|
||||
* </p>
|
||||
*/
|
||||
public class ReadyChannelTabListLinkHandler extends ListLinkHandler {
|
||||
|
||||
public interface ChannelTabExtractorBuilder extends Serializable {
|
||||
@Nonnull
|
||||
ChannelTabExtractor build(@Nonnull StreamingService service,
|
||||
@Nonnull ListLinkHandler linkHandler);
|
||||
}
|
||||
|
||||
private final ChannelTabExtractorBuilder extractorBuilder;
|
||||
|
||||
public ReadyChannelTabListLinkHandler(
|
||||
final String url,
|
||||
final String channelId,
|
||||
@Nonnull final String channelTab,
|
||||
@Nonnull final ChannelTabExtractorBuilder extractorBuilder) {
|
||||
super(url, url, channelId, List.of(channelTab), "");
|
||||
this.extractorBuilder = extractorBuilder;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public ChannelTabExtractor getChannelTabExtractor(@Nonnull final StreamingService service) {
|
||||
return extractorBuilder.build(service, new ListLinkHandler(this));
|
||||
}
|
||||
}
|
|
@ -1,7 +1,5 @@
|
|||
package org.schabi.newpipe.extractor.linkhandler;
|
||||
|
||||
import static org.schabi.newpipe.extractor.utils.Utils.EMPTY_STRING;
|
||||
|
||||
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
||||
|
||||
import java.util.Collections;
|
||||
|
@ -15,7 +13,7 @@ public abstract class SearchQueryHandlerFactory extends ListLinkHandlerFactory {
|
|||
|
||||
@Override
|
||||
public abstract String getUrl(String query, List<String> contentFilter, String sortFilter)
|
||||
throws ParsingException;
|
||||
throws ParsingException, UnsupportedOperationException;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public String getSearchString(final String url) {
|
||||
|
@ -27,7 +25,7 @@ public abstract class SearchQueryHandlerFactory extends ListLinkHandlerFactory {
|
|||
///////////////////////////////////
|
||||
|
||||
@Override
|
||||
public String getId(final String url) {
|
||||
public String getId(final String url) throws ParsingException, UnsupportedOperationException {
|
||||
return getSearchString(url);
|
||||
}
|
||||
|
||||
|
@ -39,7 +37,7 @@ public abstract class SearchQueryHandlerFactory extends ListLinkHandlerFactory {
|
|||
}
|
||||
|
||||
public SearchQueryHandler fromQuery(final String query) throws ParsingException {
|
||||
return fromQuery(query, Collections.emptyList(), EMPTY_STRING);
|
||||
return fromQuery(query, Collections.emptyList(), "");
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,9 +1,7 @@
|
|||
package org.schabi.newpipe.extractor.localization;
|
||||
|
||||
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import org.schabi.newpipe.extractor.utils.LocaleCompat;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
|
@ -13,6 +11,11 @@ import java.util.List;
|
|||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
|
||||
public class Localization implements Serializable {
|
||||
public static final Localization DEFAULT = new Localization("en", "GB");
|
||||
|
@ -25,32 +28,28 @@ public class Localization implements Serializable {
|
|||
/**
|
||||
* @param localizationCodeList a list of localization code, formatted like {@link
|
||||
* #getLocalizationCode()}
|
||||
* @throws IllegalArgumentException If any of the localizationCodeList is formatted incorrectly
|
||||
* @return list of Localization objects
|
||||
*/
|
||||
@Nonnull
|
||||
public static List<Localization> listFrom(final String... localizationCodeList) {
|
||||
final List<Localization> toReturn = new ArrayList<>();
|
||||
for (final String localizationCode : localizationCodeList) {
|
||||
toReturn.add(fromLocalizationCode(localizationCode));
|
||||
toReturn.add(fromLocalizationCode(localizationCode)
|
||||
.orElseThrow(() -> new IllegalArgumentException(
|
||||
"Not a localization code: " + localizationCode
|
||||
)));
|
||||
}
|
||||
return Collections.unmodifiableList(toReturn);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param localizationCode a localization code, formatted like {@link #getLocalizationCode()}
|
||||
* @return A Localization, if the code was valid.
|
||||
*/
|
||||
public static Localization fromLocalizationCode(final String localizationCode) {
|
||||
final int indexSeparator = localizationCode.indexOf("-");
|
||||
|
||||
final String languageCode;
|
||||
final String countryCode;
|
||||
if (indexSeparator != -1) {
|
||||
languageCode = localizationCode.substring(0, indexSeparator);
|
||||
countryCode = localizationCode.substring(indexSeparator + 1);
|
||||
} else {
|
||||
languageCode = localizationCode;
|
||||
countryCode = null;
|
||||
}
|
||||
|
||||
return new Localization(languageCode, countryCode);
|
||||
@Nonnull
|
||||
public static Optional<Localization> fromLocalizationCode(final String localizationCode) {
|
||||
return LocaleCompat.forLanguageTag(localizationCode).map(Localization::fromLocale);
|
||||
}
|
||||
|
||||
public Localization(@Nonnull final String languageCode, @Nullable final String countryCode) {
|
||||
|
@ -72,10 +71,6 @@ public class Localization implements Serializable {
|
|||
return countryCode == null ? "" : countryCode;
|
||||
}
|
||||
|
||||
public Locale asLocale() {
|
||||
return new Locale(getLanguageCode(), getCountryCode());
|
||||
}
|
||||
|
||||
public static Localization fromLocale(@Nonnull final Locale locale) {
|
||||
return new Localization(locale.getLanguage(), locale.getCountry());
|
||||
}
|
||||
|
@ -83,6 +78,8 @@ public class Localization implements Serializable {
|
|||
/**
|
||||
* Return a formatted string in the form of: {@code language-Country}, or
|
||||
* just {@code language} if country is {@code null}.
|
||||
*
|
||||
* @return A correctly formatted localizationCode for this localization.
|
||||
*/
|
||||
public String getLocalizationCode() {
|
||||
return languageCode + (countryCode == null ? "" : "-" + countryCode);
|
||||
|
|
|
@ -7,15 +7,21 @@ import org.schabi.newpipe.extractor.utils.Parser;
|
|||
import java.time.OffsetDateTime;
|
||||
import java.time.ZoneOffset;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.Collection;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.MatchResult;
|
||||
|
||||
/**
|
||||
* A helper class that is meant to be used by services that need to parse upload dates in the
|
||||
* format '2 days ago' or similar.
|
||||
* A helper class that is meant to be used by services that need to parse durations such as
|
||||
* {@code 23 seconds} and/or upload dates in the format {@code 2 days ago} or similar.
|
||||
*/
|
||||
public class TimeAgoParser {
|
||||
|
||||
private static final Pattern DURATION_PATTERN = Pattern.compile("(?:(\\d+) )?([A-z]+)");
|
||||
|
||||
private final PatternsHolder patternsHolder;
|
||||
private final OffsetDateTime now;
|
||||
|
||||
|
@ -58,36 +64,69 @@ public class TimeAgoParser {
|
|||
}
|
||||
}
|
||||
|
||||
int timeAgoAmount;
|
||||
try {
|
||||
timeAgoAmount = parseTimeAgoAmount(textualDate);
|
||||
} catch (final NumberFormatException e) {
|
||||
// If there is no valid number in the textual date,
|
||||
// assume it is 1 (as in 'a second ago').
|
||||
timeAgoAmount = 1;
|
||||
}
|
||||
|
||||
final ChronoUnit chronoUnit = parseChronoUnit(textualDate);
|
||||
return getResultFor(timeAgoAmount, chronoUnit);
|
||||
return getResultFor(parseTimeAgoAmount(textualDate), parseChronoUnit(textualDate));
|
||||
}
|
||||
|
||||
private int parseTimeAgoAmount(final String textualDate) throws NumberFormatException {
|
||||
return Integer.parseInt(textualDate.replaceAll("\\D+", ""));
|
||||
/**
|
||||
* Parses a textual duration into a duration computer number.
|
||||
*
|
||||
* @param textualDuration the textual duration to parse
|
||||
* @return the textual duration parsed, as a primitive {@code long}
|
||||
* @throws ParsingException if the textual duration could not be parsed
|
||||
*/
|
||||
public long parseDuration(final String textualDuration) throws ParsingException {
|
||||
// We can't use Matcher.results, as it is only available on Android 14 and above
|
||||
final Matcher matcher = DURATION_PATTERN.matcher(textualDuration);
|
||||
final List<MatchResult> results = new ArrayList<>();
|
||||
while (matcher.find()) {
|
||||
results.add(matcher.toMatchResult());
|
||||
}
|
||||
|
||||
return results.stream()
|
||||
.map(match -> {
|
||||
final String digits = match.group(1);
|
||||
final String word = match.group(2);
|
||||
|
||||
int amount;
|
||||
try {
|
||||
amount = Integer.parseInt(digits);
|
||||
} catch (final NumberFormatException ignored) {
|
||||
amount = 1;
|
||||
}
|
||||
|
||||
final ChronoUnit unit;
|
||||
try {
|
||||
unit = parseChronoUnit(word);
|
||||
} catch (final ParsingException ignored) {
|
||||
return 0L;
|
||||
}
|
||||
|
||||
return amount * unit.getDuration().getSeconds();
|
||||
})
|
||||
.filter(n -> n > 0)
|
||||
.reduce(Long::sum)
|
||||
.orElseThrow(() -> new ParsingException(
|
||||
"Could not parse duration \"" + textualDuration + "\""));
|
||||
}
|
||||
|
||||
private int parseTimeAgoAmount(final String textualDate) {
|
||||
try {
|
||||
return Integer.parseInt(textualDate.replaceAll("\\D+", ""));
|
||||
} catch (final NumberFormatException ignored) {
|
||||
// If there is no valid number in the textual date,
|
||||
// assume it is 1 (as in 'a second ago').
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
private ChronoUnit parseChronoUnit(final String textualDate) throws ParsingException {
|
||||
for (final Map.Entry<ChronoUnit, Collection<String>> entry
|
||||
: patternsHolder.asMap().entrySet()) {
|
||||
final ChronoUnit chronoUnit = entry.getKey();
|
||||
|
||||
for (final String agoPhrase : entry.getValue()) {
|
||||
if (textualDateMatches(textualDate, agoPhrase)) {
|
||||
return chronoUnit;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw new ParsingException("Unable to parse the date: " + textualDate);
|
||||
return patternsHolder.asMap().entrySet().stream()
|
||||
.filter(e -> e.getValue().stream()
|
||||
.anyMatch(agoPhrase -> textualDateMatches(textualDate, agoPhrase)))
|
||||
.map(Map.Entry::getKey)
|
||||
.findFirst()
|
||||
.orElseThrow(() ->
|
||||
new ParsingException("Unable to parse the date: " + textualDate));
|
||||
}
|
||||
|
||||
private boolean textualDateMatches(final String textualDate, final String agoPhrase) {
|
||||
|
@ -97,24 +136,22 @@ public class TimeAgoParser {
|
|||
|
||||
if (patternsHolder.wordSeparator().isEmpty()) {
|
||||
return textualDate.toLowerCase().contains(agoPhrase.toLowerCase());
|
||||
} else {
|
||||
final String escapedPhrase = Pattern.quote(agoPhrase.toLowerCase());
|
||||
final String escapedSeparator;
|
||||
if (patternsHolder.wordSeparator().equals(" ")) {
|
||||
}
|
||||
|
||||
final String escapedPhrase = Pattern.quote(agoPhrase.toLowerCase());
|
||||
final String escapedSeparator = patternsHolder.wordSeparator().equals(" ")
|
||||
// From JDK8 → \h - Treat horizontal spaces as a normal one
|
||||
// (non-breaking space, thin space, etc.)
|
||||
escapedSeparator = "[ \\t\\xA0\\u1680\\u180e\\u2000-\\u200a\\u202f\\u205f\\u3000]";
|
||||
} else {
|
||||
escapedSeparator = Pattern.quote(patternsHolder.wordSeparator());
|
||||
}
|
||||
// Also split the string on numbers to be able to parse strings like "2wk"
|
||||
? "[ \\t\\xA0\\u1680\\u180e\\u2000-\\u200a\\u202f\\u205f\\u3000\\d]"
|
||||
: Pattern.quote(patternsHolder.wordSeparator());
|
||||
|
||||
// (^|separator)pattern($|separator)
|
||||
// Check if the pattern is surrounded by separators or start/end of the string.
|
||||
final String pattern =
|
||||
"(^|" + escapedSeparator + ")" + escapedPhrase + "($|" + escapedSeparator + ")";
|
||||
// (^|separator)pattern($|separator)
|
||||
// Check if the pattern is surrounded by separators or start/end of the string.
|
||||
final String pattern =
|
||||
"(^|" + escapedSeparator + ")" + escapedPhrase + "($|" + escapedSeparator + ")";
|
||||
|
||||
return Parser.isMatch(pattern, textualDate.toLowerCase());
|
||||
}
|
||||
return Parser.isMatch(pattern, textualDate.toLowerCase());
|
||||
}
|
||||
|
||||
private DateWrapper getResultFor(final int timeAgoAmount, final ChronoUnit chronoUnit) {
|
||||
|
|
|
@ -1,14 +1,17 @@
|
|||
package org.schabi.newpipe.extractor.playlist;
|
||||
|
||||
import org.schabi.newpipe.extractor.Image;
|
||||
import org.schabi.newpipe.extractor.ListExtractor;
|
||||
import org.schabi.newpipe.extractor.StreamingService;
|
||||
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
||||
import org.schabi.newpipe.extractor.linkhandler.ListLinkHandler;
|
||||
import org.schabi.newpipe.extractor.stream.Description;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
import static org.schabi.newpipe.extractor.utils.Utils.EMPTY_STRING;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public abstract class PlaylistExtractor extends ListExtractor<StreamInfoItem> {
|
||||
|
||||
|
@ -18,36 +21,38 @@ public abstract class PlaylistExtractor extends ListExtractor<StreamInfoItem> {
|
|||
|
||||
public abstract String getUploaderUrl() throws ParsingException;
|
||||
public abstract String getUploaderName() throws ParsingException;
|
||||
public abstract String getUploaderAvatarUrl() throws ParsingException;
|
||||
@Nonnull
|
||||
public abstract List<Image> getUploaderAvatars() throws ParsingException;
|
||||
public abstract boolean isUploaderVerified() throws ParsingException;
|
||||
|
||||
public abstract long getStreamCount() throws ParsingException;
|
||||
|
||||
@Nonnull
|
||||
public String getThumbnailUrl() throws ParsingException {
|
||||
return EMPTY_STRING;
|
||||
public abstract Description getDescription() throws ParsingException;
|
||||
|
||||
@Nonnull
|
||||
public List<Image> getThumbnails() throws ParsingException {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public String getBannerUrl() throws ParsingException {
|
||||
// Banner can't be handled by frontend right now.
|
||||
// Whoever is willing to implement this should also implement it in the frontend.
|
||||
return EMPTY_STRING;
|
||||
public List<Image> getBanners() throws ParsingException {
|
||||
return List.of();
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public String getSubChannelName() throws ParsingException {
|
||||
return EMPTY_STRING;
|
||||
return "";
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public String getSubChannelUrl() throws ParsingException {
|
||||
return EMPTY_STRING;
|
||||
return "";
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public String getSubChannelAvatarUrl() throws ParsingException {
|
||||
return EMPTY_STRING;
|
||||
public List<Image> getSubChannelAvatars() throws ParsingException {
|
||||
return List.of();
|
||||
}
|
||||
|
||||
public PlaylistInfo.PlaylistType getPlaylistType() throws ParsingException {
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package org.schabi.newpipe.extractor.playlist;
|
||||
|
||||
import org.schabi.newpipe.extractor.Image;
|
||||
import org.schabi.newpipe.extractor.ListExtractor.InfoItemsPage;
|
||||
import org.schabi.newpipe.extractor.ListInfo;
|
||||
import org.schabi.newpipe.extractor.NewPipe;
|
||||
|
@ -8,9 +9,11 @@ import org.schabi.newpipe.extractor.StreamingService;
|
|||
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
|
||||
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
||||
import org.schabi.newpipe.extractor.linkhandler.ListLinkHandler;
|
||||
import org.schabi.newpipe.extractor.stream.Description;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
|
||||
import org.schabi.newpipe.extractor.utils.ExtractorHelper;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
@ -103,26 +106,28 @@ public final class PlaylistInfo extends ListInfo<StreamInfoItem> {
|
|||
info.addError(e);
|
||||
}
|
||||
try {
|
||||
info.setThumbnailUrl(extractor.getThumbnailUrl());
|
||||
info.setDescription(extractor.getDescription());
|
||||
} catch (final Exception e) {
|
||||
info.addError(e);
|
||||
}
|
||||
try {
|
||||
info.setThumbnails(extractor.getThumbnails());
|
||||
} catch (final Exception e) {
|
||||
info.addError(e);
|
||||
}
|
||||
try {
|
||||
info.setUploaderUrl(extractor.getUploaderUrl());
|
||||
} catch (final Exception e) {
|
||||
info.setUploaderUrl("");
|
||||
uploaderParsingErrors.add(e);
|
||||
}
|
||||
try {
|
||||
info.setUploaderName(extractor.getUploaderName());
|
||||
} catch (final Exception e) {
|
||||
info.setUploaderName("");
|
||||
uploaderParsingErrors.add(e);
|
||||
}
|
||||
try {
|
||||
info.setUploaderAvatarUrl(extractor.getUploaderAvatarUrl());
|
||||
info.setUploaderAvatars(extractor.getUploaderAvatars());
|
||||
} catch (final Exception e) {
|
||||
info.setUploaderAvatarUrl("");
|
||||
uploaderParsingErrors.add(e);
|
||||
}
|
||||
try {
|
||||
|
@ -136,12 +141,12 @@ public final class PlaylistInfo extends ListInfo<StreamInfoItem> {
|
|||
uploaderParsingErrors.add(e);
|
||||
}
|
||||
try {
|
||||
info.setSubChannelAvatarUrl(extractor.getSubChannelAvatarUrl());
|
||||
info.setSubChannelAvatars(extractor.getSubChannelAvatars());
|
||||
} catch (final Exception e) {
|
||||
uploaderParsingErrors.add(e);
|
||||
}
|
||||
try {
|
||||
info.setBannerUrl(extractor.getBannerUrl());
|
||||
info.setBanners(extractor.getBanners());
|
||||
} catch (final Exception e) {
|
||||
info.addError(e);
|
||||
}
|
||||
|
@ -165,31 +170,38 @@ public final class PlaylistInfo extends ListInfo<StreamInfoItem> {
|
|||
return info;
|
||||
}
|
||||
|
||||
private String thumbnailUrl;
|
||||
private String bannerUrl;
|
||||
private String uploaderUrl;
|
||||
private String uploaderName;
|
||||
private String uploaderAvatarUrl;
|
||||
private String uploaderUrl = "";
|
||||
private String uploaderName = "";
|
||||
private String subChannelUrl;
|
||||
private String subChannelName;
|
||||
private String subChannelAvatarUrl;
|
||||
private long streamCount = 0;
|
||||
private Description description;
|
||||
@Nonnull
|
||||
private List<Image> banners = List.of();
|
||||
@Nonnull
|
||||
private List<Image> subChannelAvatars = List.of();
|
||||
@Nonnull
|
||||
private List<Image> thumbnails = List.of();
|
||||
@Nonnull
|
||||
private List<Image> uploaderAvatars = List.of();
|
||||
private long streamCount;
|
||||
private PlaylistType playlistType;
|
||||
|
||||
public String getThumbnailUrl() {
|
||||
return thumbnailUrl;
|
||||
@Nonnull
|
||||
public List<Image> getThumbnails() {
|
||||
return thumbnails;
|
||||
}
|
||||
|
||||
public void setThumbnailUrl(final String thumbnailUrl) {
|
||||
this.thumbnailUrl = thumbnailUrl;
|
||||
public void setThumbnails(@Nonnull final List<Image> thumbnails) {
|
||||
this.thumbnails = thumbnails;
|
||||
}
|
||||
|
||||
public String getBannerUrl() {
|
||||
return bannerUrl;
|
||||
@Nonnull
|
||||
public List<Image> getBanners() {
|
||||
return banners;
|
||||
}
|
||||
|
||||
public void setBannerUrl(final String bannerUrl) {
|
||||
this.bannerUrl = bannerUrl;
|
||||
public void setBanners(@Nonnull final List<Image> banners) {
|
||||
this.banners = banners;
|
||||
}
|
||||
|
||||
public String getUploaderUrl() {
|
||||
|
@ -208,12 +220,13 @@ public final class PlaylistInfo extends ListInfo<StreamInfoItem> {
|
|||
this.uploaderName = uploaderName;
|
||||
}
|
||||
|
||||
public String getUploaderAvatarUrl() {
|
||||
return uploaderAvatarUrl;
|
||||
@Nonnull
|
||||
public List<Image> getUploaderAvatars() {
|
||||
return uploaderAvatars;
|
||||
}
|
||||
|
||||
public void setUploaderAvatarUrl(final String uploaderAvatarUrl) {
|
||||
this.uploaderAvatarUrl = uploaderAvatarUrl;
|
||||
public void setUploaderAvatars(@Nonnull final List<Image> uploaderAvatars) {
|
||||
this.uploaderAvatars = uploaderAvatars;
|
||||
}
|
||||
|
||||
public String getSubChannelUrl() {
|
||||
|
@ -232,12 +245,13 @@ public final class PlaylistInfo extends ListInfo<StreamInfoItem> {
|
|||
this.subChannelName = subChannelName;
|
||||
}
|
||||
|
||||
public String getSubChannelAvatarUrl() {
|
||||
return subChannelAvatarUrl;
|
||||
@Nonnull
|
||||
public List<Image> getSubChannelAvatars() {
|
||||
return subChannelAvatars;
|
||||
}
|
||||
|
||||
public void setSubChannelAvatarUrl(final String subChannelAvatarUrl) {
|
||||
this.subChannelAvatarUrl = subChannelAvatarUrl;
|
||||
public void setSubChannelAvatars(@Nonnull final List<Image> subChannelAvatars) {
|
||||
this.subChannelAvatars = subChannelAvatars;
|
||||
}
|
||||
|
||||
public long getStreamCount() {
|
||||
|
@ -248,6 +262,14 @@ public final class PlaylistInfo extends ListInfo<StreamInfoItem> {
|
|||
this.streamCount = streamCount;
|
||||
}
|
||||
|
||||
public Description getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public void setDescription(final Description description) {
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public PlaylistType getPlaylistType() {
|
||||
return playlistType;
|
||||
}
|
||||
|
|
|
@ -1,14 +1,20 @@
|
|||
package org.schabi.newpipe.extractor.playlist;
|
||||
|
||||
import org.schabi.newpipe.extractor.InfoItem;
|
||||
import org.schabi.newpipe.extractor.stream.Description;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
public class PlaylistInfoItem extends InfoItem {
|
||||
|
||||
private String uploaderName;
|
||||
private String uploaderUrl;
|
||||
private boolean uploaderVerified;
|
||||
/**
|
||||
* How many streams this playlist have
|
||||
*/
|
||||
private long streamCount = 0;
|
||||
private Description description;
|
||||
private PlaylistInfo.PlaylistType playlistType;
|
||||
|
||||
public PlaylistInfoItem(final int serviceId, final String url, final String name) {
|
||||
|
@ -23,6 +29,23 @@ public class PlaylistInfoItem extends InfoItem {
|
|||
this.uploaderName = uploaderName;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getUploaderUrl() {
|
||||
return uploaderUrl;
|
||||
}
|
||||
|
||||
public void setUploaderUrl(@Nullable final String uploaderUrl) {
|
||||
this.uploaderUrl = uploaderUrl;
|
||||
}
|
||||
|
||||
public boolean isUploaderVerified() {
|
||||
return uploaderVerified;
|
||||
}
|
||||
|
||||
public void setUploaderVerified(final boolean uploaderVerified) {
|
||||
this.uploaderVerified = uploaderVerified;
|
||||
}
|
||||
|
||||
public long getStreamCount() {
|
||||
return streamCount;
|
||||
}
|
||||
|
@ -31,6 +54,14 @@ public class PlaylistInfoItem extends InfoItem {
|
|||
this.streamCount = streamCount;
|
||||
}
|
||||
|
||||
public Description getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public void setDescription(final Description description) {
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public PlaylistInfo.PlaylistType getPlaylistType() {
|
||||
return playlistType;
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ package org.schabi.newpipe.extractor.playlist;
|
|||
|
||||
import org.schabi.newpipe.extractor.InfoItemExtractor;
|
||||
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
||||
import org.schabi.newpipe.extractor.stream.Description;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
|
@ -13,12 +14,34 @@ public interface PlaylistInfoItemExtractor extends InfoItemExtractor {
|
|||
*/
|
||||
String getUploaderName() throws ParsingException;
|
||||
|
||||
/**
|
||||
* Get the uploader url
|
||||
* @return the uploader url
|
||||
*/
|
||||
String getUploaderUrl() throws ParsingException;
|
||||
|
||||
/**
|
||||
* Get whether the uploader is verified
|
||||
* @return whether the uploader is verified
|
||||
*/
|
||||
boolean isUploaderVerified() throws ParsingException;
|
||||
|
||||
/**
|
||||
* Get the number of streams
|
||||
* @return the number of streams
|
||||
*/
|
||||
long getStreamCount() throws ParsingException;
|
||||
|
||||
/**
|
||||
* Get the description of the playlist if there is any.
|
||||
* Otherwise, an {@link Description#EMPTY_DESCRIPTION EMPTY_DESCRIPTION} is returned.
|
||||
* @return the playlist's description
|
||||
*/
|
||||
@Nonnull
|
||||
default Description getDescription() throws ParsingException {
|
||||
return Description.EMPTY_DESCRIPTION;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the type of this playlist, see {@link PlaylistInfo.PlaylistType} for a description
|
||||
* of types. If not overridden always returns {@link PlaylistInfo.PlaylistType#NORMAL}.
|
||||
|
|
|
@ -22,7 +22,17 @@ public class PlaylistInfoItemsCollector
|
|||
addError(e);
|
||||
}
|
||||
try {
|
||||
resultItem.setThumbnailUrl(extractor.getThumbnailUrl());
|
||||
resultItem.setUploaderUrl(extractor.getUploaderUrl());
|
||||
} catch (final Exception e) {
|
||||
addError(e);
|
||||
}
|
||||
try {
|
||||
resultItem.setUploaderVerified(extractor.isUploaderVerified());
|
||||
} catch (final Exception e) {
|
||||
addError(e);
|
||||
}
|
||||
try {
|
||||
resultItem.setThumbnails(extractor.getThumbnails());
|
||||
} catch (final Exception e) {
|
||||
addError(e);
|
||||
}
|
||||
|
@ -31,6 +41,11 @@ public class PlaylistInfoItemsCollector
|
|||
} catch (final Exception e) {
|
||||
addError(e);
|
||||
}
|
||||
try {
|
||||
resultItem.setDescription(extractor.getDescription());
|
||||
} catch (final Exception e) {
|
||||
addError(e);
|
||||
}
|
||||
try {
|
||||
resultItem.setPlaylistType(extractor.getPlaylistType());
|
||||
} catch (final Exception e) {
|
||||
|
|
|
@ -19,7 +19,7 @@ public class SearchInfo extends ListInfo<InfoItem> {
|
|||
private final String searchString;
|
||||
private String searchSuggestion;
|
||||
private boolean isCorrectedSearch;
|
||||
private List<MetaInfo> metaInfo;
|
||||
private List<MetaInfo> metaInfo = List.of();
|
||||
|
||||
public SearchInfo(final int serviceId,
|
||||
final SearchQueryHandler qIHandler,
|
||||
|
|
|
@ -12,6 +12,7 @@ import static org.schabi.newpipe.extractor.services.bandcamp.extractors.Bandcamp
|
|||
|
||||
import org.schabi.newpipe.extractor.StreamingService;
|
||||
import org.schabi.newpipe.extractor.channel.ChannelExtractor;
|
||||
import org.schabi.newpipe.extractor.channel.tabs.ChannelTabExtractor;
|
||||
import org.schabi.newpipe.extractor.comments.CommentsExtractor;
|
||||
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
|
||||
import org.schabi.newpipe.extractor.kiosk.KioskList;
|
||||
|
@ -19,11 +20,13 @@ import org.schabi.newpipe.extractor.linkhandler.LinkHandler;
|
|||
import org.schabi.newpipe.extractor.linkhandler.LinkHandlerFactory;
|
||||
import org.schabi.newpipe.extractor.linkhandler.ListLinkHandler;
|
||||
import org.schabi.newpipe.extractor.linkhandler.ListLinkHandlerFactory;
|
||||
import org.schabi.newpipe.extractor.linkhandler.ReadyChannelTabListLinkHandler;
|
||||
import org.schabi.newpipe.extractor.linkhandler.SearchQueryHandler;
|
||||
import org.schabi.newpipe.extractor.linkhandler.SearchQueryHandlerFactory;
|
||||
import org.schabi.newpipe.extractor.playlist.PlaylistExtractor;
|
||||
import org.schabi.newpipe.extractor.search.SearchExtractor;
|
||||
import org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampChannelExtractor;
|
||||
import org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampChannelTabExtractor;
|
||||
import org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampCommentsExtractor;
|
||||
import org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampExtractorHelper;
|
||||
import org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampFeaturedExtractor;
|
||||
|
@ -34,6 +37,7 @@ import org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampSearchE
|
|||
import org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampStreamExtractor;
|
||||
import org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampSuggestionExtractor;
|
||||
import org.schabi.newpipe.extractor.services.bandcamp.linkHandler.BandcampChannelLinkHandlerFactory;
|
||||
import org.schabi.newpipe.extractor.services.bandcamp.linkHandler.BandcampChannelTabLinkHandlerFactory;
|
||||
import org.schabi.newpipe.extractor.services.bandcamp.linkHandler.BandcampCommentsLinkHandlerFactory;
|
||||
import org.schabi.newpipe.extractor.services.bandcamp.linkHandler.BandcampFeaturedLinkHandlerFactory;
|
||||
import org.schabi.newpipe.extractor.services.bandcamp.linkHandler.BandcampPlaylistLinkHandlerFactory;
|
||||
|
@ -58,27 +62,32 @@ public class BandcampService extends StreamingService {
|
|||
|
||||
@Override
|
||||
public LinkHandlerFactory getStreamLHFactory() {
|
||||
return new BandcampStreamLinkHandlerFactory();
|
||||
return BandcampStreamLinkHandlerFactory.getInstance();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ListLinkHandlerFactory getChannelLHFactory() {
|
||||
return new BandcampChannelLinkHandlerFactory();
|
||||
return BandcampChannelLinkHandlerFactory.getInstance();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ListLinkHandlerFactory getChannelTabLHFactory() {
|
||||
return BandcampChannelTabLinkHandlerFactory.getInstance();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ListLinkHandlerFactory getPlaylistLHFactory() {
|
||||
return new BandcampPlaylistLinkHandlerFactory();
|
||||
return BandcampPlaylistLinkHandlerFactory.getInstance();
|
||||
}
|
||||
|
||||
@Override
|
||||
public SearchQueryHandlerFactory getSearchQHFactory() {
|
||||
return new BandcampSearchQueryHandlerFactory();
|
||||
return BandcampSearchQueryHandlerFactory.getInstance();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ListLinkHandlerFactory getCommentsLHFactory() {
|
||||
return new BandcampCommentsLinkHandlerFactory();
|
||||
return BandcampCommentsLinkHandlerFactory.getInstance();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -98,27 +107,27 @@ public class BandcampService extends StreamingService {
|
|||
|
||||
@Override
|
||||
public KioskList getKioskList() throws ExtractionException {
|
||||
|
||||
final KioskList kioskList = new KioskList(this);
|
||||
final ListLinkHandlerFactory h = BandcampFeaturedLinkHandlerFactory.getInstance();
|
||||
|
||||
try {
|
||||
kioskList.addKioskEntry(
|
||||
(streamingService, url, kioskId) -> new BandcampFeaturedExtractor(
|
||||
BandcampService.this,
|
||||
new BandcampFeaturedLinkHandlerFactory().fromUrl(FEATURED_API_URL),
|
||||
h.fromUrl(FEATURED_API_URL),
|
||||
kioskId
|
||||
),
|
||||
new BandcampFeaturedLinkHandlerFactory(),
|
||||
h,
|
||||
KIOSK_FEATURED
|
||||
);
|
||||
|
||||
kioskList.addKioskEntry(
|
||||
(streamingService, url, kioskId) -> new BandcampRadioExtractor(
|
||||
BandcampService.this,
|
||||
new BandcampFeaturedLinkHandlerFactory().fromUrl(RADIO_API_URL),
|
||||
h.fromUrl(RADIO_API_URL),
|
||||
kioskId
|
||||
),
|
||||
new BandcampFeaturedLinkHandlerFactory(),
|
||||
h,
|
||||
KIOSK_RADIO
|
||||
);
|
||||
|
||||
|
@ -136,6 +145,15 @@ public class BandcampService extends StreamingService {
|
|||
return new BandcampChannelExtractor(this, linkHandler);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelTabExtractor getChannelTabExtractor(final ListLinkHandler linkHandler) {
|
||||
if (linkHandler instanceof ReadyChannelTabListLinkHandler) {
|
||||
return ((ReadyChannelTabListLinkHandler) linkHandler).getChannelTabExtractor(this);
|
||||
} else {
|
||||
return new BandcampChannelTabExtractor(this, linkHandler);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public PlaylistExtractor getPlaylistExtractor(final ListLinkHandler linkHandler) {
|
||||
return new BandcampPlaylistExtractor(this, linkHandler);
|
||||
|
|
|
@ -0,0 +1,62 @@
|
|||
package org.schabi.newpipe.extractor.services.bandcamp.extractors;
|
||||
|
||||
import com.grack.nanojson.JsonObject;
|
||||
|
||||
import org.schabi.newpipe.extractor.Image;
|
||||
import org.schabi.newpipe.extractor.ListExtractor;
|
||||
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
||||
import org.schabi.newpipe.extractor.playlist.PlaylistInfoItemExtractor;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
public class BandcampAlbumInfoItemExtractor implements PlaylistInfoItemExtractor {
|
||||
private final JsonObject albumInfoItem;
|
||||
private final String uploaderUrl;
|
||||
|
||||
public BandcampAlbumInfoItemExtractor(final JsonObject albumInfoItem,
|
||||
final String uploaderUrl) {
|
||||
this.albumInfoItem = albumInfoItem;
|
||||
this.uploaderUrl = uploaderUrl;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() throws ParsingException {
|
||||
return albumInfoItem.getString("title");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUrl() throws ParsingException {
|
||||
return BandcampExtractorHelper.getStreamUrlFromIds(
|
||||
albumInfoItem.getLong("band_id"),
|
||||
albumInfoItem.getLong("item_id"),
|
||||
albumInfoItem.getString("item_type"));
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public List<Image> getThumbnails() throws ParsingException {
|
||||
return BandcampExtractorHelper.getImagesFromImageId(albumInfoItem.getLong("art_id"), true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUploaderName() throws ParsingException {
|
||||
return albumInfoItem.getString("band_name");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUploaderUrl() {
|
||||
return uploaderUrl;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isUploaderVerified() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getStreamCount() {
|
||||
return ListExtractor.ITEM_COUNT_UNKNOWN;
|
||||
}
|
||||
}
|
|
@ -2,23 +2,37 @@
|
|||
|
||||
package org.schabi.newpipe.extractor.services.bandcamp.extractors;
|
||||
|
||||
import static org.schabi.newpipe.extractor.Image.HEIGHT_UNKNOWN;
|
||||
import static org.schabi.newpipe.extractor.Image.WIDTH_UNKNOWN;
|
||||
import static org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampExtractorHelper.getArtistDetails;
|
||||
import static org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampExtractorHelper.getImagesFromImageId;
|
||||
import static org.schabi.newpipe.extractor.utils.Utils.replaceHttpWithHttps;
|
||||
|
||||
import com.grack.nanojson.JsonArray;
|
||||
import com.grack.nanojson.JsonObject;
|
||||
|
||||
import org.jsoup.Jsoup;
|
||||
import org.schabi.newpipe.extractor.Page;
|
||||
import org.schabi.newpipe.extractor.Image;
|
||||
import org.schabi.newpipe.extractor.Image.ResolutionLevel;
|
||||
import org.schabi.newpipe.extractor.StreamingService;
|
||||
import org.schabi.newpipe.extractor.channel.ChannelExtractor;
|
||||
import org.schabi.newpipe.extractor.channel.tabs.ChannelTabExtractor;
|
||||
import org.schabi.newpipe.extractor.downloader.Downloader;
|
||||
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
|
||||
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
||||
import org.schabi.newpipe.extractor.exceptions.ReCaptchaException;
|
||||
import org.schabi.newpipe.extractor.channel.tabs.ChannelTabs;
|
||||
import org.schabi.newpipe.extractor.linkhandler.ListLinkHandler;
|
||||
import org.schabi.newpipe.extractor.services.bandcamp.extractors.streaminfoitem.BandcampDiscographStreamInfoItemExtractor;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfoItemsCollector;
|
||||
import org.schabi.newpipe.extractor.linkhandler.ReadyChannelTabListLinkHandler;
|
||||
import org.schabi.newpipe.extractor.services.bandcamp.linkHandler.BandcampChannelTabLinkHandlerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
|
@ -31,38 +45,36 @@ public class BandcampChannelExtractor extends ChannelExtractor {
|
|||
super(service, linkHandler);
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public String getAvatarUrl() {
|
||||
if (channelInfo.getLong("bio_image_id") == 0) {
|
||||
return "";
|
||||
}
|
||||
|
||||
return BandcampExtractorHelper.getImageUrl(channelInfo.getLong("bio_image_id"), false);
|
||||
public List<Image> getAvatars() {
|
||||
return getImagesFromImageId(channelInfo.getLong("bio_image_id"), false);
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public String getBannerUrl() throws ParsingException {
|
||||
public List<Image> getBanners() throws ParsingException {
|
||||
/*
|
||||
* Mobile API does not return the header or not the correct header.
|
||||
* Therefore, we need to query the website
|
||||
*/
|
||||
try {
|
||||
final String html = getDownloader()
|
||||
.get(channelInfo.getString("bandcamp_url")
|
||||
.replace("http://", "https://"))
|
||||
.responseBody();
|
||||
.get(replaceHttpWithHttps(channelInfo.getString("bandcamp_url")))
|
||||
.responseBody();
|
||||
|
||||
return Jsoup.parse(html)
|
||||
.getElementById("customHeader")
|
||||
.getElementsByTag("img")
|
||||
.first()
|
||||
.attr("src");
|
||||
return Stream.of(Jsoup.parse(html).getElementById("customHeader"))
|
||||
.filter(Objects::nonNull)
|
||||
.flatMap(element -> element.getElementsByTag("img").stream())
|
||||
.map(element -> element.attr("src"))
|
||||
.filter(url -> !url.isEmpty())
|
||||
.map(url -> new Image(
|
||||
replaceHttpWithHttps(url), HEIGHT_UNKNOWN, WIDTH_UNKNOWN,
|
||||
ResolutionLevel.UNKNOWN))
|
||||
.collect(Collectors.toUnmodifiableList());
|
||||
|
||||
} catch (final IOException | ReCaptchaException e) {
|
||||
throw new ParsingException("Could not download artist web site", e);
|
||||
} catch (final NullPointerException e) {
|
||||
// No banner available
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -94,9 +106,10 @@ public class BandcampChannelExtractor extends ChannelExtractor {
|
|||
return null;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public String getParentChannelAvatarUrl() {
|
||||
return null;
|
||||
public List<Image> getParentChannelAvatars() {
|
||||
return List.of();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -106,35 +119,53 @@ public class BandcampChannelExtractor extends ChannelExtractor {
|
|||
|
||||
@Nonnull
|
||||
@Override
|
||||
public InfoItemsPage<StreamInfoItem> getInitialPage() throws ParsingException {
|
||||
|
||||
final StreamInfoItemsCollector collector = new StreamInfoItemsCollector(getServiceId());
|
||||
|
||||
public List<ListLinkHandler> getTabs() throws ParsingException {
|
||||
final JsonArray discography = channelInfo.getArray("discography");
|
||||
final TabExtractorBuilder builder = new TabExtractorBuilder(discography);
|
||||
|
||||
for (int i = 0; i < discography.size(); i++) {
|
||||
// A discograph is as an item appears in a discography
|
||||
final JsonObject discograph = discography.getObject(i);
|
||||
final List<ListLinkHandler> tabs = new ArrayList<>();
|
||||
|
||||
if (!discograph.getString("item_type").equals("track")) {
|
||||
boolean foundTrackItem = false;
|
||||
boolean foundAlbumItem = false;
|
||||
|
||||
for (final Object discographyItem : discography) {
|
||||
if (foundTrackItem && foundAlbumItem) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (!(discographyItem instanceof JsonObject)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
collector.commit(new BandcampDiscographStreamInfoItemExtractor(discograph, getUrl()));
|
||||
final JsonObject discographyJsonItem = (JsonObject) discographyItem;
|
||||
final String itemType = discographyJsonItem.getString("item_type");
|
||||
|
||||
if (!foundTrackItem && "track".equals(itemType)) {
|
||||
foundTrackItem = true;
|
||||
tabs.add(new ReadyChannelTabListLinkHandler(getUrl()
|
||||
+ BandcampChannelTabLinkHandlerFactory.getUrlSuffix(ChannelTabs.TRACKS),
|
||||
getId(),
|
||||
ChannelTabs.TRACKS,
|
||||
builder));
|
||||
}
|
||||
|
||||
if (!foundAlbumItem && "album".equals(itemType)) {
|
||||
foundAlbumItem = true;
|
||||
tabs.add(new ReadyChannelTabListLinkHandler(getUrl()
|
||||
+ BandcampChannelTabLinkHandlerFactory.getUrlSuffix(ChannelTabs.ALBUMS),
|
||||
getId(),
|
||||
ChannelTabs.ALBUMS,
|
||||
builder));
|
||||
}
|
||||
}
|
||||
|
||||
return new InfoItemsPage<>(collector, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public InfoItemsPage<StreamInfoItem> getPage(final Page page) {
|
||||
return null;
|
||||
return Collections.unmodifiableList(tabs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFetchPage(@Nonnull final Downloader downloader)
|
||||
throws IOException, ExtractionException {
|
||||
channelInfo = BandcampExtractorHelper.getArtistDetails(getId());
|
||||
channelInfo = getArtistDetails(getId());
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
|
@ -142,4 +173,20 @@ public class BandcampChannelExtractor extends ChannelExtractor {
|
|||
public String getName() {
|
||||
return channelInfo.getString("name");
|
||||
}
|
||||
|
||||
private static final class TabExtractorBuilder
|
||||
implements ReadyChannelTabListLinkHandler.ChannelTabExtractorBuilder {
|
||||
private final JsonArray discography;
|
||||
|
||||
TabExtractorBuilder(final JsonArray discography) {
|
||||
this.discography = discography;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public ChannelTabExtractor build(@Nonnull final StreamingService service,
|
||||
@Nonnull final ListLinkHandler linkHandler) {
|
||||
return BandcampChannelTabExtractor.fromDiscography(service, linkHandler, discography);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,9 +3,15 @@
|
|||
package org.schabi.newpipe.extractor.services.bandcamp.extractors;
|
||||
|
||||
import org.jsoup.nodes.Element;
|
||||
import org.schabi.newpipe.extractor.Image;
|
||||
import org.schabi.newpipe.extractor.channel.ChannelInfoItemExtractor;
|
||||
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.util.List;
|
||||
|
||||
import static org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampExtractorHelper.getImagesFromSearchResult;
|
||||
|
||||
public class BandcampChannelInfoItemExtractor implements ChannelInfoItemExtractor {
|
||||
|
||||
private final Element resultInfo;
|
||||
|
@ -26,15 +32,10 @@ public class BandcampChannelInfoItemExtractor implements ChannelInfoItemExtracto
|
|||
return resultInfo.getElementsByClass("itemurl").text();
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public String getThumbnailUrl() throws ParsingException {
|
||||
final Element img = searchResult.getElementsByClass("art").first()
|
||||
.getElementsByTag("img").first();
|
||||
if (img != null) {
|
||||
return img.attr("src");
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
public List<Image> getThumbnails() throws ParsingException {
|
||||
return getImagesFromSearchResult(searchResult);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -0,0 +1,95 @@
|
|||
package org.schabi.newpipe.extractor.services.bandcamp.extractors;
|
||||
|
||||
import com.grack.nanojson.JsonArray;
|
||||
import com.grack.nanojson.JsonObject;
|
||||
import org.schabi.newpipe.extractor.InfoItem;
|
||||
import org.schabi.newpipe.extractor.MultiInfoItemsCollector;
|
||||
import org.schabi.newpipe.extractor.Page;
|
||||
import org.schabi.newpipe.extractor.StreamingService;
|
||||
import org.schabi.newpipe.extractor.channel.tabs.ChannelTabExtractor;
|
||||
import org.schabi.newpipe.extractor.channel.tabs.ChannelTabs;
|
||||
import org.schabi.newpipe.extractor.downloader.Downloader;
|
||||
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
|
||||
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
||||
import org.schabi.newpipe.extractor.linkhandler.ListLinkHandler;
|
||||
import org.schabi.newpipe.extractor.services.bandcamp.extractors.streaminfoitem.BandcampDiscographStreamInfoItemExtractor;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.io.IOException;
|
||||
|
||||
public class BandcampChannelTabExtractor extends ChannelTabExtractor {
|
||||
private JsonArray discography;
|
||||
private final String filter;
|
||||
|
||||
public BandcampChannelTabExtractor(final StreamingService service,
|
||||
final ListLinkHandler linkHandler) {
|
||||
super(service, linkHandler);
|
||||
|
||||
final String tab = linkHandler.getContentFilters().get(0);
|
||||
switch (tab) {
|
||||
case ChannelTabs.TRACKS:
|
||||
filter = "track";
|
||||
break;
|
||||
case ChannelTabs.ALBUMS:
|
||||
filter = "album";
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unsupported channel tab: " + tab);
|
||||
}
|
||||
}
|
||||
|
||||
public static BandcampChannelTabExtractor fromDiscography(final StreamingService service,
|
||||
final ListLinkHandler linkHandler,
|
||||
final JsonArray discography) {
|
||||
final BandcampChannelTabExtractor tabExtractor =
|
||||
new BandcampChannelTabExtractor(service, linkHandler);
|
||||
tabExtractor.discography = discography;
|
||||
return tabExtractor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFetchPage(@Nonnull final Downloader downloader) throws ParsingException {
|
||||
if (discography == null) {
|
||||
discography = BandcampExtractorHelper.getArtistDetails(getId())
|
||||
.getArray("discography");
|
||||
}
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public InfoItemsPage<InfoItem> getInitialPage() throws IOException, ExtractionException {
|
||||
final MultiInfoItemsCollector collector = new MultiInfoItemsCollector(getServiceId());
|
||||
|
||||
for (final Object discograph : discography) {
|
||||
// A discograph is as an item appears in a discography
|
||||
if (!(discograph instanceof JsonObject)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
final JsonObject discographJsonObject = (JsonObject) discograph;
|
||||
final String itemType = discographJsonObject.getString("item_type", "");
|
||||
|
||||
if (!itemType.equals(filter)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (itemType) {
|
||||
case "track":
|
||||
collector.commit(new BandcampDiscographStreamInfoItemExtractor(
|
||||
discographJsonObject, getUrl()));
|
||||
break;
|
||||
case "album":
|
||||
collector.commit(new BandcampAlbumInfoItemExtractor(
|
||||
discographJsonObject, getUrl()));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return new InfoItemsPage<>(collector, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public InfoItemsPage<InfoItem> getPage(final Page page) {
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -1,9 +1,13 @@
|
|||
package org.schabi.newpipe.extractor.services.bandcamp.extractors;
|
||||
|
||||
import static org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampExtractorHelper.BASE_API_URL;
|
||||
|
||||
import com.grack.nanojson.JsonArray;
|
||||
import com.grack.nanojson.JsonObject;
|
||||
import com.grack.nanojson.JsonWriter;
|
||||
|
||||
import org.jsoup.Jsoup;
|
||||
import org.jsoup.nodes.Document;
|
||||
import org.jsoup.nodes.Element;
|
||||
import org.jsoup.select.Elements;
|
||||
import org.schabi.newpipe.extractor.Page;
|
||||
import org.schabi.newpipe.extractor.StreamingService;
|
||||
import org.schabi.newpipe.extractor.comments.CommentsExtractor;
|
||||
|
@ -11,13 +15,22 @@ import org.schabi.newpipe.extractor.comments.CommentsInfoItem;
|
|||
import org.schabi.newpipe.extractor.comments.CommentsInfoItemsCollector;
|
||||
import org.schabi.newpipe.extractor.downloader.Downloader;
|
||||
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
|
||||
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
||||
import org.schabi.newpipe.extractor.exceptions.ReCaptchaException;
|
||||
import org.schabi.newpipe.extractor.linkhandler.ListLinkHandler;
|
||||
import org.schabi.newpipe.extractor.utils.JsonUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.io.IOException;
|
||||
|
||||
public class BandcampCommentsExtractor extends CommentsExtractor {
|
||||
|
||||
private static final String REVIEWS_API_URL = BASE_API_URL + "/tralbumcollectors/2/reviews";
|
||||
|
||||
private Document document;
|
||||
|
||||
|
||||
|
@ -39,18 +52,85 @@ public class BandcampCommentsExtractor extends CommentsExtractor {
|
|||
|
||||
final CommentsInfoItemsCollector collector = new CommentsInfoItemsCollector(getServiceId());
|
||||
|
||||
final Elements writings = document.getElementsByClass("writing");
|
||||
final JsonObject collectorsData = JsonUtils.toJsonObject(
|
||||
document.getElementById("collectors-data").attr("data-blob"));
|
||||
final JsonArray reviews = collectorsData.getArray("reviews");
|
||||
|
||||
for (final Element writing : writings) {
|
||||
collector.commit(new BandcampCommentsInfoItemExtractor(writing, getUrl()));
|
||||
for (final Object review : reviews) {
|
||||
collector.commit(
|
||||
new BandcampCommentsInfoItemExtractor((JsonObject) review, getUrl()));
|
||||
}
|
||||
|
||||
return new InfoItemsPage<>(collector, null);
|
||||
if (!collectorsData.getBoolean("more_reviews_available")) {
|
||||
return new InfoItemsPage<>(collector, null);
|
||||
}
|
||||
|
||||
final String trackId = getTrackId();
|
||||
final String token = getNextPageToken(reviews);
|
||||
return new InfoItemsPage<>(collector, new Page(List.of(trackId, token)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public InfoItemsPage<CommentsInfoItem> getPage(final Page page)
|
||||
throws IOException, ExtractionException {
|
||||
return null;
|
||||
|
||||
final CommentsInfoItemsCollector collector = new CommentsInfoItemsCollector(getServiceId());
|
||||
|
||||
final List<String> pageIds = page.getIds();
|
||||
final String trackId = pageIds.get(0);
|
||||
final String token = pageIds.get(1);
|
||||
final JsonObject reviewsData = fetchReviewsData(trackId, token);
|
||||
final JsonArray reviews = reviewsData.getArray("results");
|
||||
|
||||
for (final Object review : reviews) {
|
||||
collector.commit(
|
||||
new BandcampCommentsInfoItemExtractor((JsonObject) review, getUrl()));
|
||||
}
|
||||
|
||||
if (!reviewsData.getBoolean("more_available")) {
|
||||
return new InfoItemsPage<>(collector, null);
|
||||
}
|
||||
|
||||
return new InfoItemsPage<>(collector,
|
||||
new Page(List.of(trackId, getNextPageToken(reviews))));
|
||||
}
|
||||
|
||||
private JsonObject fetchReviewsData(final String trackId, final String token)
|
||||
throws ParsingException {
|
||||
try {
|
||||
return JsonUtils.toJsonObject(getDownloader().postWithContentTypeJson(
|
||||
REVIEWS_API_URL,
|
||||
Collections.emptyMap(),
|
||||
JsonWriter.string().object()
|
||||
.value("tralbum_type", "t")
|
||||
.value("tralbum_id", trackId)
|
||||
.value("token", token)
|
||||
.value("count", 7)
|
||||
.array("exclude_fan_ids").end()
|
||||
.end().done().getBytes(StandardCharsets.UTF_8)).responseBody());
|
||||
} catch (final IOException | ReCaptchaException e) {
|
||||
throw new ParsingException("Could not fetch reviews", e);
|
||||
}
|
||||
}
|
||||
|
||||
private String getNextPageToken(final JsonArray reviews) throws ParsingException {
|
||||
return reviews.stream()
|
||||
.filter(JsonObject.class::isInstance)
|
||||
.map(JsonObject.class::cast)
|
||||
.map(review -> review.getString("token"))
|
||||
.reduce((a, b) -> b) // keep only the last element
|
||||
.orElseThrow(() -> new ParsingException("Could not get token"));
|
||||
}
|
||||
|
||||
private String getTrackId() throws ParsingException {
|
||||
final JsonObject pageProperties = JsonUtils.toJsonObject(
|
||||
document.selectFirst("meta[name=bc-page-properties]")
|
||||
.attr("content"));
|
||||
return Long.toString(pageProperties.getLong("item_id"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCommentsDisabled() throws ExtractionException {
|
||||
return BandcampExtractorHelper.isRadioUrl(getUrl());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,22 +1,30 @@
|
|||
package org.schabi.newpipe.extractor.services.bandcamp.extractors;
|
||||
|
||||
import org.jsoup.nodes.Element;
|
||||
import static org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampExtractorHelper.getImagesFromImageId;
|
||||
|
||||
import com.grack.nanojson.JsonObject;
|
||||
|
||||
import org.schabi.newpipe.extractor.Image;
|
||||
import org.schabi.newpipe.extractor.comments.CommentsInfoItemExtractor;
|
||||
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
||||
import org.schabi.newpipe.extractor.stream.Description;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.util.List;
|
||||
|
||||
public class BandcampCommentsInfoItemExtractor implements CommentsInfoItemExtractor {
|
||||
|
||||
private final Element writing;
|
||||
private final JsonObject review;
|
||||
private final String url;
|
||||
|
||||
public BandcampCommentsInfoItemExtractor(final Element writing, final String url) {
|
||||
this.writing = writing;
|
||||
public BandcampCommentsInfoItemExtractor(final JsonObject review, final String url) {
|
||||
this.review = review;
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() throws ParsingException {
|
||||
return writing.getElementsByClass("text").first().ownText();
|
||||
return getCommentText().getContent();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -24,23 +32,26 @@ public class BandcampCommentsInfoItemExtractor implements CommentsInfoItemExtrac
|
|||
return url;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public String getThumbnailUrl() throws ParsingException {
|
||||
return writing.getElementsByClass("thumb").attr("src");
|
||||
public List<Image> getThumbnails() throws ParsingException {
|
||||
return getUploaderAvatars();
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public String getCommentText() {
|
||||
return writing.getElementsByClass("text").first().ownText();
|
||||
public Description getCommentText() throws ParsingException {
|
||||
return new Description(review.getString("why"), Description.PLAIN_TEXT);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUploaderName() throws ParsingException {
|
||||
return writing.getElementsByClass("name").first().text();
|
||||
return review.getString("name");
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public String getUploaderAvatarUrl() {
|
||||
return writing.getElementsByClass("thumb").attr("src");
|
||||
public List<Image> getUploaderAvatars() {
|
||||
return getImagesFromImageId(review.getLong("image_id"), false);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,19 +8,79 @@ import com.grack.nanojson.JsonParserException;
|
|||
import com.grack.nanojson.JsonWriter;
|
||||
|
||||
import org.jsoup.Jsoup;
|
||||
import org.jsoup.nodes.Element;
|
||||
import org.schabi.newpipe.extractor.Image;
|
||||
import org.schabi.newpipe.extractor.Image.ResolutionLevel;
|
||||
import org.schabi.newpipe.extractor.NewPipe;
|
||||
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
||||
import org.schabi.newpipe.extractor.exceptions.ReCaptchaException;
|
||||
import org.schabi.newpipe.extractor.localization.DateWrapper;
|
||||
import org.schabi.newpipe.extractor.utils.ImageSuffix;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.DateTimeException;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import static org.schabi.newpipe.extractor.Image.HEIGHT_UNKNOWN;
|
||||
import static org.schabi.newpipe.extractor.Image.WIDTH_UNKNOWN;
|
||||
import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty;
|
||||
import static org.schabi.newpipe.extractor.utils.Utils.replaceHttpWithHttps;
|
||||
|
||||
public final class BandcampExtractorHelper {
|
||||
|
||||
/**
|
||||
* List of image IDs which preserve aspect ratio with their theoretical dimension known.
|
||||
*
|
||||
* <p>
|
||||
* Bandcamp images are not always squares, so images which preserve aspect ratio are only used.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* One of the direct consequences of this specificity is that only one dimension of images is
|
||||
* known at time, depending of the image ID.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* Note also that dimensions are only theoretical because if the image size is less than the
|
||||
* dimensions of the image ID, it will be not upscaled but kept to its original size.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* IDs come from <a href="https://gist.github.com/f2k1de/06f5fd0ae9c919a7c3693a44ee522213">the
|
||||
* GitHub Gist "Bandcamp File Format Parameters" by f2k1de</a>
|
||||
* </p>
|
||||
*/
|
||||
private static final List<ImageSuffix> IMAGE_URL_SUFFIXES_AND_RESOLUTIONS = List.of(
|
||||
// ID | HEIGHT | WIDTH
|
||||
new ImageSuffix("10.jpg", HEIGHT_UNKNOWN, 1200, ResolutionLevel.HIGH),
|
||||
new ImageSuffix("101.jpg", 90, WIDTH_UNKNOWN, ResolutionLevel.LOW),
|
||||
new ImageSuffix("170.jpg", 422, WIDTH_UNKNOWN, ResolutionLevel.MEDIUM),
|
||||
// 180 returns the same image aspect ratio and size as 171
|
||||
new ImageSuffix("171.jpg", 646, WIDTH_UNKNOWN, ResolutionLevel.MEDIUM),
|
||||
new ImageSuffix("20.jpg", HEIGHT_UNKNOWN, 1024, ResolutionLevel.HIGH),
|
||||
// 203 returns the same image aspect ratio and size as 200
|
||||
new ImageSuffix("200.jpg", 420, WIDTH_UNKNOWN, ResolutionLevel.MEDIUM),
|
||||
new ImageSuffix("201.jpg", 280, WIDTH_UNKNOWN, ResolutionLevel.MEDIUM),
|
||||
new ImageSuffix("202.jpg", 140, WIDTH_UNKNOWN, ResolutionLevel.LOW),
|
||||
new ImageSuffix("204.jpg", 360, WIDTH_UNKNOWN, ResolutionLevel.MEDIUM),
|
||||
new ImageSuffix("205.jpg", 240, WIDTH_UNKNOWN, ResolutionLevel.MEDIUM),
|
||||
new ImageSuffix("206.jpg", 180, WIDTH_UNKNOWN, ResolutionLevel.MEDIUM),
|
||||
new ImageSuffix("207.jpg", 120, WIDTH_UNKNOWN, ResolutionLevel.LOW),
|
||||
new ImageSuffix("43.jpg", 100, WIDTH_UNKNOWN, ResolutionLevel.LOW),
|
||||
new ImageSuffix("44.jpg", 200, WIDTH_UNKNOWN, ResolutionLevel.MEDIUM));
|
||||
|
||||
private static final String IMAGE_URL_APPENDIX_AND_EXTENSION_REGEX = "_\\d+\\.\\w+";
|
||||
private static final String IMAGES_DOMAIN_AND_PATH = "https://f4.bcbits.com/img/";
|
||||
|
||||
public static final String BASE_URL = "https://bandcamp.com";
|
||||
public static final String BASE_API_URL = BASE_URL + "/api";
|
||||
|
||||
|
@ -40,8 +100,8 @@ public final class BandcampExtractorHelper {
|
|||
+ "&tralbum_id=" + itemId + "&tralbum_type=" + itemType.charAt(0))
|
||||
.responseBody();
|
||||
|
||||
return JsonParser.object().from(jsonString)
|
||||
.getString("bandcamp_url").replace("http://", "https://");
|
||||
return replaceHttpWithHttps(JsonParser.object().from(jsonString)
|
||||
.getString("bandcamp_url"));
|
||||
|
||||
} catch (final JsonParserException | ReCaptchaException | IOException e) {
|
||||
throw new ParsingException("Ids could not be translated to URL", e);
|
||||
|
@ -51,41 +111,47 @@ public final class BandcampExtractorHelper {
|
|||
|
||||
/**
|
||||
* Fetch artist details from mobile endpoint.
|
||||
* <a href=https://notabug.org/fynngodau/bandcampDirect/wiki/rewindBandcamp+%E2%80%93+Fetching+artist+details>
|
||||
* <a href="https://notabug.org/fynngodau/bandcampDirect/wiki/
|
||||
* rewindBandcamp+%E2%80%93+Fetching+artist+details">
|
||||
* More technical info.</a>
|
||||
*/
|
||||
public static JsonObject getArtistDetails(final String id) throws ParsingException {
|
||||
try {
|
||||
return
|
||||
JsonParser.object().from(
|
||||
NewPipe.getDownloader().post(
|
||||
BASE_API_URL + "/mobile/22/band_details",
|
||||
null,
|
||||
JsonWriter.string()
|
||||
.object()
|
||||
.value("band_id", id)
|
||||
.end()
|
||||
.done()
|
||||
.getBytes()
|
||||
).responseBody()
|
||||
);
|
||||
return JsonParser.object().from(NewPipe.getDownloader().postWithContentTypeJson(
|
||||
BASE_API_URL + "/mobile/22/band_details",
|
||||
Collections.emptyMap(),
|
||||
JsonWriter.string()
|
||||
.object()
|
||||
.value("band_id", id)
|
||||
.end()
|
||||
.done()
|
||||
.getBytes(StandardCharsets.UTF_8)).responseBody());
|
||||
} catch (final IOException | ReCaptchaException | JsonParserException e) {
|
||||
throw new ParsingException("Could not download band details", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate image url from image ID.
|
||||
* <p>
|
||||
* The appendix "_10" was chosen because it provides images sized 1200x1200. Other integer
|
||||
* values are possible as well (e.g. 0 is a very large resolution, possibly the original).
|
||||
* Generate an image url from an image ID.
|
||||
*
|
||||
* @param id The image ID
|
||||
* @param album True if this is the cover of an album or track
|
||||
* @return URL of image with this ID sized 1200x1200
|
||||
* <p>
|
||||
* The image ID {@code 10} was chosen because it provides images wide up to 1200px (when
|
||||
* the original image width is more than or equal this resolution).
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* Other integer values are possible as well (e.g. 0 is a very large resolution, possibly the
|
||||
* original); see {@link #IMAGE_URL_SUFFIXES_AND_RESOLUTIONS} for more details about image
|
||||
* resolution IDs.
|
||||
* </p>
|
||||
*
|
||||
* @param id the image ID
|
||||
* @param isAlbum whether the image is the cover of an album or a track
|
||||
* @return a URL of the image with this ID with a width up to 1200px
|
||||
*/
|
||||
public static String getImageUrl(final long id, final boolean album) {
|
||||
return "https://f4.bcbits.com/img/" + (album ? 'a' : "") + id + "_10.jpg";
|
||||
@Nonnull
|
||||
public static String getImageUrl(final long id, final boolean isAlbum) {
|
||||
return IMAGES_DOMAIN_AND_PATH + (isAlbum ? 'a' : "") + id + "_10.jpg";
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -119,7 +185,7 @@ public final class BandcampExtractorHelper {
|
|||
/**
|
||||
* Whether the URL points to a radio kiosk.
|
||||
* @param url the URL to check
|
||||
* @return true if the URL matches <code>https://bandcamp.com/?show=SHOW_ID</code>
|
||||
* @return true if the URL matches {@code https://bandcamp.com/?show=SHOW_ID}
|
||||
*/
|
||||
public static boolean isRadioUrl(final String url) {
|
||||
return url.toLowerCase().matches("https?://bandcamp\\.com/\\?show=\\d+");
|
||||
|
@ -134,4 +200,95 @@ public final class BandcampExtractorHelper {
|
|||
throw new ParsingException("Could not parse date '" + textDate + "'", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of images from a search result {@link Element}.
|
||||
*
|
||||
* <p>
|
||||
* This method will call {@link #getImagesFromImageUrl(String)} using the first non null and
|
||||
* non empty image URL found from the {@code src} attribute of {@code img} HTML elements, or an
|
||||
* empty string if no valid image URL was found.
|
||||
* </p>
|
||||
*
|
||||
* @param searchResult a search result {@link Element}
|
||||
* @return an unmodifiable list of {@link Image}s, which is never null but can be empty, in the
|
||||
* case where no valid image URL was found
|
||||
*/
|
||||
@Nonnull
|
||||
public static List<Image> getImagesFromSearchResult(@Nonnull final Element searchResult) {
|
||||
return getImagesFromImageUrl(searchResult.getElementsByClass("art")
|
||||
.stream()
|
||||
.flatMap(element -> element.getElementsByTag("img").stream())
|
||||
.map(element -> element.attr("src"))
|
||||
.filter(imageUrl -> !isNullOrEmpty(imageUrl))
|
||||
.findFirst()
|
||||
.orElse(""));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all images which have resolutions preserving aspect ratio from an image URL.
|
||||
*
|
||||
* <p>
|
||||
* This method will remove the image ID and its extension from the end of the URL and then call
|
||||
* {@link #getImagesFromImageBaseUrl(String)}.
|
||||
* </p>
|
||||
*
|
||||
* @param imageUrl the full URL of an image provided by Bandcamp, such as in its HTML code
|
||||
* @return an unmodifiable list of {@link Image}s, which is never null but can be empty, in the
|
||||
* case where the image URL has been not extracted (and so is null or empty)
|
||||
*/
|
||||
@Nonnull
|
||||
public static List<Image> getImagesFromImageUrl(@Nullable final String imageUrl) {
|
||||
if (isNullOrEmpty(imageUrl)) {
|
||||
return List.of();
|
||||
}
|
||||
|
||||
return getImagesFromImageBaseUrl(
|
||||
imageUrl.replaceFirst(IMAGE_URL_APPENDIX_AND_EXTENSION_REGEX, "_"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all images which have resolutions preserving aspect ratio from an image ID.
|
||||
*
|
||||
* <p>
|
||||
* This method will call {@link #getImagesFromImageBaseUrl(String)}.
|
||||
* </p>
|
||||
*
|
||||
* @param id the id of an image provided by Bandcamp
|
||||
* @param isAlbum whether the image is the cover of an album
|
||||
* @return an unmodifiable list of {@link Image}s, which is never null but can be empty, in the
|
||||
* case where the image ID has been not extracted (and so equal to 0)
|
||||
*/
|
||||
@Nonnull
|
||||
public static List<Image> getImagesFromImageId(final long id, final boolean isAlbum) {
|
||||
if (id == 0) {
|
||||
return List.of();
|
||||
}
|
||||
|
||||
return getImagesFromImageBaseUrl(IMAGES_DOMAIN_AND_PATH + (isAlbum ? 'a' : "") + id + "_");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all images resolutions preserving aspect ratio from a base image URL.
|
||||
*
|
||||
* <p>
|
||||
* Base image URLs are images containing the image path, a {@code a} letter if it comes from an
|
||||
* album, its ID and an underscore.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* Images resolutions returned are the ones of {@link #IMAGE_URL_SUFFIXES_AND_RESOLUTIONS}.
|
||||
* </p>
|
||||
*
|
||||
* @param baseUrl the base URL of the image
|
||||
* @return an unmodifiable and non-empty list of {@link Image}s
|
||||
*/
|
||||
@Nonnull
|
||||
private static List<Image> getImagesFromImageBaseUrl(@Nonnull final String baseUrl) {
|
||||
return IMAGE_URL_SUFFIXES_AND_RESOLUTIONS.stream()
|
||||
.map(imageSuffix -> new Image(baseUrl + imageSuffix.getSuffix(),
|
||||
imageSuffix.getHeight(), imageSuffix.getWidth(),
|
||||
imageSuffix.getResolutionLevel()))
|
||||
.collect(Collectors.toUnmodifiableList());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,6 +18,8 @@ import org.schabi.newpipe.extractor.playlist.PlaylistInfoItemsCollector;
|
|||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Collections;
|
||||
|
||||
import static org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampExtractorHelper.BASE_API_URL;
|
||||
|
||||
|
@ -40,11 +42,11 @@ public class BandcampFeaturedExtractor extends KioskExtractor<PlaylistInfoItem>
|
|||
public void onFetchPage(@Nonnull final Downloader downloader)
|
||||
throws IOException, ExtractionException {
|
||||
try {
|
||||
json = JsonParser.object().from(
|
||||
getDownloader().post(
|
||||
FEATURED_API_URL, null, "{\"platform\":\"\",\"version\":0}".getBytes()
|
||||
).responseBody()
|
||||
);
|
||||
json = JsonParser.object().from(getDownloader().postWithContentTypeJson(
|
||||
FEATURED_API_URL,
|
||||
Collections.emptyMap(),
|
||||
"{\"platform\":\"\",\"version\":0}".getBytes(StandardCharsets.UTF_8))
|
||||
.responseBody());
|
||||
} catch (final JsonParserException e) {
|
||||
throw new ParsingException("Could not parse Bandcamp featured API response", e);
|
||||
}
|
||||
|
|
|
@ -1,31 +1,38 @@
|
|||
package org.schabi.newpipe.extractor.services.bandcamp.extractors;
|
||||
|
||||
import static org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampExtractorHelper.getImagesFromImageId;
|
||||
import static org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampExtractorHelper.getImagesFromImageUrl;
|
||||
import static org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampStreamExtractor.getAlbumInfoJson;
|
||||
import static org.schabi.newpipe.extractor.utils.JsonUtils.getJsonData;
|
||||
import static org.schabi.newpipe.extractor.utils.Utils.HTTPS;
|
||||
|
||||
import com.grack.nanojson.JsonArray;
|
||||
import com.grack.nanojson.JsonObject;
|
||||
import com.grack.nanojson.JsonParserException;
|
||||
|
||||
import org.jsoup.Jsoup;
|
||||
import org.jsoup.nodes.Document;
|
||||
import org.jsoup.nodes.Element;
|
||||
import org.jsoup.select.Elements;
|
||||
import org.schabi.newpipe.extractor.Image;
|
||||
import org.schabi.newpipe.extractor.Page;
|
||||
import org.schabi.newpipe.extractor.StreamingService;
|
||||
import org.schabi.newpipe.extractor.downloader.Downloader;
|
||||
import org.schabi.newpipe.extractor.exceptions.ContentNotAvailableException;
|
||||
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
|
||||
import org.schabi.newpipe.extractor.exceptions.PaidContentException;
|
||||
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
||||
import org.schabi.newpipe.extractor.linkhandler.ListLinkHandler;
|
||||
import org.schabi.newpipe.extractor.playlist.PlaylistExtractor;
|
||||
import org.schabi.newpipe.extractor.services.bandcamp.extractors.streaminfoitem.BandcampPlaylistStreamInfoItemExtractor;
|
||||
import org.schabi.newpipe.extractor.stream.Description;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfoItemsCollector;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.io.IOException;
|
||||
import java.util.Objects;
|
||||
import java.util.List;
|
||||
|
||||
import static org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampExtractorHelper.getImageUrl;
|
||||
import static org.schabi.newpipe.extractor.utils.JsonUtils.getJsonData;
|
||||
import static org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampStreamExtractor.getAlbumInfoJson;
|
||||
import static org.schabi.newpipe.extractor.utils.Utils.EMPTY_STRING;
|
||||
import static org.schabi.newpipe.extractor.utils.Utils.HTTPS;
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
public class BandcampPlaylistExtractor extends PlaylistExtractor {
|
||||
|
||||
|
@ -64,17 +71,17 @@ public class BandcampPlaylistExtractor extends PlaylistExtractor {
|
|||
|
||||
if (trackInfo.isEmpty()) {
|
||||
// Albums without trackInfo need to be purchased before they can be played
|
||||
throw new ContentNotAvailableException("Album needs to be purchased");
|
||||
throw new PaidContentException("Album needs to be purchased");
|
||||
}
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public String getThumbnailUrl() throws ParsingException {
|
||||
public List<Image> getThumbnails() throws ParsingException {
|
||||
if (albumJson.isNull("art_id")) {
|
||||
return EMPTY_STRING;
|
||||
return List.of();
|
||||
} else {
|
||||
return getImageUrl(albumJson.getLong("art_id"), true);
|
||||
return getImagesFromImageId(albumJson.getLong("art_id"), true);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -90,14 +97,14 @@ public class BandcampPlaylistExtractor extends PlaylistExtractor {
|
|||
return albumJson.getString("artist");
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public String getUploaderAvatarUrl() {
|
||||
try {
|
||||
return Objects.requireNonNull(document.getElementsByClass("band-photo").first())
|
||||
.attr("src");
|
||||
} catch (final NullPointerException e) {
|
||||
return EMPTY_STRING;
|
||||
}
|
||||
public List<Image> getUploaderAvatars() {
|
||||
return getImagesFromImageUrl(document.getElementsByClass("band-photo")
|
||||
.stream()
|
||||
.map(element -> element.attr("src"))
|
||||
.findFirst()
|
||||
.orElse(""));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -110,6 +117,32 @@ public class BandcampPlaylistExtractor extends PlaylistExtractor {
|
|||
return trackInfo.size();
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public Description getDescription() throws ParsingException {
|
||||
final Element tInfo = document.getElementById("trackInfo");
|
||||
if (tInfo == null) {
|
||||
throw new ParsingException("Could not find trackInfo in document");
|
||||
}
|
||||
final Elements about = tInfo.getElementsByClass("tralbum-about");
|
||||
final Elements credits = tInfo.getElementsByClass("tralbum-credits");
|
||||
final Element license = document.getElementById("license");
|
||||
if (about.isEmpty() && credits.isEmpty() && license == null) {
|
||||
return Description.EMPTY_DESCRIPTION;
|
||||
}
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
if (!about.isEmpty()) {
|
||||
sb.append(Objects.requireNonNull(about.first()).html());
|
||||
}
|
||||
if (!credits.isEmpty()) {
|
||||
sb.append(Objects.requireNonNull(credits.first()).html());
|
||||
}
|
||||
if (license != null) {
|
||||
sb.append(license.html());
|
||||
}
|
||||
return new Description(sb.toString(), Description.HTML);
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public InfoItemsPage<StreamInfoItem> getInitialPage() throws ExtractionException {
|
||||
|
@ -126,7 +159,7 @@ public class BandcampPlaylistExtractor extends PlaylistExtractor {
|
|||
} else {
|
||||
// Pretend every track has the same cover art as the album
|
||||
collector.commit(new BandcampPlaylistStreamInfoItemExtractor(
|
||||
track, getUploaderUrl(), getThumbnailUrl()));
|
||||
track, getUploaderUrl(), getThumbnails()));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,9 +1,13 @@
|
|||
package org.schabi.newpipe.extractor.services.bandcamp.extractors;
|
||||
|
||||
import org.jsoup.nodes.Element;
|
||||
import org.schabi.newpipe.extractor.Image;
|
||||
import org.schabi.newpipe.extractor.playlist.PlaylistInfoItemExtractor;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.util.List;
|
||||
|
||||
import static org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampExtractorHelper.getImagesFromSearchResult;
|
||||
|
||||
public class BandcampPlaylistInfoItemExtractor implements PlaylistInfoItemExtractor {
|
||||
private final Element searchResult;
|
||||
|
@ -20,6 +24,16 @@ public class BandcampPlaylistInfoItemExtractor implements PlaylistInfoItemExtrac
|
|||
.split(" by")[0];
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUploaderUrl() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isUploaderVerified() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getStreamCount() {
|
||||
final String length = resultInfo.getElementsByClass("length").text();
|
||||
|
@ -36,14 +50,9 @@ public class BandcampPlaylistInfoItemExtractor implements PlaylistInfoItemExtrac
|
|||
return resultInfo.getElementsByClass("itemurl").text();
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public String getThumbnailUrl() {
|
||||
final Element img = searchResult.getElementsByClass("art").first()
|
||||
.getElementsByTag("img").first();
|
||||
if (img != null) {
|
||||
return img.attr("src");
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
public List<Image> getThumbnails() {
|
||||
return getImagesFromSearchResult(searchResult);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,9 +1,14 @@
|
|||
package org.schabi.newpipe.extractor.services.bandcamp.extractors;
|
||||
|
||||
import com.grack.nanojson.JsonObject;
|
||||
import org.schabi.newpipe.extractor.Image;
|
||||
import org.schabi.newpipe.extractor.playlist.PlaylistInfoItemExtractor;
|
||||
import org.schabi.newpipe.extractor.utils.Utils;
|
||||
|
||||
import static org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampExtractorHelper.getImageUrl;
|
||||
import javax.annotation.Nonnull;
|
||||
import java.util.List;
|
||||
|
||||
import static org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampExtractorHelper.getImagesFromImageId;
|
||||
|
||||
public class BandcampPlaylistInfoItemFeaturedExtractor implements PlaylistInfoItemExtractor {
|
||||
|
||||
|
@ -18,6 +23,16 @@ public class BandcampPlaylistInfoItemFeaturedExtractor implements PlaylistInfoIt
|
|||
return featuredStory.getString("band_name");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUploaderUrl() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isUploaderVerified() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getStreamCount() {
|
||||
return featuredStory.getInt("num_streamable_tracks");
|
||||
|
@ -30,12 +45,14 @@ public class BandcampPlaylistInfoItemFeaturedExtractor implements PlaylistInfoIt
|
|||
|
||||
@Override
|
||||
public String getUrl() {
|
||||
return featuredStory.getString("item_url").replaceAll("http://", "https://");
|
||||
return Utils.replaceHttpWithHttps(featuredStory.getString("item_url"));
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public String getThumbnailUrl() {
|
||||
return featuredStory.has("art_id") ? getImageUrl(featuredStory.getLong("art_id"), true)
|
||||
: getImageUrl(featuredStory.getLong("item_art_id"), true);
|
||||
public List<Image> getThumbnails() {
|
||||
return featuredStory.has("art_id")
|
||||
? getImagesFromImageId(featuredStory.getLong("art_id"), true)
|
||||
: getImagesFromImageId(featuredStory.getLong("item_art_id"), true);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,15 +3,20 @@
|
|||
package org.schabi.newpipe.extractor.services.bandcamp.extractors;
|
||||
|
||||
import com.grack.nanojson.JsonObject;
|
||||
import org.schabi.newpipe.extractor.Image;
|
||||
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
||||
import org.schabi.newpipe.extractor.localization.DateWrapper;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfoItemExtractor;
|
||||
import org.schabi.newpipe.extractor.stream.StreamType;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampExtractorHelper.BASE_URL;
|
||||
import static org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampExtractorHelper.getImageUrl;
|
||||
import static org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampExtractorHelper.getImagesFromImageId;
|
||||
import static org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampExtractorHelper.parseDate;
|
||||
|
||||
public class BandcampRadioInfoItemExtractor implements StreamInfoItemExtractor {
|
||||
|
||||
|
@ -39,7 +44,7 @@ public class BandcampRadioInfoItemExtractor implements StreamInfoItemExtractor {
|
|||
@Nullable
|
||||
@Override
|
||||
public DateWrapper getUploadDate() throws ParsingException {
|
||||
return BandcampExtractorHelper.parseDate(getTextualUploadDate());
|
||||
return parseDate(getTextualUploadDate());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -52,9 +57,10 @@ public class BandcampRadioInfoItemExtractor implements StreamInfoItemExtractor {
|
|||
return BASE_URL + "/?show=" + show.getInt("id");
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public String getThumbnailUrl() {
|
||||
return getImageUrl(show.getLong("image_id"), false);
|
||||
public List<Image> getThumbnails() {
|
||||
return getImagesFromImageId(show.getLong("image_id"), false);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -78,12 +84,6 @@ public class BandcampRadioInfoItemExtractor implements StreamInfoItemExtractor {
|
|||
return "";
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public String getUploaderAvatarUrl() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isUploaderVerified() throws ParsingException {
|
||||
return false;
|
||||
|
|
|
@ -1,10 +1,19 @@
|
|||
package org.schabi.newpipe.extractor.services.bandcamp.extractors;
|
||||
|
||||
import static org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampExtractorHelper.BASE_API_URL;
|
||||
import static org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampExtractorHelper.BASE_URL;
|
||||
import static org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampExtractorHelper.getImageUrl;
|
||||
import static org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampExtractorHelper.getImagesFromImageId;
|
||||
|
||||
import com.grack.nanojson.JsonArray;
|
||||
import com.grack.nanojson.JsonObject;
|
||||
import com.grack.nanojson.JsonParser;
|
||||
import com.grack.nanojson.JsonParserException;
|
||||
|
||||
import org.jsoup.Jsoup;
|
||||
import org.jsoup.nodes.Element;
|
||||
import org.schabi.newpipe.extractor.Image;
|
||||
import org.schabi.newpipe.extractor.Image.ResolutionLevel;
|
||||
import org.schabi.newpipe.extractor.MediaFormat;
|
||||
import org.schabi.newpipe.extractor.NewPipe;
|
||||
import org.schabi.newpipe.extractor.StreamingService;
|
||||
|
@ -19,19 +28,18 @@ import org.schabi.newpipe.extractor.stream.AudioStream;
|
|||
import org.schabi.newpipe.extractor.stream.Description;
|
||||
import org.schabi.newpipe.extractor.stream.StreamSegment;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import static org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampExtractorHelper.BASE_API_URL;
|
||||
import static org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampExtractorHelper.BASE_URL;
|
||||
import static org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampExtractorHelper.getImageUrl;
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
public class BandcampRadioStreamExtractor extends BandcampStreamExtractor {
|
||||
|
||||
private static final String OPUS_LO = "opus-lo";
|
||||
private static final String MP3_128 = "mp3-128";
|
||||
private JsonObject showInfo;
|
||||
|
||||
public BandcampRadioStreamExtractor(final StreamingService service,
|
||||
|
@ -77,9 +85,11 @@ public class BandcampRadioStreamExtractor extends BandcampStreamExtractor {
|
|||
|
||||
@Nonnull
|
||||
@Override
|
||||
public String getUploaderName() {
|
||||
return Jsoup.parse(showInfo.getString("image_caption"))
|
||||
.getElementsByTag("a").first().text();
|
||||
public String getUploaderName() throws ParsingException {
|
||||
return Jsoup.parse(showInfo.getString("image_caption")).getElementsByTag("a").stream()
|
||||
.map(Element::text)
|
||||
.findFirst()
|
||||
.orElseThrow(() -> new ParsingException("Could not get uploader name"));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
|
@ -90,14 +100,16 @@ public class BandcampRadioStreamExtractor extends BandcampStreamExtractor {
|
|||
|
||||
@Nonnull
|
||||
@Override
|
||||
public String getThumbnailUrl() throws ParsingException {
|
||||
return getImageUrl(showInfo.getLong("show_image_id"), false);
|
||||
public List<Image> getThumbnails() throws ParsingException {
|
||||
return getImagesFromImageId(showInfo.getLong("show_image_id"), false);
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public String getUploaderAvatarUrl() {
|
||||
return BASE_URL + "/img/buttons/bandcamp-button-circle-whitecolor-512.png";
|
||||
public List<Image> getUploaderAvatars() {
|
||||
return Collections.singletonList(
|
||||
new Image(BASE_URL + "/img/buttons/bandcamp-button-circle-whitecolor-512.png",
|
||||
512, 512, ResolutionLevel.MEDIUM));
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
|
@ -113,23 +125,27 @@ public class BandcampRadioStreamExtractor extends BandcampStreamExtractor {
|
|||
|
||||
@Override
|
||||
public List<AudioStream> getAudioStreams() {
|
||||
final ArrayList<AudioStream> list = new ArrayList<>();
|
||||
final List<AudioStream> audioStreams = new ArrayList<>();
|
||||
final JsonObject streams = showInfo.getObject("audio_stream");
|
||||
|
||||
if (streams.has("opus-lo")) {
|
||||
list.add(new AudioStream(
|
||||
streams.getString("opus-lo"),
|
||||
MediaFormat.OPUS, 100
|
||||
));
|
||||
}
|
||||
if (streams.has("mp3-128")) {
|
||||
list.add(new AudioStream(
|
||||
streams.getString("mp3-128"),
|
||||
MediaFormat.MP3, 128
|
||||
));
|
||||
if (streams.has(MP3_128)) {
|
||||
audioStreams.add(new AudioStream.Builder()
|
||||
.setId(MP3_128)
|
||||
.setContent(streams.getString(MP3_128), true)
|
||||
.setMediaFormat(MediaFormat.MP3)
|
||||
.setAverageBitrate(128)
|
||||
.build());
|
||||
}
|
||||
|
||||
return list;
|
||||
if (streams.has(OPUS_LO)) {
|
||||
audioStreams.add(new AudioStream.Builder()
|
||||
.setId(OPUS_LO)
|
||||
.setContent(streams.getString(OPUS_LO), true)
|
||||
.setMediaFormat(MediaFormat.OPUS)
|
||||
.setAverageBitrate(100).build());
|
||||
}
|
||||
|
||||
return audioStreams;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
|
|
|
@ -3,10 +3,14 @@
|
|||
package org.schabi.newpipe.extractor.services.bandcamp.extractors;
|
||||
|
||||
import org.jsoup.nodes.Element;
|
||||
import org.schabi.newpipe.extractor.Image;
|
||||
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
||||
import org.schabi.newpipe.extractor.playlist.PlaylistInfoItemExtractor;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.util.List;
|
||||
|
||||
import static org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampExtractorHelper.getImagesFromImageUrl;
|
||||
|
||||
/**
|
||||
* Extracts recommended albums from tracks' website
|
||||
|
@ -25,12 +29,13 @@ public class BandcampRelatedPlaylistInfoItemExtractor implements PlaylistInfoIte
|
|||
|
||||
@Override
|
||||
public String getUrl() throws ParsingException {
|
||||
return relatedAlbum.getElementsByClass("title-and-artist").attr("abs:href");
|
||||
return relatedAlbum.getElementsByClass("album-link").attr("abs:href");
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public String getThumbnailUrl() throws ParsingException {
|
||||
return relatedAlbum.getElementsByClass("album-art").attr("src");
|
||||
public List<Image> getThumbnails() throws ParsingException {
|
||||
return getImagesFromImageUrl(relatedAlbum.getElementsByClass("album-art").attr("src"));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -38,6 +43,16 @@ public class BandcampRelatedPlaylistInfoItemExtractor implements PlaylistInfoIte
|
|||
return relatedAlbum.getElementsByClass("by-artist").text().replace("by ", "");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUploaderUrl() throws ParsingException {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isUploaderVerified() throws ParsingException {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getStreamCount() throws ParsingException {
|
||||
return -1;
|
||||
|
|
|
@ -2,28 +2,28 @@
|
|||
|
||||
package org.schabi.newpipe.extractor.services.bandcamp.extractors;
|
||||
|
||||
import edu.umd.cs.findbugs.annotations.NonNull;
|
||||
import org.jsoup.Jsoup;
|
||||
import org.jsoup.nodes.Document;
|
||||
import org.jsoup.nodes.Element;
|
||||
import org.jsoup.select.Elements;
|
||||
import org.schabi.newpipe.extractor.InfoItem;
|
||||
import org.schabi.newpipe.extractor.MetaInfo;
|
||||
import org.schabi.newpipe.extractor.MultiInfoItemsCollector;
|
||||
import org.schabi.newpipe.extractor.Page;
|
||||
import org.schabi.newpipe.extractor.StreamingService;
|
||||
import org.schabi.newpipe.extractor.downloader.Downloader;
|
||||
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
|
||||
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
||||
import org.schabi.newpipe.extractor.linkhandler.SearchQueryHandler;
|
||||
import org.schabi.newpipe.extractor.MultiInfoItemsCollector;
|
||||
import org.schabi.newpipe.extractor.search.SearchExtractor;
|
||||
import org.schabi.newpipe.extractor.services.bandcamp.extractors.streaminfoitem.BandcampSearchStreamInfoItemExtractor;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
public class BandcampSearchExtractor extends SearchExtractor {
|
||||
|
||||
public BandcampSearchExtractor(final StreamingService service,
|
||||
|
@ -31,7 +31,7 @@ public class BandcampSearchExtractor extends SearchExtractor {
|
|||
super(service, linkHandler);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Nonnull
|
||||
@Override
|
||||
public String getSearchSuggestion() {
|
||||
return "";
|
||||
|
@ -50,40 +50,30 @@ public class BandcampSearchExtractor extends SearchExtractor {
|
|||
|
||||
public InfoItemsPage<InfoItem> getPage(final Page page)
|
||||
throws IOException, ExtractionException {
|
||||
final String html = getDownloader().get(page.getUrl()).responseBody();
|
||||
|
||||
final MultiInfoItemsCollector collector = new MultiInfoItemsCollector(getServiceId());
|
||||
final Document d = Jsoup.parse(getDownloader().get(page.getUrl()).responseBody());
|
||||
|
||||
|
||||
final Document d = Jsoup.parse(html);
|
||||
|
||||
final Elements searchResultsElements = d.getElementsByClass("searchresult");
|
||||
|
||||
for (final Element searchResult : searchResultsElements) {
|
||||
|
||||
final String type = searchResult.getElementsByClass("result-info").first()
|
||||
.getElementsByClass("itemtype").first().text();
|
||||
for (final Element searchResult : d.getElementsByClass("searchresult")) {
|
||||
final String type = searchResult.getElementsByClass("result-info").stream()
|
||||
.flatMap(element -> element.getElementsByClass("itemtype").stream())
|
||||
.map(Element::text)
|
||||
.findFirst()
|
||||
.orElse("");
|
||||
|
||||
switch (type) {
|
||||
default:
|
||||
continue;
|
||||
case "FAN":
|
||||
// don't display fan results
|
||||
break;
|
||||
|
||||
case "ARTIST":
|
||||
collector.commit(new BandcampChannelInfoItemExtractor(searchResult));
|
||||
break;
|
||||
|
||||
case "ALBUM":
|
||||
collector.commit(new BandcampPlaylistInfoItemExtractor(searchResult));
|
||||
break;
|
||||
|
||||
case "TRACK":
|
||||
collector.commit(new BandcampSearchStreamInfoItemExtractor(searchResult, null));
|
||||
break;
|
||||
default:
|
||||
// don't display fan results ("FAN") or other things
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Count pages
|
||||
|
@ -92,7 +82,10 @@ public class BandcampSearchExtractor extends SearchExtractor {
|
|||
return new InfoItemsPage<>(collector, null);
|
||||
}
|
||||
|
||||
final Elements pages = pageLists.first().getElementsByTag("li");
|
||||
final Elements pages = pageLists.stream()
|
||||
.map(element -> element.getElementsByTag("li"))
|
||||
.findFirst()
|
||||
.orElseGet(Elements::new);
|
||||
|
||||
// Find current page
|
||||
int currentPage = -1;
|
||||
|
|
|
@ -2,7 +2,11 @@
|
|||
|
||||
package org.schabi.newpipe.extractor.services.bandcamp.extractors;
|
||||
|
||||
import static org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampExtractorHelper.getImageUrl;
|
||||
import static org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampExtractorHelper.getImagesFromImageId;
|
||||
import static org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampExtractorHelper.getImagesFromImageUrl;
|
||||
import static org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampExtractorHelper.parseDate;
|
||||
import static org.schabi.newpipe.extractor.utils.Utils.HTTPS;
|
||||
import static org.schabi.newpipe.extractor.utils.Utils.replaceHttpWithHttps;
|
||||
|
||||
import com.grack.nanojson.JsonObject;
|
||||
import com.grack.nanojson.JsonParserException;
|
||||
|
@ -10,11 +14,12 @@ import com.grack.nanojson.JsonParserException;
|
|||
import org.jsoup.Jsoup;
|
||||
import org.jsoup.nodes.Document;
|
||||
import org.jsoup.nodes.Element;
|
||||
import org.jsoup.select.Elements;
|
||||
import org.schabi.newpipe.extractor.Image;
|
||||
import org.schabi.newpipe.extractor.MediaFormat;
|
||||
import org.schabi.newpipe.extractor.StreamingService;
|
||||
import org.schabi.newpipe.extractor.downloader.Downloader;
|
||||
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
|
||||
import org.schabi.newpipe.extractor.exceptions.PaidContentException;
|
||||
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
||||
import org.schabi.newpipe.extractor.linkhandler.LinkHandler;
|
||||
import org.schabi.newpipe.extractor.localization.DateWrapper;
|
||||
|
@ -28,15 +33,14 @@ import org.schabi.newpipe.extractor.utils.JsonUtils;
|
|||
import org.schabi.newpipe.extractor.utils.Utils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
public class BandcampStreamExtractor extends StreamExtractor {
|
||||
|
||||
private JsonObject albumJson;
|
||||
private JsonObject current;
|
||||
private Document document;
|
||||
|
@ -58,6 +62,10 @@ public class BandcampStreamExtractor extends StreamExtractor {
|
|||
// In this case, we are actually viewing an album page!
|
||||
throw new ExtractionException("Page is actually an album, not a track");
|
||||
}
|
||||
|
||||
if (albumJson.getArray("trackinfo").getObject(0).isNull("file")) {
|
||||
throw new PaidContentException("This track is not available without being purchased");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -88,13 +96,13 @@ public class BandcampStreamExtractor extends StreamExtractor {
|
|||
public String getUploaderUrl() throws ParsingException {
|
||||
final String[] parts = getUrl().split("/");
|
||||
// https: (/) (/) * .bandcamp.com (/) and leave out the rest
|
||||
return "https://" + parts[2] + "/";
|
||||
return HTTPS + parts[2] + "/";
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public String getUrl() throws ParsingException {
|
||||
return albumJson.getString("url").replace("http://", "https://");
|
||||
return replaceHttpWithHttps(albumJson.getString("url"));
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
|
@ -112,53 +120,54 @@ public class BandcampStreamExtractor extends StreamExtractor {
|
|||
@Nullable
|
||||
@Override
|
||||
public DateWrapper getUploadDate() throws ParsingException {
|
||||
return BandcampExtractorHelper.parseDate(getTextualUploadDate());
|
||||
return parseDate(getTextualUploadDate());
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public String getThumbnailUrl() throws ParsingException {
|
||||
public List<Image> getThumbnails() throws ParsingException {
|
||||
if (albumJson.isNull("art_id")) {
|
||||
return "";
|
||||
} else {
|
||||
return getImageUrl(albumJson.getLong("art_id"), true);
|
||||
return List.of();
|
||||
}
|
||||
|
||||
return getImagesFromImageId(albumJson.getLong("art_id"), true);
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public String getUploaderAvatarUrl() {
|
||||
try {
|
||||
return document.getElementsByClass("band-photo").first().attr("src");
|
||||
} catch (final NullPointerException e) {
|
||||
return "";
|
||||
}
|
||||
public List<Image> getUploaderAvatars() {
|
||||
return getImagesFromImageUrl(document.getElementsByClass("band-photo")
|
||||
.stream()
|
||||
.map(element -> element.attr("src"))
|
||||
.findFirst()
|
||||
.orElse(""));
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public Description getDescription() {
|
||||
final String s = Utils.nonEmptyAndNullJoin(
|
||||
"\n\n",
|
||||
new String[]{
|
||||
current.getString("about"),
|
||||
current.getString("lyrics"),
|
||||
current.getString("credits")
|
||||
}
|
||||
);
|
||||
final String s = Utils.nonEmptyAndNullJoin("\n\n", current.getString("about"),
|
||||
current.getString("lyrics"), current.getString("credits"));
|
||||
return new Description(s, Description.PLAIN_TEXT);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<AudioStream> getAudioStreams() {
|
||||
final List<AudioStream> audioStreams = new ArrayList<>();
|
||||
return Collections.singletonList(new AudioStream.Builder()
|
||||
.setId("mp3-128")
|
||||
.setContent(albumJson.getArray("trackinfo")
|
||||
.getObject(0)
|
||||
.getObject("file")
|
||||
.getString("mp3-128"), true)
|
||||
.setMediaFormat(MediaFormat.MP3)
|
||||
.setAverageBitrate(128)
|
||||
.build());
|
||||
}
|
||||
|
||||
audioStreams.add(new AudioStream(
|
||||
albumJson.getArray("trackinfo").getObject(0)
|
||||
.getObject("file").getString("mp3-128"),
|
||||
MediaFormat.MP3, 128
|
||||
));
|
||||
return audioStreams;
|
||||
@Override
|
||||
public long getLength() throws ParsingException {
|
||||
return (long) albumJson.getArray("trackinfo").getObject(0)
|
||||
.getDouble("duration");
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -179,11 +188,11 @@ public class BandcampStreamExtractor extends StreamExtractor {
|
|||
@Override
|
||||
public PlaylistInfoItemsCollector getRelatedItems() {
|
||||
final PlaylistInfoItemsCollector collector = new PlaylistInfoItemsCollector(getServiceId());
|
||||
final Elements recommendedAlbums = document.getElementsByClass("recommended-album");
|
||||
document.getElementsByClass("recommended-album")
|
||||
.stream()
|
||||
.map(BandcampRelatedPlaylistInfoItemExtractor::new)
|
||||
.forEach(collector::commit);
|
||||
|
||||
for (final Element album : recommendedAlbums) {
|
||||
collector.commit(new BandcampRelatedPlaylistInfoItemExtractor(album));
|
||||
}
|
||||
return collector;
|
||||
}
|
||||
|
||||
|
@ -201,9 +210,11 @@ public class BandcampStreamExtractor extends StreamExtractor {
|
|||
@Nonnull
|
||||
@Override
|
||||
public String getLicence() {
|
||||
/* Tests resulted in this mapping of ints to licence:
|
||||
/*
|
||||
Tests resulted in this mapping of ints to licence:
|
||||
https://cloud.disroot.org/s/ZTWBxbQ9fKRmRWJ/preview (screenshot from a Bandcamp artist's
|
||||
account) */
|
||||
account)
|
||||
*/
|
||||
|
||||
switch (current.getInt("license_type")) {
|
||||
case 1:
|
||||
|
@ -228,14 +239,9 @@ public class BandcampStreamExtractor extends StreamExtractor {
|
|||
@Nonnull
|
||||
@Override
|
||||
public List<String> getTags() {
|
||||
final Elements tagElements = document.getElementsByAttributeValue("itemprop", "keywords");
|
||||
|
||||
final List<String> tags = new ArrayList<>();
|
||||
|
||||
for (final Element e : tagElements) {
|
||||
tags.add(e.text());
|
||||
}
|
||||
|
||||
return tags;
|
||||
return document.getElementsByAttributeValue("itemprop", "keywords")
|
||||
.stream()
|
||||
.map(Element::text)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,7 +4,6 @@ package org.schabi.newpipe.extractor.services.bandcamp.extractors;
|
|||
|
||||
import static org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampExtractorHelper.BASE_API_URL;
|
||||
|
||||
import com.grack.nanojson.JsonArray;
|
||||
import com.grack.nanojson.JsonObject;
|
||||
import com.grack.nanojson.JsonParser;
|
||||
import com.grack.nanojson.JsonParserException;
|
||||
|
@ -14,12 +13,12 @@ import org.schabi.newpipe.extractor.StreamingService;
|
|||
import org.schabi.newpipe.extractor.downloader.Downloader;
|
||||
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
|
||||
import org.schabi.newpipe.extractor.suggestion.SuggestionExtractor;
|
||||
import org.schabi.newpipe.extractor.utils.Utils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URLEncoder;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class BandcampSuggestionExtractor extends SuggestionExtractor {
|
||||
|
||||
|
@ -34,25 +33,16 @@ public class BandcampSuggestionExtractor extends SuggestionExtractor {
|
|||
|
||||
try {
|
||||
final JsonObject fuzzyResults = JsonParser.object().from(downloader
|
||||
.get(AUTOCOMPLETE_URL + URLEncoder.encode(query, "UTF-8")).responseBody());
|
||||
.get(AUTOCOMPLETE_URL + Utils.encodeUrlUtf8(query)).responseBody());
|
||||
|
||||
final JsonArray jsonArray = fuzzyResults.getObject("auto")
|
||||
.getArray("results");
|
||||
|
||||
final List<String> suggestions = new ArrayList<>();
|
||||
|
||||
for (final Object fuzzyResult : jsonArray) {
|
||||
final String res = ((JsonObject) fuzzyResult).getString("name");
|
||||
|
||||
if (!suggestions.contains(res)) {
|
||||
suggestions.add(res);
|
||||
}
|
||||
}
|
||||
|
||||
return suggestions;
|
||||
return fuzzyResults.getObject("auto").getArray("results").stream()
|
||||
.filter(JsonObject.class::isInstance)
|
||||
.map(JsonObject.class::cast)
|
||||
.map(jsonObject -> jsonObject.getString("name"))
|
||||
.distinct()
|
||||
.collect(Collectors.toList());
|
||||
} catch (final JsonParserException e) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,10 +1,14 @@
|
|||
package org.schabi.newpipe.extractor.services.bandcamp.extractors.streaminfoitem;
|
||||
|
||||
import com.grack.nanojson.JsonObject;
|
||||
import org.schabi.newpipe.extractor.Image;
|
||||
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
||||
import org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampExtractorHelper;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.annotation.Nonnull;
|
||||
import java.util.List;
|
||||
|
||||
import static org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampExtractorHelper.getImagesFromImageId;
|
||||
|
||||
public class BandcampDiscographStreamInfoItemExtractor extends BandcampStreamInfoItemExtractor {
|
||||
|
||||
|
@ -20,12 +24,6 @@ public class BandcampDiscographStreamInfoItemExtractor extends BandcampStreamInf
|
|||
return discograph.getString("band_name");
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public String getUploaderAvatarUrl() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return discograph.getString("title");
|
||||
|
@ -40,11 +38,10 @@ public class BandcampDiscographStreamInfoItemExtractor extends BandcampStreamInf
|
|||
);
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public String getThumbnailUrl() throws ParsingException {
|
||||
return BandcampExtractorHelper.getImageUrl(
|
||||
discograph.getLong("art_id"), true
|
||||
);
|
||||
public List<Image> getThumbnails() throws ParsingException {
|
||||
return getImagesFromImageId(discograph.getLong("art_id"), true);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -3,19 +3,21 @@
|
|||
package org.schabi.newpipe.extractor.services.bandcamp.extractors.streaminfoitem;
|
||||
|
||||
import com.grack.nanojson.JsonObject;
|
||||
import org.schabi.newpipe.extractor.Image;
|
||||
import org.schabi.newpipe.extractor.StreamingService;
|
||||
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
|
||||
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
||||
import org.schabi.newpipe.extractor.stream.StreamExtractor;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.annotation.Nonnull;
|
||||
import java.io.IOException;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public class BandcampPlaylistStreamInfoItemExtractor extends BandcampStreamInfoItemExtractor {
|
||||
|
||||
private final JsonObject track;
|
||||
private String substituteCoverUrl;
|
||||
private List<Image> substituteCovers;
|
||||
private final StreamingService service;
|
||||
|
||||
public BandcampPlaylistStreamInfoItemExtractor(final JsonObject track,
|
||||
|
@ -24,13 +26,14 @@ public class BandcampPlaylistStreamInfoItemExtractor extends BandcampStreamInfoI
|
|||
super(uploaderUrl);
|
||||
this.track = track;
|
||||
this.service = service;
|
||||
substituteCovers = Collections.emptyList();
|
||||
}
|
||||
|
||||
public BandcampPlaylistStreamInfoItemExtractor(final JsonObject track,
|
||||
final String uploaderUrl,
|
||||
final String substituteCoverUrl) {
|
||||
final List<Image> substituteCovers) {
|
||||
this(track, uploaderUrl, (StreamingService) null);
|
||||
this.substituteCoverUrl = substituteCoverUrl;
|
||||
this.substituteCovers = substituteCovers;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -56,28 +59,23 @@ public class BandcampPlaylistStreamInfoItemExtractor extends BandcampStreamInfoI
|
|||
return "";
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public String getUploaderAvatarUrl() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Each track can have its own cover art. Therefore, unless a substitute is provided,
|
||||
* the thumbnail is extracted using a stream extractor.
|
||||
*/
|
||||
@Nonnull
|
||||
@Override
|
||||
public String getThumbnailUrl() throws ParsingException {
|
||||
if (substituteCoverUrl != null) {
|
||||
return substituteCoverUrl;
|
||||
} else {
|
||||
public List<Image> getThumbnails() throws ParsingException {
|
||||
if (substituteCovers.isEmpty()) {
|
||||
try {
|
||||
final StreamExtractor extractor = service.getStreamExtractor(getUrl());
|
||||
extractor.fetchPage();
|
||||
return extractor.getThumbnailUrl();
|
||||
return extractor.getThumbnails();
|
||||
} catch (final ExtractionException | IOException e) {
|
||||
throw new ParsingException("could not download cover art location", e);
|
||||
throw new ParsingException("Could not download cover art location", e);
|
||||
}
|
||||
}
|
||||
|
||||
return substituteCovers;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,9 +1,13 @@
|
|||
package org.schabi.newpipe.extractor.services.bandcamp.extractors.streaminfoitem;
|
||||
|
||||
import org.jsoup.nodes.Element;
|
||||
import org.schabi.newpipe.extractor.Image;
|
||||
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.annotation.Nonnull;
|
||||
import java.util.List;
|
||||
|
||||
import static org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampExtractorHelper.getImagesFromSearchResult;
|
||||
|
||||
public class BandcampSearchStreamInfoItemExtractor extends BandcampStreamInfoItemExtractor {
|
||||
|
||||
|
@ -28,12 +32,6 @@ public class BandcampSearchStreamInfoItemExtractor extends BandcampStreamInfoIte
|
|||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public String getUploaderAvatarUrl() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() throws ParsingException {
|
||||
return resultInfo.getElementsByClass("heading").text();
|
||||
|
@ -44,15 +42,10 @@ public class BandcampSearchStreamInfoItemExtractor extends BandcampStreamInfoIte
|
|||
return resultInfo.getElementsByClass("itemurl").text();
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public String getThumbnailUrl() throws ParsingException {
|
||||
final Element img = searchResult.getElementsByClass("art").first()
|
||||
.getElementsByTag("img").first();
|
||||
if (img != null) {
|
||||
return img.attr("src");
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
public List<Image> getThumbnails() throws ParsingException {
|
||||
return getImagesFromSearchResult(searchResult);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -10,6 +10,7 @@ import org.schabi.newpipe.extractor.exceptions.ReCaptchaException;
|
|||
import org.schabi.newpipe.extractor.linkhandler.ListLinkHandlerFactory;
|
||||
import org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampExtractorHelper;
|
||||
import org.schabi.newpipe.extractor.utils.JsonUtils;
|
||||
import org.schabi.newpipe.extractor.utils.Utils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
@ -17,11 +18,20 @@ import java.util.List;
|
|||
/**
|
||||
* Artist do have IDs that are useful
|
||||
*/
|
||||
public class BandcampChannelLinkHandlerFactory extends ListLinkHandlerFactory {
|
||||
public final class BandcampChannelLinkHandlerFactory extends ListLinkHandlerFactory {
|
||||
|
||||
private static final BandcampChannelLinkHandlerFactory INSTANCE
|
||||
= new BandcampChannelLinkHandlerFactory();
|
||||
|
||||
private BandcampChannelLinkHandlerFactory() {
|
||||
}
|
||||
|
||||
public static BandcampChannelLinkHandlerFactory getInstance() {
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId(final String url) throws ParsingException {
|
||||
public String getId(final String url) throws ParsingException, UnsupportedOperationException {
|
||||
try {
|
||||
final String response = NewPipe.getDownloader().get(url).responseBody();
|
||||
|
||||
|
@ -41,16 +51,13 @@ public class BandcampChannelLinkHandlerFactory extends ListLinkHandlerFactory {
|
|||
*/
|
||||
@Override
|
||||
public String getUrl(final String id, final List<String> contentFilter, final String sortFilter)
|
||||
throws ParsingException {
|
||||
try {
|
||||
return BandcampExtractorHelper.getArtistDetails(id)
|
||||
.getString("bandcamp_url")
|
||||
.replace("http://", "https://");
|
||||
} catch (final NullPointerException e) {
|
||||
throws ParsingException, UnsupportedOperationException {
|
||||
final JsonObject artistDetails = BandcampExtractorHelper.getArtistDetails(id);
|
||||
if (artistDetails.getBoolean("error")) {
|
||||
throw new ParsingException(
|
||||
"JSON does not contain URL (invalid id?) or is otherwise invalid", e);
|
||||
"JSON does not contain a channel URL (invalid id?) or is otherwise invalid");
|
||||
}
|
||||
|
||||
return Utils.replaceHttpWithHttps(artistDetails.getString("bandcamp_url"));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -61,22 +68,21 @@ public class BandcampChannelLinkHandlerFactory extends ListLinkHandlerFactory {
|
|||
|
||||
final String lowercaseUrl = url.toLowerCase();
|
||||
|
||||
// https: | | artist.bandcamp.com | releases
|
||||
// 0 1 2 3
|
||||
// https: | | artist.bandcamp.com | releases - music - album - track ( | name)
|
||||
// 0 1 2 3 (4)
|
||||
final String[] splitUrl = lowercaseUrl.split("/");
|
||||
|
||||
// URL is too short
|
||||
if (splitUrl.length < 3) {
|
||||
if (splitUrl.length != 3 && splitUrl.length != 4) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Must have "releases" or "music" as segment after url or none at all
|
||||
if (splitUrl.length > 3 && !(
|
||||
splitUrl[3].equals("releases") || splitUrl[3].equals("music")
|
||||
)) {
|
||||
|
||||
// Must have "releases", "music", "album" or "track" as segment after URL or none at all
|
||||
if (splitUrl.length == 4 && !(splitUrl[3].equals("releases")
|
||||
|| splitUrl[3].equals("music")
|
||||
|| splitUrl[3].equals("album")
|
||||
|| splitUrl[3].equals("track"))) {
|
||||
return false;
|
||||
|
||||
} else {
|
||||
if (splitUrl[2].equals("daily.bandcamp.com")) {
|
||||
// Refuse links to daily.bandcamp.com as that is not an artist
|
||||
|
|
|
@ -0,0 +1,72 @@
|
|||
|
||||
package org.schabi.newpipe.extractor.services.bandcamp.linkHandler;
|
||||
|
||||
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
||||
import org.schabi.newpipe.extractor.channel.tabs.ChannelTabs;
|
||||
import org.schabi.newpipe.extractor.exceptions.UnsupportedTabException;
|
||||
import org.schabi.newpipe.extractor.linkhandler.ListLinkHandlerFactory;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.util.List;
|
||||
|
||||
public final class BandcampChannelTabLinkHandlerFactory extends ListLinkHandlerFactory {
|
||||
|
||||
private static final BandcampChannelTabLinkHandlerFactory INSTANCE
|
||||
= new BandcampChannelTabLinkHandlerFactory();
|
||||
|
||||
private BandcampChannelTabLinkHandlerFactory() {
|
||||
}
|
||||
|
||||
public static BandcampChannelTabLinkHandlerFactory getInstance() {
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a tab's URL suffix.
|
||||
*
|
||||
* <p>
|
||||
* These URLs don't actually exist on the Bandcamp website, as both albums and tracks are
|
||||
* listed on the main page, but redirect to the main page, which is perfect for us as we need a
|
||||
* unique URL for each tab.
|
||||
* </p>
|
||||
*
|
||||
* @param tab the tab value, which must not be null
|
||||
* @return a URL suffix
|
||||
* @throws UnsupportedTabException if the tab is not supported
|
||||
*/
|
||||
@Nonnull
|
||||
public static String getUrlSuffix(@Nonnull final String tab) throws UnsupportedTabException {
|
||||
switch (tab) {
|
||||
case ChannelTabs.TRACKS:
|
||||
return "/track";
|
||||
case ChannelTabs.ALBUMS:
|
||||
return "/album";
|
||||
}
|
||||
throw new UnsupportedTabException(tab);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId(final String url) throws ParsingException, UnsupportedOperationException {
|
||||
return BandcampChannelLinkHandlerFactory.getInstance().getId(url);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUrl(final String id, final List<String> contentFilter, final String sortFilter)
|
||||
throws ParsingException, UnsupportedOperationException {
|
||||
return BandcampChannelLinkHandlerFactory.getInstance().getUrl(id)
|
||||
+ getUrlSuffix(contentFilter.get(0));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onAcceptUrl(final String url) throws ParsingException {
|
||||
return BandcampChannelLinkHandlerFactory.getInstance().onAcceptUrl(url);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getAvailableContentFilter() {
|
||||
return new String[]{
|
||||
ChannelTabs.TRACKS,
|
||||
ChannelTabs.ALBUMS,
|
||||
};
|
||||
}
|
||||
}
|
|
@ -10,15 +10,29 @@ import java.util.List;
|
|||
* Like in {@link BandcampStreamLinkHandlerFactory}, tracks have no meaningful IDs except for
|
||||
* their URLs
|
||||
*/
|
||||
public class BandcampCommentsLinkHandlerFactory extends ListLinkHandlerFactory {
|
||||
public final class BandcampCommentsLinkHandlerFactory extends ListLinkHandlerFactory {
|
||||
|
||||
private static final BandcampCommentsLinkHandlerFactory INSTANCE
|
||||
= new BandcampCommentsLinkHandlerFactory();
|
||||
|
||||
private BandcampCommentsLinkHandlerFactory() {
|
||||
}
|
||||
|
||||
public static BandcampCommentsLinkHandlerFactory getInstance() {
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId(final String url) throws ParsingException {
|
||||
public String getId(final String url) throws ParsingException, UnsupportedOperationException {
|
||||
return url;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onAcceptUrl(final String url) throws ParsingException {
|
||||
if (BandcampExtractorHelper.isRadioUrl(url)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Don't accept URLs that don't point to a track
|
||||
if (!url.toLowerCase().matches("https?://.+\\..+/(track|album)/.+")) {
|
||||
return false;
|
||||
|
@ -31,7 +45,8 @@ public class BandcampCommentsLinkHandlerFactory extends ListLinkHandlerFactory {
|
|||
@Override
|
||||
public String getUrl(final String id,
|
||||
final List<String> contentFilter,
|
||||
final String sortFilter) throws ParsingException {
|
||||
final String sortFilter)
|
||||
throws ParsingException, UnsupportedOperationException {
|
||||
return id;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
package org.schabi.newpipe.extractor.services.bandcamp.linkHandler;
|
||||
|
||||
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
||||
import org.schabi.newpipe.extractor.linkhandler.ListLinkHandlerFactory;
|
||||
import org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampExtractorHelper;
|
||||
import org.schabi.newpipe.extractor.utils.Utils;
|
||||
|
@ -13,12 +14,23 @@ import static org.schabi.newpipe.extractor.services.bandcamp.extractors.Bandcamp
|
|||
import static org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampRadioExtractor.KIOSK_RADIO;
|
||||
import static org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampRadioExtractor.RADIO_API_URL;
|
||||
|
||||
public class BandcampFeaturedLinkHandlerFactory extends ListLinkHandlerFactory {
|
||||
public final class BandcampFeaturedLinkHandlerFactory extends ListLinkHandlerFactory {
|
||||
|
||||
private static final BandcampFeaturedLinkHandlerFactory INSTANCE =
|
||||
new BandcampFeaturedLinkHandlerFactory();
|
||||
|
||||
private BandcampFeaturedLinkHandlerFactory() {
|
||||
}
|
||||
|
||||
public static BandcampFeaturedLinkHandlerFactory getInstance() {
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUrl(final String id,
|
||||
final List<String> contentFilter,
|
||||
final String sortFilter) {
|
||||
final String sortFilter)
|
||||
throws ParsingException, UnsupportedOperationException {
|
||||
if (id.equals(KIOSK_FEATURED)) {
|
||||
return FEATURED_API_URL; // doesn't have a website
|
||||
} else if (id.equals(KIOSK_RADIO)) {
|
||||
|
@ -29,7 +41,7 @@ public class BandcampFeaturedLinkHandlerFactory extends ListLinkHandlerFactory {
|
|||
}
|
||||
|
||||
@Override
|
||||
public String getId(final String url) {
|
||||
public String getId(final String url) throws ParsingException, UnsupportedOperationException {
|
||||
final String fixedUrl = Utils.replaceHttpWithHttps(url);
|
||||
if (BandcampExtractorHelper.isRadioUrl(fixedUrl) || fixedUrl.equals(RADIO_API_URL)) {
|
||||
return KIOSK_RADIO;
|
||||
|
|
|
@ -11,16 +11,28 @@ import java.util.List;
|
|||
/**
|
||||
* Just as with streams, the album ids are essentially useless for us.
|
||||
*/
|
||||
public class BandcampPlaylistLinkHandlerFactory extends ListLinkHandlerFactory {
|
||||
public final class BandcampPlaylistLinkHandlerFactory extends ListLinkHandlerFactory {
|
||||
|
||||
private static final BandcampPlaylistLinkHandlerFactory INSTANCE
|
||||
= new BandcampPlaylistLinkHandlerFactory();
|
||||
|
||||
private BandcampPlaylistLinkHandlerFactory() {
|
||||
}
|
||||
|
||||
public static BandcampPlaylistLinkHandlerFactory getInstance() {
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId(final String url) throws ParsingException {
|
||||
public String getId(final String url) throws ParsingException, UnsupportedOperationException {
|
||||
return getUrl(url);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUrl(final String url,
|
||||
final List<String> contentFilter,
|
||||
final String sortFilter) throws ParsingException {
|
||||
final String sortFilter)
|
||||
throws ParsingException, UnsupportedOperationException {
|
||||
return url;
|
||||
}
|
||||
|
||||
|
|
|
@ -2,24 +2,34 @@
|
|||
|
||||
package org.schabi.newpipe.extractor.services.bandcamp.linkHandler;
|
||||
|
||||
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
||||
import org.schabi.newpipe.extractor.linkhandler.SearchQueryHandlerFactory;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URLEncoder;
|
||||
import java.util.List;
|
||||
|
||||
import static org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampExtractorHelper.BASE_URL;
|
||||
|
||||
public class BandcampSearchQueryHandlerFactory extends SearchQueryHandlerFactory {
|
||||
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
||||
import org.schabi.newpipe.extractor.linkhandler.SearchQueryHandlerFactory;
|
||||
import org.schabi.newpipe.extractor.utils.Utils;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.util.List;
|
||||
|
||||
public final class BandcampSearchQueryHandlerFactory extends SearchQueryHandlerFactory {
|
||||
|
||||
private static final BandcampSearchQueryHandlerFactory INSTANCE
|
||||
= new BandcampSearchQueryHandlerFactory();
|
||||
|
||||
private BandcampSearchQueryHandlerFactory() {
|
||||
}
|
||||
|
||||
public static BandcampSearchQueryHandlerFactory getInstance() {
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUrl(final String query,
|
||||
final List<String> contentFilter,
|
||||
final String sortFilter) throws ParsingException {
|
||||
final String sortFilter)
|
||||
throws ParsingException, UnsupportedOperationException {
|
||||
try {
|
||||
return BASE_URL + "/search?q=" + URLEncoder.encode(query, "UTF-8") + "&page=1";
|
||||
return BASE_URL + "/search?q=" + Utils.encodeUrlUtf8(query) + "&page=1";
|
||||
} catch (final UnsupportedEncodingException e) {
|
||||
throw new ParsingException("query \"" + query + "\" could not be encoded", e);
|
||||
}
|
||||
|
|
|
@ -14,14 +14,24 @@ import static org.schabi.newpipe.extractor.services.bandcamp.extractors.Bandcamp
|
|||
*
|
||||
* <p>Radio (bandcamp weekly) shows do have ids.</p>
|
||||
*/
|
||||
public class BandcampStreamLinkHandlerFactory extends LinkHandlerFactory {
|
||||
public final class BandcampStreamLinkHandlerFactory extends LinkHandlerFactory {
|
||||
|
||||
private static final BandcampStreamLinkHandlerFactory INSTANCE
|
||||
= new BandcampStreamLinkHandlerFactory();
|
||||
|
||||
private BandcampStreamLinkHandlerFactory() {
|
||||
}
|
||||
|
||||
public static BandcampStreamLinkHandlerFactory getInstance() {
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @see BandcampStreamLinkHandlerFactory
|
||||
*/
|
||||
@Override
|
||||
public String getId(final String url) throws ParsingException {
|
||||
public String getId(final String url) throws ParsingException, UnsupportedOperationException {
|
||||
if (BandcampExtractorHelper.isRadioUrl(url)) {
|
||||
return url.split("bandcamp.com/\\?show=")[1];
|
||||
} else {
|
||||
|
@ -34,7 +44,8 @@ public class BandcampStreamLinkHandlerFactory extends LinkHandlerFactory {
|
|||
* @see BandcampStreamLinkHandlerFactory
|
||||
*/
|
||||
@Override
|
||||
public String getUrl(final String input) {
|
||||
public String getUrl(final String input)
|
||||
throws ParsingException, UnsupportedOperationException {
|
||||
if (input.matches("\\d+")) {
|
||||
return BASE_URL + "/?show=" + input;
|
||||
} else {
|
||||
|
|
|
@ -6,6 +6,7 @@ import static java.util.Arrays.asList;
|
|||
|
||||
import org.schabi.newpipe.extractor.StreamingService;
|
||||
import org.schabi.newpipe.extractor.channel.ChannelExtractor;
|
||||
import org.schabi.newpipe.extractor.channel.tabs.ChannelTabExtractor;
|
||||
import org.schabi.newpipe.extractor.comments.CommentsExtractor;
|
||||
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
|
||||
import org.schabi.newpipe.extractor.kiosk.KioskList;
|
||||
|
@ -13,10 +14,12 @@ import org.schabi.newpipe.extractor.linkhandler.LinkHandler;
|
|||
import org.schabi.newpipe.extractor.linkhandler.LinkHandlerFactory;
|
||||
import org.schabi.newpipe.extractor.linkhandler.ListLinkHandler;
|
||||
import org.schabi.newpipe.extractor.linkhandler.ListLinkHandlerFactory;
|
||||
import org.schabi.newpipe.extractor.linkhandler.ReadyChannelTabListLinkHandler;
|
||||
import org.schabi.newpipe.extractor.linkhandler.SearchQueryHandler;
|
||||
import org.schabi.newpipe.extractor.linkhandler.SearchQueryHandlerFactory;
|
||||
import org.schabi.newpipe.extractor.playlist.PlaylistExtractor;
|
||||
import org.schabi.newpipe.extractor.search.SearchExtractor;
|
||||
import org.schabi.newpipe.extractor.services.media_ccc.extractors.MediaCCCChannelTabExtractor;
|
||||
import org.schabi.newpipe.extractor.services.media_ccc.extractors.MediaCCCConferenceExtractor;
|
||||
import org.schabi.newpipe.extractor.services.media_ccc.extractors.MediaCCCConferenceKiosk;
|
||||
import org.schabi.newpipe.extractor.services.media_ccc.extractors.MediaCCCLiveStreamExtractor;
|
||||
|
@ -27,8 +30,6 @@ import org.schabi.newpipe.extractor.services.media_ccc.extractors.MediaCCCSearch
|
|||
import org.schabi.newpipe.extractor.services.media_ccc.extractors.MediaCCCStreamExtractor;
|
||||
import org.schabi.newpipe.extractor.services.media_ccc.linkHandler.MediaCCCConferenceLinkHandlerFactory;
|
||||
import org.schabi.newpipe.extractor.services.media_ccc.linkHandler.MediaCCCConferencesListLinkHandlerFactory;
|
||||
import org.schabi.newpipe.extractor.services.media_ccc.linkHandler.MediaCCCLiveListLinkHandlerFactory;
|
||||
import org.schabi.newpipe.extractor.services.media_ccc.linkHandler.MediaCCCRecentListLinkHandlerFactory;
|
||||
import org.schabi.newpipe.extractor.services.media_ccc.linkHandler.MediaCCCSearchQueryHandlerFactory;
|
||||
import org.schabi.newpipe.extractor.services.media_ccc.linkHandler.MediaCCCStreamLinkHandlerFactory;
|
||||
import org.schabi.newpipe.extractor.stream.StreamExtractor;
|
||||
|
@ -47,12 +48,19 @@ public class MediaCCCService extends StreamingService {
|
|||
|
||||
@Override
|
||||
public LinkHandlerFactory getStreamLHFactory() {
|
||||
return new MediaCCCStreamLinkHandlerFactory();
|
||||
return MediaCCCStreamLinkHandlerFactory.getInstance();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ListLinkHandlerFactory getChannelLHFactory() {
|
||||
return new MediaCCCConferenceLinkHandlerFactory();
|
||||
return MediaCCCConferenceLinkHandlerFactory.getInstance();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ListLinkHandlerFactory getChannelTabLHFactory() {
|
||||
// there is just one channel tab in MediaCCC, the one containing conferences, so there is
|
||||
// no need for a specific channel tab link handler, but we can just use the channel one
|
||||
return MediaCCCConferenceLinkHandlerFactory.getInstance();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -62,7 +70,7 @@ public class MediaCCCService extends StreamingService {
|
|||
|
||||
@Override
|
||||
public SearchQueryHandlerFactory getSearchQHFactory() {
|
||||
return new MediaCCCSearchQueryHandlerFactory();
|
||||
return MediaCCCSearchQueryHandlerFactory.getInstance();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -78,6 +86,18 @@ public class MediaCCCService extends StreamingService {
|
|||
return new MediaCCCConferenceExtractor(this, linkHandler);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelTabExtractor getChannelTabExtractor(final ListLinkHandler linkHandler) {
|
||||
if (linkHandler instanceof ReadyChannelTabListLinkHandler) {
|
||||
// conference data has already been fetched, let the ReadyChannelTabListLinkHandler
|
||||
// create a MediaCCCChannelTabExtractor with that data
|
||||
return ((ReadyChannelTabListLinkHandler) linkHandler).getChannelTabExtractor(this);
|
||||
} else {
|
||||
// conference data has not been fetched yet, so pass null instead
|
||||
return new MediaCCCChannelTabExtractor(this, linkHandler, null);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public PlaylistExtractor getPlaylistExtractor(final ListLinkHandler linkHandler) {
|
||||
return null;
|
||||
|
@ -91,40 +111,41 @@ public class MediaCCCService extends StreamingService {
|
|||
@Override
|
||||
public KioskList getKioskList() throws ExtractionException {
|
||||
final KioskList list = new KioskList(this);
|
||||
final ListLinkHandlerFactory h = MediaCCCConferencesListLinkHandlerFactory.getInstance();
|
||||
|
||||
// add kiosks here e.g.:
|
||||
try {
|
||||
list.addKioskEntry(
|
||||
(streamingService, url, kioskId) -> new MediaCCCConferenceKiosk(
|
||||
MediaCCCService.this,
|
||||
new MediaCCCConferencesListLinkHandlerFactory().fromUrl(url),
|
||||
h.fromUrl(url),
|
||||
kioskId
|
||||
),
|
||||
new MediaCCCConferencesListLinkHandlerFactory(),
|
||||
"conferences"
|
||||
h,
|
||||
MediaCCCConferenceKiosk.KIOSK_ID
|
||||
);
|
||||
|
||||
list.addKioskEntry(
|
||||
(streamingService, url, kioskId) -> new MediaCCCRecentKiosk(
|
||||
MediaCCCService.this,
|
||||
new MediaCCCRecentListLinkHandlerFactory().fromUrl(url),
|
||||
h.fromUrl(url),
|
||||
kioskId
|
||||
),
|
||||
new MediaCCCRecentListLinkHandlerFactory(),
|
||||
"recent"
|
||||
h,
|
||||
MediaCCCRecentKiosk.KIOSK_ID
|
||||
);
|
||||
|
||||
list.addKioskEntry(
|
||||
(streamingService, url, kioskId) -> new MediaCCCLiveStreamKiosk(
|
||||
MediaCCCService.this,
|
||||
new MediaCCCLiveListLinkHandlerFactory().fromUrl(url),
|
||||
h.fromUrl(url),
|
||||
kioskId
|
||||
),
|
||||
new MediaCCCLiveListLinkHandlerFactory(),
|
||||
"live"
|
||||
h,
|
||||
MediaCCCLiveStreamKiosk.KIOSK_ID
|
||||
);
|
||||
|
||||
list.setDefaultKiosk("recent");
|
||||
list.setDefaultKiosk(MediaCCCRecentKiosk.KIOSK_ID);
|
||||
} catch (final Exception e) {
|
||||
throw new ExtractionException(e);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,69 @@
|
|||
package org.schabi.newpipe.extractor.services.media_ccc.extractors;
|
||||
|
||||
import com.grack.nanojson.JsonObject;
|
||||
|
||||
import org.schabi.newpipe.extractor.InfoItem;
|
||||
import org.schabi.newpipe.extractor.ListExtractor;
|
||||
import org.schabi.newpipe.extractor.MultiInfoItemsCollector;
|
||||
import org.schabi.newpipe.extractor.Page;
|
||||
import org.schabi.newpipe.extractor.StreamingService;
|
||||
import org.schabi.newpipe.extractor.channel.tabs.ChannelTabExtractor;
|
||||
import org.schabi.newpipe.extractor.downloader.Downloader;
|
||||
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
|
||||
import org.schabi.newpipe.extractor.linkhandler.ListLinkHandler;
|
||||
import org.schabi.newpipe.extractor.services.media_ccc.extractors.infoItems.MediaCCCStreamInfoItemExtractor;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Objects;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* MediaCCC does not really have channel tabs, but rather a list of videos for each conference,
|
||||
* so this class just acts as a videos channel tab extractor.
|
||||
*/
|
||||
public class MediaCCCChannelTabExtractor extends ChannelTabExtractor {
|
||||
@Nullable
|
||||
private JsonObject conferenceData;
|
||||
|
||||
/**
|
||||
* @param conferenceData will be not-null if conference data has already been fetched by
|
||||
* {@link MediaCCCConferenceExtractor}. Otherwise, if this parameter is
|
||||
* {@code null}, conference data will be fetched anew.
|
||||
*/
|
||||
public MediaCCCChannelTabExtractor(final StreamingService service,
|
||||
final ListLinkHandler linkHandler,
|
||||
@Nullable final JsonObject conferenceData) {
|
||||
super(service, linkHandler);
|
||||
this.conferenceData = conferenceData;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFetchPage(@Nonnull final Downloader downloader)
|
||||
throws ExtractionException, IOException {
|
||||
if (conferenceData == null) {
|
||||
// only fetch conference data if we don't have it already
|
||||
conferenceData = MediaCCCConferenceExtractor.fetchConferenceData(downloader, getId());
|
||||
}
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public ListExtractor.InfoItemsPage<InfoItem> getInitialPage() {
|
||||
final MultiInfoItemsCollector collector =
|
||||
new MultiInfoItemsCollector(getServiceId());
|
||||
Objects.requireNonNull(conferenceData) // will surely be != null after onFetchPage
|
||||
.getArray("events")
|
||||
.stream()
|
||||
.filter(JsonObject.class::isInstance)
|
||||
.map(JsonObject.class::cast)
|
||||
.forEach(event -> collector.commit(new MediaCCCStreamInfoItemExtractor(event)));
|
||||
return new ListExtractor.InfoItemsPage<>(collector, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ListExtractor.InfoItemsPage<InfoItem> getPage(final Page page) {
|
||||
return ListExtractor.InfoItemsPage.emptyPage();
|
||||
}
|
||||
}
|
|
@ -1,23 +1,27 @@
|
|||
package org.schabi.newpipe.extractor.services.media_ccc.extractors;
|
||||
|
||||
import com.grack.nanojson.JsonArray;
|
||||
import static org.schabi.newpipe.extractor.services.media_ccc.extractors.MediaCCCParsingHelper.getImageListFromLogoImageUrl;
|
||||
|
||||
import com.grack.nanojson.JsonObject;
|
||||
import com.grack.nanojson.JsonParser;
|
||||
import com.grack.nanojson.JsonParserException;
|
||||
import org.schabi.newpipe.extractor.Page;
|
||||
|
||||
import org.schabi.newpipe.extractor.Image;
|
||||
import org.schabi.newpipe.extractor.StreamingService;
|
||||
import org.schabi.newpipe.extractor.channel.ChannelExtractor;
|
||||
import org.schabi.newpipe.extractor.channel.tabs.ChannelTabs;
|
||||
import org.schabi.newpipe.extractor.downloader.Downloader;
|
||||
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
|
||||
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
||||
import org.schabi.newpipe.extractor.linkhandler.ListLinkHandler;
|
||||
import org.schabi.newpipe.extractor.services.media_ccc.extractors.infoItems.MediaCCCStreamInfoItemExtractor;
|
||||
import org.schabi.newpipe.extractor.linkhandler.ReadyChannelTabListLinkHandler;
|
||||
import org.schabi.newpipe.extractor.services.media_ccc.linkHandler.MediaCCCConferenceLinkHandlerFactory;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfoItemsCollector;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.io.IOException;
|
||||
|
||||
public class MediaCCCConferenceExtractor extends ChannelExtractor {
|
||||
private JsonObject conferenceData;
|
||||
|
@ -27,14 +31,29 @@ public class MediaCCCConferenceExtractor extends ChannelExtractor {
|
|||
super(service, linkHandler);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAvatarUrl() {
|
||||
return conferenceData.getString("logo_url");
|
||||
static JsonObject fetchConferenceData(@Nonnull final Downloader downloader,
|
||||
@Nonnull final String conferenceId)
|
||||
throws IOException, ExtractionException {
|
||||
final String conferenceUrl
|
||||
= MediaCCCConferenceLinkHandlerFactory.CONFERENCE_API_ENDPOINT + conferenceId;
|
||||
try {
|
||||
return JsonParser.object().from(downloader.get(conferenceUrl).responseBody());
|
||||
} catch (final JsonParserException jpe) {
|
||||
throw new ExtractionException("Could not parse json returned by URL: " + conferenceUrl);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public String getBannerUrl() {
|
||||
return conferenceData.getString("logo_url");
|
||||
public List<Image> getAvatars() {
|
||||
return getImageListFromLogoImageUrl(conferenceData.getString("logo_url"));
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public List<Image> getBanners() {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -62,9 +81,10 @@ public class MediaCCCConferenceExtractor extends ChannelExtractor {
|
|||
return "";
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public String getParentChannelAvatarUrl() {
|
||||
return "";
|
||||
public List<Image> getParentChannelAvatars() {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -74,30 +94,18 @@ public class MediaCCCConferenceExtractor extends ChannelExtractor {
|
|||
|
||||
@Nonnull
|
||||
@Override
|
||||
public InfoItemsPage<StreamInfoItem> getInitialPage() {
|
||||
final StreamInfoItemsCollector collector = new StreamInfoItemsCollector(getServiceId());
|
||||
final JsonArray events = conferenceData.getArray("events");
|
||||
for (int i = 0; i < events.size(); i++) {
|
||||
collector.commit(new MediaCCCStreamInfoItemExtractor(events.getObject(i)));
|
||||
}
|
||||
return new InfoItemsPage<>(collector, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public InfoItemsPage<StreamInfoItem> getPage(final Page page) {
|
||||
return InfoItemsPage.emptyPage();
|
||||
public List<ListLinkHandler> getTabs() throws ParsingException {
|
||||
// avoid keeping a reference to MediaCCCConferenceExtractor inside the lambda
|
||||
final JsonObject theConferenceData = conferenceData;
|
||||
return List.of(new ReadyChannelTabListLinkHandler(getUrl(), getId(), ChannelTabs.VIDEOS,
|
||||
(service, linkHandler) ->
|
||||
new MediaCCCChannelTabExtractor(service, linkHandler, theConferenceData)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFetchPage(@Nonnull final Downloader downloader)
|
||||
throws IOException, ExtractionException {
|
||||
final String conferenceUrl
|
||||
= MediaCCCConferenceLinkHandlerFactory.CONFERENCE_API_ENDPOINT + getId();
|
||||
try {
|
||||
conferenceData = JsonParser.object().from(downloader.get(conferenceUrl).responseBody());
|
||||
} catch (final JsonParserException jpe) {
|
||||
throw new ExtractionException("Could not parse json returnd by url: " + conferenceUrl);
|
||||
}
|
||||
conferenceData = fetchConferenceData(downloader, getId());
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
|
|
|
@ -21,6 +21,8 @@ import java.io.IOException;
|
|||
import javax.annotation.Nonnull;
|
||||
|
||||
public class MediaCCCConferenceKiosk extends KioskExtractor<ChannelInfoItem> {
|
||||
|
||||
public static final String KIOSK_ID = "conferences";
|
||||
private JsonObject doc;
|
||||
|
||||
public MediaCCCConferenceKiosk(final StreamingService streamingService,
|
||||
|
|
|
@ -1,8 +1,13 @@
|
|||
package org.schabi.newpipe.extractor.services.media_ccc.extractors;
|
||||
|
||||
import static org.schabi.newpipe.extractor.services.media_ccc.extractors.MediaCCCParsingHelper.getThumbnailsFromLiveStreamItem;
|
||||
import static org.schabi.newpipe.extractor.stream.AudioStream.UNKNOWN_BITRATE;
|
||||
import static org.schabi.newpipe.extractor.stream.Stream.ID_UNKNOWN;
|
||||
|
||||
import com.grack.nanojson.JsonArray;
|
||||
import com.grack.nanojson.JsonObject;
|
||||
|
||||
import org.schabi.newpipe.extractor.Image;
|
||||
import org.schabi.newpipe.extractor.MediaFormat;
|
||||
import org.schabi.newpipe.extractor.StreamingService;
|
||||
import org.schabi.newpipe.extractor.downloader.Downloader;
|
||||
|
@ -10,18 +15,26 @@ import org.schabi.newpipe.extractor.exceptions.ExtractionException;
|
|||
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
||||
import org.schabi.newpipe.extractor.linkhandler.LinkHandler;
|
||||
import org.schabi.newpipe.extractor.stream.AudioStream;
|
||||
import org.schabi.newpipe.extractor.stream.DeliveryMethod;
|
||||
import org.schabi.newpipe.extractor.stream.Description;
|
||||
import org.schabi.newpipe.extractor.stream.Stream;
|
||||
import org.schabi.newpipe.extractor.stream.StreamExtractor;
|
||||
import org.schabi.newpipe.extractor.stream.StreamType;
|
||||
import org.schabi.newpipe.extractor.stream.VideoStream;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
public class MediaCCCLiveStreamExtractor extends StreamExtractor {
|
||||
private static final String STREAMS = "streams";
|
||||
private static final String URLS = "urls";
|
||||
private static final String URL = "url";
|
||||
|
||||
private JsonObject conference = null;
|
||||
private String group = "";
|
||||
private JsonObject room = null;
|
||||
|
@ -34,19 +47,22 @@ public class MediaCCCLiveStreamExtractor extends StreamExtractor {
|
|||
@Override
|
||||
public void onFetchPage(@Nonnull final Downloader downloader)
|
||||
throws IOException, ExtractionException {
|
||||
final JsonArray doc =
|
||||
MediaCCCParsingHelper.getLiveStreams(downloader, getExtractorLocalization());
|
||||
// find correct room
|
||||
final JsonArray doc = MediaCCCParsingHelper.getLiveStreams(downloader,
|
||||
getExtractorLocalization());
|
||||
// Find the correct room
|
||||
for (int c = 0; c < doc.size(); c++) {
|
||||
conference = doc.getObject(c);
|
||||
final JsonArray groups = conference.getArray("groups");
|
||||
final JsonObject conferenceObject = doc.getObject(c);
|
||||
final JsonArray groups = conferenceObject.getArray("groups");
|
||||
for (int g = 0; g < groups.size(); g++) {
|
||||
group = groups.getObject(g).getString("group");
|
||||
final String groupObject = groups.getObject(g).getString("group");
|
||||
final JsonArray rooms = groups.getObject(g).getArray("rooms");
|
||||
for (int r = 0; r < rooms.size(); r++) {
|
||||
room = rooms.getObject(r);
|
||||
if (getId().equals(
|
||||
conference.getString("slug") + "/" + room.getString("slug"))) {
|
||||
final JsonObject roomObject = rooms.getObject(r);
|
||||
if (getId().equals(conferenceObject.getString("slug") + "/"
|
||||
+ roomObject.getString("slug"))) {
|
||||
conference = conferenceObject;
|
||||
group = groupObject;
|
||||
room = roomObject;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -63,8 +79,8 @@ public class MediaCCCLiveStreamExtractor extends StreamExtractor {
|
|||
|
||||
@Nonnull
|
||||
@Override
|
||||
public String getThumbnailUrl() throws ParsingException {
|
||||
return room.getString("thumb");
|
||||
public List<Image> getThumbnails() throws ParsingException {
|
||||
return getThumbnailsFromLiveStreamItem(room);
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
|
@ -91,69 +107,155 @@ public class MediaCCCLiveStreamExtractor extends StreamExtractor {
|
|||
return conference.getString("conference");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the URL of the first DASH stream found.
|
||||
*
|
||||
* <p>
|
||||
* There can be several DASH streams, so the URL of the first one found is returned by this
|
||||
* method.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* You can find the other DASH video streams by using {@link #getVideoStreams()}
|
||||
* </p>
|
||||
*/
|
||||
@Nonnull
|
||||
@Override
|
||||
public String getDashMpdUrl() throws ParsingException {
|
||||
return getManifestOfDeliveryMethodWanted("dash");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the URL of the first HLS stream found.
|
||||
*
|
||||
* <p>
|
||||
* There can be several HLS streams, so the URL of the first one found is returned by this
|
||||
* method.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* You can find the other HLS video streams by using {@link #getVideoStreams()}
|
||||
* </p>
|
||||
*/
|
||||
@Nonnull
|
||||
@Override
|
||||
public String getHlsUrl() {
|
||||
// TODO: There are multiple HLS streams.
|
||||
// Make getHlsUrl() and getDashMpdUrl() return lists of VideoStreams,
|
||||
// so the user can choose a resolution.
|
||||
for (int s = 0; s < room.getArray("streams").size(); s++) {
|
||||
final JsonObject stream = room.getArray("streams").getObject(s);
|
||||
if (stream.getString("type").equals("video")) {
|
||||
if (stream.has("hls")) {
|
||||
return stream.getObject("urls").getObject("hls").getString("url");
|
||||
}
|
||||
}
|
||||
}
|
||||
return "";
|
||||
return getManifestOfDeliveryMethodWanted("hls");
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
private String getManifestOfDeliveryMethodWanted(@Nonnull final String deliveryMethod) {
|
||||
return room.getArray(STREAMS).stream()
|
||||
.filter(JsonObject.class::isInstance)
|
||||
.map(JsonObject.class::cast)
|
||||
.map(streamObject -> streamObject.getObject(URLS))
|
||||
.filter(urls -> urls.has(deliveryMethod))
|
||||
.map(urls -> urls.getObject(deliveryMethod).getString(URL, ""))
|
||||
.findFirst()
|
||||
.orElse("");
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<AudioStream> getAudioStreams() throws IOException, ExtractionException {
|
||||
final List<AudioStream> audioStreams = new ArrayList<>();
|
||||
for (int s = 0; s < room.getArray("streams").size(); s++) {
|
||||
final JsonObject stream = room.getArray("streams").getObject(s);
|
||||
if (stream.getString("type").equals("audio")) {
|
||||
for (final String type : stream.getObject("urls").keySet()) {
|
||||
final JsonObject url = stream.getObject("urls").getObject(type);
|
||||
audioStreams.add(new AudioStream(url.getString("url"),
|
||||
MediaFormat.getFromSuffix(type), -1));
|
||||
}
|
||||
}
|
||||
}
|
||||
return audioStreams;
|
||||
return getStreams("audio",
|
||||
dto -> {
|
||||
final AudioStream.Builder builder = new AudioStream.Builder()
|
||||
.setId(dto.urlValue.getString("tech", ID_UNKNOWN))
|
||||
.setContent(dto.urlValue.getString(URL), true)
|
||||
.setAverageBitrate(UNKNOWN_BITRATE);
|
||||
|
||||
if ("hls".equals(dto.urlKey)) {
|
||||
// We don't know with the type string what media format will
|
||||
// have HLS streams.
|
||||
// However, the tech string may contain some information
|
||||
// about the media format used.
|
||||
return builder.setDeliveryMethod(DeliveryMethod.HLS)
|
||||
.build();
|
||||
}
|
||||
|
||||
return builder.setMediaFormat(MediaFormat.getFromSuffix(dto.urlKey))
|
||||
.build();
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<VideoStream> getVideoStreams() throws IOException, ExtractionException {
|
||||
final List<VideoStream> videoStreams = new ArrayList<>();
|
||||
for (int s = 0; s < room.getArray("streams").size(); s++) {
|
||||
final JsonObject stream = room.getArray("streams").getObject(s);
|
||||
if (stream.getString("type").equals("video")) {
|
||||
final String resolution = stream.getArray("videoSize").getInt(0) + "x"
|
||||
+ stream.getArray("videoSize").getInt(1);
|
||||
for (final String type : stream.getObject("urls").keySet()) {
|
||||
if (!type.equals("hls")) {
|
||||
final JsonObject url = stream.getObject("urls").getObject(type);
|
||||
videoStreams.add(new VideoStream(
|
||||
url.getString("url"),
|
||||
MediaFormat.getFromSuffix(type),
|
||||
resolution));
|
||||
return getStreams("video",
|
||||
dto -> {
|
||||
final JsonArray videoSize = dto.streamJsonObj.getArray("videoSize");
|
||||
|
||||
final VideoStream.Builder builder = new VideoStream.Builder()
|
||||
.setId(dto.urlValue.getString("tech", ID_UNKNOWN))
|
||||
.setContent(dto.urlValue.getString(URL), true)
|
||||
.setIsVideoOnly(false)
|
||||
.setResolution(videoSize.getInt(0) + "x" + videoSize.getInt(1));
|
||||
|
||||
if ("hls".equals(dto.urlKey)) {
|
||||
// We don't know with the type string what media format will
|
||||
// have HLS streams.
|
||||
// However, the tech string may contain some information
|
||||
// about the media format used.
|
||||
return builder.setDeliveryMethod(DeliveryMethod.HLS)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return builder.setMediaFormat(MediaFormat.getFromSuffix(dto.urlKey))
|
||||
.build();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This is just an internal class used in {@link #getStreams(String, Function)} to tie together
|
||||
* the stream json object, its URL key and its URL value. An object of this class would be
|
||||
* temporary and the three values it holds would be <b>convert</b>ed to a proper {@link Stream}
|
||||
* object based on the wanted stream type.
|
||||
*/
|
||||
private static final class MediaCCCLiveStreamMapperDTO {
|
||||
final JsonObject streamJsonObj;
|
||||
final String urlKey;
|
||||
final JsonObject urlValue;
|
||||
|
||||
MediaCCCLiveStreamMapperDTO(final JsonObject streamJsonObj,
|
||||
final String urlKey,
|
||||
final JsonObject urlValue) {
|
||||
this.streamJsonObj = streamJsonObj;
|
||||
this.urlKey = urlKey;
|
||||
this.urlValue = urlValue;
|
||||
}
|
||||
return videoStreams;
|
||||
}
|
||||
|
||||
private <T extends Stream> List<T> getStreams(
|
||||
@Nonnull final String streamType,
|
||||
@Nonnull final Function<MediaCCCLiveStreamMapperDTO, T> converter) {
|
||||
return room.getArray(STREAMS).stream()
|
||||
// Ensure that we use only process JsonObjects
|
||||
.filter(JsonObject.class::isInstance)
|
||||
.map(JsonObject.class::cast)
|
||||
// Only process streams of requested type
|
||||
.filter(streamJsonObj -> streamType.equals(streamJsonObj.getString("type")))
|
||||
// Flatmap Urls and ensure that we use only process JsonObjects
|
||||
.flatMap(streamJsonObj -> streamJsonObj.getObject(URLS).entrySet().stream()
|
||||
.filter(e -> e.getValue() instanceof JsonObject)
|
||||
.map(e -> new MediaCCCLiveStreamMapperDTO(
|
||||
streamJsonObj,
|
||||
e.getKey(),
|
||||
(JsonObject) e.getValue())))
|
||||
// The DASH manifest will be extracted with getDashMpdUrl
|
||||
.filter(dto -> !"dash".equals(dto.urlKey))
|
||||
// Convert
|
||||
.map(converter)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<VideoStream> getVideoOnlyStreams() {
|
||||
return null;
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public StreamType getStreamType() throws ParsingException {
|
||||
return StreamType.LIVE_STREAM; // TODO: video and audio only streams are both available
|
||||
return StreamType.LIVE_STREAM;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
|
|
|
@ -16,6 +16,8 @@ import javax.annotation.Nonnull;
|
|||
import java.io.IOException;
|
||||
|
||||
public class MediaCCCLiveStreamKiosk extends KioskExtractor<StreamInfoItem> {
|
||||
|
||||
public static final String KIOSK_ID = "live";
|
||||
private JsonArray doc;
|
||||
|
||||
public MediaCCCLiveStreamKiosk(final StreamingService streamingService,
|
||||
|
@ -36,13 +38,16 @@ public class MediaCCCLiveStreamKiosk extends KioskExtractor<StreamInfoItem> {
|
|||
final StreamInfoItemsCollector collector = new StreamInfoItemsCollector(getServiceId());
|
||||
for (int c = 0; c < doc.size(); c++) {
|
||||
final JsonObject conference = doc.getObject(c);
|
||||
final JsonArray groups = conference.getArray("groups");
|
||||
for (int g = 0; g < groups.size(); g++) {
|
||||
final String group = groups.getObject(g).getString("group");
|
||||
final JsonArray rooms = groups.getObject(g).getArray("rooms");
|
||||
for (int r = 0; r < rooms.size(); r++) {
|
||||
final JsonObject room = rooms.getObject(r);
|
||||
collector.commit(new MediaCCCLiveStreamKioskExtractor(conference, group, room));
|
||||
if (conference.getBoolean("isCurrentlyStreaming")) {
|
||||
final JsonArray groups = conference.getArray("groups");
|
||||
for (int g = 0; g < groups.size(); g++) {
|
||||
final String group = groups.getObject(g).getString("group");
|
||||
final JsonArray rooms = groups.getObject(g).getArray("rooms");
|
||||
for (int r = 0; r < rooms.size(); r++) {
|
||||
final JsonObject room = rooms.getObject(r);
|
||||
collector.commit(new MediaCCCLiveStreamKioskExtractor(
|
||||
conference, group, room));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -59,6 +64,6 @@ public class MediaCCCLiveStreamKiosk extends KioskExtractor<StreamInfoItem> {
|
|||
@Nonnull
|
||||
@Override
|
||||
public String getName() throws ParsingException {
|
||||
return "live";
|
||||
return KIOSK_ID;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,12 +1,17 @@
|
|||
package org.schabi.newpipe.extractor.services.media_ccc.extractors;
|
||||
|
||||
import com.grack.nanojson.JsonObject;
|
||||
import org.schabi.newpipe.extractor.Image;
|
||||
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
||||
import org.schabi.newpipe.extractor.localization.DateWrapper;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfoItemExtractor;
|
||||
import org.schabi.newpipe.extractor.stream.StreamType;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.List;
|
||||
|
||||
import static org.schabi.newpipe.extractor.services.media_ccc.extractors.MediaCCCParsingHelper.getThumbnailsFromLiveStreamItem;
|
||||
|
||||
public class MediaCCCLiveStreamKioskExtractor implements StreamInfoItemExtractor {
|
||||
|
||||
|
@ -32,9 +37,10 @@ public class MediaCCCLiveStreamKioskExtractor implements StreamInfoItemExtractor
|
|||
return roomInfo.getString("link");
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public String getThumbnailUrl() throws ParsingException {
|
||||
return roomInfo.getString("thumb");
|
||||
public List<Image> getThumbnails() throws ParsingException {
|
||||
return getThumbnailsFromLiveStreamItem(roomInfo);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -75,12 +81,6 @@ public class MediaCCCLiveStreamKioskExtractor implements StreamInfoItemExtractor
|
|||
return "https://media.ccc.de/c/" + conferenceInfo.getString("slug");
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public String getUploaderAvatarUrl() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isUploaderVerified() throws ParsingException {
|
||||
return false;
|
||||
|
|
|
@ -1,21 +1,33 @@
|
|||
package org.schabi.newpipe.extractor.services.media_ccc.extractors;
|
||||
|
||||
import com.grack.nanojson.JsonArray;
|
||||
import com.grack.nanojson.JsonObject;
|
||||
import com.grack.nanojson.JsonParser;
|
||||
import com.grack.nanojson.JsonParserException;
|
||||
import org.schabi.newpipe.extractor.Image;
|
||||
import org.schabi.newpipe.extractor.Image.ResolutionLevel;
|
||||
import org.schabi.newpipe.extractor.downloader.Downloader;
|
||||
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
|
||||
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
||||
import org.schabi.newpipe.extractor.exceptions.ReCaptchaException;
|
||||
import org.schabi.newpipe.extractor.localization.Localization;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import java.io.IOException;
|
||||
import java.time.OffsetDateTime;
|
||||
import java.time.format.DateTimeParseException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import static org.schabi.newpipe.extractor.Image.HEIGHT_UNKNOWN;
|
||||
import static org.schabi.newpipe.extractor.Image.WIDTH_UNKNOWN;
|
||||
import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty;
|
||||
|
||||
public final class MediaCCCParsingHelper {
|
||||
// {conference_slug}/{room_slug}
|
||||
// conference_slug/room_slug
|
||||
private static final Pattern LIVE_STREAM_ID_PATTERN = Pattern.compile("\\w+/\\w+");
|
||||
private static JsonArray liveStreams = null;
|
||||
|
||||
|
@ -69,4 +81,98 @@ public final class MediaCCCParsingHelper {
|
|||
}
|
||||
return liveStreams;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an {@link Image} list from a given image logo URL.
|
||||
*
|
||||
* <p>
|
||||
* If the image URL is null or empty, an empty list is returned; otherwise, a singleton list is
|
||||
* returned containing an {@link Image} with the image URL with its height, width and
|
||||
* resolution unknown.
|
||||
* </p>
|
||||
*
|
||||
* @param logoImageUrl a logo image URL, which can be null or empty
|
||||
* @return an unmodifiable list of {@link Image}s, which is always empty or a singleton
|
||||
*/
|
||||
@Nonnull
|
||||
public static List<Image> getImageListFromLogoImageUrl(@Nullable final String logoImageUrl) {
|
||||
if (isNullOrEmpty(logoImageUrl)) {
|
||||
return List.of();
|
||||
}
|
||||
|
||||
return List.of(new Image(logoImageUrl, HEIGHT_UNKNOWN, WIDTH_UNKNOWN,
|
||||
ResolutionLevel.UNKNOWN));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the {@link Image} list of thumbnails from a given stream item.
|
||||
*
|
||||
* <p>
|
||||
* MediaCCC API provides two thumbnails for a stream item: a {@code thumb_url} one, which is
|
||||
* medium quality and a {@code poster_url} one, which is high quality in most cases.
|
||||
* </p>
|
||||
*
|
||||
* @param streamItem a stream JSON item of MediaCCC's API, which must not be null
|
||||
* @return an unmodifiable list, which is never null but can be empty.
|
||||
*/
|
||||
@Nonnull
|
||||
public static List<Image> getThumbnailsFromStreamItem(@Nonnull final JsonObject streamItem) {
|
||||
return getThumbnailsFromObject(streamItem, "thumb_url", "poster_url");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the {@link Image} list of thumbnails from a given live stream item.
|
||||
*
|
||||
* <p>
|
||||
* MediaCCC API provides two URL thumbnails for a livestream item: a {@code thumb} one,
|
||||
* which should be medium quality and a {@code poster_url} one, which should be high quality.
|
||||
* </p>
|
||||
*
|
||||
* @param liveStreamItem a stream JSON item of MediaCCC's API, which must not be null
|
||||
* @return an unmodifiable list, which is never null but can be empty.
|
||||
*/
|
||||
@Nonnull
|
||||
public static List<Image> getThumbnailsFromLiveStreamItem(
|
||||
@Nonnull final JsonObject liveStreamItem) {
|
||||
return getThumbnailsFromObject(liveStreamItem, "thumb", "poster");
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility method to get an {@link Image} list of thumbnails from a stream or a livestream.
|
||||
*
|
||||
* <p>
|
||||
* MediaCCC's API thumbnails come from two elements: a {@code thumb} element, which links to a
|
||||
* medium thumbnail and a {@code poster} element, which links to a high thumbnail.
|
||||
* </p>
|
||||
* <p>
|
||||
* Thumbnails are only added if their URLs are not null or empty.
|
||||
* </p>
|
||||
*
|
||||
* @param streamOrLivestreamItem a (live)stream JSON item of MediaCCC's API, which must not be
|
||||
* null
|
||||
* @param thumbUrlKey the name of the {@code thumb} URL key
|
||||
* @param posterUrlKey the name of the {@code poster} URL key
|
||||
* @return an unmodifiable list, which is never null but can be empty.
|
||||
*/
|
||||
@Nonnull
|
||||
private static List<Image> getThumbnailsFromObject(
|
||||
@Nonnull final JsonObject streamOrLivestreamItem,
|
||||
@Nonnull final String thumbUrlKey,
|
||||
@Nonnull final String posterUrlKey) {
|
||||
final List<Image> imageList = new ArrayList<>(2);
|
||||
|
||||
final String thumbUrl = streamOrLivestreamItem.getString(thumbUrlKey);
|
||||
if (!isNullOrEmpty(thumbUrl)) {
|
||||
imageList.add(new Image(thumbUrl, HEIGHT_UNKNOWN, WIDTH_UNKNOWN,
|
||||
ResolutionLevel.MEDIUM));
|
||||
}
|
||||
|
||||
final String posterUrl = streamOrLivestreamItem.getString(posterUrlKey);
|
||||
if (!isNullOrEmpty(posterUrl)) {
|
||||
imageList.add(new Image(posterUrl, HEIGHT_UNKNOWN, WIDTH_UNKNOWN,
|
||||
ResolutionLevel.HIGH));
|
||||
}
|
||||
|
||||
return Collections.unmodifiableList(imageList);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@ import org.schabi.newpipe.extractor.exceptions.ExtractionException;
|
|||
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
||||
import org.schabi.newpipe.extractor.kiosk.KioskExtractor;
|
||||
import org.schabi.newpipe.extractor.linkhandler.ListLinkHandler;
|
||||
import org.schabi.newpipe.extractor.localization.DateWrapper;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfoItemsCollector;
|
||||
|
||||
|
@ -22,6 +23,8 @@ import javax.annotation.Nonnull;
|
|||
|
||||
public class MediaCCCRecentKiosk extends KioskExtractor<StreamInfoItem> {
|
||||
|
||||
public static final String KIOSK_ID = "recent";
|
||||
|
||||
private JsonObject doc;
|
||||
|
||||
public MediaCCCRecentKiosk(final StreamingService streamingService,
|
||||
|
@ -49,12 +52,12 @@ public class MediaCCCRecentKiosk extends KioskExtractor<StreamInfoItem> {
|
|||
|
||||
// Streams in the recent kiosk are not ordered by the release date.
|
||||
// Sort them to have the latest stream at the beginning of the list.
|
||||
Comparator<StreamInfoItem> comparator = Comparator.comparing(
|
||||
streamInfoItem -> streamInfoItem.getUploadDate().offsetDateTime());
|
||||
comparator = comparator.reversed();
|
||||
|
||||
final StreamInfoItemsCollector collector =
|
||||
new StreamInfoItemsCollector(getServiceId(), comparator);
|
||||
final Comparator<StreamInfoItem> comparator = Comparator
|
||||
.comparing(StreamInfoItem::getUploadDate, Comparator
|
||||
.nullsLast(Comparator.comparing(DateWrapper::offsetDateTime)))
|
||||
.reversed();
|
||||
final StreamInfoItemsCollector collector = new StreamInfoItemsCollector(getServiceId(),
|
||||
comparator);
|
||||
|
||||
events.stream()
|
||||
.filter(JsonObject.class::isInstance)
|
||||
|
@ -76,6 +79,6 @@ public class MediaCCCRecentKiosk extends KioskExtractor<StreamInfoItem> {
|
|||
@Nonnull
|
||||
@Override
|
||||
public String getName() throws ParsingException {
|
||||
return "recent";
|
||||
return KIOSK_ID;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ package org.schabi.newpipe.extractor.services.media_ccc.extractors;
|
|||
|
||||
import com.grack.nanojson.JsonObject;
|
||||
|
||||
import org.schabi.newpipe.extractor.Image;
|
||||
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
||||
import org.schabi.newpipe.extractor.localization.DateWrapper;
|
||||
import org.schabi.newpipe.extractor.services.media_ccc.linkHandler.MediaCCCConferenceLinkHandlerFactory;
|
||||
|
@ -10,9 +11,13 @@ import org.schabi.newpipe.extractor.stream.StreamType;
|
|||
|
||||
import java.time.ZonedDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.List;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import static org.schabi.newpipe.extractor.services.media_ccc.extractors.MediaCCCParsingHelper.getImageListFromLogoImageUrl;
|
||||
|
||||
public class MediaCCCRecentKioskExtractor implements StreamInfoItemExtractor {
|
||||
|
||||
private final JsonObject event;
|
||||
|
@ -31,9 +36,10 @@ public class MediaCCCRecentKioskExtractor implements StreamInfoItemExtractor {
|
|||
return event.getString("frontend_link");
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public String getThumbnailUrl() throws ParsingException {
|
||||
return event.getString("thumb_url");
|
||||
public List<Image> getThumbnails() throws ParsingException {
|
||||
return getImageListFromLogoImageUrl(event.getString("poster_url"));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -65,17 +71,11 @@ public class MediaCCCRecentKioskExtractor implements StreamInfoItemExtractor {
|
|||
|
||||
@Override
|
||||
public String getUploaderUrl() throws ParsingException {
|
||||
return new MediaCCCConferenceLinkHandlerFactory()
|
||||
return MediaCCCConferenceLinkHandlerFactory.getInstance()
|
||||
.fromUrl(event.getString("conference_url")) // API URL
|
||||
.getUrl(); // web URL
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public String getUploaderAvatarUrl() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isUploaderVerified() throws ParsingException {
|
||||
return false;
|
||||
|
|
|
@ -9,8 +9,10 @@ import com.grack.nanojson.JsonObject;
|
|||
import com.grack.nanojson.JsonParser;
|
||||
import com.grack.nanojson.JsonParserException;
|
||||
|
||||
import org.schabi.newpipe.extractor.Image;
|
||||
import org.schabi.newpipe.extractor.InfoItem;
|
||||
import org.schabi.newpipe.extractor.MetaInfo;
|
||||
import org.schabi.newpipe.extractor.MultiInfoItemsCollector;
|
||||
import org.schabi.newpipe.extractor.Page;
|
||||
import org.schabi.newpipe.extractor.StreamingService;
|
||||
import org.schabi.newpipe.extractor.channel.ChannelInfoItem;
|
||||
|
@ -18,7 +20,6 @@ import org.schabi.newpipe.extractor.channel.ChannelInfoItemExtractor;
|
|||
import org.schabi.newpipe.extractor.downloader.Downloader;
|
||||
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
|
||||
import org.schabi.newpipe.extractor.linkhandler.SearchQueryHandler;
|
||||
import org.schabi.newpipe.extractor.MultiInfoItemsCollector;
|
||||
import org.schabi.newpipe.extractor.search.SearchExtractor;
|
||||
import org.schabi.newpipe.extractor.services.media_ccc.extractors.infoItems.MediaCCCStreamInfoItemExtractor;
|
||||
import org.schabi.newpipe.extractor.services.media_ccc.linkHandler.MediaCCCConferencesListLinkHandlerFactory;
|
||||
|
@ -38,7 +39,8 @@ public class MediaCCCSearchExtractor extends SearchExtractor {
|
|||
super(service, linkHandler);
|
||||
try {
|
||||
conferenceKiosk = new MediaCCCConferenceKiosk(service,
|
||||
new MediaCCCConferencesListLinkHandlerFactory().fromId("conferences"),
|
||||
MediaCCCConferencesListLinkHandlerFactory.getInstance()
|
||||
.fromId("conferences"),
|
||||
"conferences");
|
||||
} catch (final Exception e) {
|
||||
e.printStackTrace();
|
||||
|
@ -156,9 +158,10 @@ public class MediaCCCSearchExtractor extends SearchExtractor {
|
|||
return item.getUrl();
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public String getThumbnailUrl() {
|
||||
return item.getThumbnailUrl();
|
||||
public List<Image> getThumbnails() {
|
||||
return item.getThumbnails();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,10 +1,17 @@
|
|||
package org.schabi.newpipe.extractor.services.media_ccc.extractors;
|
||||
|
||||
import static org.schabi.newpipe.extractor.services.media_ccc.extractors.MediaCCCParsingHelper.getImageListFromLogoImageUrl;
|
||||
import static org.schabi.newpipe.extractor.services.media_ccc.extractors.MediaCCCParsingHelper.getThumbnailsFromStreamItem;
|
||||
import static org.schabi.newpipe.extractor.services.media_ccc.extractors.MediaCCCParsingHelper.parseDateFrom;
|
||||
import static org.schabi.newpipe.extractor.stream.AudioStream.UNKNOWN_BITRATE;
|
||||
import static org.schabi.newpipe.extractor.stream.Stream.ID_UNKNOWN;
|
||||
|
||||
import com.grack.nanojson.JsonArray;
|
||||
import com.grack.nanojson.JsonObject;
|
||||
import com.grack.nanojson.JsonParser;
|
||||
import com.grack.nanojson.JsonParserException;
|
||||
|
||||
import org.schabi.newpipe.extractor.Image;
|
||||
import org.schabi.newpipe.extractor.MediaFormat;
|
||||
import org.schabi.newpipe.extractor.StreamingService;
|
||||
import org.schabi.newpipe.extractor.downloader.Downloader;
|
||||
|
@ -21,6 +28,7 @@ import org.schabi.newpipe.extractor.stream.StreamExtractor;
|
|||
import org.schabi.newpipe.extractor.stream.StreamType;
|
||||
import org.schabi.newpipe.extractor.stream.VideoStream;
|
||||
import org.schabi.newpipe.extractor.utils.JsonUtils;
|
||||
import org.schabi.newpipe.extractor.utils.LocaleCompat;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
|
@ -47,13 +55,13 @@ public class MediaCCCStreamExtractor extends StreamExtractor {
|
|||
@Nonnull
|
||||
@Override
|
||||
public DateWrapper getUploadDate() throws ParsingException {
|
||||
return new DateWrapper(MediaCCCParsingHelper.parseDateFrom(getTextualUploadDate()));
|
||||
return new DateWrapper(parseDateFrom(getTextualUploadDate()));
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public String getThumbnailUrl() {
|
||||
return data.getString("thumb_url");
|
||||
public List<Image> getThumbnails() {
|
||||
return getThumbnailsFromStreamItem(data);
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
|
@ -87,8 +95,8 @@ public class MediaCCCStreamExtractor extends StreamExtractor {
|
|||
|
||||
@Nonnull
|
||||
@Override
|
||||
public String getUploaderAvatarUrl() {
|
||||
return conferenceData.getString("logo_url");
|
||||
public List<Image> getUploaderAvatars() {
|
||||
return getImageListFromLogoImageUrl(conferenceData.getString("logo_url"));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -99,7 +107,7 @@ public class MediaCCCStreamExtractor extends StreamExtractor {
|
|||
final JsonObject recording = recordings.getObject(i);
|
||||
final String mimeType = recording.getString("mime_type");
|
||||
if (mimeType.startsWith("audio")) {
|
||||
//first we need to resolve the actual video data from CDN
|
||||
// First we need to resolve the actual video data from the CDN
|
||||
final MediaFormat mediaFormat;
|
||||
if (mimeType.endsWith("opus")) {
|
||||
mediaFormat = MediaFormat.OPUS;
|
||||
|
@ -108,11 +116,30 @@ public class MediaCCCStreamExtractor extends StreamExtractor {
|
|||
} else if (mimeType.endsWith("ogg")) {
|
||||
mediaFormat = MediaFormat.OGG;
|
||||
} else {
|
||||
throw new ExtractionException("Unknown media format: " + mimeType);
|
||||
mediaFormat = null;
|
||||
}
|
||||
|
||||
audioStreams.add(new AudioStream(recording.getString("recording_url"),
|
||||
mediaFormat, -1));
|
||||
final AudioStream.Builder builder = new AudioStream.Builder()
|
||||
.setId(recording.getString("filename", ID_UNKNOWN))
|
||||
.setContent(recording.getString("recording_url"), true)
|
||||
.setMediaFormat(mediaFormat)
|
||||
.setAverageBitrate(UNKNOWN_BITRATE);
|
||||
|
||||
final String language = recording.getString("language");
|
||||
// If the language contains a - symbol, this means that the stream has an audio
|
||||
// track with multiple languages, so there is no specific language for this stream
|
||||
// Don't set the audio language in this case
|
||||
if (language != null && !language.contains("-")) {
|
||||
builder.setAudioLocale(LocaleCompat.forLanguageTag(language).orElseThrow(() ->
|
||||
new ParsingException(
|
||||
"Cannot convert this language to a locale: " + language)
|
||||
));
|
||||
}
|
||||
|
||||
// Not checking containsSimilarStream here, since MediaCCC does not provide enough
|
||||
// information to decide whether two streams are similar. Hence that method would
|
||||
// always return false, e.g. even for different language variations.
|
||||
audioStreams.add(builder.build());
|
||||
}
|
||||
}
|
||||
return audioStreams;
|
||||
|
@ -126,21 +153,29 @@ public class MediaCCCStreamExtractor extends StreamExtractor {
|
|||
final JsonObject recording = recordings.getObject(i);
|
||||
final String mimeType = recording.getString("mime_type");
|
||||
if (mimeType.startsWith("video")) {
|
||||
//first we need to resolve the actual video data from CDN
|
||||
|
||||
// First we need to resolve the actual video data from the CDN
|
||||
final MediaFormat mediaFormat;
|
||||
if (mimeType.endsWith("webm")) {
|
||||
mediaFormat = MediaFormat.WEBM;
|
||||
} else if (mimeType.endsWith("mp4")) {
|
||||
mediaFormat = MediaFormat.MPEG_4;
|
||||
} else {
|
||||
throw new ExtractionException("Unknown media format: " + mimeType);
|
||||
mediaFormat = null;
|
||||
}
|
||||
|
||||
videoStreams.add(new VideoStream(recording.getString("recording_url"),
|
||||
mediaFormat, recording.getInt("height") + "p"));
|
||||
// Not checking containsSimilarStream here, since MediaCCC does not provide enough
|
||||
// information to decide whether two streams are similar. Hence that method would
|
||||
// always return false, e.g. even for different language variations.
|
||||
videoStreams.add(new VideoStream.Builder()
|
||||
.setId(recording.getString("filename", ID_UNKNOWN))
|
||||
.setContent(recording.getString("recording_url"), true)
|
||||
.setIsVideoOnly(false)
|
||||
.setMediaFormat(mediaFormat)
|
||||
.setResolution(recording.getInt("height") + "p")
|
||||
.build());
|
||||
}
|
||||
}
|
||||
|
||||
return videoStreams;
|
||||
}
|
||||
|
||||
|
@ -163,7 +198,8 @@ public class MediaCCCStreamExtractor extends StreamExtractor {
|
|||
conferenceData = JsonParser.object()
|
||||
.from(downloader.get(data.getString("conference_url")).responseBody());
|
||||
} catch (final JsonParserException jpe) {
|
||||
throw new ExtractionException("Could not parse json returned by url: " + videoUrl, jpe);
|
||||
throw new ExtractionException("Could not parse json returned by URL: " + videoUrl,
|
||||
jpe);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -2,10 +2,16 @@ package org.schabi.newpipe.extractor.services.media_ccc.extractors.infoItems;
|
|||
|
||||
import com.grack.nanojson.JsonObject;
|
||||
|
||||
import org.schabi.newpipe.extractor.Image;
|
||||
import org.schabi.newpipe.extractor.ListExtractor;
|
||||
import org.schabi.newpipe.extractor.channel.ChannelInfoItemExtractor;
|
||||
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.util.List;
|
||||
|
||||
import static org.schabi.newpipe.extractor.services.media_ccc.extractors.MediaCCCParsingHelper.getImageListFromLogoImageUrl;
|
||||
|
||||
public class MediaCCCConferenceInfoItemExtractor implements ChannelInfoItemExtractor {
|
||||
private final JsonObject conference;
|
||||
|
||||
|
@ -43,8 +49,9 @@ public class MediaCCCConferenceInfoItemExtractor implements ChannelInfoItemExtra
|
|||
return conference.getString("url");
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public String getThumbnailUrl() {
|
||||
return conference.getString("logo_url");
|
||||
public List<Image> getThumbnails() {
|
||||
return getImageListFromLogoImageUrl(conference.getString("logo_url"));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,13 +1,18 @@
|
|||
package org.schabi.newpipe.extractor.services.media_ccc.extractors.infoItems;
|
||||
|
||||
import com.grack.nanojson.JsonObject;
|
||||
import org.schabi.newpipe.extractor.Image;
|
||||
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
||||
import org.schabi.newpipe.extractor.localization.DateWrapper;
|
||||
import org.schabi.newpipe.extractor.services.media_ccc.extractors.MediaCCCParsingHelper;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfoItemExtractor;
|
||||
import org.schabi.newpipe.extractor.stream.StreamType;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.List;
|
||||
|
||||
import static org.schabi.newpipe.extractor.services.media_ccc.extractors.MediaCCCParsingHelper.getThumbnailsFromStreamItem;
|
||||
|
||||
public class MediaCCCStreamInfoItemExtractor implements StreamInfoItemExtractor {
|
||||
private final JsonObject event;
|
||||
|
@ -46,12 +51,6 @@ public class MediaCCCStreamInfoItemExtractor implements StreamInfoItemExtractor
|
|||
return event.getString("conference_url");
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public String getUploaderAvatarUrl() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isUploaderVerified() throws ParsingException {
|
||||
return false;
|
||||
|
@ -84,8 +83,9 @@ public class MediaCCCStreamInfoItemExtractor implements StreamInfoItemExtractor
|
|||
+ event.getString("guid");
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public String getThumbnailUrl() {
|
||||
return event.getString("thumb_url");
|
||||
public List<Image> getThumbnails() {
|
||||
return getThumbnailsFromStreamItem(event);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,12 +1,22 @@
|
|||
package org.schabi.newpipe.extractor.services.media_ccc.linkHandler;
|
||||
|
||||
import org.schabi.newpipe.extractor.channel.tabs.ChannelTabs;
|
||||
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
||||
import org.schabi.newpipe.extractor.linkhandler.ListLinkHandlerFactory;
|
||||
import org.schabi.newpipe.extractor.utils.Parser;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class MediaCCCConferenceLinkHandlerFactory extends ListLinkHandlerFactory {
|
||||
/**
|
||||
* Since MediaCCC does not really have channel tabs (i.e. it only has one single "tab" with videos),
|
||||
* this link handler acts both as the channel link handler and the channel tab link handler. That's
|
||||
* why {@link #getAvailableContentFilter()} has been overridden.
|
||||
*/
|
||||
public final class MediaCCCConferenceLinkHandlerFactory extends ListLinkHandlerFactory {
|
||||
|
||||
private static final MediaCCCConferenceLinkHandlerFactory INSTANCE
|
||||
= new MediaCCCConferenceLinkHandlerFactory();
|
||||
|
||||
public static final String CONFERENCE_API_ENDPOINT
|
||||
= "https://api.media.ccc.de/public/conferences/";
|
||||
public static final String CONFERENCE_PATH = "https://media.ccc.de/c/";
|
||||
|
@ -14,15 +24,23 @@ public class MediaCCCConferenceLinkHandlerFactory extends ListLinkHandlerFactory
|
|||
= "(?:(?:(?:api\\.)?media\\.ccc\\.de/public/conferences/)"
|
||||
+ "|(?:media\\.ccc\\.de/[bc]/))([^/?&#]*)";
|
||||
|
||||
private MediaCCCConferenceLinkHandlerFactory() {
|
||||
}
|
||||
|
||||
public static MediaCCCConferenceLinkHandlerFactory getInstance() {
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUrl(final String id,
|
||||
final List<String> contentFilter,
|
||||
final String sortFilter) throws ParsingException {
|
||||
final String sortFilter)
|
||||
throws ParsingException, UnsupportedOperationException {
|
||||
return CONFERENCE_PATH + id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId(final String url) throws ParsingException {
|
||||
public String getId(final String url) throws ParsingException, UnsupportedOperationException {
|
||||
return Parser.matchGroup1(ID_PATTERN, url);
|
||||
}
|
||||
|
||||
|
@ -34,4 +52,15 @@ public class MediaCCCConferenceLinkHandlerFactory extends ListLinkHandlerFactory
|
|||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @see MediaCCCConferenceLinkHandlerFactory
|
||||
* @return MediaCCC's only channel "tab", i.e. {@link ChannelTabs#VIDEOS}
|
||||
*/
|
||||
@Override
|
||||
public String[] getAvailableContentFilter() {
|
||||
return new String[]{
|
||||
ChannelTabs.VIDEOS,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,15 +5,28 @@ import org.schabi.newpipe.extractor.linkhandler.ListLinkHandlerFactory;
|
|||
|
||||
import java.util.List;
|
||||
|
||||
public class MediaCCCConferencesListLinkHandlerFactory extends ListLinkHandlerFactory {
|
||||
public final class MediaCCCConferencesListLinkHandlerFactory extends ListLinkHandlerFactory {
|
||||
|
||||
private static final MediaCCCConferencesListLinkHandlerFactory INSTANCE =
|
||||
new MediaCCCConferencesListLinkHandlerFactory();
|
||||
|
||||
private MediaCCCConferencesListLinkHandlerFactory() {
|
||||
}
|
||||
|
||||
public static MediaCCCConferencesListLinkHandlerFactory getInstance() {
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId(final String url) throws ParsingException {
|
||||
public String getId(final String url) throws ParsingException, UnsupportedOperationException {
|
||||
return "conferences";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUrl(final String id, final List<String> contentFilter,
|
||||
final String sortFilter) throws ParsingException {
|
||||
public String getUrl(final String id,
|
||||
final List<String> contentFilter,
|
||||
final String sortFilter)
|
||||
throws ParsingException, UnsupportedOperationException {
|
||||
return "https://media.ccc.de/public/conferences";
|
||||
}
|
||||
|
||||
|
|
|
@ -6,11 +6,22 @@ import org.schabi.newpipe.extractor.linkhandler.ListLinkHandlerFactory;
|
|||
import java.util.List;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class MediaCCCLiveListLinkHandlerFactory extends ListLinkHandlerFactory {
|
||||
public final class MediaCCCLiveListLinkHandlerFactory extends ListLinkHandlerFactory {
|
||||
|
||||
private static final MediaCCCLiveListLinkHandlerFactory INSTANCE =
|
||||
new MediaCCCLiveListLinkHandlerFactory();
|
||||
|
||||
private static final String STREAM_PATTERN = "^(?:https?://)?media\\.ccc\\.de/live$";
|
||||
|
||||
private MediaCCCLiveListLinkHandlerFactory() {
|
||||
}
|
||||
|
||||
public static MediaCCCLiveListLinkHandlerFactory getInstance() {
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId(final String url) throws ParsingException {
|
||||
public String getId(final String url) throws ParsingException, UnsupportedOperationException {
|
||||
return "live";
|
||||
}
|
||||
|
||||
|
@ -22,7 +33,8 @@ public class MediaCCCLiveListLinkHandlerFactory extends ListLinkHandlerFactory {
|
|||
@Override
|
||||
public String getUrl(final String id,
|
||||
final List<String> contentFilter,
|
||||
final String sortFilter) throws ParsingException {
|
||||
final String sortFilter)
|
||||
throws ParsingException, UnsupportedOperationException {
|
||||
// FIXME: wrong URL; should be https://streaming.media.ccc.de/{conference_slug}/{room_slug}
|
||||
return "https://media.ccc.de/live";
|
||||
}
|
||||
|
|
|
@ -1,32 +0,0 @@
|
|||
package org.schabi.newpipe.extractor.services.media_ccc.linkHandler;
|
||||
|
||||
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
||||
import org.schabi.newpipe.extractor.linkhandler.LinkHandlerFactory;
|
||||
import org.schabi.newpipe.extractor.utils.Parser;
|
||||
|
||||
public class MediaCCCLiveStreamLinkHandlerFactory extends LinkHandlerFactory {
|
||||
public static final String VIDEO_API_ENDPOINT = "https://api.media.ccc.de/public/events/";
|
||||
private static final String VIDEO_PATH = "https://streaming.media.ccc.de/v/";
|
||||
private static final String ID_PATTERN
|
||||
= "(?:(?:(?:api\\.)?media\\.ccc\\.de/public/events/)"
|
||||
+ "|(?:media\\.ccc\\.de/v/))([^/?&#]*)";
|
||||
|
||||
@Override
|
||||
public String getId(final String url) throws ParsingException {
|
||||
return Parser.matchGroup1(ID_PATTERN, url);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUrl(final String id) throws ParsingException {
|
||||
return VIDEO_PATH + id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onAcceptUrl(final String url) {
|
||||
try {
|
||||
return getId(url) != null;
|
||||
} catch (final ParsingException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,15 +1,27 @@
|
|||
package org.schabi.newpipe.extractor.services.media_ccc.linkHandler;
|
||||
|
||||
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
||||
import org.schabi.newpipe.extractor.linkhandler.ListLinkHandlerFactory;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class MediaCCCRecentListLinkHandlerFactory extends ListLinkHandlerFactory {
|
||||
public final class MediaCCCRecentListLinkHandlerFactory extends ListLinkHandlerFactory {
|
||||
|
||||
private static final MediaCCCRecentListLinkHandlerFactory INSTANCE =
|
||||
new MediaCCCRecentListLinkHandlerFactory();
|
||||
|
||||
private static final String PATTERN = "^(https?://)?media\\.ccc\\.de/recent/?$";
|
||||
|
||||
private MediaCCCRecentListLinkHandlerFactory() {
|
||||
}
|
||||
|
||||
public static MediaCCCRecentListLinkHandlerFactory getInstance() {
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId(final String url) {
|
||||
public String getId(final String url) throws ParsingException, UnsupportedOperationException {
|
||||
return "recent";
|
||||
}
|
||||
|
||||
|
@ -21,7 +33,8 @@ public class MediaCCCRecentListLinkHandlerFactory extends ListLinkHandlerFactory
|
|||
@Override
|
||||
public String getUrl(final String id,
|
||||
final List<String> contentFilter,
|
||||
final String sortFilter) {
|
||||
final String sortFilter)
|
||||
throws ParsingException, UnsupportedOperationException {
|
||||
return "https://media.ccc.de/recent";
|
||||
}
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue