Compare commits

...

131 Commits

Author SHA1 Message Date
Pierre Rudloff ec95a8f1b7
Merge branch 'release/3.2.0-alpha' 2023-04-22 23:21:32 +02:00
Pierre Rudloff a9da2314af
Merge branch 'master' into develop 2023-04-22 23:16:52 +02:00
dependabot[bot] fcb3d2e84c
Bump guzzlehttp/psr7 from 1.9.0 to 1.9.1 (#436)
Bumps [guzzlehttp/psr7](https://github.com/guzzle/psr7) from 1.9.0 to 1.9.1.
- [Release notes](https://github.com/guzzle/psr7/releases)
- [Changelog](https://github.com/guzzle/psr7/blob/1.9.1/CHANGELOG.md)
- [Commits](https://github.com/guzzle/psr7/compare/1.9.0...1.9.1)

---
updated-dependencies:
- dependency-name: guzzlehttp/psr7
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-04-19 23:47:18 +02:00
Pierre Rudloff f09b7b43d7
Merge branch 'master' into develop 2023-03-30 21:46:49 +02:00
dependabot[bot] a4146a63c8
Bump smarty/smarty from 4.3.0 to 4.3.1 (#431)
Bumps [smarty/smarty](https://github.com/smarty-php/smarty) from 4.3.0 to 4.3.1.
- [Release notes](https://github.com/smarty-php/smarty/releases)
- [Changelog](https://github.com/smarty-php/smarty/blob/master/CHANGELOG.md)
- [Commits](https://github.com/smarty-php/smarty/compare/v4.3.0...v4.3.1)

---
updated-dependencies:
- dependency-name: smarty/smarty
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-30 21:46:39 +02:00
Pierre Rudloff 3e45f19682
Switch to yt-dlp (fixes #432)
youtube-dl has no new release since 2021
2023-03-30 21:41:08 +02:00
Pierre Rudloff 9564764633
ucfirst Smarty modifier is deprecated
See https://github.com/smarty-php/smarty/issues/813
2023-03-21 20:25:47 +01:00
Pierre Rudloff 71647158d3
Stronger typying now that we target PHP 7.4 2023-03-21 20:20:14 +01:00
Pierre Rudloff b23ce88be8
fixup! fix dockerfile permission issue (#426) 2023-03-21 20:07:31 +01:00
Pierre Rudloff b52a582539
Stop supporting PHP 7.3 (#430)
It is unmaintained
2023-03-21 20:07:08 +01:00
Pierre Rudloff 7bfe55fff6
"git describe" needs to be non-interactive so we can get its output 2023-03-11 14:37:28 +01:00
Pierre Rudloff 9d8bff3c42 Updated robo to 3.0
Various dependencies update
 -----BEGIN SSH SIGNATURE-----
 U1NIU0lHAAAAAQAAAZcAAAAHc3NoLXJzYQAAAAMBAAEAAAGBAJ1cjUGHGZz2Xlnuxq9dZ2
 wA/r2ineVomdWGZBiq9GYcfUirCaArD49YsXbdQKkcHhrQ+gMUNQP4iOWjuPBG5WAPrORz
 LXUXwmk0UHdEPdJ2LymHXQsyYyHaldikt3gfsiD0ny/Uf3V9lL3vnrEUhQAxIYN87RaQun
 OL0S5y5wOp3vr8DwMlvAUDfZbzZgJ56nzVWRElVLNuQCQSZta9imqnJpzHQf4KTMut191S
 hgQjBA0TWeOAqGVeIMOctLqNY1gL5QUSyg5RYmBHjResH8tnOkfPElE1Ih19MCCQ9Eaubw
 g1cS6Ls6LgbTIdvyMud6ep+0iF3ifj0g7w1X6NvQfQp0JcWkU3eXyQfOGjdePZG9Kn+27Q
 EeMv47cyCGuMdGbpbFoD/yR82T2n78rPmxbYEnPnJSmuy30Wi8reYeaQUP2z+Krexk5tGQ
 a/thYIk1yhN+Ui3nSr78sATwgqateS6VvNw8nY315PUzmf4Bk0kDCnj7Q7z4wenHAhkayU
 FwAAAANnaXQAAAAAAAAABnNoYTUxMgAAAZQAAAAMcnNhLXNoYTItNTEyAAABgG9ULjUS0q
 UP06d9PYGfxYL3aADqwS5eczhU4B3QsUD5aab3q1qbqUfkRZ7Gvhy3lK4UXJvyy5kGC5Ba
 1u+WBlCuX4ki6Vgum0skw612V5cKOdcr5eDDc5jYRPwNd3P/Aa/YoBc1Ev4a1Ozlj9g2MR
 2XmDHxaoW6tzjsGYgPhihr1eIDAQm+BxeccDdjs9lsSiHfpcW6Vg7MdBzxZFPBEbpeEp6I
 WsvWVntjwBKFXG+cx/nBq+HNwdBHOq5ncE3eLHG7loNb5O3cof9t8Y91z+P6Cnw8r6lulu
 RIOgBfv/fIgZTIXq/UZ/bjUmbDrpCfDm2mtSi57X4Iaj5ZBXAu0tGwl46ZhGjDnwxBcBGn
 DcaLWUhVL19JEhKwq3APdV++ZEpeU4+G4VbuYvjbLe3kT/S/AMaw/5H1D7CFPQB30AeLbl
 l+1QWoc3bW8rY0FqBHpaFl5mgvu3VL23H8O+VhrzueM/CK22aaBt86bvUVHQnqLnQQ82xo
 oARjISpOfp0xjw==
 -----END SSH SIGNATURE-----
gpgsig -----BEGIN SSH SIGNATURE-----
 U1NIU0lHAAAAAQAAAZcAAAAHc3NoLXJzYQAAAAMBAAEAAAGBAJ1cjUGHGZz2Xlnuxq9dZ2
 wA/r2ineVomdWGZBiq9GYcfUirCaArD49YsXbdQKkcHhrQ+gMUNQP4iOWjuPBG5WAPrORz
 LXUXwmk0UHdEPdJ2LymHXQsyYyHaldikt3gfsiD0ny/Uf3V9lL3vnrEUhQAxIYN87RaQun
 OL0S5y5wOp3vr8DwMlvAUDfZbzZgJ56nzVWRElVLNuQCQSZta9imqnJpzHQf4KTMut191S
 hgQjBA0TWeOAqGVeIMOctLqNY1gL5QUSyg5RYmBHjResH8tnOkfPElE1Ih19MCCQ9Eaubw
 g1cS6Ls6LgbTIdvyMud6ep+0iF3ifj0g7w1X6NvQfQp0JcWkU3eXyQfOGjdePZG9Kn+27Q
 EeMv47cyCGuMdGbpbFoD/yR82T2n78rPmxbYEnPnJSmuy30Wi8reYeaQUP2z+Krexk5tGQ
 a/thYIk1yhN+Ui3nSr78sATwgqateS6VvNw8nY315PUzmf4Bk0kDCnj7Q7z4wenHAhkayU
 FwAAAANnaXQAAAAAAAAABnNoYTUxMgAAAZQAAAAMcnNhLXNoYTItNTEyAAABgJrUC15ar9
 VQj/LfmlMNN7+ec1D17Bk4q7/XH27FgUyRCHXLFS4hm0GhtjIZAaA9jErCt23NcAFan7cI
 WQyL6AAqqq/DOshmaYXY7Zj+5vQXbiJjDcGu1IsdGEFAA/DIAr721vxfKrri3aArucWK3T
 ymPliFOqhL1qyxKqFxrADJmcqZeL9XzoQ0RHxayyN8XRYf5Px8vrmxkgvcI7wiy2W554qk
 3DR7UlcHX/rg1H8B6W+PO7WK0JWdFoRRR/EErmo2VzCfEo/3USxJPPNUrhEv6K02WUVzib
 1ac6cjuIsb0xHE2zVQO6hgpIH9L73Ef6pOUcEnlu2zqE1FPgJc5u0q5MDXQyNMGTtcKk8U
 /UfNJw55umSMneOTHnhZ3AfmNYAmxWGGfwpbc2Y/rrOdBXWVhJzoPZWRKuOANk65+9NIjC
 20KLEeZSvodADv+f+WGrsVDHB2NUKLG5YuuWANH/s07a9Mm7I/XedWgRE7wh/WzfT93XKn
 BwDuEKTCMHOZwQ==
 -----END SSH SIGNATURE-----

Merge tag '3.1.1' into develop

Updated robo to 3.0
Various dependencies update
2023-03-11 14:32:35 +01:00
Pierre Rudloff 2bef4d551d
Merge branch 'release/3.1.1' 2023-03-11 14:30:30 +01:00
Pierre Rudloff f475fa2a47
Lint 2023-03-03 00:18:00 +01:00
Pierre Rudloff 8e6e88a2b2
Dependencies update 2023-03-03 00:14:36 +01:00
Pierre Rudloff 2d60fd32ef Merge branch 'master' into develop 2022-11-07 21:20:44 +01:00
Pierre Rudloff f32412e861 fixup! fix dockerfile permission issue (#426) 2022-11-07 21:19:52 +01:00
Samuel Tan d060650833
fix dockerfile permission issue (#426) 2022-10-25 22:43:26 +02:00
Pierre Rudloff 4e09393fd9 Update robo to 3.0
To fix a PHP 8 compatibility notice
2022-10-16 15:42:19 +02:00
Pierre Rudloff 5d5a6624b8 Merge tag '3.1.0' into develop
Removed every reference to alltubedownload.net (#422)
Updated youtube-dl to 2021.12.17
Updated alltube-library to 0.1.3
Updated Smarty to 4.0
PHP 8 compatibility
Various refactoring and typying improvement
2022-10-16 15:36:55 +02:00
Pierre Rudloff 36a91c8d4d Merge branch 'release/3.1.0' 2022-10-16 15:36:37 +02:00
Pierre Rudloff 1031ad152d Remove every reference to alltubedownload.net (#422) 2022-10-16 15:22:47 +02:00
Pierre Rudloff 87e30f2e87 Merge branch 'master' into develop 2022-10-16 15:16:38 +02:00
dependabot[bot] 3b6b1f0387
Bump smarty/smarty from 3.1.45 to 3.1.47 (#425)
Bumps smarty/smarty from 3.1.45 to 3.1.47.

---
updated-dependencies:
- dependency-name: smarty/smarty
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-09-25 15:09:49 +02:00
Pierre Rudloff b95fed4935 Update phpstan
To fix compatibility with PHP 8.1
2022-06-28 23:08:03 +02:00
Pierre Rudloff b5f757b562 Merge branch 'master' into develop 2022-06-28 23:07:07 +02:00
Pierre Rudloff ffeda5ea90 Declare allowed composer plugins 2022-06-28 23:05:34 +02:00
Pierre Rudloff e9efc6ef71 Update symfony/string
To avoid redeclaring functions that already exist: af4b27f47b
2022-06-28 23:04:03 +02:00
dependabot[bot] 550371db7c
Bump guzzlehttp/guzzle from 6.5.7 to 6.5.8 (#418)
Bumps [guzzlehttp/guzzle](https://github.com/guzzle/guzzle) from 6.5.7 to 6.5.8.
- [Release notes](https://github.com/guzzle/guzzle/releases)
- [Changelog](https://github.com/guzzle/guzzle/blob/6.5.8/CHANGELOG.md)
- [Commits](https://github.com/guzzle/guzzle/compare/6.5.7...6.5.8)

---
updated-dependencies:
- dependency-name: guzzlehttp/guzzle
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-06-28 22:52:44 +02:00
dependabot[bot] 4e826e554d
Bump guzzlehttp/guzzle from 6.5.6 to 6.5.7 (#415)
Bumps [guzzlehttp/guzzle](https://github.com/guzzle/guzzle) from 6.5.6 to 6.5.7.
- [Release notes](https://github.com/guzzle/guzzle/releases)
- [Changelog](https://github.com/guzzle/guzzle/blob/6.5.7/CHANGELOG.md)
- [Commits](https://github.com/guzzle/guzzle/compare/6.5.6...6.5.7)

---
updated-dependencies:
- dependency-name: guzzlehttp/guzzle
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-06-14 22:05:02 +02:00
Pierre Rudloff 1055ce0c4b Merge branch 'master' into develop 2022-06-08 00:10:34 +02:00
Pierre Rudloff 57dd9a7dd3 Convert issue template to form 2022-06-08 00:08:01 +02:00
Pierre Rudloff e53393d670 Force port for canonical URL (#410) 2022-06-01 21:57:46 +02:00
Pierre Rudloff f6ae6eded3 Merge branch 'master' into develop 2022-05-28 23:52:51 +02:00
dependabot[bot] e7fd4c6bc4
Bump guzzlehttp/guzzle from 6.5.5 to 6.5.6 (#412)
Bumps [guzzlehttp/guzzle](https://github.com/guzzle/guzzle) from 6.5.5 to 6.5.6.
- [Release notes](https://github.com/guzzle/guzzle/releases)
- [Changelog](https://github.com/guzzle/guzzle/blob/6.5.6/CHANGELOG.md)
- [Commits](https://github.com/guzzle/guzzle/compare/6.5.5...6.5.6)

---
updated-dependencies:
- dependency-name: guzzlehttp/guzzle
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-05-28 23:50:56 +02:00
dependabot[bot] b894cdd6ce
Bump smarty/smarty from 3.1.43 to 3.1.45 (#413)
Bumps smarty/smarty from 3.1.43 to 3.1.45.

---
updated-dependencies:
- dependency-name: smarty/smarty
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-05-28 23:50:20 +02:00
Pierre Rudloff e81b8c75a8 Add void return types 2022-05-28 23:44:34 +02:00
Pierre Rudloff e3187a7258 Merge branch 'master' into develop 2022-04-07 22:15:36 +02:00
Liu Wenyuan 1d1e804b71
Update (redo) zh_CN translation (#369) 2022-04-07 22:14:08 +02:00
dependabot[bot] 6731fcdf96
Bump guzzlehttp/psr7 from 1.6.1 to 1.8.5 (#406)
Bumps [guzzlehttp/psr7](https://github.com/guzzle/psr7) from 1.6.1 to 1.8.5.
- [Release notes](https://github.com/guzzle/psr7/releases)
- [Changelog](https://github.com/guzzle/psr7/blob/1.8.5/CHANGELOG.md)
- [Commits](https://github.com/guzzle/psr7/compare/1.6.1...1.8.5)

---
updated-dependencies:
- dependency-name: guzzlehttp/psr7
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-03-30 09:11:45 +02:00
Pierre Rudloff 10b7658240 Merge tag '3.0.3' into develop
Fixed a vulnerability that could be used to trigger either an open redirect or a SSRF attack
2022-03-08 09:36:00 +01:00
Pierre Rudloff 3d09289104 Merge branch 'hotfix/3.0.3' 2022-03-08 09:33:44 +01:00
Pierre Rudloff 8913f27716 Disable the generic extractor entirely
It can be used for SSRF attacks even when redirects are disabled
2022-03-08 09:29:57 +01:00
Pierre Rudloff 113b3d5e50 Some videos have no format 2022-03-06 22:55:33 +01:00
Pierre Rudloff edaf6f82c0 fixup! LinkHeaderMiddleware should use the same URL as ViewFactory This way the X-Forwarded-Path header is used to generate the Link header 2022-02-27 23:47:19 +01:00
Pierre Rudloff f814ebc492 Missing exception in @throws tag 2022-02-27 23:45:59 +01:00
Pierre Rudloff dad8b6d704 Use HTTPS URLs in tests 2022-02-27 23:44:36 +01:00
Pierre Rudloff 363bf9b08c fixup! Prevent SSRF requests By validating the provided URL before passing it to youtube-dl 2022-02-27 23:36:51 +01:00
Pierre Rudloff 732baccd63 Make the watch route generate a full YouTube URL (fixes #402) 2022-02-27 23:32:08 +01:00
Pierre Rudloff 7f28275fb0 Merge tag '3.0.2' into develop
Fixed a SSRF vulnerability that could be used to send a request to an internal hostname
2022-02-27 12:34:23 +01:00
Pierre Rudloff 148a171b24 Merge branch 'hotfix/3.0.2' 2022-02-27 12:32:36 +01:00
Pierre Rudloff 1b099bb983 Patch youtube-dl to disable redirects
In order to prevent SSRF attacks using redirects
2022-02-27 12:30:15 +01:00
Pierre Rudloff 3a4f09dda0 Prevent SSRF requests
By validating the provided URL before passing it to youtube-dl
2022-02-27 11:00:33 +01:00
Pierre Rudloff bf4a761d3a Make UglyRouter compatible with routes with parameters (#399) 2022-02-23 21:30:58 +01:00
Pierre Rudloff 6ad0486468 Use Python 3.8.12 on Heroku 2022-02-22 23:10:54 +01:00
Pierre Rudloff e246ab03e9 Partial PHP 8 compatibility
But we still need to update rinvex/countries
2022-02-22 22:58:57 +01:00
Pierre Rudloff e567f9c9fa Update annotated-command
To fix PHP 8 compatibility issues: https://github.com/consolidation/annotated-command/pull/210
2022-02-20 14:19:41 +01:00
Pierre Rudloff 64ac180a53 Merge branch 'master' into develop 2022-02-20 14:07:21 +01:00
Pierre Rudloff 2afbfb4bf2 fixup! Don't redirect to REQUEST_URI when browsing to index.php Instead, we can make sure everything works correctly on index.php 2022-02-20 14:06:59 +01:00
Pierre Rudloff 9410d4b49b LinkHeaderMiddleware should use the same URL as ViewFactory
This way the X-Forwarded-Path header is used to generate the Link header
2022-02-20 13:55:44 +01:00
Pierre Rudloff bfaea0e381 Merge tag '3.0.1' into develop
Fixed an open redirect vulnerability that could be used to construct an URL redirecting to an arbitraty domain
2022-02-20 13:34:53 +01:00
Pierre Rudloff 3ab22c654a Merge branch 'hotfix/3.0.1' 2022-02-20 13:31:40 +01:00
Pierre Rudloff bc14b6e45c Don't redirect to REQUEST_URI when browsing to index.php
Instead, we can make sure everything works correctly on index.php
2022-02-20 13:28:57 +01:00
Pierre Rudloff acbd2008ca Merge branch 'master' into develop 2022-02-19 20:48:02 +01:00
Pierre Rudloff cf82f1cc8f
Add security policy 2022-02-19 20:47:53 +01:00
Pierre Rudloff 5677ce719a Update youtube-dl to 2021.12.17 (#395) 2022-02-17 22:13:56 +01:00
Pierre Rudloff 655490eeb3 Use HTTPS URLs in composer.json 2022-02-17 22:00:08 +01:00
Pierre Rudloff 18847e4d75 More robust way to detect CI in tests 2022-02-07 22:30:47 +01:00
Pierre Rudloff fe771886d9 Replace Travis with GitHub actions
travis-ci.org does not run tests anymore
2022-02-07 22:26:33 +01:00
Pierre Rudloff 27439c7e14 Simplify overly complicated format selection template 2022-02-06 20:46:38 +01:00
Pierre Rudloff d9ba01f017 Generate <img> tags with Smarty 2022-02-06 19:17:05 +01:00
Pierre Rudloff ce9b4d9a48 Update Smarty to 4.0 2022-02-06 18:43:08 +01:00
Pierre Rudloff 7cd42e6c6b Fix MP3 option size 2022-02-03 21:57:00 +01:00
Pierre Rudloff ac8c53375a Easier to maintain template structure
This the head and footer don't have to be included everytime and the hierarchy is easier to read
2022-02-03 21:41:07 +01:00
Pierre Rudloff de74808459 More readable way to include HTML in translated strings 2022-02-03 21:07:13 +01:00
Pierre Rudloff bdf5554430 Use HTTPS links 2022-02-03 20:55:09 +01:00
Pierre Rudloff b8c88aecf5 Improve typing 2022-02-03 20:52:18 +01:00
Pierre Rudloff d46563f994 Simplify code 2022-02-03 20:21:04 +01:00
Pierre Rudloff 781b5c8bc2 phpcs does not like full namespaces 2022-02-03 20:03:55 +01:00
Pierre Rudloff ffd9275500 Correct way to use interface constant 2022-02-03 20:01:56 +01:00
Pierre Rudloff 6fef87f58b Use HTML dumper for Smarty collector 2022-01-27 00:15:05 +01:00
Pierre Rudloff 835170f4b5 Use phpmnd to detect magic numbers 2022-01-27 00:03:37 +01:00
Pierre Rudloff 5ed15afe1f Use constant for HTTP response code 2022-01-26 23:58:25 +01:00
Pierre Rudloff 359c358df1 Symfony 5.0 is not maintained anymore 2022-01-26 23:53:14 +01:00
Pierre Rudloff c44979bbae
Merge pull request #385 from LoganTann/master
fix: manifest causes 404 when making pwa shortcut
2022-01-17 20:24:57 +01:00
Pierre Rudloff 8f3f1cdaf8 Merge branch 'master' into develop 2022-01-17 20:14:06 +01:00
ShinProg (Logan Tann) 1464b2c319
fix: manifest causes 404 when making pwa shortcut
fixes #384
2022-01-17 11:38:38 +01:00
dependabot[bot] fb78ecb410 Bump smarty/smarty from 3.1.39 to 3.1.43 (#383)
Bumps smarty/smarty from 3.1.39 to 3.1.43.

---
updated-dependencies:
- dependency-name: smarty/smarty
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-01-13 00:05:49 +01:00
Pierre Rudloff d744ee557e Build Link header from an array 2021-10-19 23:14:38 +02:00
Pierre Rudloff 5d40523cf4 Don't hardcode class name 2021-10-18 13:16:28 +02:00
Pierre Rudloff 55db198d39 Upgrade phpunit to 9.5
So we stop depending on the unmaintained php-token-stream
2021-10-17 21:14:39 +02:00
M*C*O 60f924f4bf
Document X-Forwarded-Proto in README (#368) 2021-07-25 15:02:03 +02:00
Pierre Rudloff 607efaa292 fixup! Fix small typos (#333) 2021-06-06 19:15:44 +02:00
Pierre Rudloff f3ffa90a2e Update alltube-library to 0.1.3 2021-05-13 13:03:10 +02:00
Pierre Rudloff a95d1de67e Update alltube-library to 0.1.2 2021-05-05 21:48:10 +02:00
Pierre Rudloff 1753adf478 Merge tag '3.0.0' into develop
This release contains several breaking changes:

The Video class is now available as a separate Composer package (rudloff/alltube-library)
The release package and Docker image now contain only production dependencies
youtube-dl is now a production dependency
Composer does not install ffmpeg or phantomjs anymore
The "avconv" and "avconvVerbosity" options are now respectively "ffmpeg" and "ffmpegVerbosity"

Other changes:

Setting the "stream" option to "ask" now works correctly
New locales are automatically detected
New Italian translation (thanks to @holoitsme)
If the "best" format does not exist, it will fall back to "bestvideo"
Composer 2 compatibility
youtube-dl and ffmpeg commands are now logged when debug mode is enabled
404 and 405 error pages now have the same style as the other pages
The new "defaultAudio" option allows converting to audio by default (thanks to @bellington3)
The Heroku build now uses Python 3 (thanks to @telegrambotdev)
The app now supports the container Heroku stack (thanks to @telegrambotdev)
The new "convertSeek" option allows disabling seeking when converting to audio (thanks to @bellington3)
Exceptions are now logged
AllTube can now run correctly behind a reverse proxy with a custom path or port (thanks to @bellington3)
2021-04-20 23:19:29 +02:00
Pierre Rudloff eeda434b2f Merge branch 'release-3.0.0' 2021-04-20 23:16:45 +02:00
Pierre Rudloff b902c9027b Upgrade youtube-dl to 2021.04.01 (fixes #349) 2021-04-02 21:05:50 +02:00
Advizormcpe1 be3f7d9a82 Updated japanese translation 2021-02-28 18:34:07 +01:00
Pierre Rudloff 97d6532388 Merge branch 'master' into develop 2021-02-26 22:53:09 +01:00
dependabot[bot] 6ab19b6d84
Bump smarty/smarty from 3.1.33 to 3.1.39 (#346)
Bumps smarty/smarty from 3.1.33 to 3.1.39.

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-02-26 22:48:17 +01:00
Pierre Rudloff 73e4fc1b13 Missing root_path service in tests 2021-02-22 21:16:29 +01:00
Pierre Rudloff 9688244285 Remove donation links 2021-02-22 21:12:23 +01:00
Pierre Rudloff 104a866188 Japanese translation 2021-02-22 21:10:58 +01:00
Pierre Rudloff 3cfd450258 Use a stable release of debugbar-smarty 2021-02-11 19:36:11 +01:00
Pierre Rudloff 1e17dff21e Use the new root_path service to make some code more portable 2021-02-09 22:35:32 +01:00
Pierre Rudloff f2be3a7e5b Use relative paths on debug error page 2021-02-09 22:31:41 +01:00
Pierre Rudloff 9a27e7764a Upgrade grumphp to 1.3
In order to use the new securitychecker_enlightn task
2021-02-07 13:40:02 +01:00
Pierre Rudloff 36ba147430 phpstan update 2021-02-07 12:42:03 +01:00
Pierre Rudloff 50fe879f16 Add route info to debug bar 2021-02-07 12:24:16 +01:00
Pierre Rudloff 9af922f3f1 Add Smarty collector to debug bar 2021-02-07 00:03:37 +01:00
Pierre Rudloff bba5090ec3 We can't be sure of the class of the logger 2021-02-06 18:22:19 +01:00
Pierre Rudloff 5c0ed594f3 Debug bar 2021-02-06 15:35:09 +01:00
Pierre Rudloff 58f79c5012 Use enlightn/security-checker instead of sensiolabs/security-checker (fixes #342) 2021-02-02 21:26:26 +01:00
Pierre Rudloff 5d550a100d Disable coverage in phpunit
It causes an error on Travis and it is not used anymore
2021-02-02 00:27:24 +01:00
Pierre Rudloff 077bb4762e Merge branch 'master' into develop 2021-02-01 21:27:07 +01:00
prog-it 03d6a59b26
Russian translation (#341) 2021-02-01 21:26:23 +01:00
Pierre Rudloff 05311ac7b6 Add return types 2020-12-17 22:49:21 +01:00
Pierre Rudloff f2785bca03 Clear Smarty compiled templates before controller tests
In order to avoid permission errors
2020-12-17 22:30:19 +01:00
Pierre Rudloff 9921b6210f Merge tag '3.0.0-beta5' into develop
Composer 2 compatibility
Updated youtube-dl to 2020.11.12
Make sure locale is always set, even on first request
2020-12-17 20:07:44 +01:00
Pierre Rudloff c9aa41d206 Merge branch 'release-3.0.0-beta5' 2020-12-17 20:06:09 +01:00
Éric Gaspar a5bda1d35e
Fix small typos (#333)
* Fix small typos

- *Fix cap on YouTube, AllTube...*

* Fix YouTube cap
2020-12-05 15:00:46 +01:00
Pierre Rudloff f2bef49ad6 fixup! Make sure locale is always set, even on first request 2020-11-21 14:38:37 +01:00
Pierre Rudloff f184bda59b Make sure locale is always set, even on first request 2020-11-21 14:20:01 +01:00
Pierre Rudloff 05959b17f0 Upgrade grumphp to 1.1
In order to improve output in CI
2020-11-16 23:31:18 +01:00
Pierre Rudloff b5a585443a fixup! The youtube-dl repository is back 2020-11-16 23:28:36 +01:00
Pierre Rudloff 81e42057f9 Upgrade composer-dangling-locked-deps to 0.2.1
To fix Composer 2 compatibility
2020-11-16 23:21:32 +01:00
Pierre Rudloff a800a058fa The youtube-dl repository is back 2020-11-16 20:48:03 +01:00
Pierre Rudloff 2d1d69a1f1 Refactor some Robo code 2020-11-16 20:45:49 +01:00
Tripp Sanders bd2b72721f
Remove an extra S (#328)
* Remove an extra s

* Remove a few extra S’s
2020-11-08 19:53:42 +01:00
Pierre Rudloff c844d3bf74 Merge tag '3.0.0-beta4' into develop
Fixed the CSP because it was breaking downloads on Chrome (#327)
Upgraded youtube-dl to 2020.11.01.1 (#326)
2020-11-04 23:08:02 +01:00
85 changed files with 5777 additions and 2777 deletions

64
.github/ISSUE_TEMPLATE/bug.yml vendored Normal file
View File

@ -0,0 +1,64 @@
---
name: New issue
description: Please answer these questions when reporting a new issue.
body:
- type: input
attributes:
label: What is your operating system (Windows, Linux, OSX, etc.)?
validations:
required: true
- type: input
attributes:
label: What is your web server (Apache, IIS, etc.)?
validations:
required: true
- type: input
attributes:
label: What version of AllTube are you using?
validations:
required: true
- type: dropdown
attributes:
label: How did you install AllTube?
options:
- Git
- Release package
- Docker
- Other (please specify)
validations:
required: true
- type: input
attributes:
label: What version of PHP are you using?
validations:
required: true
- type: input
attributes:
label: What version of Python are you using?
validations:
required: true
- type: input
attributes:
label: What version of youtube-dl are you using?
validations:
required: true
- type: textarea
attributes:
label: Do you get any PHP-related errors in your webserver's logs?
description: If so, paste them here.
render: plain text
- type: textarea
attributes:
label: What is the content of your "config/config.yml" file?
description: If you don't have this file, you can ignore this question.
render: yml
- type: input
attributes:
label: Please provide the URL of a video that causes the issue.
validations:
required: true
- type: textarea
attributes:
label: Describe your issue
validations:
required: true

25
.github/workflows/ci.yml vendored Normal file
View File

@ -0,0 +1,25 @@
---
name: Tests
on:
- push
- pull_request
jobs:
tests:
runs-on: ubuntu-latest
strategy:
matrix:
php-version:
- '7.4'
- '8.0'
- '8.1'
steps:
- uses: actions/checkout@v2
- name: Use PHP ${{ matrix.php-version }}
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php-version }}
tools: composer
- run: composer install --no-progress
- run: composer check-platform-reqs
- run: composer lint
- run: composer test

View File

@ -18,9 +18,6 @@ FileETag None
<ifmodule mod_rewrite.c> <ifmodule mod_rewrite.c>
RewriteEngine On RewriteEngine On
RewriteCond %{HTTP_HOST} ^alltube\.herokuapp\.com$ [NC]
RewriteRule ^(.*)$ https://www.alltubedownload.net/$1 [R=301,L]
RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^ index.php [QSA,L] RewriteRule ^ index.php [QSA,L]
</ifmodule> </ifmodule>

View File

@ -1,12 +0,0 @@
---
language: php
php: 7.3
addons:
apt:
packages:
- language-pack-fr
install: composer install --no-progress
script:
- composer check-platform-reqs
- composer lint
- composer test

View File

@ -1,7 +1,6 @@
FROM php:7.3-apache FROM php:7.4-apache
RUN apt-get update RUN apt-get update
RUN apt-get install -y libicu-dev xz-utils git python libgmp-dev unzip ffmpeg RUN apt-get install -y libicu-dev xz-utils git python libgmp-dev unzip ffmpeg
RUN docker-php-ext-install mbstring
RUN docker-php-ext-install intl RUN docker-php-ext-install intl
RUN docker-php-ext-install gmp RUN docker-php-ext-install gmp
RUN a2enmod rewrite RUN a2enmod rewrite
@ -10,4 +9,7 @@ COPY resources/php.ini /usr/local/etc/php/
COPY . /var/www/html/ COPY . /var/www/html/
RUN php composer.phar check-platform-reqs --no-dev RUN php composer.phar check-platform-reqs --no-dev
RUN php composer.phar install --prefer-dist --no-progress --no-dev --optimize-autoloader RUN php composer.phar install --prefer-dist --no-progress --no-dev --optimize-autoloader
RUN mkdir /var/www/html/templates_c/
RUN chmod 770 -R /var/www/html/templates_c/
RUN chown www-data -R /var/www/html/templates_c/
ENV CONVERT=1 ENV CONVERT=1

View File

@ -1,31 +0,0 @@
# New issue
## Your environment
Please answer these questions when reporting a new issue:
**What is your operating system (Windows, Linux, OSX, etc.)?**
**What is your web server (Apache, IIS, etc.)?**
**What version of AllTube are you using?**
**How did you install AllTube (with Git or with a release package)?**
**What version of PHP are you using?**
**What version of Python are you using?**
**What version of youtube-dl are you using?**
**Do you get any PHP-related errors in your webserver's logs?**
**What is the content of your `config/config.yml` file?**
```yaml
# Insert content here.
```
**Please provide the URL of a video that causes the issue.**
## Describe your issue

View File

@ -1,8 +1,6 @@
# AllTube Download # AllTube Download
[![Donate using Liberapay](https://liberapay.com/assets/widgets/donate.svg)](https://liberapay.com/Rudloff/donate) HTML GUI for youtube-dl
HTML GUI for youtube-dl ([alltubedownload.net](http://alltubedownload.net/))
![Screenshot](img/screenshot.png "AllTube GUI screenshot") ![Screenshot](img/screenshot.png "AllTube GUI screenshot")
@ -81,6 +79,7 @@ If you want to serve the application under a basepath and/or with a different in
* X-Forwarded-Host (ex. `another.domain.com`) * X-Forwarded-Host (ex. `another.domain.com`)
* X-Forwarded-Path (ex: `/alltube`) * X-Forwarded-Path (ex: `/alltube`)
* X-Forwarded-Port (ex: `5555`) * X-Forwarded-Port (ex: `5555`)
* X-Forwarded-Proto (ex: `https`)
### Apache ### Apache
@ -166,7 +165,7 @@ so that you can reuse it in your projects.
## JSON API ## JSON API
We also provide a JSON API that you can use like this: We also provide a JSON API that you can use like this:
[/json?url=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DdQw4w9WgXcQ](https://alltubedownload.net/json?url=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DdQw4w9WgXcQ) `/json?url=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DdQw4w9WgXcQ`
It returns a JSON object generated by youtube-dl. It returns a JSON object generated by youtube-dl.
You can find a list of all the properties [in the youtube-dl documentation](https://github.com/ytdl-org/youtube-dl#output-template). You can find a list of all the properties [in the youtube-dl documentation](https://github.com/ytdl-org/youtube-dl#output-template).

16
SECURITY.md Normal file
View File

@ -0,0 +1,16 @@
# Security Policy
## Supported Versions
Only the latest major release is supported.
| Version | Supported |
|---------|-----------|
| 3.x | Yes |
| 2.x | No |
| 1.x | No |
## Reporting a Vulnerability
If you need to report a vulnerability, you can send an e-mail to
[contact@rudloff.pro](mailto:contact@rudloff.pro).

View File

@ -2,7 +2,6 @@
"name": "AllTube Download", "name": "AllTube Download",
"description": "HTML GUI for youtube-dl", "description": "HTML GUI for youtube-dl",
"repository": "https://github.com/Rudloff/alltube.git", "repository": "https://github.com/Rudloff/alltube.git",
"logo": "https://alltubedownload.net/img/logo.png",
"keywords": [ "keywords": [
"alltube", "alltube",
"download", "download",
@ -28,6 +27,5 @@
"value": "false", "value": "false",
"required": false "required": false
} }
}, }
"website": "https://alltubedownload.net/"
} }

View File

@ -8,6 +8,7 @@ use Alltube\Controller\JsonController;
use Alltube\Exception\ConfigException; use Alltube\Exception\ConfigException;
use Alltube\Exception\DependencyException; use Alltube\Exception\DependencyException;
use Alltube\Factory\ConfigFactory; use Alltube\Factory\ConfigFactory;
use Alltube\Factory\DebugBarFactory;
use Alltube\Factory\LocaleManagerFactory; use Alltube\Factory\LocaleManagerFactory;
use Alltube\Factory\LoggerFactory; use Alltube\Factory\LoggerFactory;
use Alltube\Factory\SessionFactory; use Alltube\Factory\SessionFactory;
@ -16,6 +17,7 @@ use Alltube\Middleware\CspMiddleware;
use Alltube\Middleware\LinkHeaderMiddleware; use Alltube\Middleware\LinkHeaderMiddleware;
use Alltube\Middleware\LocaleMiddleware; use Alltube\Middleware\LocaleMiddleware;
use Alltube\Middleware\RouterPathMiddleware; use Alltube\Middleware\RouterPathMiddleware;
use DebugBar\DebugBarException;
use Slim\Container; use Slim\Container;
use SmartyException; use SmartyException;
@ -26,6 +28,7 @@ class App extends \Slim\App
* @throws ConfigException * @throws ConfigException
* @throws DependencyException * @throws DependencyException
* @throws SmartyException * @throws SmartyException
* @throws DebugBarException
*/ */
public function __construct() public function __construct()
{ {
@ -34,6 +37,8 @@ class App extends \Slim\App
/** @var Container $container */ /** @var Container $container */
$container = $this->getContainer(); $container = $this->getContainer();
$container['root_path'] = $this->getRootPath();
// Config. // Config.
$container['config'] = ConfigFactory::create($container); $container['config'] = ConfigFactory::create($container);
@ -43,16 +48,21 @@ class App extends \Slim\App
// Locales. // Locales.
$container['locale'] = LocaleManagerFactory::create($container); $container['locale'] = LocaleManagerFactory::create($container);
// Smarty.
$container['view'] = ViewFactory::create($container);
// Logger. // Logger.
$container['logger'] = LoggerFactory::create($container); $container['logger'] = LoggerFactory::create($container);
if ($container->get('config')->debug) {
// Debug bar.
$container['debugbar'] = DebugBarFactory::create($container);
}
// Smarty.
$container['view'] = ViewFactory::create($container);
// Middlewares. // Middlewares.
$this->add(new LocaleMiddleware($container)); $this->add(new LocaleMiddleware($container));
$this->add(new CspMiddleware($container)); $this->add(new CspMiddleware($container));
$this->add(new LinkHeaderMiddleware($container)); $this->add(new LinkHeaderMiddleware());
$this->add(new RouterPathMiddleware($container)); $this->add(new RouterPathMiddleware($container));
// Controllers. // Controllers.
@ -84,7 +94,7 @@ class App extends \Slim\App
$this->any( $this->any(
'/watch', '/watch',
[$frontController, 'info'] [$frontController, 'watch']
); );
$this->any( $this->any(
@ -102,4 +112,17 @@ class App extends \Slim\App
[$jsonController, 'json'] [$jsonController, 'json']
)->setName('json'); )->setName('json');
} }
/**
* @return string|null
*/
private function getRootPath(): ?string
{
// realpath() can return false but we prefer using null.
if ($rootPath = realpath(__DIR__ . '/../')) {
return $rootPath;
}
return null;
}
} }

View File

@ -19,90 +19,97 @@ use Jawira\CaseConverter\Convert;
*/ */
class Config class Config
{ {
/** /**
* youtube-dl binary path. * youtube-dl binary path.
* *
* @var string * @var string
*/ */
public $youtubedl = 'vendor/ytdl-org/youtube-dl/youtube_dl/__main__.py'; public string $youtubedl = 'vendor/yt-dlp/yt-dlp/yt_dlp/__main__.py';
/** /**
* python binary path. * python binary path.
* *
* @var string * @var string
*/ */
public $python = '/usr/bin/python'; public string $python = '/usr/bin/python';
/** /**
* youtube-dl parameters. * youtube-dl parameters.
* *
* @var string[] * @var string[]
*/ */
public $params = ['--no-warnings', '--ignore-errors', '--flat-playlist', '--restrict-filenames', '--no-playlist']; public array $params = [
'--no-warnings',
'--ignore-errors',
'--flat-playlist',
'--restrict-filenames',
'--no-playlist',
'--use-extractors',
'default,-generic',
];
/** /**
* Enable audio conversion. * Enable audio conversion.
* *
* @var bool * @var bool
*/ */
public $convert = false; public bool $convert = false;
/** /**
* Enable advanced conversion mode. * Enable advanced conversion mode.
* *
* @var bool * @var bool
*/ */
public $convertAdvanced = false; public bool $convertAdvanced = false;
/** /**
* List of formats available in advanced conversion mode. * List of formats available in advanced conversion mode.
* *
* @var string[] * @var string[]
*/ */
public $convertAdvancedFormats = ['mp3', 'avi', 'flv', 'wav']; public array $convertAdvancedFormats = ['mp3', 'avi', 'flv', 'wav'];
/** /**
* ffmpeg binary path. * ffmpeg binary path.
* *
* @var string * @var string
*/ */
public $ffmpeg = '/usr/bin/ffmpeg'; public string $ffmpeg = '/usr/bin/ffmpeg';
/** /**
* Path to the directory that contains the phantomjs binary. * Path to the directory that contains the phantomjs binary.
* *
* @var string * @var string
*/ */
public $phantomjsDir = '/usr/bin/'; public string $phantomjsDir = '/usr/bin/';
/** /**
* Disable URL rewriting. * Disable URL rewriting.
* *
* @var bool * @var bool
*/ */
public $uglyUrls = false; public bool $uglyUrls = false;
/** /**
* Stream downloaded files trough server? * Stream downloaded files trough server?
* *
* @var bool * @var bool
*/ */
public $stream = false; public bool $stream = false;
/** /**
* Allow to remux video + audio? * Allow to remux video + audio?
* *
* @var bool * @var bool
*/ */
public $remux = false; public bool $remux = false;
/** /**
* MP3 bitrate when converting (in kbit/s). * MP3 bitrate when converting (in kbit/s).
* *
* @var int * @var int
*/ */
public $audioBitrate = 128; public int $audioBitrate = 128;
/** /**
* ffmpeg logging level. * ffmpeg logging level.
@ -110,21 +117,21 @@ class Config
* *
* @var string * @var string
*/ */
public $ffmpegVerbosity = 'error'; public string $ffmpegVerbosity = 'error';
/** /**
* App name. * App name.
* *
* @var string * @var string
*/ */
public $appName = 'AllTube Download'; public string $appName = 'AllTube Download';
/** /**
* Generic formats supported by youtube-dl. * Generic formats supported by youtube-dl.
* *
* @var string[] * @var string[]
*/ */
public $genericFormats = [ public array $genericFormats = [
'best/bestvideo' => 'Best', 'best/bestvideo' => 'Best',
'bestvideo+bestaudio' => 'Remux best video with best audio', 'bestvideo+bestaudio' => 'Remux best video with best audio',
'worst/worstvideo' => 'Worst', 'worst/worstvideo' => 'Worst',
@ -135,26 +142,26 @@ class Config
* *
* @var bool * @var bool
*/ */
public $debug = false; public bool $debug = false;
/** /**
* Default to audio. * Default to audio.
* *
* @var bool * @var bool
*/ */
public $defaultAudio = false; public bool $defaultAudio = false;
/** /**
* Disable audio conversion from/to seeker. * Disable audio conversion from/to seeker.
* *
* @var bool * @var bool
*/ */
public $convertSeek = true; public bool $convertSeek = true;
/** /**
* Config constructor. * Config constructor.
* *
* @param mixed[] $options Options * @param scalar[]|scalar[][]|null[] $options Options
* @throws ConfigException * @throws ConfigException
*/ */
public function __construct(array $options = []) public function __construct(array $options = [])
@ -187,7 +194,7 @@ class Config
* *
* @return string * @return string
*/ */
public static function addHttpToFormat(string $format) public static function addHttpToFormat(string $format): string
{ {
$newFormat = []; $newFormat = [];
foreach (explode('/', $format) as $subformat) { foreach (explode('/', $format) as $subformat) {
@ -205,7 +212,7 @@ class Config
* @throws ConfigException If Python is missing * @throws ConfigException If Python is missing
* @throws ConfigException If youtube-dl is missing * @throws ConfigException If youtube-dl is missing
*/ */
private function validateOptions() private function validateOptions(): void
{ {
if (!is_file($this->youtubedl)) { if (!is_file($this->youtubedl)) {
throw new ConfigException("Can't find youtube-dl at " . $this->youtubedl); throw new ConfigException("Can't find youtube-dl at " . $this->youtubedl);
@ -222,11 +229,11 @@ class Config
/** /**
* Apply the provided options. * Apply the provided options.
* *
* @param mixed[] $options Options * @param scalar[]|scalar[][]|null[] $options Options
* *
* @return void * @return void
*/ */
private function applyOptions(array $options) private function applyOptions(array $options): void
{ {
foreach ($options as $option => $value) { foreach ($options as $option => $value) {
if (isset($this->$option) && isset($value)) { if (isset($this->$option) && isset($value)) {
@ -243,7 +250,7 @@ class Config
* @return void * @return void
* @throws ConfigException * @throws ConfigException
*/ */
private function getEnv() private function getEnv(): void
{ {
foreach (get_object_vars($this) as $prop => $value) { foreach (get_object_vars($this) as $prop => $value) {
try { try {
@ -266,7 +273,7 @@ class Config
* @return Config * @return Config
* @throws ConfigException * @throws ConfigException
*/ */
public static function fromFile(string $file) public static function fromFile(string $file): Config
{ {
if (is_file($file)) { if (is_file($file)) {
return new self(Yaml::parse(strval(file_get_contents($file)))); return new self(Yaml::parse(strval(file_get_contents($file))));
@ -278,11 +285,11 @@ class Config
/** /**
* Manually set some options. * Manually set some options.
* *
* @param mixed[] $options Options (see `config/config.example.yml` for available options) * @param scalar[]|scalar[][]|null[] $options Options (see `config/config.example.yml` for available options)
* @return void * @return void
* @throws ConfigException * @throws ConfigException
*/ */
public function setOptions(array $options) public function setOptions(array $options): void
{ {
$this->applyOptions($options); $this->applyOptions($options);
$this->validateOptions(); $this->validateOptions();
@ -293,7 +300,7 @@ class Config
* *
* @return Downloader * @return Downloader
*/ */
public function getDownloader() public function getDownloader(): Downloader
{ {
return new Downloader( return new Downloader(
$this->youtubedl, $this->youtubedl,
@ -308,7 +315,7 @@ class Config
/** /**
* @return string * @return string
*/ */
public function getAppVersion() public function getAppVersion(): string
{ {
$version = PrettyVersions::getRootPackageVersion(); $version = PrettyVersions::getRootPackageVersion();

View File

@ -11,8 +11,11 @@ use Alltube\Library\Downloader;
use Alltube\Library\Video; use Alltube\Library\Video;
use Alltube\LocaleManager; use Alltube\LocaleManager;
use Aura\Session\Segment; use Aura\Session\Segment;
use Consolidation\Log\Logger; use Graby\HttpClient\Plugin\ServerSideRequestForgeryProtection\Exception\InvalidURLException;
use Graby\HttpClient\Plugin\ServerSideRequestForgeryProtection\Options;
use Graby\HttpClient\Plugin\ServerSideRequestForgeryProtection\Url;
use Psr\Container\ContainerInterface; use Psr\Container\ContainerInterface;
use Psr\Log\LoggerInterface;
use Slim\Http\Request; use Slim\Http\Request;
use Slim\Http\Response; use Slim\Http\Response;
use Slim\Router; use Slim\Router;
@ -27,21 +30,21 @@ abstract class BaseController
* *
* @var Video * @var Video
*/ */
protected $video; protected Video $video;
/** /**
* Default youtube-dl format. * Default youtube-dl format.
* *
* @var string * @var string
*/ */
protected $defaultFormat = 'best/bestvideo'; protected string $defaultFormat = 'best/bestvideo';
/** /**
* Slim dependency container. * Slim dependency container.
* *
* @var ContainerInterface * @var ContainerInterface
*/ */
protected $container; protected ContainerInterface $container;
/** /**
* Config instance. * Config instance.
@ -72,7 +75,7 @@ abstract class BaseController
protected $downloader; protected $downloader;
/** /**
* @var Logger * @var LoggerInterface
*/ */
protected $logger; protected $logger;
@ -111,7 +114,7 @@ abstract class BaseController
* *
* @return string format * @return string format
*/ */
protected function getFormat(Request $request) protected function getFormat(Request $request): string
{ {
$format = $request->getQueryParam('format'); $format = $request->getQueryParam('format');
if (!isset($format)) { if (!isset($format)) {
@ -126,11 +129,12 @@ abstract class BaseController
* *
* @param Request $request PSR-7 request * @param Request $request PSR-7 request
* *
* @return string Password * @return string|null Password
* @throws InvalidURLException
*/ */
protected function getPassword(Request $request) protected function getPassword(Request $request): ?string
{ {
$url = $request->getQueryParam('url'); $url = $this->getVideoPageUrl($request);
$password = $request->getParam('password'); $password = $request->getParam('password');
if (isset($password)) { if (isset($password)) {
@ -151,10 +155,23 @@ abstract class BaseController
* *
* @return Response HTTP response * @return Response HTTP response
*/ */
protected function displayError(Request $request, Response $response, string $message) protected function displayError(Request $request, Response $response, string $message): Response
{ {
$controller = new FrontController($this->container); $controller = new FrontController($this->container);
return $controller->displayError($request, $response, $message); return $controller->displayError($request, $response, $message);
} }
/**
* @param Request $request
* @return string
* @throws InvalidURLException
*/
protected function getVideoPageUrl(Request $request): string
{
// Prevent SSRF attacks.
$parts = Url::validateUrl($request->getQueryParam('url'), new Options());
return $parts['url'];
}
} }

View File

@ -19,6 +19,7 @@ use Alltube\Library\Exception\YoutubedlException;
use Alltube\Stream\ConvertedPlaylistArchiveStream; use Alltube\Stream\ConvertedPlaylistArchiveStream;
use Alltube\Stream\PlaylistArchiveStream; use Alltube\Stream\PlaylistArchiveStream;
use Alltube\Stream\YoutubeStream; use Alltube\Stream\YoutubeStream;
use Graby\HttpClient\Plugin\ServerSideRequestForgeryProtection\Exception\InvalidURLException;
use Slim\Http\Request; use Slim\Http\Request;
use Slim\Http\Response; use Slim\Http\Response;
use Slim\Http\StatusCode; use Slim\Http\StatusCode;
@ -37,56 +38,53 @@ class DownloadController extends BaseController
* *
* @return Response HTTP response * @return Response HTTP response
* @throws AlltubeLibraryException * @throws AlltubeLibraryException
* @throws InvalidURLException
*/ */
public function download(Request $request, Response $response) public function download(Request $request, Response $response): Response
{ {
$url = $request->getQueryParam('url'); $url = $this->getVideoPageUrl($request);
if (isset($url)) { $this->video = $this->downloader->getVideo($url, $this->getFormat($request), $this->getPassword($request));
$this->video = $this->downloader->getVideo($url, $this->getFormat($request), $this->getPassword($request));
try { try {
if ($this->config->convert && $request->getQueryParam('audio')) { if ($this->config->convert && $request->getQueryParam('audio')) {
// Audio convert. // Audio convert.
return $this->getAudioResponse($request, $response); return $this->getAudioResponse($request, $response);
} elseif ($this->config->convertAdvanced && !is_null($request->getQueryParam('customConvert'))) { } elseif ($this->config->convertAdvanced && !is_null($request->getQueryParam('customConvert'))) {
// Advance convert. // Advance convert.
return $this->getConvertedResponse($request, $response); return $this->getConvertedResponse($request, $response);
} }
// Regular download. // Regular download.
return $this->getDownloadResponse($request, $response); return $this->getDownloadResponse($request, $response);
} catch (PasswordException $e) { } catch (PasswordException $e) {
$frontController = new FrontController($this->container); $frontController = new FrontController($this->container);
return $frontController->password($request, $response); return $frontController->password($request, $response);
} catch (WrongPasswordException $e) { } catch (WrongPasswordException $e) {
return $this->displayError($request, $response, $this->localeManager->t('Wrong password')); return $this->displayError($request, $response, $this->localeManager->t('Wrong password'));
} catch (PlaylistConversionException $e) { } catch (PlaylistConversionException $e) {
return $this->displayError(
$request,
$response,
$this->localeManager->t('Conversion of playlists is not supported.')
);
} catch (InvalidProtocolConversionException $e) {
if (in_array($this->video->protocol, ['m3u8', 'm3u8_native'])) {
return $this->displayError( return $this->displayError(
$request, $request,
$response, $response,
$this->localeManager->t('Conversion of playlists is not supported.') $this->localeManager->t('Conversion of M3U8 files is not supported.')
); );
} catch (InvalidProtocolConversionException $e) { } elseif ($this->video->protocol == 'http_dash_segments') {
if (in_array($this->video->protocol, ['m3u8', 'm3u8_native'])) { return $this->displayError(
return $this->displayError( $request,
$request, $response,
$response, $this->localeManager->t('Conversion of DASH segments is not supported.')
$this->localeManager->t('Conversion of M3U8 files is not supported.') );
); } else {
} elseif ($this->video->protocol == 'http_dash_segments') { throw $e;
return $this->displayError(
$request,
$response,
$this->localeManager->t('Conversion of DASH segments is not supported.')
);
} else {
throw $e;
}
} }
} else {
return $response->withRedirect($this->router->pathFor('index'));
} }
} }
@ -99,7 +97,7 @@ class DownloadController extends BaseController
* @return Response HTTP response * @return Response HTTP response
* @throws AlltubeLibraryException * @throws AlltubeLibraryException
*/ */
private function getConvertedAudioResponse(Request $request, Response $response) private function getConvertedAudioResponse(Request $request, Response $response): Response
{ {
$from = null; $from = null;
$to = null; $to = null;
@ -135,7 +133,7 @@ class DownloadController extends BaseController
* @throws PasswordException * @throws PasswordException
* @throws WrongPasswordException * @throws WrongPasswordException
*/ */
private function getAudioResponse(Request $request, Response $response) private function getAudioResponse(Request $request, Response $response): Response
{ {
if (!empty($request->getQueryParam('from')) || !empty($request->getQueryParam('to'))) { if (!empty($request->getQueryParam('from')) || !empty($request->getQueryParam('to'))) {
// Force convert when we need to seek. // Force convert when we need to seek.
@ -174,7 +172,7 @@ class DownloadController extends BaseController
* @return Response HTTP response * @return Response HTTP response
* @throws AlltubeLibraryException * @throws AlltubeLibraryException
*/ */
private function getStream(Request $request, Response $response) private function getStream(Request $request, Response $response): Response
{ {
if (isset($this->video->entries)) { if (isset($this->video->entries)) {
if ($this->config->convert && $request->getQueryParam('audio')) { if ($this->config->convert && $request->getQueryParam('audio')) {
@ -222,13 +220,12 @@ class DownloadController extends BaseController
if ($request->isGet()) { if ($request->isGet()) {
$response = $response->withBody($body); $response = $response->withBody($body);
} }
$response = $response->withHeader(
return $response->withHeader(
'Content-Disposition', 'Content-Disposition',
'attachment; filename="' . 'attachment; filename="' .
$this->video->getFilename() . '"' $this->video->getFilename() . '"'
); );
return $response;
} }
/** /**
@ -240,7 +237,7 @@ class DownloadController extends BaseController
* @return Response HTTP response * @return Response HTTP response
* @throws AlltubeLibraryException * @throws AlltubeLibraryException
*/ */
private function getRemuxStream(Request $request, Response $response) private function getRemuxStream(Request $request, Response $response): Response
{ {
if (!$this->config->remux) { if (!$this->config->remux) {
throw new RemuxException('You need to enable remux mode to merge two formats.'); throw new RemuxException('You need to enable remux mode to merge two formats.');
@ -267,7 +264,7 @@ class DownloadController extends BaseController
* @return Response HTTP response * @return Response HTTP response
* @throws AlltubeLibraryException * @throws AlltubeLibraryException
*/ */
private function getDownloadResponse(Request $request, Response $response) private function getDownloadResponse(Request $request, Response $response): Response
{ {
try { try {
$videoUrls = $this->video->getUrl(); $videoUrls = $this->video->getUrl();
@ -306,7 +303,7 @@ class DownloadController extends BaseController
* @throws YoutubedlException * @throws YoutubedlException
* @throws PopenStreamException * @throws PopenStreamException
*/ */
private function getConvertedResponse(Request $request, Response $response) private function getConvertedResponse(Request $request, Response $response): Response
{ {
$response = $response->withHeader( $response = $response->withHeader(
'Content-Disposition', 'Content-Disposition',

View File

@ -12,7 +12,10 @@ use Alltube\Library\Exception\WrongPasswordException;
use Alltube\Locale; use Alltube\Locale;
use Alltube\Middleware\CspMiddleware; use Alltube\Middleware\CspMiddleware;
use Exception; use Exception;
use Graby\HttpClient\Plugin\ServerSideRequestForgeryProtection\Exception\InvalidURLException;
use Slim\Http\StatusCode; use Slim\Http\StatusCode;
use Slim\Http\Uri;
use stdClass;
use Symfony\Component\ErrorHandler\ErrorRenderer\HtmlErrorRenderer; use Symfony\Component\ErrorHandler\ErrorRenderer\HtmlErrorRenderer;
use Throwable; use Throwable;
use Psr\Container\ContainerInterface; use Psr\Container\ContainerInterface;
@ -52,7 +55,7 @@ class FrontController extends BaseController
* *
* @return Response HTTP response * @return Response HTTP response
*/ */
public function index(Request $request, Response $response) public function index(Request $request, Response $response): Response
{ {
$this->view->render( $this->view->render(
$response, $response,
@ -60,7 +63,7 @@ class FrontController extends BaseController
[ [
'class' => 'index', 'class' => 'index',
'description' => $this->localeManager->t( 'description' => $this->localeManager->t(
'Easily download videos from Youtube, Dailymotion, Vimeo and other websites.' 'Easily download videos from YouTube, Dailymotion, Vimeo and other websites.'
), ),
'supportedLocales' => $this->localeManager->getSupportedLocales(), 'supportedLocales' => $this->localeManager->getSupportedLocales(),
] ]
@ -78,7 +81,7 @@ class FrontController extends BaseController
* *
* @return Response * @return Response
*/ */
public function locale(Request $request, Response $response, array $data) public function locale(Request $request, Response $response, array $data): Response
{ {
$this->localeManager->setLocale(new Locale($data['locale'])); $this->localeManager->setLocale(new Locale($data['locale']));
@ -94,7 +97,7 @@ class FrontController extends BaseController
* @return Response HTTP response * @return Response HTTP response
* @throws AlltubeLibraryException * @throws AlltubeLibraryException
*/ */
public function extractors(Request $request, Response $response) public function extractors(Request $request, Response $response): Response
{ {
$this->view->render( $this->view->render(
$response, $response,
@ -103,7 +106,7 @@ class FrontController extends BaseController
'extractors' => $this->downloader->getExtractors(), 'extractors' => $this->downloader->getExtractors(),
'class' => 'extractors', 'class' => 'extractors',
'title' => $this->localeManager->t('Supported websites'), 'title' => $this->localeManager->t('Supported websites'),
'description' => $this->localeManager->t('List of all supported websites from which Alltube Download ' . 'description' => $this->localeManager->t('List of all supported websites from which AllTube Download ' .
'can extract video or audio files'), 'can extract video or audio files'),
] ]
); );
@ -119,7 +122,7 @@ class FrontController extends BaseController
* *
* @return Response HTTP response * @return Response HTTP response
*/ */
public function password(Request $request, Response $response) public function password(Request $request, Response $response): Response
{ {
$this->view->render( $this->view->render(
$response, $response,
@ -128,7 +131,7 @@ class FrontController extends BaseController
'class' => 'password', 'class' => 'password',
'title' => $this->localeManager->t('Password prompt'), 'title' => $this->localeManager->t('Password prompt'),
'description' => $this->localeManager->t( 'description' => $this->localeManager->t(
'You need a password in order to download this video with Alltube Download' 'You need a password in order to download this video with AllTube Download'
), ),
] ]
); );
@ -145,7 +148,7 @@ class FrontController extends BaseController
* @return Response HTTP response * @return Response HTTP response
* @throws AlltubeLibraryException * @throws AlltubeLibraryException
*/ */
private function getInfoResponse(Request $request, Response $response) private function getInfoResponse(Request $request, Response $response): Response
{ {
try { try {
$this->video->getJson(); $this->video->getJson();
@ -175,6 +178,50 @@ class FrontController extends BaseController
] ]
); );
} }
$formats = [];
$genericFormatsLabel = $this->localeManager->t('Generic formats');
$detailedFormatsLabel = $this->localeManager->t('Detailed formats');
foreach ($this->config->genericFormats as $id => $genericFormat) {
$formats[$genericFormatsLabel][$id] = $this->localeManager->t($genericFormat);
}
$json = $this->video->getJson();
if (isset($json->formats)) {
/** @var stdClass $format */
foreach ($json->formats as $format) {
if ($this->config->stream || in_array($format->protocol, ['http', 'https'])) {
$formatParts = [
// File extension
$format->ext,
];
if (isset($format->width) || isset($format->height)) {
// Video dimensions
$formatParts[] = implode('x', array_filter([$format->width, $format->height]));
}
if (isset($format->filesize)) {
// File size
$formatParts[] = round($format->filesize / 1000000, 2) . ' MB';
}
if (isset($format->format_note)) {
// Format name
$formatParts[] = $format->format_note;
}
if (isset($format->format_id)) {
// Format ID
$formatParts[] = '(' . $format->format_id . ')';
}
$formats[$detailedFormatsLabel][$format->format_id] = implode(' ', $formatParts);
}
}
}
$this->view->render( $this->view->render(
$response, $response,
$template, $template,
@ -184,6 +231,7 @@ class FrontController extends BaseController
'title' => $title, 'title' => $title,
'description' => $description, 'description' => $description,
'defaultFormat' => $this->defaultFormat, 'defaultFormat' => $this->defaultFormat,
'formats' => $formats
] ]
); );
@ -198,24 +246,21 @@ class FrontController extends BaseController
* *
* @return Response HTTP response * @return Response HTTP response
* @throws AlltubeLibraryException * @throws AlltubeLibraryException
* @throws InvalidURLException
*/ */
public function info(Request $request, Response $response) public function info(Request $request, Response $response): Response
{ {
$url = $request->getQueryParam('url') ?: $request->getQueryParam('v'); $url = $this->getVideoPageUrl($request);
if (isset($url) && !empty($url)) { $this->video = $this->downloader->getVideo($url, $this->getFormat($request), $this->getPassword($request));
$this->video = $this->downloader->getVideo($url, $this->getFormat($request), $this->getPassword($request));
if ($this->config->convert && $request->getQueryParam('audio')) { if ($this->config->convert && $request->getQueryParam('audio')) {
// We skip the info page and get directly to the download. // We skip the info page and get directly to the download.
return $response->withRedirect( return $response->withRedirect(
$this->router->pathFor('download', [], $request->getQueryParams()) $this->router->pathFor('download', [], $request->getQueryParams())
); );
} else {
return $this->getInfoResponse($request, $response);
}
} else { } else {
return $response->withRedirect($this->router->pathFor('index')); return $this->getInfoResponse($request, $response);
} }
} }
@ -228,7 +273,7 @@ class FrontController extends BaseController
* *
* @return Response HTTP response * @return Response HTTP response
*/ */
protected function displayError(Request $request, Response $response, string $message) protected function displayError(Request $request, Response $response, string $message): Response
{ {
$this->view->render( $this->view->render(
$response, $response,
@ -248,7 +293,7 @@ class FrontController extends BaseController
* @param Response $response * @param Response $response
* @return Response * @return Response
*/ */
public function notFound(Request $request, Response $response) public function notFound(Request $request, Response $response): Response
{ {
return $this->displayError($request, $response, $this->localeManager->t('Page not found')) return $this->displayError($request, $response, $this->localeManager->t('Page not found'))
->withStatus(StatusCode::HTTP_NOT_FOUND); ->withStatus(StatusCode::HTTP_NOT_FOUND);
@ -259,7 +304,7 @@ class FrontController extends BaseController
* @param Response $response * @param Response $response
* @return Response * @return Response
*/ */
public function notAllowed(Request $request, Response $response) public function notAllowed(Request $request, Response $response): Response
{ {
return $this->displayError($request, $response, $this->localeManager->t('Method not allowed')) return $this->displayError($request, $response, $this->localeManager->t('Method not allowed'))
->withStatus(StatusCode::HTTP_METHOD_NOT_ALLOWED); ->withStatus(StatusCode::HTTP_METHOD_NOT_ALLOWED);
@ -274,7 +319,7 @@ class FrontController extends BaseController
* *
* @return Response HTTP response * @return Response HTTP response
*/ */
public function error(Request $request, Response $response, Throwable $error) public function error(Request $request, Response $response, Throwable $error): Response
{ {
$this->logger->error($error); $this->logger->error($error);
@ -285,7 +330,7 @@ class FrontController extends BaseController
$response = $cspMiddleware->applyHeader($response); $response = $cspMiddleware->applyHeader($response);
if ($this->config->debug) { if ($this->config->debug) {
$renderer = new HtmlErrorRenderer(true); $renderer = new HtmlErrorRenderer(true, null, null, $this->container->get('root_path'));
$exception = $renderer->render($error); $exception = $renderer->render($error);
$response->getBody()->write($exception->getAsString()); $response->getBody()->write($exception->getAsString());
@ -304,4 +349,24 @@ class FrontController extends BaseController
return $this->displayError($request, $response, $message); return $this->displayError($request, $response, $message);
} }
} }
/**
* Route that mimics YouTube video URLs ("/watch?v=foo")
*
* @param Request $request
* @param Response $response
* @return Response
*/
public function watch(Request $request, Response $response): Response
{
// We build a full YouTube URL from the video ID.
$youtubeUri = Uri::createFromString('https://www.youtube.com/watch')
->withQuery(http_build_query(['v' => $request->getQueryParam('v')]));
// Then pass it to the info route.
return $response->withRedirect(
Uri::createFromString($this->router->pathFor('info'))
->withQuery(http_build_query(['url' => strval($youtubeUri)]))
);
}
} }

View File

@ -7,6 +7,7 @@
namespace Alltube\Controller; namespace Alltube\Controller;
use Alltube\Library\Exception\AlltubeLibraryException; use Alltube\Library\Exception\AlltubeLibraryException;
use Graby\HttpClient\Plugin\ServerSideRequestForgeryProtection\Exception\InvalidURLException;
use Slim\Http\Request; use Slim\Http\Request;
use Slim\Http\Response; use Slim\Http\Response;
use Slim\Http\StatusCode; use Slim\Http\StatusCode;
@ -25,11 +26,11 @@ class JsonController extends BaseController
* @return Response HTTP response * @return Response HTTP response
* @throws AlltubeLibraryException * @throws AlltubeLibraryException
*/ */
public function json(Request $request, Response $response) public function json(Request $request, Response $response): Response
{ {
$url = $request->getQueryParam('url'); try {
$url = $this->getVideoPageUrl($request);
if (isset($url)) {
$this->video = $this->downloader->getVideo( $this->video = $this->downloader->getVideo(
$url, $url,
$this->getFormat($request), $this->getFormat($request),
@ -37,8 +38,8 @@ class JsonController extends BaseController
); );
return $response->withJson($this->video->getJson()); return $response->withJson($this->video->getJson());
} else { } catch (InvalidURLException $e) {
return $response->withJson(['error' => 'You need to provide the url parameter']) return $response->withJson(['error' => $e->getMessage()])
->withStatus(StatusCode::HTTP_BAD_REQUEST); ->withStatus(StatusCode::HTTP_BAD_REQUEST);
} }
} }

View File

@ -2,6 +2,7 @@
namespace Alltube; namespace Alltube;
use Slim\Http\StatusCode;
use Symfony\Component\ErrorHandler\ErrorRenderer\HtmlErrorRenderer; use Symfony\Component\ErrorHandler\ErrorRenderer\HtmlErrorRenderer;
use Throwable; use Throwable;
@ -11,25 +12,24 @@ use Throwable;
*/ */
class ErrorHandler class ErrorHandler
{ {
/** /**
* Last resort if the error has not been caught by the Slim error handler for some reason. * Last resort if the error has not been caught by the Slim error handler for some reason.
* @param Throwable $e * @param Throwable $e
* @return void * @return void
*/ */
public static function handle(Throwable $e) public static function handle(Throwable $e): void
{ {
error_log($e); error_log($e);
if (class_exists(HtmlErrorRenderer::class)) { if (class_exists(HtmlErrorRenderer::class)) {
// If dev dependencies are loaded, we can use symfony/error-handler. // If dev dependencies are loaded, we can use symfony/error-handler.
$renderer = new HtmlErrorRenderer(true); $renderer = new HtmlErrorRenderer(true, null, null, dirname(__DIR__));
$exception = $renderer->render($e); $exception = $renderer->render($e);
http_response_code($exception->getStatusCode()); http_response_code($exception->getStatusCode());
die($exception->getAsString()); die($exception->getAsString());
} else { } else {
http_response_code(500); http_response_code(StatusCode::HTTP_INTERNAL_SERVER_ERROR);
die('Error when starting the app: ' . htmlentities($e->getMessage())); die('Error when starting the app: ' . htmlentities($e->getMessage()));
} }
} }

View File

@ -10,5 +10,4 @@ use Exception;
*/ */
class ConfigException extends Exception class ConfigException extends Exception
{ {
} }

View File

@ -10,5 +10,4 @@ use Exception;
*/ */
class DependencyException extends Exception class DependencyException extends Exception
{ {
} }

View File

@ -14,15 +14,14 @@ use Symfony\Component\ErrorHandler\Debug;
*/ */
class ConfigFactory class ConfigFactory
{ {
/** /**
* @param Container $container * @param Container $container
* @return Config * @return Config
* @throws ConfigException * @throws ConfigException
*/ */
public static function create(Container $container) public static function create(Container $container): Config
{ {
$configPath = __DIR__ . '/../../config/config.yml'; $configPath = $container->get('root_path') . '/config/config.yml';
if (is_file($configPath)) { if (is_file($configPath)) {
$config = Config::fromFile($configPath); $config = Config::fromFile($configPath);
} else { } else {

View File

@ -0,0 +1,47 @@
<?php
namespace Alltube\Factory;
use DebugBar\DataCollector\ConfigCollector;
use DebugBar\DataCollector\MemoryCollector;
use DebugBar\DataCollector\MessagesCollector;
use DebugBar\DataCollector\PhpInfoCollector;
use DebugBar\DataCollector\RequestDataCollector;
use DebugBar\DebugBar;
use DebugBar\DebugBarException;
use Kitchenu\Debugbar\DataCollector\SlimRouteCollector;
use Slim\Container;
/**
* Class DebugBarFactory
* @package Alltube\Factory
*/
class DebugBarFactory
{
/**
* @param Container $container
* @return DebugBar
* @throws DebugBarException
*/
public static function create(Container $container): DebugBar
{
$debugBar = new DebugBar();
$requestCollector = new RequestDataCollector();
$configCollector = new ConfigCollector(get_object_vars($container->get('config')));
$debugBar->addCollector(new PhpInfoCollector())
->addCollector(new MessagesCollector())
->addCollector($requestCollector)
->addCollector(new MemoryCollector())
->addCollector($configCollector)
->addCollector(new SlimRouteCollector($container->get('router'), $container->get('request')));
$container->get('logger')->add('debugbar', $debugBar->getCollector('messages'));
$requestCollector->useHtmlVarDumper();
$configCollector->useHtmlVarDumper();
return $debugBar;
}
}

View File

@ -4,6 +4,7 @@ namespace Alltube\Factory;
use Alltube\Exception\DependencyException; use Alltube\Exception\DependencyException;
use Alltube\LocaleManager; use Alltube\LocaleManager;
use Locale;
use Slim\Container; use Slim\Container;
/** /**
@ -12,15 +13,14 @@ use Slim\Container;
*/ */
class LocaleManagerFactory class LocaleManagerFactory
{ {
/** /**
* @param Container $container * @param Container $container
* @return LocaleManager|null * @return LocaleManager
* @throws DependencyException * @throws DependencyException
*/ */
public static function create(Container $container) public static function create(Container $container): LocaleManager
{ {
if (!class_exists('Locale')) { if (!class_exists(Locale::class)) {
throw new DependencyException('You need to install the intl extension for PHP.'); throw new DependencyException('You need to install the intl extension for PHP.');
} }

View File

@ -3,9 +3,11 @@
namespace Alltube\Factory; namespace Alltube\Factory;
use Consolidation\Log\Logger; use Consolidation\Log\Logger;
use Consolidation\Log\LoggerManager;
use Consolidation\Log\LogOutputStyler; use Consolidation\Log\LogOutputStyler;
use Slim\Container; use Slim\Container;
use Symfony\Component\Console\Output\ConsoleOutput; use Symfony\Component\Console\Output\ConsoleOutput;
use Symfony\Component\Console\Output\OutputInterface;
/** /**
* Class LoggerFactory * Class LoggerFactory
@ -13,23 +15,26 @@ use Symfony\Component\Console\Output\ConsoleOutput;
*/ */
class LoggerFactory class LoggerFactory
{ {
/** /**
* @param Container $container * @param Container $container
* @return Logger * @return LoggerManager
*/ */
public static function create(Container $container) public static function create(Container $container): LoggerManager
{ {
$config = $container->get('config'); $config = $container->get('config');
if ($config->debug) { if ($config->debug) {
$verbosity = ConsoleOutput::VERBOSITY_DEBUG; $verbosity = OutputInterface::VERBOSITY_DEBUG;
} else { } else {
$verbosity = ConsoleOutput::VERBOSITY_NORMAL; $verbosity = OutputInterface::VERBOSITY_NORMAL;
} }
$loggerManager = new LoggerManager();
$logger = new Logger(new ConsoleOutput($verbosity)); $logger = new Logger(new ConsoleOutput($verbosity));
$logger->setLogOutputStyler(new LogOutputStyler()); $logger->setLogOutputStyler(new LogOutputStyler());
return $logger; $loggerManager->add('default', $logger);
return $loggerManager;
} }
} }

View File

@ -7,6 +7,7 @@
namespace Alltube\Factory; namespace Alltube\Factory;
use Aura\Session\Session; use Aura\Session\Session;
use Aura\Session\SessionFactory as AuraSessionFactory;
use Slim\Container; use Slim\Container;
/** /**
@ -14,16 +15,15 @@ use Slim\Container;
*/ */
class SessionFactory class SessionFactory
{ {
/** /**
* Get the current session. * Get the current session.
* *
* @param Container $container * @param Container $container
* @return Session * @return Session
*/ */
public static function create(Container $container) public static function create(Container $container): Session
{ {
$session_factory = new \Aura\Session\SessionFactory(); $session_factory = new AuraSessionFactory();
$session = $session_factory->newInstance($_COOKIE); $session = $session_factory->newInstance($_COOKIE);
$session->setCookieParams(['httponly' => true]); $session->setCookieParams(['httponly' => true]);

View File

@ -7,6 +7,7 @@
namespace Alltube\Factory; namespace Alltube\Factory;
use Alltube\LocaleManager; use Alltube\LocaleManager;
use Junker\DebugBar\Bridge\SmartyCollector;
use Psr\Container\ContainerInterface; use Psr\Container\ContainerInterface;
use Slim\Http\Request; use Slim\Http\Request;
use Slim\Http\Uri; use Slim\Http\Uri;
@ -20,39 +21,41 @@ use SmartyException;
class ViewFactory class ViewFactory
{ {
/** /**
* Generate the canonical URL of the current page. * @param Uri $uri
* * @return Uri
* @param Request $request PSR-7 Request
*
* @return string URL
*/ */
private static function getCanonicalUrl(Request $request) private static function cleanBasePath(Uri $uri): Uri
{ {
/** @var Uri $uri */ $basePath = $uri->getBasePath();
$uri = $request->getUri(); if (str_ends_with($basePath, 'index.php')) {
$basePath = dirname($basePath);
if ($basePath == '/') {
/*
* Calling withBasePath('/') does nothing,
* we have to use an empty string instead.
*/
$basePath = '';
}
return $uri->withBasePath('') /*
->withHost('alltubedownload.net') * When the base path ends with index.php,
->withScheme('https'); * routing works correctly, but it breaks the URL of static assets using {base_url}.
* So we alter the base path but only in the URI used by SmartyPlugins.
*/
$uri = $uri->withBasePath($basePath);
}
return $uri;
} }
/** /**
* Create Smarty view object. * Create a URI suitable for templates.
* *
* @param ContainerInterface $container Slim dependency container * @param Request $request
* @param Request|null $request PSR-7 request * @return Uri
*
* @return Smarty
* @throws SmartyException
*/ */
public static function create(ContainerInterface $container, Request $request = null) public static function prepareUri(Request $request): Uri
{ {
if (!isset($request)) {
$request = $container->get('request');
}
$view = new Smarty(__DIR__ . '/../../templates/');
/** @var Uri $uri */ /** @var Uri $uri */
$uri = $request->getUri(); $uri = $request->getUri();
if (in_array('https', $request->getHeader('X-Forwarded-Proto'))) { if (in_array('https', $request->getHeader('X-Forwarded-Proto'))) {
@ -72,6 +75,28 @@ class ViewFactory
$uri = $uri->withBasePath($path); $uri = $uri->withBasePath($path);
} }
return self::cleanBasePath($uri);
}
/**
* Create Smarty view object.
*
* @param ContainerInterface $container Slim dependency container
* @param Request|null $request PSR-7 request
*
* @return Smarty
* @throws SmartyException
*/
public static function create(ContainerInterface $container, Request $request = null): Smarty
{
if (!isset($request)) {
$request = $container->get('request');
}
$view = new Smarty($container->get('root_path') . '/templates/');
$uri = self::prepareUri($request);
/** @var LocaleManager $localeManager */ /** @var LocaleManager $localeManager */
$localeManager = $container->get('locale'); $localeManager = $container->get('locale');
@ -80,11 +105,24 @@ class ViewFactory
$view->registerPlugin('function', 'base_url', [$smartyPlugins, 'baseUrl']); $view->registerPlugin('function', 'base_url', [$smartyPlugins, 'baseUrl']);
$view->registerPlugin('block', 't', [$localeManager, 'smartyTranslate']); $view->registerPlugin('block', 't', [$localeManager, 'smartyTranslate']);
$view->offsetSet('canonical', self::getCanonicalUrl($request)); $view->offsetSet('locale', $container->get('locale'));
$view->offsetSet('locale', $container->get('locale')->getLocale());
$view->offsetSet('config', $container->get('config')); $view->offsetSet('config', $container->get('config'));
$view->offsetSet('domain', $uri->withBasePath('')->getBaseUrl()); $view->offsetSet('domain', $uri->withBasePath('')->getBaseUrl());
if ($container->has('debugbar')) {
$debugBar = $container->get('debugbar');
$collector = new SmartyCollector($view->getSmarty());
$collector->useHtmlVarDumper();
$debugBar->addCollector($collector);
$view->offsetSet(
'debug_render',
$debugBar->getJavascriptRenderer(
$uri->getBaseUrl() . '/vendor/maximebf/debugbar/src/DebugBar/Resources/'
)
);
}
return $view; return $view;
} }
} }

View File

@ -48,7 +48,7 @@ class Locale
* *
* @return string ISO 15897 code * @return string ISO 15897 code
*/ */
public function __toString() public function __toString(): string
{ {
return $this->getIso15897(); return $this->getIso15897();
} }
@ -58,9 +58,9 @@ class Locale
* *
* @return string * @return string
*/ */
public function getFullName() public function getFullName(): string
{ {
return PHPLocale::getDisplayName($this->getIso15897(), $this->getIso15897()); return mb_convert_case(PHPLocale::getDisplayName($this->getIso15897(), $this->getIso15897()), MB_CASE_TITLE);
} }
/** /**
@ -68,7 +68,7 @@ class Locale
* *
* @return string * @return string
*/ */
public function getIso15897() public function getIso15897(): string
{ {
if (isset($this->region)) { if (isset($this->region)) {
return $this->language . '_' . $this->region; return $this->language . '_' . $this->region;
@ -82,7 +82,7 @@ class Locale
* *
* @return string * @return string
*/ */
public function getBcp47() public function getBcp47(): string
{ {
if (isset($this->region)) { if (isset($this->region)) {
return $this->language . '-' . $this->region; return $this->language . '-' . $this->region;
@ -96,7 +96,7 @@ class Locale
* *
* @return string * @return string
*/ */
public function getIso3166() public function getIso3166(): string
{ {
return strtolower($this->region); return strtolower($this->region);
} }

View File

@ -17,7 +17,6 @@ use Symfony\Component\Translation\Loader\PoFileLoader;
*/ */
class LocaleManager class LocaleManager
{ {
/** /**
* Path to locales. * Path to locales.
*/ */
@ -28,14 +27,14 @@ class LocaleManager
* *
* @var Locale|null * @var Locale|null
*/ */
private $curLocale; private ?Locale $curLocale = null;
/** /**
* Session segment used to store session variables. * Session segment used to store session variables.
* *
* @var Segment * @var Segment
*/ */
private $sessionSegment; private Segment $sessionSegment;
/** /**
* Default locale. * Default locale.
@ -49,7 +48,7 @@ class LocaleManager
* *
* @var Translator * @var Translator
*/ */
private $translator; private Translator $translator;
/** /**
* LocaleManager constructor. * LocaleManager constructor.
@ -80,7 +79,7 @@ class LocaleManager
* *
* @return Locale[] * @return Locale[]
*/ */
public function getSupportedLocales() public function getSupportedLocales(): array
{ {
$return = [ $return = [
new Locale('en_US') new Locale('en_US')
@ -103,7 +102,7 @@ class LocaleManager
* *
* @return Locale|null * @return Locale|null
*/ */
public function getLocale() public function getLocale(): ?Locale
{ {
return $this->curLocale; return $this->curLocale;
} }
@ -114,7 +113,7 @@ class LocaleManager
* @param Locale $locale Locale * @param Locale $locale Locale
* @return void * @return void
*/ */
public function setLocale(Locale $locale) public function setLocale(Locale $locale): void
{ {
$this->translator->setLocale($locale->getIso15897()); $this->translator->setLocale($locale->getIso15897());
$this->curLocale = $locale; $this->curLocale = $locale;
@ -125,7 +124,7 @@ class LocaleManager
* Unset the current locale. * Unset the current locale.
* @return void * @return void
*/ */
public function unsetLocale() public function unsetLocale(): void
{ {
$this->translator->setLocale(self::DEFAULT_LOCALE); $this->translator->setLocale(self::DEFAULT_LOCALE);
$this->curLocale = null; $this->curLocale = null;
@ -135,14 +134,14 @@ class LocaleManager
/** /**
* Smarty "t" block. * Smarty "t" block.
* *
* @param mixed[] $params Block parameters * @param string[]|string[][] $params Block parameters
* @param string|null $text Block content * @param string|null $text Block content
* *
* @return string Translated string * @return string Translated string
*/ */
public function smartyTranslate(array $params, string $text = null) public function smartyTranslate(array $params, string $text = null): string
{ {
if (isset($params['params'])) { if (isset($params['params']) && is_array($params['params'])) {
return $this->t($text, $params['params']); return $this->t($text, $params['params']);
} else { } else {
return $this->t($text); return $this->t($text);
@ -154,10 +153,10 @@ class LocaleManager
* *
* @param string|null $string $string String to translate * @param string|null $string $string String to translate
* *
* @param mixed[] $params * @param string[] $params
* @return string Translated string * @return string Translated string
*/ */
public function t(string $string = null, array $params = []) public function t(string $string = null, array $params = []): string
{ {
if (isset($string)) { if (isset($string)) {
return $this->translator->trans($string, $params); return $this->translator->trans($string, $params);

View File

@ -15,7 +15,6 @@ use Slim\Http\Response;
*/ */
class CspMiddleware class CspMiddleware
{ {
/** /**
* @var Config * @var Config
*/ */
@ -34,10 +33,11 @@ class CspMiddleware
* @param Response $response * @param Response $response
* @return MessageInterface * @return MessageInterface
*/ */
public function applyHeader(Response $response) public function applyHeader(Response $response): MessageInterface
{ {
$csp = new CSPBuilder(); $csp = new CSPBuilder();
$csp->addDirective('default-src', []) $csp->disableOldBrowserSupport()
->addDirective('default-src', [])
->addDirective('font-src', ['self' => true]) ->addDirective('font-src', ['self' => true])
->addDirective('style-src', ['self' => true]) ->addDirective('style-src', ['self' => true])
->addDirective('manifest-src', ['self' => true]) ->addDirective('manifest-src', ['self' => true])
@ -47,9 +47,10 @@ class CspMiddleware
->addSource('img-src', '*'); ->addSource('img-src', '*');
if ($this->config->debug) { if ($this->config->debug) {
// So symfony/debug and symfony/error-handler can work. // So maximebf/debugbar, symfony/debug and symfony/error-handler can work.
$csp->setDirective('script-src', ['unsafe-inline' => true]) $csp->setDirective('script-src', ['self' => true, 'unsafe-inline' => true])
->setDirective('style-src', ['self' => true, 'unsafe-inline' => true]); ->setDirective('style-src', ['self' => true, 'unsafe-inline' => true])
->addSource('img-src', 'data:');
} }
return $csp->injectCSPHeader($response); return $csp->injectCSPHeader($response);

View File

@ -2,10 +2,9 @@
namespace Alltube\Middleware; namespace Alltube\Middleware;
use Psr\Container\ContainerInterface; use Alltube\Factory\ViewFactory;
use Slim\Http\Request; use Slim\Http\Request;
use Slim\Http\Response; use Slim\Http\Response;
use Slim\Router;
/** /**
* Class LinkHeaderMiddleware * Class LinkHeaderMiddleware
@ -13,20 +12,6 @@ use Slim\Router;
*/ */
class LinkHeaderMiddleware class LinkHeaderMiddleware
{ {
/**
* @var Router
*/
private $router;
/**
* LinkHeaderMiddleware constructor.
* @param ContainerInterface $container
*/
public function __construct(ContainerInterface $container)
{
$this->router = $container->get('router');
}
/** /**
* @param Request $request * @param Request $request
* @param Response $response * @param Response $response
@ -35,12 +20,19 @@ class LinkHeaderMiddleware
*/ */
public function __invoke(Request $request, Response $response, callable $next) public function __invoke(Request $request, Response $response, callable $next)
{ {
$uri = ViewFactory::prepareUri($request);
$response = $response->withHeader( $response = $response->withHeader(
'Link', 'Link',
'<' . $this->router->getBasePath() . '/css/style.css>; rel=preload; as=style' implode(
'; ',
[
'<' . $uri->getBasePath() . '/css/style.css>',
'rel=preload', 'as=style'
]
)
); );
return $next($request, $response); return $next($request, $response);
} }
} }

View File

@ -38,11 +38,11 @@ class LocaleMiddleware
/** /**
* Test if a locale can be used for the current user. * Test if a locale can be used for the current user.
* *
* @param mixed[] $proposedLocale Locale array created by AcceptLanguage::parse() * @param string[] $proposedLocale Locale array created by AcceptLanguage::parse()
* *
* @return Locale|null Locale if chosen, nothing otherwise * @return Locale|null Locale if chosen, nothing otherwise
*/ */
public function testLocale(array $proposedLocale) public function testLocale(array $proposedLocale): ?Locale
{ {
foreach ($this->localeManager->getSupportedLocales() as $locale) { foreach ($this->localeManager->getSupportedLocales() as $locale) {
$parsedLocale = AcceptLanguage::parse($locale); $parsedLocale = AcceptLanguage::parse($locale);
@ -67,7 +67,7 @@ class LocaleMiddleware
* *
* @return Response * @return Response
*/ */
public function __invoke(Request $request, Response $response, callable $next) public function __invoke(Request $request, Response $response, callable $next): Response
{ {
$headers = $request->getHeader('Accept-Language'); $headers = $request->getHeader('Accept-Language');
$curLocale = $this->localeManager->getLocale(); $curLocale = $this->localeManager->getLocale();

View File

@ -14,7 +14,6 @@ use Robo\Tasks;
*/ */
class ReleaseCommand extends Tasks class ReleaseCommand extends Tasks
{ {
/** /**
* Create release archive * Create release archive
* @return void * @return void
@ -27,12 +26,12 @@ class ReleaseCommand extends Tasks
$gitTask = $this->taskExec('git'); $gitTask = $this->taskExec('git');
$result = $gitTask $result = $gitTask
->arg('describe') ->arg('describe')
->interactive(false)
->run(); ->run();
$result->provideOutputdata();
$tmpDir = $this->_tmpDir(); $tmpDir = $this->_tmpDir();
$filename = 'alltube-' . trim((string)$result->getOutputData()) . '.zip'; $filename = 'alltube-' . trim($result->getMessage()) . '.zip';
/** @var FilesystemStack $rmTask */ /** @var FilesystemStack $rmTask */
$rmTask = $this->taskFilesystemStack(); $rmTask = $this->taskFilesystemStack();

View File

@ -23,7 +23,7 @@ class ConvertedPlaylistArchiveStream extends PlaylistArchiveStream
* @return void * @return void
* @throws AlltubeLibraryException * @throws AlltubeLibraryException
*/ */
protected function startVideoStream(Video $video) protected function startVideoStream(Video $video): void
{ {
$this->curVideoStream = new Stream($this->downloader->getAudioStream($video)); $this->curVideoStream = new Stream($this->downloader->getAudioStream($video));

View File

@ -24,7 +24,7 @@ class PlaylistArchiveStream extends ZipArchive implements StreamInterface
* *
* @var Video[] * @var Video[]
*/ */
private $videos = []; private array $videos = [];
/** /**
* Stream used to store data before it is sent to the browser. * Stream used to store data before it is sent to the browser.
@ -38,21 +38,21 @@ class PlaylistArchiveStream extends ZipArchive implements StreamInterface
* *
* @var StreamInterface * @var StreamInterface
*/ */
protected $curVideoStream; protected StreamInterface $curVideoStream;
/** /**
* True if the archive is complete. * True if the archive is complete.
* *
* @var bool * @var bool
*/ */
private $isComplete = false; private bool $isComplete = false;
/** /**
* Downloader object. * Downloader object.
* *
* @var Downloader * @var Downloader
*/ */
protected $downloader; protected Downloader $downloader;
/** /**
* PlaylistArchiveStream constructor. * PlaylistArchiveStream constructor.
@ -83,7 +83,7 @@ class PlaylistArchiveStream extends ZipArchive implements StreamInterface
* *
* @return void * @return void
*/ */
protected function send($data) protected function send($data): void
{ {
$pos = $this->tell(); $pos = $this->tell();
@ -113,7 +113,7 @@ class PlaylistArchiveStream extends ZipArchive implements StreamInterface
* *
* @return int|null * @return int|null
*/ */
public function getSize() public function getSize(): ?int
{ {
return null; return null;
} }
@ -123,7 +123,7 @@ class PlaylistArchiveStream extends ZipArchive implements StreamInterface
* *
* @return bool * @return bool
*/ */
public function isSeekable() public function isSeekable(): bool
{ {
return true; return true;
} }
@ -133,7 +133,7 @@ class PlaylistArchiveStream extends ZipArchive implements StreamInterface
* *
* @return void * @return void
*/ */
public function rewind() public function rewind(): void
{ {
rewind($this->buffer); rewind($this->buffer);
} }
@ -143,7 +143,7 @@ class PlaylistArchiveStream extends ZipArchive implements StreamInterface
* *
* @return bool * @return bool
*/ */
public function isWritable() public function isWritable(): bool
{ {
return true; return true;
} }
@ -153,7 +153,7 @@ class PlaylistArchiveStream extends ZipArchive implements StreamInterface
* *
* @return bool * @return bool
*/ */
public function isReadable() public function isReadable(): bool
{ {
return true; return true;
} }
@ -173,7 +173,7 @@ class PlaylistArchiveStream extends ZipArchive implements StreamInterface
* *
* @param string|null $key string $key Specific metadata to retrieve. * @param string|null $key string $key Specific metadata to retrieve.
* *
* @return array|mixed|null * @return mixed|null
*/ */
public function getMetadata($key = null) public function getMetadata($key = null)
{ {
@ -208,7 +208,7 @@ class PlaylistArchiveStream extends ZipArchive implements StreamInterface
* *
* @return string * @return string
*/ */
public function __toString() public function __toString(): string
{ {
$this->rewind(); $this->rewind();
@ -233,7 +233,7 @@ class PlaylistArchiveStream extends ZipArchive implements StreamInterface
* *
* @return void * @return void
*/ */
public function seek($offset, $whence = SEEK_SET) public function seek($offset, $whence = SEEK_SET): void
{ {
fseek($this->buffer, $offset, $whence); fseek($this->buffer, $offset, $whence);
} }
@ -243,7 +243,7 @@ class PlaylistArchiveStream extends ZipArchive implements StreamInterface
* *
* @return bool * @return bool
*/ */
public function eof() public function eof(): bool
{ {
return $this->isComplete && feof($this->buffer); return $this->isComplete && feof($this->buffer);
} }
@ -256,7 +256,7 @@ class PlaylistArchiveStream extends ZipArchive implements StreamInterface
* @return void * @return void
* @throws AlltubeLibraryException * @throws AlltubeLibraryException
*/ */
protected function startVideoStream(Video $video) protected function startVideoStream(Video $video): void
{ {
$response = $this->downloader->getHttpResponse($video); $response = $this->downloader->getHttpResponse($video);
@ -272,12 +272,12 @@ class PlaylistArchiveStream extends ZipArchive implements StreamInterface
/** /**
* Read data from the stream. * Read data from the stream.
* *
* @param mixed $count Number of bytes to read * @param mixed $length Number of bytes to read
* *
* @return string|false * @return string|false
* @throws AlltubeLibraryException * @throws AlltubeLibraryException
*/ */
public function read($count) public function read($length)
{ {
// If the archive is complete, we only read the remaining buffer. // If the archive is complete, we only read the remaining buffer.
if (!$this->isComplete) { if (!$this->isComplete) {
@ -297,15 +297,22 @@ class PlaylistArchiveStream extends ZipArchive implements StreamInterface
} }
} else { } else {
// Continue streaming the current video. // Continue streaming the current video.
$this->stream_file_part($this->curVideoStream->read($count)); $this->stream_file_part($this->curVideoStream->read($length));
} }
} else { } else {
// Start streaming the first video. // Start streaming the first video.
$this->startVideoStream(current($this->videos)); $video = current($this->videos);
if ($video) {
$this->startVideoStream($video);
} else {
$this->push_error('Playlist was empty');
$this->finish();
$this->isComplete = true;
}
} }
} }
return fread($this->buffer, $count); return fread($this->buffer, $length);
} }
/** /**
@ -313,7 +320,7 @@ class PlaylistArchiveStream extends ZipArchive implements StreamInterface
* *
* @return void * @return void
*/ */
public function close() public function close(): void
{ {
if (is_resource($this->buffer)) { if (is_resource($this->buffer)) {
fclose($this->buffer); fclose($this->buffer);

View File

@ -20,7 +20,7 @@ class YoutubeChunkStream implements StreamInterface
* *
* @var ResponseInterface * @var ResponseInterface
*/ */
private $response; private ResponseInterface $response;
/** /**
* YoutubeChunkStream constructor. * YoutubeChunkStream constructor.
@ -39,7 +39,7 @@ class YoutubeChunkStream implements StreamInterface
* *
* @return string * @return string
*/ */
public function read($length) public function read($length): string
{ {
$size = intval($this->response->getHeader('Content-Length')[0]); $size = intval($this->response->getHeader('Content-Length')[0]);
if ($size - $this->tell() < $length) { if ($size - $this->tell() < $length) {
@ -53,7 +53,7 @@ class YoutubeChunkStream implements StreamInterface
/** /**
* Reads all data from the stream into a string, from the beginning to end. * Reads all data from the stream into a string, from the beginning to end.
*/ */
public function __toString() public function __toString(): string
{ {
return (string)$this->response->getBody(); return (string)$this->response->getBody();
} }
@ -63,7 +63,7 @@ class YoutubeChunkStream implements StreamInterface
* *
* @return void * @return void
*/ */
public function close() public function close(): void
{ {
$this->response->getBody()->close(); $this->response->getBody()->close();
} }
@ -83,7 +83,7 @@ class YoutubeChunkStream implements StreamInterface
* *
* @return int|null * @return int|null
*/ */
public function getSize() public function getSize(): ?int
{ {
return $this->response->getBody()->getSize(); return $this->response->getBody()->getSize();
} }
@ -93,7 +93,7 @@ class YoutubeChunkStream implements StreamInterface
* *
* @return int * @return int
*/ */
public function tell() public function tell(): int
{ {
return $this->response->getBody()->tell(); return $this->response->getBody()->tell();
} }
@ -103,7 +103,7 @@ class YoutubeChunkStream implements StreamInterface
* *
* @return bool * @return bool
*/ */
public function eof() public function eof(): bool
{ {
return $this->response->getBody()->eof(); return $this->response->getBody()->eof();
} }
@ -113,7 +113,7 @@ class YoutubeChunkStream implements StreamInterface
* *
* @return bool * @return bool
*/ */
public function isSeekable() public function isSeekable(): bool
{ {
return $this->response->getBody()->isSeekable(); return $this->response->getBody()->isSeekable();
} }
@ -126,7 +126,7 @@ class YoutubeChunkStream implements StreamInterface
* *
* @return void * @return void
*/ */
public function seek($offset, $whence = SEEK_SET) public function seek($offset, $whence = SEEK_SET): void
{ {
$this->response->getBody()->seek($offset, $whence); $this->response->getBody()->seek($offset, $whence);
} }
@ -136,7 +136,7 @@ class YoutubeChunkStream implements StreamInterface
* *
* @return void * @return void
*/ */
public function rewind() public function rewind(): void
{ {
$this->response->getBody()->rewind(); $this->response->getBody()->rewind();
} }
@ -146,7 +146,7 @@ class YoutubeChunkStream implements StreamInterface
* *
* @return bool * @return bool
*/ */
public function isWritable() public function isWritable(): bool
{ {
return $this->response->getBody()->isWritable(); return $this->response->getBody()->isWritable();
} }
@ -156,9 +156,9 @@ class YoutubeChunkStream implements StreamInterface
* *
* @param mixed $string The string that is to be written * @param mixed $string The string that is to be written
* *
* @return mixed * @return int
*/ */
public function write($string) public function write($string): int
{ {
return $this->response->getBody()->write($string); return $this->response->getBody()->write($string);
} }
@ -168,7 +168,7 @@ class YoutubeChunkStream implements StreamInterface
* *
* @return bool * @return bool
*/ */
public function isReadable() public function isReadable(): bool
{ {
return $this->response->getBody()->isReadable(); return $this->response->getBody()->isReadable();
} }
@ -178,7 +178,7 @@ class YoutubeChunkStream implements StreamInterface
* *
* @return string * @return string
*/ */
public function getContents() public function getContents(): string
{ {
return $this->response->getBody()->getContents(); return $this->response->getBody()->getContents();
} }
@ -188,7 +188,7 @@ class YoutubeChunkStream implements StreamInterface
* *
* @param string|null $key Specific metadata to retrieve. * @param string|null $key Specific metadata to retrieve.
* *
* @return array|mixed|null * @return mixed|null
*/ */
public function getMetadata($key = null) public function getMetadata($key = null)
{ {

View File

@ -22,11 +22,11 @@ class UglyRouter extends Router
* *
* @param ServerRequestInterface $request The current HTTP request object * @param ServerRequestInterface $request The current HTTP request object
* *
* @return mixed[] * @return int[]|string[]|array[]
* *
* @link https://github.com/nikic/FastRoute/blob/master/src/Dispatcher.php * @link https://github.com/nikic/FastRoute/blob/master/src/Dispatcher.php
*/ */
public function dispatch(ServerRequestInterface $request) public function dispatch(ServerRequestInterface $request): array
{ {
$params = $request->getQueryParams(); $params = $request->getQueryParams();
$uri = new Uri('', ''); $uri = new Uri('', '');
@ -53,9 +53,9 @@ class UglyRouter extends Router
* @throws InvalidArgumentException If required data not provided * @throws InvalidArgumentException If required data not provided
* @throws RuntimeException If named route does not exist * @throws RuntimeException If named route does not exist
*/ */
public function pathFor($name, array $data = [], array $queryParams = []) public function pathFor($name, array $data = [], array $queryParams = []): string
{ {
$queryParams['page'] = $name; $queryParams['page'] = $this->relativePathFor($name, $data);
$url = Uri::createFromString($this->relativePathFor($name, $data, $queryParams))->withPath(''); $url = Uri::createFromString($this->relativePathFor($name, $data, $queryParams))->withPath('');
if ($this->basePath) { if ($this->basePath) {

View File

@ -1,9 +1,8 @@
{ {
"name": "rudloff/alltube", "name": "rudloff/alltube",
"type": "project",
"description": "HTML GUI for youtube-dl", "description": "HTML GUI for youtube-dl",
"homepage": "http://alltubedownload.net/",
"license": "GPL-3.0-only", "license": "GPL-3.0-only",
"type": "project",
"authors": [ "authors": [
{ {
"name": "Pierre Rudloff", "name": "Pierre Rudloff",
@ -14,48 +13,86 @@
{ {
"name": "Olivier Haquette", "name": "Olivier Haquette",
"email": "contact@olivierhaquette.fr", "email": "contact@olivierhaquette.fr",
"homepage": "http://olivierhaquette.fr/", "homepage": "https://ographik.fr/",
"role": "Designer" "role": "Designer"
} }
], ],
"require": { "require": {
"php": ">=7.3", "php": ">=7.4",
"ext-intl": "*", "ext-intl": "*",
"ext-json": "*", "ext-json": "*",
"aura/session": "^2.1", "aura/session": "^2.1",
"barracudanetworks/archivestream-php": "^1.0", "barracudanetworks/archivestream-php": "^1.0",
"consolidation/log": "^2.0", "consolidation/log": "^2.0",
"cweagans/composer-patches": "^1.7",
"j0k3r/httplug-ssrf-plugin": "^2.0",
"jawira/case-converter": "^3.4", "jawira/case-converter": "^3.4",
"jean85/pretty-package-versions": "^1.3", "jean85/pretty-package-versions": "^1.3",
"mathmarques/smarty-view": "^1.1", "mathmarques/smarty-view": "^1.2",
"oomphinc/composer-installers-extender": "^2.0", "oomphinc/composer-installers-extender": "^2.0",
"paragonie/csp-builder": "^2.5", "paragonie/csp-builder": "^2.5",
"rinvex/countries": "^6.1", "rinvex/countries": "^7.3",
"rudloff/alltube-library": "^0.1.1", "rudloff/alltube-library": "^0.1.3",
"symfony/finder": "^5.0", "symfony/finder": "^5.4",
"symfony/translation": "^4.0", "symfony/translation": "^4.0",
"symfony/yaml": "^4.0", "symfony/yaml": "^4.0",
"webfontkit/open-sans": "^1.0", "webfontkit/open-sans": "^1.0",
"ytdl-org/youtube-dl": "2020.11.01.1", "yt-dlp/yt-dlp": "^2023.03",
"zonuexe/http-accept-language": "^0.4.1" "zonuexe/http-accept-language": "^0.4.1"
}, },
"require-dev": { "require-dev": {
"consolidation/robo": "^2.1", "consolidation/robo": "^3.0",
"ergebnis/composer-normalize": "^2.6", "enlightn/security-checker": "^1.4",
"insite/composer-dangling-locked-deps": "^0.2.0", "ergebnis/composer-normalize": "^2.20",
"insite/composer-dangling-locked-deps": "^0.2.1",
"junker/debugbar-smarty": "^0.1.0",
"kitchenu/slim-debugbar": "^1.1",
"maximebf/debugbar": "^1.16",
"php-mock/php-mock-mockery": "^1.3", "php-mock/php-mock-mockery": "^1.3",
"phpro/grumphp": "^1.0", "phpro/grumphp": "^1.3",
"phpstan/phpstan": "^0.12.25", "phpstan/phpstan": "^0.12.72",
"phpunit/phpunit": "^8.4", "phpunit/phpunit": "^9.5",
"sensiolabs/security-checker": "^6.0", "povils/phpmnd": "^2.5",
"smarty-gettext/smarty-gettext": "^1.6", "smarty-gettext/smarty-gettext": "^1.6",
"squizlabs/php_codesniffer": "^3.5", "squizlabs/php_codesniffer": "^3.5",
"symfony/error-handler": "^5.0", "symfony/error-handler": "^5.4",
"symfony/var-dumper": "^5.0" "symfony/var-dumper": "^5.4"
},
"repositories": [
{
"type": "package",
"package": {
"name": "yt-dlp/yt-dlp",
"version": "2023.03.04",
"dist": {
"type": "tar",
"url": "https://github.com/yt-dlp/yt-dlp/releases/download/2023.03.04/yt-dlp.tar.gz"
}
}
}
],
"autoload": {
"psr-4": {
"Alltube\\": "classes/"
}
},
"autoload-dev": {
"psr-4": {
"Alltube\\Test\\": "tests/"
}
}, },
"config": { "config": {
"allow-plugins": {
"composer/installers": true,
"cweagans/composer-patches": true,
"ergebnis/composer-normalize": true,
"insite/composer-dangling-locked-deps": true,
"oomphinc/composer-installers-extender": true,
"php-http/discovery": true,
"phpro/grumphp": true
},
"platform": { "platform": {
"php": "7.3.11" "php": "7.4.33"
}, },
"sort-packages": true "sort-packages": true
}, },
@ -70,31 +107,9 @@
}, },
"installer-types": [ "installer-types": [
"library" "library"
] ],
"patches": {}
}, },
"autoload": {
"psr-4": {
"Alltube\\": "classes/"
}
},
"autoload-dev": {
"psr-4": {
"Alltube\\Test\\": "tests/"
}
},
"repositories": [
{
"type": "package",
"package": {
"name": "ytdl-org/youtube-dl",
"version": "2020.11.01.1",
"dist": {
"type": "tar",
"url": "https://files.pythonhosted.org/packages/2d/70/bd9ec7f14efab2811fc4bd39cd586ed5e292547552310ee863ff84fa7791/youtube_dl-2020.11.1.1.tar.gz"
}
}
}
],
"scripts": { "scripts": {
"lint": "grumphp run --ansi", "lint": "grumphp run --ansi",
"release": "robo release --ansi", "release": "robo release --ansi",

6149
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
--- ---
# Path to your youtube-dl binary # Path to your youtube-dl binary
youtubedl: vendor/ytdl-org/youtube-dl/youtube_dl/__main__.py youtubedl: vendor/yt-dlp/yt-dlp/yt_dlp/__main__.py
# Path to your python binary # Path to your python binary
python: /usr/bin/python python: /usr/bin/python
@ -12,6 +12,8 @@ params:
- --flat-playlist - --flat-playlist
- --restrict-filenames - --restrict-filenames
- --no-playlist - --no-playlist
- --use-extractors
- default,-generic
# True to enable audio conversion # True to enable audio conversion
convert: false convert: false

View File

@ -255,7 +255,7 @@ footer a:hover {
margin-top: 12px; margin-top: 12px;
position: relative; position: relative;
text-align: left; text-align: left;
width: 622px; width: 100%;
} }
.mp3-inner { .mp3-inner {
@ -545,6 +545,7 @@ h1 {
.thumb { .thumb {
max-width: 700px; max-width: 700px;
height: auto;
} }
.format { .format {

View File

@ -10,9 +10,10 @@ grumphp:
xmllint: ~ xmllint: ~
yamllint: ~ yamllint: ~
composer: ~ composer: ~
securitychecker: ~ securitychecker_enlightn: ~
composer_normalize: ~ composer_normalize: ~
composer_dangling_locked_deps: ~ composer_dangling_locked_deps: ~
phpmnd: ~
phpcs: phpcs:
standard: PSR12 standard: PSR12
phpstan: phpstan:

View File

@ -98,7 +98,7 @@ msgid "Share on Facebook"
msgstr "شاركها على فيسبوك" msgstr "شاركها على فيسبوك"
#: templates/index.tpl:8 #: templates/index.tpl:8
msgid "Copy here the URL of your video (Youtube, Dailymotion, etc.)" msgid "Copy here the URL of your video (YouTube, Dailymotion, etc.)"
msgstr "انسخ هنا رابط الفيديو (يوتيوب، انستقرام، وغيرها)" msgstr "انسخ هنا رابط الفيديو (يوتيوب، انستقرام، وغيرها)"
#: templates/index.tpl:25 #: templates/index.tpl:25

View File

@ -131,8 +131,8 @@ msgid "Video password"
msgstr "Videopasswort" msgstr "Videopasswort"
#: templates/index.tpl:8 #: templates/index.tpl:8
msgid "Copy here the URL of your video (Youtube, Dailymotion, etc.)" msgid "Copy here the URL of your video (YouTube, Dailymotion, etc.)"
msgstr "Kopiere hier die URL deines Videos (Youtube, Dailymotion, etc.) hinein" msgstr "Kopiere hier die URL deines Videos (YouTube, Dailymotion, etc.) hinein"
#: templates/index.tpl:25 #: templates/index.tpl:25
msgid "Audio only (MP3)" msgid "Audio only (MP3)"

View File

@ -106,8 +106,8 @@ msgid "Share on Facebook"
msgstr "Compartir en Facebook" msgstr "Compartir en Facebook"
#: templates/index.tpl:8 #: templates/index.tpl:8
msgid "Copy here the URL of your video (Youtube, Dailymotion, etc.)" msgid "Copy here the URL of your video (YouTube, Dailymotion, etc.)"
msgstr "Copia aquí la URL de tu vídeo (Youtube, Dailymotion, etc.)" msgstr "Copia aquí la URL de tu vídeo (YouTube, Dailymotion, etc.)"
#: templates/index.tpl:23 #: templates/index.tpl:23
msgid "Audio only (MP3)" msgid "Audio only (MP3)"

View File

@ -69,8 +69,8 @@ msgid "Video password"
msgstr "Mot de passe de la vidéo" msgstr "Mot de passe de la vidéo"
#: templates/index.tpl:8 #: templates/index.tpl:8
msgid "Copy here the URL of your video (Youtube, Dailymotion, etc.)" msgid "Copy here the URL of your video (YouTube, Dailymotion, etc.)"
msgstr "Copiez ici l'URL de votre vidéo (Youtube, Dailymotion, etc.)" msgstr "Copiez ici l'URL de votre vidéo (YouTube, Dailymotion, etc.)"
#: templates/index.tpl:25 #: templates/index.tpl:25
msgid "Audio only (MP3)" msgid "Audio only (MP3)"
@ -219,17 +219,17 @@ msgstr "Impossible de trouver l'URL de la vidéo."
#: controllers/FrontController.php:64 #: controllers/FrontController.php:64
msgid "" msgid ""
"Easily download videos from Youtube, Dailymotion, Vimeo and other websites." "Easily download videos from YouTube, Dailymotion, Vimeo and other websites."
msgstr "" msgstr ""
"Téléchargez facilement des vidéos depuis Youtube, Dailymotion, Vimeo et " "Téléchargez facilement des vidéos depuis YouTube, Dailymotion, Vimeo et "
"d'autres sites web." "d'autres sites web."
#: controllers/FrontController.php:110 #: controllers/FrontController.php:110
msgid "" msgid ""
"List of all supported websites from which Alltube Download can extract video " "List of all supported websites from which AllTube Download can extract video "
"or audio files" "or audio files"
msgstr "" msgstr ""
"Liste de tous les sites web depuis lesquels Alltube Download peut extraire " "Liste de tous les sites web depuis lesquels AllTube Download peut extraire "
"des fichiers vidéo ou audio" "des fichiers vidéo ou audio"
#: controllers/FrontController.php:136 #: controllers/FrontController.php:136
@ -238,9 +238,9 @@ msgstr "Demande de mot de passe"
#: controllers/FrontController.php:138 #: controllers/FrontController.php:138
msgid "" msgid ""
"You need a password in order to download this video with Alltube Download" "You need a password in order to download this video with AllTube Download"
msgstr "" msgstr ""
"Vous avez besoin d'un mot de passe pour télécharger cette vidéo avec Alltube " "Vous avez besoin d'un mot de passe pour télécharger cette vidéo avec AllTube "
"Download" "Download"
#: controllers/FrontController.php:169 #: controllers/FrontController.php:169

View File

@ -59,8 +59,8 @@ msgid "Video password"
msgstr "Password del video" msgstr "Password del video"
#: templates/index.tpl:8 #: templates/index.tpl:8
msgid "Copy here the URL of your video (Youtube, Dailymotion, etc.)" msgid "Copy here the URL of your video (YouTube, Dailymotion, etc.)"
msgstr "Copia qui l'URL del video (Youtube, Dailymotion, ecc.)" msgstr "Copia qui l'URL del video (YouTube, Dailymotion, ecc.)"
#: templates/index.tpl:25 #: templates/index.tpl:25
msgid "Audio only (MP3)" msgid "Audio only (MP3)"

View File

@ -0,0 +1,218 @@
msgid ""
msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: POEditor.com\n"
"Project-Id-Version: Alltube Download\n"
"Language: ja-JP\n"
#: templates/inc/footer.tpl:8
msgid "Code by @dev"
msgstr "作成: @dev"
#: templates/inc/footer.tpl:16
msgid "Design by @designer"
msgstr "デザイン: @designer"
#: templates/inc/footer.tpl:21
msgid "Get the code"
msgstr "プログラムをダウンロード"
#: templates/inc/footer.tpl:29
msgid "Based on @youtubedl"
msgstr "本ソフトは@youtubedlを基に構成されています。"
#: templates/inc/footer.tpl:33
msgid "Donate using Liberapay"
msgstr "Liberapayで寄付"
#: templates/inc/footer.tpl:35
msgid "Donate"
msgstr "寄付"
#: templates/inc/header.tpl:4
msgid "Switch language"
msgstr "言語の変更"
#: templates/inc/header.tpl:8
msgid "Set language"
msgstr "言語を設定"
#: templates/info.tpl:11
msgid "You are going to download @title."
msgstr "ダウンロード対象: @title"
#: templates/info.tpl:29
msgid "Available formats:"
msgstr "ダウンロード可能な形式"
#: templates/info.tpl:31
msgid "Generic formats"
msgstr "よく使われる形式"
#: templates/info.tpl:35
msgid "Best"
msgstr "最高品質"
#: templates/info.tpl:36
msgid "Remux best video with best audio"
msgstr "最高品質に再エンコード"
#: templates/info.tpl:37
msgid "Worst"
msgstr "最低品質"
#: templates/info.tpl:42
msgid "Detailed formats"
msgstr "その他の形式"
#: templates/info.tpl:86
msgid "Stream the video through the server"
msgstr "ビデオをサーバーに設置してダウンロード"
#: templates/info.tpl:92
msgid "Convert into a custom format:"
msgstr "指定した形式への変換"
#: templates/info.tpl:93
msgid "Custom format"
msgstr "カスタム形式"
#: templates/info.tpl:93
msgid "Format to convert to"
msgstr "ファイル形式の変換"
#: templates/info.tpl:98
msgid "with"
msgstr "と"
#: templates/info.tpl:99
msgid "Bit rate"
msgstr "ビットレート"
#: templates/info.tpl:100
msgid "Custom bitrate"
msgstr "カスタム・ビットレート"
#: templates/info.tpl:103
msgid "kbit/s audio"
msgstr "kbps 音声"
#: templates/info.tpl:107 templates/playlist.tpl:38 templates/password.tpl:11
#: templates/index.tpl:19
msgid "Download"
msgstr "ダウンロード"
#: templates/playlist.tpl:12
msgid "Videos extracted from @title:"
msgstr "デコードの対象: @title"
#: templates/playlist.tpl:39
msgid "More options"
msgstr "詳細設定"
#: templates/extractors.tpl:4 classes/Controller/FrontController.php:111
msgid "Supported websites"
msgstr "ダウンロードに対応しているサイト"
#: templates/error.tpl:5
msgid "An error occurred"
msgstr "予期せぬエラーが発生しました。"
#: templates/password.tpl:5
msgid "This video is protected"
msgstr "このビデオにはプロテクトがかかっています。"
#: templates/password.tpl:6
msgid "You need a password in order to download this video."
msgstr "このビデオをダウンロードするには閲覧用のパスワードが必要です。"
#: templates/password.tpl:8
msgid "Video password"
msgstr "閲覧用パスワード"
#: templates/index.tpl:8
msgid "Copy here the URL of your video (YouTube, Dailymotion, etc.)"
msgstr "動画のリンク(URL)を入力欄に入力してください。(例:YouTube,Dailymotion等。)"
#: templates/index.tpl:25
msgid "Audio only (MP3)"
msgstr "音声のみのダウンロード(mp3形式)"
#: templates/index.tpl:29
msgid "From"
msgstr "から"
#: templates/index.tpl:32
msgid "to"
msgstr "へ"
#: templates/index.tpl:41
msgid "See all supported websites"
msgstr "ダウンロード可能なサイトを見る"
#: templates/index.tpl:43
msgid "Drag this to your bookmarks bar:"
msgstr "ブックマークバーにドラッグして登録"
#: templates/index.tpl:45
msgid "Bookmarklet"
msgstr "ブックマークボタン"
#: classes/Controller/DownloadController.php:64
#: classes/Controller/FrontController.php:166
msgid "Wrong password"
msgstr "パスワードが間違っています。"
#: classes/Controller/DownloadController.php:69
msgid "Conversion of playlists is not supported."
msgstr "プレイリストからの読み出しには対応しておりません。"
#: classes/Controller/DownloadController.php:76
msgid "Conversion of M3U8 files is not supported."
msgstr "HLS(m3u8)プレイリストファイルからの読み出しには対応しておりません。"
#: classes/Controller/DownloadController.php:82
msgid "Conversion of DASH segments is not supported."
msgstr "MPEG DASHストリーミングからの読み出しには対応しておりません。"
#: classes/Controller/FrontController.php:65
msgid "Easily download videos from YouTube, Dailymotion, Vimeo and other websites."
msgstr "Youtubeから動画を簡単にダウンロード!、DailymotionやVimeo等にも対応しております"
#: classes/Controller/FrontController.php:112
msgid "List of all supported websites from which AllTube Download can extract video or audio files"
msgstr "AllTube上でのダウンロードおよびファイルの変換に対応している音声または動画ファイルのサイト"
#: classes/Controller/FrontController.php:138
msgid "Password prompt"
msgstr "パスワード画面"
#: classes/Controller/FrontController.php:140
msgid "You need a password in order to download this video with AllTube Download"
msgstr "このビデオをAllTubeでダウンロードするにはパスワードが必要です。"
#: classes/Controller/FrontController.php:174
msgid "Video download"
msgstr "動画をダウンロード"
#: classes/Controller/FrontController.php:176
msgid "Download video from @extractor"
msgstr "@extractor からダウンロードします。"
#: classes/Controller/FrontController.php:182
msgid "Download @title from @extractor"
msgstr "@extractor から @title をダウンロードします"
#: classes/Controller/FrontController.php:255
msgid "Error"
msgstr "エラーが発生しました。"
#: classes/Controller/FrontController.php:271
msgid "Page not found"
msgstr "存在しないページです。"
#: classes/Controller/FrontController.php:282
msgid "Method not allowed"
msgstr "無効なリクエストです。"

View File

@ -71,8 +71,8 @@ msgid "Video password"
msgstr "Hasło do wideo" msgstr "Hasło do wideo"
#: templates/index.tpl:8 #: templates/index.tpl:8
msgid "Copy here the URL of your video (Youtube, Dailymotion, etc.)" msgid "Copy here the URL of your video (YouTube, Dailymotion, etc.)"
msgstr "Zamieść link do wideo (Yotube, Dailymotion, itp.)" msgstr "Zamieść link do wideo (YouTube, Dailymotion, itp.)"
#: templates/index.tpl:25 #: templates/index.tpl:25
msgid "Audio only (MP3)" msgid "Audio only (MP3)"

View File

@ -106,8 +106,8 @@ msgid "Share on Facebook"
msgstr "Compartilhe no Facebook" msgstr "Compartilhe no Facebook"
#: templates/index.tpl:8 #: templates/index.tpl:8
msgid "Copy here the URL of your video (Youtube, Dailymotion, etc.)" msgid "Copy here the URL of your video (YouTube, Dailymotion, etc.)"
msgstr "Cole aqui a URL do vídeo (Youtube, Dailymotion, etc.)" msgstr "Cole aqui a URL do vídeo (YouTube, Dailymotion, etc.)"
#: templates/index.tpl:24 #: templates/index.tpl:24
msgid "Audio only (MP3)" msgid "Audio only (MP3)"

View File

@ -0,0 +1,231 @@
msgid ""
msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Project-Id-Version: \n"
"POT-Creation-Date: \n"
"PO-Revision-Date: \n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: Poedit 2.0.1\n"
"Last-Translator: progit <pash.vld@gmail.com>\n"
"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n"
"%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
"Language: ru_RU\n"
#: templates/inc/footer.tpl:8
msgid "Code by @dev"
msgstr "Код @dev"
#: templates/inc/footer.tpl:16
msgid "Design by @designer"
msgstr "Дизайн @designer"
#: templates/inc/footer.tpl:21
msgid "Get the code"
msgstr "Получить код"
#: templates/inc/footer.tpl:29
msgid "Based on @youtubedl"
msgstr "На основе @youtubedl"
#: templates/inc/footer.tpl:33
msgid "Donate using Liberapay"
msgstr "Пожертвовать с помощью Liberapay"
#: templates/inc/footer.tpl:35
msgid "Donate"
msgstr "Пожертвовать"
#: templates/inc/header.tpl:4
msgid "Switch language"
msgstr "Смена языка"
#: templates/inc/header.tpl:8
msgid "Set language"
msgstr "Установить язык"
#: templates/info.tpl:11
msgid "You are going to download @title."
msgstr "Вы собираетесь скачать @title."
#: templates/info.tpl:29
msgid "Available formats:"
msgstr "Доступные форматы:"
#: templates/info.tpl:31
msgid "Generic formats"
msgstr "Общие форматы"
#: templates/info.tpl:35
msgid "Best"
msgstr "Лучшее качество"
#: templates/info.tpl:36
msgid "Remux best video with best audio"
msgstr "Перекодировать лучшее видео с лучшим звуком"
#: templates/info.tpl:37
msgid "Worst"
msgstr "Худшее качество"
#: templates/info.tpl:42
msgid "Detailed formats"
msgstr "Другие форматы"
#: templates/info.tpl:86
msgid "Stream the video through the server"
msgstr "Потоковое видео через сервер"
#: templates/info.tpl:92
msgid "Convert into a custom format:"
msgstr "Преобразовать в пользовательский формат:"
#: templates/info.tpl:93
msgid "Custom format"
msgstr "Пользовательский формат"
#: templates/info.tpl:93
msgid "Format to convert to"
msgstr "Формат для преобразования в"
#: templates/info.tpl:98
msgid "with"
msgstr "с"
#: templates/info.tpl:99
msgid "Bit rate"
msgstr "Битрейт"
#: templates/info.tpl:100
msgid "Custom bitrate"
msgstr "Пользовательский битрейт"
#: templates/info.tpl:103
msgid "kbit/s audio"
msgstr "кбит/с аудио"
#: templates/info.tpl:107 templates/playlist.tpl:38 templates/password.tpl:11
#: templates/index.tpl:19
msgid "Download"
msgstr "Скачать"
#: templates/playlist.tpl:12
msgid "Videos extracted from @title:"
msgstr "Извлеченные видео из @title:"
#: templates/playlist.tpl:39
msgid "More options"
msgstr "Больше вариантов"
#: templates/extractors.tpl:4 classes/Controller/FrontController.php:111
msgid "Supported websites"
msgstr "Поддерживаемые сайты"
#: templates/error.tpl:5
msgid "An error occurred"
msgstr "Произошла ошибка"
#: templates/password.tpl:5
msgid "This video is protected"
msgstr "Это видео защищено"
#: templates/password.tpl:6
msgid "You need a password in order to download this video."
msgstr "Чтобы скачать это видео, вам нужен пароль."
#: templates/password.tpl:8
msgid "Video password"
msgstr "Пароль для видео"
#: templates/index.tpl:8
msgid "Copy here the URL of your video (YouTube, Dailymotion, etc.)"
msgstr "Скопируйте сюда URL вашего видео (YouTube, Dailymotion и т. д.)"
#: templates/index.tpl:25
msgid "Audio only (MP3)"
msgstr "Только звук (MP3)"
#: templates/index.tpl:29
msgid "From"
msgstr "Из"
#: templates/index.tpl:32
msgid "to"
msgstr "в"
#: templates/index.tpl:41
msgid "See all supported websites"
msgstr "Просмотреть все поддерживаемые веб-сайты"
#: templates/index.tpl:43
msgid "Drag this to your bookmarks bar:"
msgstr "Перетащите это на панель закладок:"
#: templates/index.tpl:45
msgid "Bookmarklet"
msgstr "Букмарклет"
#: classes/Controller/DownloadController.php:64
#: classes/Controller/FrontController.php:166
msgid "Wrong password"
msgstr "Неправильный пароль"
#: classes/Controller/DownloadController.php:69
msgid "Conversion of playlists is not supported."
msgstr "Конвертация плейлистов не поддерживается."
#: classes/Controller/DownloadController.php:76
msgid "Conversion of M3U8 files is not supported."
msgstr "Конвертация файлов M3U8 не поддерживается."
#: classes/Controller/DownloadController.php:82
msgid "Conversion of DASH segments is not supported."
msgstr "Конвертация сегментов DASH не поддерживается."
#: classes/Controller/FrontController.php:65
msgid ""
"Easily download videos from YouTube, Dailymotion, Vimeo and other websites."
msgstr "Легко скачивайте видео с YouTube, Dailymotion, Vimeo и других сайтов."
#: classes/Controller/FrontController.php:112
msgid ""
"List of all supported websites from which AllTube Download can extract video "
"or audio files"
msgstr ""
"Список всех поддерживаемых веб-сайтов, с которых AllTube Download может "
"извлекать видео или аудио файлы"
#: classes/Controller/FrontController.php:138
msgid "Password prompt"
msgstr "Запрос пароля"
#: classes/Controller/FrontController.php:140
msgid ""
"You need a password in order to download this video with AllTube Download"
msgstr ""
"Чтобы скачать это видео с помощью AllTube Download, вам потребуется пароль"
#: classes/Controller/FrontController.php:174
msgid "Video download"
msgstr "Скачать видео"
#: classes/Controller/FrontController.php:176
msgid "Download video from @extractor"
msgstr "Скачать видео из @extractor"
#: classes/Controller/FrontController.php:182
msgid "Download @title from @extractor"
msgstr "Скачать @title из @extractor"
#: classes/Controller/FrontController.php:255
msgid "Error"
msgstr "Ошибка"
#: classes/Controller/FrontController.php:271
msgid "Page not found"
msgstr "Страница не найдена"
#: classes/Controller/FrontController.php:282
msgid "Method not allowed"
msgstr "Метод не разрешен"

View File

@ -127,7 +127,7 @@ msgid "Video password"
msgstr "" msgstr ""
#: templates/index.tpl:8 #: templates/index.tpl:8
msgid "Copy here the URL of your video (Youtube, Dailymotion, etc.)" msgid "Copy here the URL of your video (YouTube, Dailymotion, etc.)"
msgstr "" msgstr ""
#: templates/index.tpl:25 #: templates/index.tpl:25
@ -173,12 +173,12 @@ msgstr ""
#: classes/Controller/FrontController.php:65 #: classes/Controller/FrontController.php:65
msgid "" msgid ""
"Easily download videos from Youtube, Dailymotion, Vimeo and other websites." "Easily download videos from YouTube, Dailymotion, Vimeo and other websites."
msgstr "" msgstr ""
#: classes/Controller/FrontController.php:112 #: classes/Controller/FrontController.php:112
msgid "" msgid ""
"List of all supported websites from which Alltube Download can extract video " "List of all supported websites from which AllTube Download can extract video "
"or audio files" "or audio files"
msgstr "" msgstr ""
@ -188,7 +188,7 @@ msgstr ""
#: classes/Controller/FrontController.php:140 #: classes/Controller/FrontController.php:140
msgid "" msgid ""
"You need a password in order to download this video with Alltube Download" "You need a password in order to download this video with AllTube Download"
msgstr "" msgstr ""
#: classes/Controller/FrontController.php:174 #: classes/Controller/FrontController.php:174

View File

@ -65,8 +65,8 @@ msgid "Video password"
msgstr "Video parolası" msgstr "Video parolası"
#: templates/index.tpl:8 #: templates/index.tpl:8
msgid "Copy here the URL of your video (Youtube, Dailymotion, etc.)" msgid "Copy here the URL of your video (YouTube, Dailymotion, etc.)"
msgstr "Videonuzun URL'sini buraya kopyalayın (Youtube, Dailymotion, vb.)" msgstr "Videonuzun URL'sini buraya kopyalayın (YouTube, Dailymotion, vb.)"
#: templates/index.tpl:25 #: templates/index.tpl:25
msgid "Audio only (MP3)" msgid "Audio only (MP3)"

View File

@ -1,134 +1,227 @@
msgid "" msgid ""
msgstr "" msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: POEditor.com\n"
"Project-Id-Version: AllTube Download\n"
"Language: zh-CN\n"
#: templates/error.tpl:6 #: templates/inc/footer.tpl:8
msgid "Please check the URL of your video." msgid "Code by @dev"
msgstr "请检查您的视频的 URL。" msgstr "由 @dev 开发"
#: templates/playlist.tpl:5
msgid "Videos extracted from"
msgstr "视频提取自"
#: templates/playlist.tpl:7
msgid ":"
msgstr ""
#: templates/playlist.tpl:26 templates/password.tpl:10 templates/video.tpl:83
#: templates/video.tpl:86 templates/index.tpl:18
msgid "Download"
msgstr "下载"
#: templates/playlist.tpl:27
msgid "More options"
msgstr "更多选项"
#: templates/password.tpl:5
msgid "This video is protected"
msgstr "这个视频受保护"
#: templates/password.tpl:6
msgid "You need a password in order to download this video."
msgstr "你需要密码才能下载这个视频。"
#: templates/password.tpl:8
msgid "Video password"
msgstr "视频密码"
#: templates/extractors.tpl:4
msgid "Supported websites"
msgstr "支持的网站"
#: templates/video.tpl:6
msgid "You are going to download"
msgstr "你即将下载"
#: templates/video.tpl:24
msgid "Available formats:"
msgstr "可用的格式︰"
#: templates/video.tpl:29
msgid "Generic formats"
msgstr "通用格式"
#: templates/video.tpl:32
msgid "Best"
msgstr "最佳"
#: templates/video.tpl:37
msgid "Remux best video with best audio"
msgstr "重新封装最佳视频与最佳音频"
#: templates/video.tpl:41
msgid "Worst"
msgstr "最差"
#: templates/video.tpl:44
msgid "Detailed formats"
msgstr "详细格式"
#: templates/inc/footer.tpl:4
msgid "Code by"
msgstr "代码来自"
#: templates/inc/footer.tpl:6
msgid "Design by"
msgstr "设计来自"
#: templates/inc/footer.tpl:12
msgid "AllTube Download on Facebook"
msgstr "去Alltube Download的Facebook页面"
#: templates/inc/footer.tpl:12
msgid "Like us on Facebook"
msgstr "在Facebook关注我们"
#: templates/inc/footer.tpl:14
msgid "Get the code"
msgstr "获取代码"
#: templates/inc/footer.tpl:16 #: templates/inc/footer.tpl:16
msgid "Based on" msgid "Design by @designer"
msgstr "基于" msgstr "由 @designer 设计"
#: templates/inc/header.tpl:21 #: templates/inc/footer.tpl:21
msgid "Share on Twitter" msgid "Get the code"
msgstr "分享到 Twitter" msgstr "获取源代码"
#: templates/inc/header.tpl:23 #: templates/inc/footer.tpl:29
msgid "Share on Facebook" msgid "Based on @youtubedl"
msgstr "分享到 Facebook" msgstr "基于 @youtubedl"
#: templates/index.tpl:8 #: templates/inc/footer.tpl:33
msgid "Copy here the URL of your video (Youtube, Dailymotion, etc.)" msgid "Donate using Liberapay"
msgstr "在这里复制您的视频 Youtube、 Dailymotion 等) 的 URL" msgstr "使用 Liberapay 捐赠"
#: templates/index.tpl:23 #: templates/inc/footer.tpl:35
msgid "Audio only (MP3)" msgid "Donate"
msgstr "仅限音频mp3" msgstr "捐赠"
#: templates/index.tpl:28
msgid "See all supported websites"
msgstr "请参阅支持的所有网站"
#: templates/index.tpl:30
msgid "Drag this to your bookmarks bar:"
msgstr "把这个拖到你的书签:"
#: templates/index.tpl:31
msgid "Bookmarklet"
msgstr "书签工具"
#: templates/inc/header.tpl:4 #: templates/inc/header.tpl:4
msgid "Switch language" msgid "Switch language"
msgstr "切换语言" msgstr "切换语言"
#: templates/inc/header.tpl:8
msgid "Set language"
msgstr "设置语言"
#: templates/info.tpl:11
msgid "You are going to download @title."
msgstr "您将要下载 @title。"
#: templates/info.tpl:29
msgid "Available formats:"
msgstr "可用的格式:"
#: templates/info.tpl:31
msgid "Generic formats"
msgstr "通用格式"
#: templates/info.tpl:35
msgid "Best"
msgstr "最佳"
#: templates/info.tpl:36
msgid "Remux best video with best audio"
msgstr "重新封装最佳视频和最佳音频"
#: templates/info.tpl:37
msgid "Worst"
msgstr "最差"
#: templates/info.tpl:42
msgid "Detailed formats"
msgstr "详细格式"
#: templates/info.tpl:86
msgid "Stream the video through the server"
msgstr "通过服务器传输视频"
#: templates/info.tpl:92
msgid "Convert into a custom format:"
msgstr "转换为自定义格式:"
#: templates/info.tpl:93
msgid "Custom format"
msgstr "自定义格式"
#: templates/info.tpl:93
msgid "Format to convert to"
msgstr "要转换到的格式"
#: templates/info.tpl:98
# Other translators: Please check that file for context
msgid "with"
msgstr ",并带"
#: templates/info.tpl:99
msgid "Bit rate"
msgstr "比特率"
#: templates/info.tpl:100
msgid "Custom bitrate"
msgstr "自定义比特率"
#: templates/info.tpl:103
msgid "kbit/s audio"
msgstr "kbit/s 的音频"
#: templates/info.tpl:107 templates/playlist.tpl:38 templates/password.tpl:11
#: templates/index.tpl:19
msgid "Download"
msgstr "下载"
#: templates/playlist.tpl:12
msgid "Videos extracted from @title:"
msgstr "从 @title 中提取的视频:"
#: templates/playlist.tpl:39
msgid "More options"
msgstr "更多选项"
#: templates/extractors.tpl:4 classes/Controller/FrontController.php:111
msgid "Supported websites"
msgstr "支持的网站"
#: templates/error.tpl:5 #: templates/error.tpl:5
msgid "An error occurred" msgid "An error occurred"
msgstr "出错了" msgstr "出错了"
#: templates/password.tpl:5
msgid "This video is protected"
msgstr "此视频受保护"
#: templates/password.tpl:6
msgid "You need a password in order to download this video."
msgstr "您需要密码才能下载此视频。"
#: templates/password.tpl:8
msgid "Video password"
msgstr "视频密码"
#: templates/index.tpl:8
# I don't think this needs to be a 100% match
msgid "Copy here the URL of your video (YouTube, Dailymotion, etc.)"
msgstr "在此处粘贴视频网址"
#: templates/index.tpl:25
msgid "Audio only (MP3)"
msgstr "仅音频MP3"
#: templates/index.tpl:29
# Still check that file for context
msgid "From"
msgstr "从"
#: templates/index.tpl:32
msgid "to"
msgstr "到"
#: templates/index.tpl:41
msgid "See all supported websites"
msgstr "查看所有支持的网站"
#: templates/index.tpl:43
msgid "Drag this to your bookmarks bar:"
msgstr "您可以把这个书签工具拖到您的书签栏中:"
#: templates/index.tpl:45
msgid "Bookmarklet"
msgstr "书签工具"
#: classes/Controller/DownloadController.php:64
#: classes/Controller/FrontController.php:166
msgid "Wrong password"
msgstr "密码错误"
#: classes/Controller/DownloadController.php:69
msgid "Conversion of playlists is not supported."
msgstr "不支持转换播放列表。"
#: classes/Controller/DownloadController.php:76
msgid "Conversion of M3U8 files is not supported."
msgstr "不支持转换 M3U8 文件。"
#: classes/Controller/DownloadController.php:82
# ref. Chinese Wikipedia article about DASH
msgid "Conversion of DASH segments is not supported."
msgstr "不支持转换 DASH 片段。"
#: classes/Controller/FrontController.php:65
msgid ""
"Easily download videos from YouTube, Dailymotion, Vimeo and other websites."
msgstr ""
"轻松从 YouTube、Dailymotion、Vimeo 等网站下载视频。"
#: classes/Controller/FrontController.php:112
# NOTE: DON'T translate AllTube Download
msgid ""
"List of all supported websites from which AllTube Download can extract video "
"or audio files"
msgstr ""
"AllTube Download 能够提取视频"
"或音频文件的的所有网站"
#: classes/Controller/FrontController.php:138
msgid "Password prompt"
msgstr "密码提示"
#: classes/Controller/FrontController.php:140
msgid ""
"You need a password in order to download this video with AllTube Download"
msgstr ""
"您需要密码才能使用 AllTube Download 下载此视频"
#: classes/Controller/FrontController.php:174
# Download page header?
msgid "Video download"
msgstr "下载视频"
#: classes/Controller/FrontController.php:176
msgid "Download video from @extractor"
msgstr "从 @extractor 下载视频"
#: classes/Controller/FrontController.php:182
msgid "Download @title from @extractor"
msgstr "从 @extractor 下载 @title"
#: classes/Controller/FrontController.php:255
msgid "Error"
msgstr "错误"
#: classes/Controller/FrontController.php:271
msgid "Page not found"
msgstr "找不到页面"
#: classes/Controller/FrontController.php:282
msgid "Method not allowed"
msgstr "不允许此请求方法"

View File

@ -5,11 +5,6 @@ require_once __DIR__ . '/vendor/autoload.php';
use Alltube\App; use Alltube\App;
use Alltube\ErrorHandler; use Alltube\ErrorHandler;
if (isset($_SERVER['REQUEST_URI']) && strpos($_SERVER['REQUEST_URI'], '/index.php') !== false) {
header('Location: ' . str_ireplace('/index.php', '/', $_SERVER['REQUEST_URI']));
die;
}
try { try {
// Create app. // Create app.
$app = new App(); $app = new App();

View File

@ -1,17 +1,14 @@
<?xml version="1.0"?> <?xml version="1.0"?>
<phpunit bootstrap="tests/bootstrap.php"> <phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" bootstrap="tests/bootstrap.php"
<filter> xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.3/phpunit.xsd">
<whitelist> <coverage>
<include>
<directory>classes/</directory> <directory>classes/</directory>
</whitelist> </include>
</filter> </coverage>
<testsuites> <testsuites>
<testsuite name="Tests"> <testsuite name="Tests">
<directory>tests/</directory> <directory>tests/</directory>
</testsuite> </testsuite>
</testsuites> </testsuites>
<logging>
<log type="coverage-html" target="coverage/"/>
<log type="coverage-clover" target="clover.xml"/>
</logging>
</phpunit> </phpunit>

View File

@ -6,20 +6,6 @@ Most recent browsers automatically play a video
if it is a format they know how to play. if it is a format they know how to play.
You can usually download the video by doing *File > Save to* or *ctrl + S*. You can usually download the video by doing *File > Save to* or *ctrl + S*.
## [alltubedownload.net](https://alltubedownload.net) is too slow
[alltubedownload.net](https://alltubedownload.net) is hosted on a free [Heroku server](https://www.heroku.com/pricing)
so it has low RAM and CPU.
AllTube probably won't switch to a more expensive hosting
because this project does not earn any financial ressources
(although [donations are welcome](https://liberapay.com/Rudloff/))
and you are encouraged to host it yourself.
## alltubedownload.net often says "An error occurred in the application…"
See above.
## Change config parameters ## Change config parameters
You need to create a YAML file called `config.yml` in the `config/` folder. You need to create a YAML file called `config.yml` in the `config/` folder.
@ -69,8 +55,7 @@ There are two known workarounds:
* You can run AllTube locally on your computer. * You can run AllTube locally on your computer.
* You can enable streaming videos through the server (see below). * You can enable streaming videos through the server (see below).
Please note that this can use a lot of resources on the server Please note that this can use a lot of resources on the server.
(which is why we won't enable it on alltubedownload.net).
## I get a 404 error on every page except the index ## I get a 404 error on every page except the index
@ -91,7 +76,7 @@ You need to add this to your `config.yml` file:
stream: true stream: true
``` ```
Note that this can use a lot of ressources on your server. Note that this can use a lot of resources on your server.
## Download M3U videos ## Download M3U videos
@ -129,7 +114,7 @@ remux: true
## Convert videos to something other than MP3 ## Convert videos to something other than MP3
By default the `convert` option only allows converting to MP3, By default the `convert` option only allows converting to MP3,
in order to keep things simple and ressources usage low. in order to keep things simple and resources usage low.
However, you can use the `convertAdvanced` option like this: However, you can use the `convertAdvanced` option like this:
```yaml ```yaml

View File

@ -31,7 +31,7 @@
} }
], ],
"lang": "en", "lang": "en",
"start_url": "./", "start_url": "../",
"theme_color": "#4F4F4F", "theme_color": "#4F4F4F",
"background_color": "#EBEBEB", "background_color": "#EBEBEB",
"orientation": "portrait" "orientation": "portrait"

View File

@ -1,12 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url>
<loc>https://alltubedownload.net/</loc>
<changefreq>yearly</changefreq>
<priority>1</priority>
</url>
<url>
<loc>https://alltubedownload.net/extractors</loc>
<changefreq>weekly</changefreq>
</url>
</urlset>

View File

@ -1 +0,0 @@
Sitemap: https://alltubedownload.net/resources/sitemap.xml

View File

@ -1 +1 @@
python-3.8.6 python-3.8.12

View File

@ -1,8 +1,8 @@
{include file='inc/head.tpl'} {extends file='page.tpl'}
<div class="wrapper"> {block name='main'}
<main class="main error"> <div class="error">
{include file="inc/logo.tpl"} {include file="inc/logo.tpl"}
<h2>{t}An error occurred{/t}</h2> <h2>{t}An error occurred{/t}</h2>
<p><i>{$error|escape|nl2br}</i></p> <p><i>{$error|escape|nl2br}</i></p>
</main> </div>
{include file='inc/footer.tpl'} {/block}

View File

@ -1,12 +1,12 @@
{include file='inc/head.tpl'} {extends file='page.tpl'}
{include file='inc/header.tpl'} {block name='main'}
{include file='inc/logo.tpl'} {include file='inc/logo.tpl'}
<h2 class="titre">{t}Supported websites{/t}</h2> <h2 class="titre">{t}Supported websites{/t}</h2>
<div class="tripleliste"> <div class="tripleliste">
<ul> <ul>
{foreach $extractors as $extractor} {foreach $extractors as $extractor}
<li>{$extractor}</li> <li>{$extractor}</li>
{/foreach} {/foreach}
</ul> </ul>
</div> </div>
{include file='inc/footer.tpl'} {/block}

View File

@ -1,18 +1,11 @@
</div>
<footer class="small-font"> <footer class="small-font">
<div class="footer_wrapper"> <div class="footer_wrapper">
{$dev="<a rel='author' target='blank' {include file='snippets/dev.tpl' assign=dev}
href='http://rudloff.pro/'>
Pierre Rudloff
</a>"}
{t params=['@dev'=>$dev]}Code by @dev{/t} {t params=['@dev'=>$dev]}Code by @dev{/t}
&middot; &middot;
{$designer="<a rel='author' target='blank' {include file='snippets/designer.tpl' assign=designer}
href='http://olivierhaquette.fr'>
Olivier Haquette
</a>"}
{t params=['@designer' => $designer]}Design by @designer{/t} {t params=['@designer' => $designer]}Design by @designer{/t}
&middot; &middot;
@ -23,19 +16,7 @@
&middot; &middot;
{$youtubedl="<a href='http://ytdl-org.github.io/youtube-dl/'> {include file='snippets/youtubedl.tpl' assign=youtubedl}
youtube-dl
</a>"}
{t params=['@youtubedl'=>$youtubedl]}Based on @youtubedl{/t} {t params=['@youtubedl'=>$youtubedl]}Based on @youtubedl{/t}
&middot;
<a rel="noopener" target="_blank" title="{t}Donate using Liberapay{/t}"
href="https://liberapay.com/Rudloff/donate">
{t}Donate{/t}
</a>
</div> </div>
</footer> </footer>
</div>
</body>
</html>

View File

@ -1,5 +1,3 @@
<!doctype html>
<html {if isset($locale)}lang="{$locale->getBcp47()}"{/if}>
<head> <head>
<meta charset="UTF-8"/> <meta charset="UTF-8"/>
<meta name=viewport content="width=device-width, initial-scale=1"/> <meta name=viewport content="width=device-width, initial-scale=1"/>
@ -11,7 +9,6 @@
<link rel="stylesheet" href="{base_url}/assets/open-sans/open-sans.css"/> <link rel="stylesheet" href="{base_url}/assets/open-sans/open-sans.css"/>
<link rel="stylesheet" href="{base_url}/css/style.css"/> <link rel="stylesheet" href="{base_url}/css/style.css"/>
<title>{$config->appName}{if isset($title)} - {$title|escape}{/if}</title> <title>{$config->appName}{if isset($title)} - {$title|escape}{/if}</title>
<link rel="canonical" href="{$canonical}"/>
<link rel="icon" href="{base_url}/img/favicon.png"/> <link rel="icon" href="{base_url}/img/favicon.png"/>
<meta property="og:title" content="{$config->appName}{if isset($title)} - {$title|escape}{/if}"/> <meta property="og:title" content="{$config->appName}{if isset($title)} - {$title|escape}{/if}"/>
<meta property="og:image" content="{base_url}/img/logo.png"/> <meta property="og:image" content="{base_url}/img/logo.png"/>
@ -22,6 +19,8 @@
<meta name="theme-color" content="#4F4F4F"/> <meta name="theme-color" content="#4F4F4F"/>
<link rel="manifest" href="{base_url}/resources/manifest.json"/> <link rel="manifest" href="{base_url}/resources/manifest.json"/>
<meta name="generator" content="AllTube Download ({$config->getAppVersion()})"/> <meta name="generator" content="AllTube Download ({$config->getAppVersion()})"/>
{if isset($debug_render)}
{$debug_render->renderHead()}
{/if}
</head> </head>
<body>
<div class="page {$class}">

View File

@ -2,8 +2,8 @@
{if isset($supportedLocales) AND count($supportedLocales) > 1} {if isset($supportedLocales) AND count($supportedLocales) > 1}
<div class="locales small-font"> <div class="locales small-font">
<button class="localesBtn small-font" title="{t}Switch language{/t}"> <button class="localesBtn small-font" title="{t}Switch language{/t}">
{if isset($locale) AND $locale->getCountry()} {if $locale->getLocale()->getCountry()}
{$locale->getCountry()->getEmoji()} {$locale->getLocale()->getCountry()->getEmoji()}
{else} {else}
{t}Set language{/t} {t}Set language{/t}
{/if} {/if}
@ -18,7 +18,7 @@
{if $supportedLocale->getCountry()} {if $supportedLocale->getCountry()}
{$supportedLocale->getCountry()->getEmoji()} {$supportedLocale->getCountry()->getEmoji()}
{/if} {/if}
{$supportedLocale->getFullName()|ucfirst} {$supportedLocale->getFullName()}
</a> </a>
</li> </li>
{/if} {/if}
@ -27,4 +27,3 @@
</div> </div>
{/if} {/if}
</header> </header>
<div class="wrapper">

View File

@ -1,5 +1,7 @@
<h1 class="logobis"> <h1 class="logobis">
<a class="logocompatible" href="{base_url}"> <a class="logocompatible" href="{path_for name="index"}">
<span class="logocompatiblemask"><img src="{base_url}/img/logocompatiblemask.png" width="447" height="107" <span class="logocompatiblemask">
alt="{$config->appName}"/></span> {html_image file='img/logocompatiblemask.png' path_prefix={base_url}|cat:'/' alt=$config->appName}
</a></h1> </span>
</a>
</h1>

View File

@ -1,17 +1,17 @@
{include file='inc/head.tpl'} {extends file='page.tpl'}
{include file='inc/header.tpl'} {block name='main'}
<main class="main"> <div>
<div><img class="logo" src="{base_url}/img/logo.png" {html_image file='img/logo.png' path_prefix={base_url}|cat:'/' alt=$config->appName class="logo"}
alt="{$config->appName}" width="328" height="284"></div> </div>
<form action="{path_for name="info"}"> <form action="{path_for name="info"}">
<label class="labelurl" for="url"> <label class="labelurl" for="url">
{t}Copy here the URL of your video (Youtube, Dailymotion, etc.){/t} {t}Copy here the URL of your video (YouTube, Dailymotion, etc.){/t}
</label> </label>
<div class="champs"> <div class="champs">
<span class="URLinput_wrapper"> <span class="URLinput_wrapper">
<!-- We used to have an autofocus attribute on this field but it triggerd a very specific CSS bug: https://github.com/Rudloff/alltube/issues/117 --> <!-- We used to have an autofocus attribute on this field but it triggerd a very specific CSS bug: https://github.com/Rudloff/alltube/issues/117 -->
<input class="URLinput large-font" type="url" name="url" id="url" <input class="URLinput large-font" type="url" name="url" id="url"
required placeholder="http://example.com/video"/> required placeholder="https://example.com/video"/>
</span> </span>
{if $config->uglyUrls} {if $config->uglyUrls}
<input type="hidden" name="page" value="info"/> <input type="hidden" name="page" value="info"/>
@ -20,18 +20,23 @@
{if $config->convert} {if $config->convert}
<div class="mp3 small-font"> <div class="mp3 small-font">
<div class="mp3-inner"> <div class="mp3-inner">
<input type="checkbox" id="audio" class="audio" name="audio" {($config->defaultAudio) ? 'checked' : ''}> <input type="checkbox" id="audio" class="audio"
name="audio" {($config->defaultAudio) ? 'checked' : ''}>
<label for="audio"><span class="ui"></span> <label for="audio"><span class="ui"></span>
{t}Audio only (MP3){/t} {t}Audio only (MP3){/t}
</label> </label>
{if $config->convertSeek} {if $config->convertSeek}
<div class="seekOptions"> <div class="seekOptions">
<label for="from">{t}From{/t}</label> <input type="text" pattern="(\d+:)?(\d+:)?\d+(\.\d+)?" <label for="from">{t}From{/t}</label> <input type="text"
placeholder="HH:MM:SS" value="" name="from" pattern="(\d+:)?(\d+:)?\d+(\.\d+)?"
id="from"/> placeholder="HH:MM:SS" value=""
<label for="to">{t}to{/t}</label> <input type="text" pattern="(\d+:)?(\d+:)?\d+(\.\d+)?" name="from"
placeholder="HH:MM:SS" value="" name="to" id="to"/> id="from"/>
</div> <label for="to">{t}to{/t}</label> <input type="text"
pattern="(\d+:)?(\d+:)?\d+(\.\d+)?"
placeholder="HH:MM:SS" value="" name="to"
id="to"/>
</div>
{/if} {/if}
</div> </div>
</div> </div>
@ -44,6 +49,4 @@
<a class="bookmarklet small-font" <a class="bookmarklet small-font"
href="javascript:window.location='{$domain}{path_for name='info' queryParams=['url' => '%url%']}'.replace('%url%', encodeURIComponent(location.href));">{t}Bookmarklet{/t}</a> href="javascript:window.location='{$domain}{path_for name='info' queryParams=['url' => '%url%']}'.replace('%url%', encodeURIComponent(location.href));">{t}Bookmarklet{/t}</a>
</div> </div>
{/block}
</main>
{include file='inc/footer.tpl'}

View File

@ -1,111 +1,59 @@
{include file="inc/head.tpl"} {extends file='page.tpl'}
<div class="wrapper"> {block name='main'}
<div itemscope itemtype="http://schema.org/VideoObject"> <div itemscope itemtype="https://schema.org/VideoObject">
<main class="main"> {include file="inc/logo.tpl"}
{include file="inc/logo.tpl"} {include file='snippets/title.tpl' assign=title}
{$title="<i itemprop='name'> <p id="download_intro">
<a itemprop='url' id='video_link' {t params=['@title' => $title]}You are going to download @title.{/t}
href='{$video->webpage_url}'> </p>
{$video->title}</a></i>"} {if isset($video->thumbnail)}
<p id="download_intro"> {html_image file=$video->thumbnail itemprop="thumbnailUrl" class="thumb"}
{t params=['@title' => $title]}You are going to download @title.{/t} {/if}
</p> {if isset($video->description)}
{if isset($video->thumbnail)} <meta itemprop="description" content="{$video->description|escape}"/>
<img itemprop="thumbnailUrl" class="thumb" src="{$video->thumbnail}" alt=""/> {/if}
{if isset($video->upload_date)}
<meta itemprop="uploadDate" content="{$video->upload_date}"/>
{/if}
<br/>
<form action="{path_for name="download"}">
<input type="hidden" name="url" value="{$video->webpage_url}"/>
{if $config->uglyUrls}
<input type="hidden" name="page" value="download"/>
{/if} {/if}
{if isset($video->description)} {if isset($video->formats) && count($video->formats) > 1}
<meta itemprop="description" content="{$video->description|escape}"/> <h3><label for="format">{t}Available formats:{/t}</label></h3>
{*
To make the default generic formats translatable:
{t}Best{/t}
{t}Remux best video with best audio{/t}
{t}Worst{/t}
*}
{html_options name='format' options=$formats selected=$defaultFormat id="format" class="formats monospace"}
<br/>
<br/>
{/if} {/if}
{if isset($video->upload_date)} {if $config->stream}
<meta itemprop="uploadDate" content="{$video->upload_date}"/> <input type="checkbox" {if $config->stream !== 'ask'}checked{/if} name="stream" id="stream"/>
<label for="stream">{t}Stream the video through the server{/t}</label>
<br/>
<br/>
{/if} {/if}
<br/> {if $config->convertAdvanced}
<form action="{path_for name="download"}"> <input type="checkbox" name="customConvert" id="customConvert"/>
<input type="hidden" name="url" value="{$video->webpage_url}"/> <label for="customConvert">{t}Convert into a custom format:{/t}</label>
{if $config->uglyUrls} {html_options name='customFormat' values=$config->convertAdvancedFormats output=$config->convertAdvancedFormats
<input type="hidden" name="page" value="download"/> title="{t}Custom format{/t}" name="customFormat" aria-label="{t}Format to convert to{/t}"}
{/if} {t}with{/t}
{if isset($video->formats) && count($video->formats) > 1} <label for="customBitrate" class="sr-only">{t}Bit rate{/t}</label>
<h3><label for="format">{t}Available formats:{/t}</label></h3> <input type="number" value="{$config->audioBitrate}" title="{t}Custom bitrate{/t}"
<select name="format" id="format" class="formats monospace"> class="customBitrate"
<optgroup label="{t}Generic formats{/t}"> name="customBitrate" id="customBitrate" aria-describedby="customBitrateUnit"/>
{foreach $config->genericFormats as $format => $name} <span id="customBitrateUnit">{t}kbit/s audio{/t}</span>
{* <br/>
To make the default generic formats translatable: <br/>
{t}Best{/t} {/if}
{t}Remux best video with best audio{/t} <input class="downloadBtn" type="submit" value="{t}Download{/t}"/><br/>
{t}Worst{/t} </form>
*}
<option value="{$format}">{t}{$name}{/t}</option>
{/foreach}
</optgroup>
<optgroup label="{t}Detailed formats{/t}" class="monospace">
{foreach $video->formats as $format}
{if $config->stream || $format->protocol|in_array:array('http', 'https')}
{strip}
<option value="{$format->format_id}">
{$format->ext}
{for $foo=1 to (5 - ($format->ext|strlen))}
&nbsp;
{/for}
{if isset($format->width)}
{$format->width}x{$format->height}
{for $foo=1 to (10 - (("{$format->width}x{$format->height}")|strlen))}
&nbsp;
{/for}
{else}
{for $foo=1 to 10}
&nbsp;
{/for}
{/if}
{if isset($format->filesize)}
{($format->filesize/1000000)|round:2} MB
{for $foo=1 to (7 - (($format->filesize/1000000)|round:2|strlen))}
&nbsp;
{/for}
{else}
{for $foo=1 to 10}
&nbsp;
{/for}
{/if}
{if isset($format->format_note)}
{$format->format_note}
{/if}
&nbsp;({$format->format_id})
</option>
{/strip}
{/if}
{/foreach}
</optgroup>
</select>
<br/>
<br/>
{/if}
{if $config->stream}
<input type="checkbox" {if $config->stream !== 'ask'}checked{/if} name="stream" id="stream"/>
<label for="stream">{t}Stream the video through the server{/t}</label>
<br/>
<br/>
{/if}
{if $config->convertAdvanced}
<input type="checkbox" name="customConvert" id="customConvert"/>
<label for="customConvert">{t}Convert into a custom format:{/t}</label>
<select title="{t}Custom format{/t}" name="customFormat" aria-label="{t}Format to convert to{/t}">
{foreach $config->convertAdvancedFormats as $format}
<option>{$format}</option>
{/foreach}
</select>
{t}with{/t}
<label for="customBitrate" class="sr-only">{t}Bit rate{/t}</label>
<input type="number" value="{$config->audioBitrate}" title="{t}Custom bitrate{/t}"
class="customBitrate"
name="customBitrate" id="customBitrate" aria-describedby="customBitrateUnit"/>
<span id="customBitrateUnit">{t}kbit/s audio{/t}</span>
<br/>
<br/>
{/if}
<input class="downloadBtn" type="submit" value="{t}Download{/t}"/><br/>
</form>
</main>
</div> </div>
{include file="inc/footer.tpl"} {/block}

18
templates/page.tpl Normal file
View File

@ -0,0 +1,18 @@
<!doctype html>
<html lang="{$locale->getLocale()->getBcp47()}">
{include file='inc/head.tpl'}
<body>
<div class="page {$class}">
{include file='inc/header.tpl'}
<div class="wrapper">
<main class="main">
{block name="main"}{/block}
</main>
</div>
{include file='inc/footer.tpl'}
</div>
{if isset($debug_render)}
{$debug_render->render()}
{/if}
</body>
</html>

View File

@ -1,14 +1,12 @@
{include file='inc/head.tpl'} {extends file='page.tpl'}
<div class="wrapper"> {block name='main'}
<main class="main"> {include file="inc/logo.tpl"}
{include file="inc/logo.tpl"} <h2>{t}This video is protected{/t}</h2>
<h2>{t}This video is protected{/t}</h2> <p>{t}You need a password in order to download this video.{/t}</p>
<p>{t}You need a password in order to download this video.{/t}</p> <form action="" method="POST">
<form action="" method="POST"> <label class="sr-only" for="password">{t}Video password{/t}</label>
<label class="sr-only" for="password">{t}Video password{/t}</label> <input class="URLinput" type="password" name="password" id="password"/>
<input class="URLinput" type="password" name="password" id="password"/> <br/><br/>
<br/><br/> <input class="downloadBtn" type="submit" value="{t}Download{/t}"/>
<input class="downloadBtn" type="submit" value="{t}Download{/t}"/> </form>
</form> {/block}
</main>
{include file='inc/footer.tpl'}

View File

@ -1,44 +1,40 @@
{include file="inc/head.tpl"} {extends file='page.tpl'}
<div class="wrapper"> {block name='main'}
<main class="main"> {include file="inc/logo.tpl"}
{include file="inc/logo.tpl"}
{if isset($video->title)} {if isset($video->title)}
{$title="<i> {include file='snippets/title.tpl' assign=title}
<a href='{$video->webpage_url}'> <p>
{$video->title}</a> {t params=['@title'=>$title]}Videos extracted from @title:{/t}
</i>"} </p>
<p> {/if}
{t params=['@title'=>$title]}Videos extracted from @title:{/t}
</p>
{/if}
{if $config->stream} {if $config->stream}
<a href="{path_for name="download"}?url={$video->webpage_url}" class="downloadBtn">Download everything</a> <a href="{path_for name="download"}?url={$video->webpage_url}" class="downloadBtn">Download everything</a>
{/if} {/if}
{foreach $video->entries as $entry} {foreach $video->entries as $entry}
<div class="playlist-entry"> <div class="playlist-entry">
<h3 class="playlist-entry-title"><a target="_blank" href="{strip} <h3 class="playlist-entry-title">
<a target="_blank" href="{strip}
{if isset($entry->ie_key) and $entry->ie_key == Youtube and !filter_var($entry->url, FILTER_VALIDATE_URL)} {if isset($entry->ie_key) and $entry->ie_key == Youtube and !filter_var($entry->url, FILTER_VALIDATE_URL)}
https://www.youtube.com/watch?v= https://www.youtube.com/watch?v=
{/if} {/if}
{$entry->url} {$entry->url}
{/strip}"> {/strip}">
{if !isset($entry->title)} {if !isset($entry->title)}
{if $entry->ie_key == YoutubePlaylist} {if $entry->ie_key == YoutubePlaylist}
Playlist Playlist
{else}
Video
{/if}
{else} {else}
{$entry->title} Video
{/if} {/if}
</a></h3> {else}
<a target="_blank" class="downloadBtn" {$entry->title}
href="{path_for name="download"}?url={$entry->url}">{t}Download{/t}</a> {/if}
<a target="_blank" href="{path_for name="info"}?url={$entry->url}">{t}More options{/t}</a> </a>
</div> </h3>
{/foreach} <a target="_blank" class="downloadBtn"
href="{path_for name="download"}?url={$entry->url}">{t}Download{/t}</a>
</main> <a target="_blank" href="{path_for name="info"}?url={$entry->url}">{t}More options{/t}</a>
{include file="inc/footer.tpl"} </div>
{/foreach}
{/block}

View File

@ -0,0 +1,4 @@
<a rel="author" target="blank"
href="https://ographik.fr/">
Olivier Haquette
</a>

View File

@ -0,0 +1,4 @@
<a rel="author" target="blank"
href="https://rudloff.pro/">
Pierre Rudloff
</a>

View File

@ -0,0 +1,5 @@
<i itemprop="name">
<a itemprop="url" id="video_link"
href="{$video->webpage_url}">
{$video->title}</a>
</i>

View File

@ -0,0 +1,3 @@
<a href="https://ytdl-org.github.io/youtube-dl/">
youtube-dl
</a>

View File

@ -6,7 +6,9 @@
namespace Alltube\Test; namespace Alltube\Test;
use OndraM\CiDetector\CiDetector;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
use PHPUnit\Util\Test;
/** /**
* Abstract class used by every test. * Abstract class used by every test.
@ -18,7 +20,7 @@ abstract class BaseTest extends TestCase
* *
* @return string Path to file * @return string Path to file
*/ */
protected function getConfigFile() protected function getConfigFile(): string
{ {
return __DIR__ . '/../config/config_test.yml'; return __DIR__ . '/../config/config_test.yml';
} }
@ -37,7 +39,11 @@ abstract class BaseTest extends TestCase
*/ */
protected function checkRequirements() protected function checkRequirements()
{ {
$annotations = $this->getAnnotations(); $ciDetector = new CiDetector();
$annotations = Test::parseTestMethodAnnotations(
static::class,
$this->getName()
);
$requires = []; $requires = [];
if (isset($annotations['class']['requires'])) { if (isset($annotations['class']['requires'])) {
@ -48,7 +54,7 @@ abstract class BaseTest extends TestCase
} }
foreach ($requires as $require) { foreach ($requires as $require) {
if ($require == 'download' && getenv('CI')) { if ($require == 'download' && $ciDetector->isCiDetected()) {
$this->markTestSkipped('Do not run tests that download videos on CI.'); $this->markTestSkipped('Do not run tests that download videos on CI.');
} }
} }

View File

@ -14,7 +14,6 @@ use Alltube\Exception\ConfigException;
*/ */
class ConfigTest extends BaseTest class ConfigTest extends BaseTest
{ {
/** /**
* Test the getInstance function. * Test the getInstance function.
* *

View File

@ -40,6 +40,7 @@ abstract class ContainerTest extends BaseTest
$this->checkRequirements(); $this->checkRequirements();
$this->container = new Container(['environment' => Environment::mock()]); $this->container = new Container(['environment' => Environment::mock()]);
$this->container['root_path'] = dirname(__DIR__);
$this->container['config'] = Config::fromFile($this->getConfigFile()); $this->container['config'] = Config::fromFile($this->getConfigFile());
$this->container['session'] = SessionFactory::create($this->container); $this->container['session'] = SessionFactory::create($this->container);
$this->container['locale'] = LocaleManagerFactory::create($this->container); $this->container['locale'] = LocaleManagerFactory::create($this->container);

View File

@ -6,12 +6,14 @@
namespace Alltube\Test; namespace Alltube\Test;
use Alltube\Locale;
use Alltube\Controller\BaseController; use Alltube\Controller\BaseController;
use Alltube\Controller\DownloadController; use Alltube\Controller\DownloadController;
use Alltube\Controller\FrontController; use Alltube\Controller\FrontController;
use Alltube\Exception\ConfigException; use Alltube\Exception\ConfigException;
use Alltube\Exception\DependencyException; use Alltube\Exception\DependencyException;
use Slim\Http\Response; use Slim\Http\Response;
use Slim\Views\Smarty;
use SmartyException; use SmartyException;
/** /**
@ -34,6 +36,8 @@ abstract class ControllerTest extends ContainerTest
{ {
parent::setUp(); parent::setUp();
$this->container->get('locale')->setLocale(new Locale('en_US'));
$frontController = new FrontController($this->container); $frontController = new FrontController($this->container);
$downloadController = new DownloadController($this->container); $downloadController = new DownloadController($this->container);
@ -49,6 +53,12 @@ abstract class ControllerTest extends ContainerTest
->setName('locale'); ->setName('locale');
$router->map(['GET'], '/redirect', [$downloadController, 'download']) $router->map(['GET'], '/redirect', [$downloadController, 'download'])
->setName('download'); ->setName('download');
/** @var Smarty $view */
$view = $this->container->get('view');
// Make sure we start the tests without compiled templates.
$view->getSmarty()->clearCompiledTemplate();
} }
/** /**
@ -59,7 +69,7 @@ abstract class ControllerTest extends ContainerTest
* *
* @return Response HTTP response * @return Response HTTP response
*/ */
protected function getRequestResult(string $request, array $params) protected function getRequestResult(string $request, array $params): Response
{ {
return $this->controller->$request( return $this->controller->$request(
$this->container->get('request')->withQueryParams($params), $this->container->get('request')->withQueryParams($params),

View File

@ -17,7 +17,6 @@ use SmartyException;
*/ */
class ConvertedPlaylistArchiveStreamTest extends StreamTest class ConvertedPlaylistArchiveStreamTest extends StreamTest
{ {
/** /**
* Prepare tests. * Prepare tests.
* *

View File

@ -108,14 +108,6 @@ class DownloadControllerTest extends ControllerTest
public function testDownloadWithRtmpStream() public function testDownloadWithRtmpStream()
{ {
$this->markTestIncomplete('We need to find another RTMP video.'); $this->markTestIncomplete('We need to find another RTMP video.');
$config = $this->container->get('config');
$config->setOptions(['stream' => true]);
$this->assertRequestIsOk(
'download',
['url' => 'http://www.rtvnh.nl/video/131946', 'format' => 'rtmp-264']
);
} }
/** /**
@ -161,7 +153,7 @@ class DownloadControllerTest extends ControllerTest
*/ */
public function testDownloadWithMissingPassword() public function testDownloadWithMissingPassword()
{ {
$this->assertRequestIsClientError('download', ['url' => 'http://vimeo.com/68375962']); $this->assertRequestIsClientError('download', ['url' => 'https://vimeo.com/68375962']);
} }
/** /**
@ -172,7 +164,7 @@ class DownloadControllerTest extends ControllerTest
public function testDownloadWithError() public function testDownloadWithError()
{ {
$this->expectException(YoutubedlException::class); $this->expectException(YoutubedlException::class);
$this->getRequestResult('download', ['url' => 'http://example.com/foo']); $this->getRequestResult('download', ['url' => 'https://example.com/foo']);
} }
/** /**

View File

@ -11,6 +11,7 @@ use Alltube\Exception\ConfigException;
use Alltube\Exception\DependencyException; use Alltube\Exception\DependencyException;
use Alltube\Library\Exception\AlltubeLibraryException; use Alltube\Library\Exception\AlltubeLibraryException;
use Exception; use Exception;
use Graby\HttpClient\Plugin\ServerSideRequestForgeryProtection\Exception\InvalidURLException;
use Slim\Http\Environment; use Slim\Http\Environment;
use Slim\Http\Request; use Slim\Http\Request;
use SmartyException; use SmartyException;
@ -113,7 +114,8 @@ class FrontControllerTest extends ControllerTest
*/ */
public function testInfoWithoutUrl() public function testInfoWithoutUrl()
{ {
$this->assertRequestIsRedirect('info'); $this->expectException(InvalidURLException::class);
$this->getRequestResult('info', []);
} }
/** /**
@ -184,12 +186,12 @@ class FrontControllerTest extends ControllerTest
* *
* @return void * @return void
* @requires download * @requires download
* @throws AlltubeLibraryException * @throws AlltubeLibraryException|InvalidURLException
*/ */
public function testInfoWithPassword() public function testInfoWithPassword()
{ {
$result = $this->controller->info( $result = $this->controller->info(
$this->container->get('request')->withQueryParams(['url' => 'http://vimeo.com/68375962']) $this->container->get('request')->withQueryParams(['url' => 'https://vimeo.com/68375962'])
->withParsedBody(['password' => 'youtube-dl']), ->withParsedBody(['password' => 'youtube-dl']),
$this->container->get('response') $this->container->get('response')
); );
@ -204,8 +206,8 @@ class FrontControllerTest extends ControllerTest
*/ */
public function testInfoWithMissingPassword() public function testInfoWithMissingPassword()
{ {
$this->assertRequestIsClientError('info', ['url' => 'http://vimeo.com/68375962']); $this->assertRequestIsClientError('info', ['url' => 'https://vimeo.com/68375962']);
$this->assertRequestIsClientError('info', ['url' => 'http://vimeo.com/68375962', 'audio' => true]); $this->assertRequestIsClientError('info', ['url' => 'https://vimeo.com/68375962', 'audio' => true]);
} }
/** /**

View File

@ -49,7 +49,7 @@ class JsonControllerTest extends ControllerTest
public function testJsonWithError() public function testJsonWithError()
{ {
$this->expectException(YoutubedlException::class); $this->expectException(YoutubedlException::class);
$this->getRequestResult('json', ['url' => 'http://example.com/foo']); $this->getRequestResult('json', ['url' => 'https://example.com/foo']);
} }
/** /**

View File

@ -85,26 +85,32 @@ class LocaleMiddlewareTest extends ContainerTest
* Check that the request contains an Accept-Language header. * Check that the request contains an Accept-Language header.
* *
* @param Request $request PSR-7 request * @param Request $request PSR-7 request
* @param Response $response
* *
* @return void * @return Response
*/ */
public function assertHeader(Request $request) public function assertHeader(Request $request, Response $response): Response
{ {
$header = $request->getHeader('Accept-Language'); $header = $request->getHeader('Accept-Language');
$this->assertEquals('foo-BAR', $header[0]); $this->assertEquals('foo-BAR', $header[0]);
return $response;
} }
/** /**
* Check that the request contains no Accept-Language header. * Check that the request contains no Accept-Language header.
* *
* @param Request $request PSR-7 request * @param Request $request PSR-7 request
* @param Response $response
* *
* @return void * @return Response
*/ */
public function assertNoHeader(Request $request) public function assertNoHeader(Request $request, Response $response): Response
{ {
$header = $request->getHeader('Accept-Language'); $header = $request->getHeader('Accept-Language');
$this->assertEmpty($header); $this->assertEmpty($header);
return $response;
} }
/** /**

View File

@ -54,7 +54,7 @@ class LocaleTest extends ContainerTest
*/ */
public function testGetFullName() public function testGetFullName()
{ {
$this->assertEquals('français (France)', $this->localeObject->getFullName()); $this->assertEquals('Français (France)', $this->localeObject->getFullName());
} }
/** /**

View File

@ -37,7 +37,16 @@ class UglyRouterTest extends ContainerTest
parent::setUp(); parent::setUp();
$this->router = new UglyRouter(); $this->router = new UglyRouter();
$this->router->map(['GET'], '/foo', 'print')->setName('foo'); $this->router->map(['GET'], '/foo', [$this, 'fakeHandler'])->setName('foo');
}
/**
* Empty function that only exists so that our route can have a handler.
*
* @return void
*/
private function fakeHandler()
{
} }
/** /**
@ -54,7 +63,7 @@ class UglyRouterTest extends ContainerTest
Environment::mock( Environment::mock(
[ [
'REQUEST_METHOD' => 'GET', 'REQUEST_METHOD' => 'GET',
'QUERY_STRING' => 'page=foo', 'QUERY_STRING' => 'page=foo',
] ]
) )
) )
@ -70,7 +79,7 @@ class UglyRouterTest extends ContainerTest
public function testPathFor() public function testPathFor()
{ {
$this->assertEquals( $this->assertEquals(
'/?page=foo', '/?page=%2Ffoo',
$this->router->pathFor('foo', [], []) $this->router->pathFor('foo', [], [])
); );
} }
@ -84,7 +93,7 @@ class UglyRouterTest extends ContainerTest
{ {
$this->router->setBasePath('/bar'); $this->router->setBasePath('/bar');
$this->assertEquals( $this->assertEquals(
'/bar/?page=foo', '/bar/?page=%2Ffoo',
$this->router->pathFor('foo', [], []) $this->router->pathFor('foo', [], [])
); );
} }

View File

@ -104,7 +104,7 @@ class VideoTest extends ContainerTest
*/ */
public function testgetUrlWithPassword() public function testgetUrlWithPassword()
{ {
$video = new Video($this->downloader, 'http://vimeo.com/68375962', 'best', 'youtube-dl'); $video = new Video($this->downloader, 'https://vimeo.com/68375962', 'best', 'youtube-dl');
foreach ($video->getUrl() as $videoURL) { foreach ($video->getUrl() as $videoURL) {
$this->assertStringContainsString('vimeocdn.com', $videoURL); $this->assertStringContainsString('vimeocdn.com', $videoURL);
} }
@ -119,7 +119,7 @@ class VideoTest extends ContainerTest
public function testgetUrlWithMissingPassword() public function testgetUrlWithMissingPassword()
{ {
$this->expectException(PasswordException::class); $this->expectException(PasswordException::class);
$video = new Video($this->downloader, 'http://vimeo.com/68375962', $this->format); $video = new Video($this->downloader, 'https://vimeo.com/68375962', $this->format);
$video->getUrl(); $video->getUrl();
} }
@ -132,7 +132,7 @@ class VideoTest extends ContainerTest
public function testgetUrlWithWrongPassword() public function testgetUrlWithWrongPassword()
{ {
$this->expectException(WrongPasswordException::class); $this->expectException(WrongPasswordException::class);
$video = new Video($this->downloader, 'http://vimeo.com/68375962', 'best', 'foo'); $video = new Video($this->downloader, 'https://vimeo.com/68375962', 'best', 'foo');
$video->getUrl(); $video->getUrl();
} }
@ -157,7 +157,7 @@ class VideoTest extends ContainerTest
* *
* @return array[] * @return array[]
*/ */
public function urlProvider() public function urlProvider(): array
{ {
return [ return [
[ [
@ -174,7 +174,7 @@ class VideoTest extends ContainerTest
'googlevideo.com', 'googlevideo.com',
], ],
[ [
'http://www.bbc.co.uk/programmes/b039g8p7', 'bestaudio/best', 'https://www.bbc.co.uk/programmes/b039g8p7', 'bestaudio/best',
'Kaleidoscope_Leonard_Cohen-b039d07m', 'Kaleidoscope_Leonard_Cohen-b039d07m',
'flv', 'flv',
'bbcodspdns.fcod.llnwd.net', 'bbcodspdns.fcod.llnwd.net',
@ -193,7 +193,7 @@ class VideoTest extends ContainerTest
* *
* @return array[] * @return array[]
*/ */
public function remuxUrlProvider() public function remuxUrlProvider(): array
{ {
return [ return [
[ [
@ -210,7 +210,7 @@ class VideoTest extends ContainerTest
* *
* @return array[] * @return array[]
*/ */
public function m3uUrlProvider() public function m3uUrlProvider(): array
{ {
return [ return [
[ [
@ -227,7 +227,7 @@ class VideoTest extends ContainerTest
* *
* @return array[] * @return array[]
*/ */
public function rtmpUrlProvider() public function rtmpUrlProvider(): array
{ {
return [ return [
[ [
@ -244,10 +244,10 @@ class VideoTest extends ContainerTest
* *
* @return array[] * @return array[]
*/ */
public function errorUrlProvider() public function errorUrlProvider(): array
{ {
return [ return [
['http://example.com/video'], ['https://example.com/video'],
]; ];
} }
@ -479,16 +479,11 @@ class VideoTest extends ContainerTest
* @param string $format Format * @param string $format Format
* *
* @return void * @return void
* @throws AlltubeLibraryException
* @dataProvider rtmpUrlProvider * @dataProvider rtmpUrlProvider
*/ */
public function testGetRtmpStream(string $url, string $format) public function testGetRtmpStream(string $url, string $format)
{ {
$this->markTestIncomplete('We need to find another RTMP video.'); $this->markTestIncomplete('We need to find another RTMP video.');
$video = new Video($this->downloader, $url, $format);
$this->assertStream($this->downloader->getRtmpStream($video));
} }
/** /**