diff --git a/app/Http/Controllers/Controller.php b/app/Http/Controllers/Controller.php index c1d762c..d39a243 100644 --- a/app/Http/Controllers/Controller.php +++ b/app/Http/Controllers/Controller.php @@ -2,8 +2,6 @@ namespace App\Http\Controllers; -use App\Http\SymfonyCastDl\HtmlParser; -use App\Http\SymfonyCastDl\SymfonyCastDlService; use Illuminate\Foundation\Auth\Access\AuthorizesRequests; use Illuminate\Foundation\Bus\DispatchesJobs; use Illuminate\Foundation\Validation\ValidatesRequests; @@ -13,8 +11,4 @@ class Controller extends BaseController { use AuthorizesRequests, DispatchesJobs, ValidatesRequests; - public function index(HtmlParser $htmlParser) - { - $service = new SymfonyCastDlService($htmlParser); - } } diff --git a/app/Http/Controllers/CourseController.php b/app/Http/Controllers/CourseController.php new file mode 100644 index 0000000..4a8aab2 --- /dev/null +++ b/app/Http/Controllers/CourseController.php @@ -0,0 +1,25 @@ +videoSize($chapter); + + dd($chapter->toArray()); + } +} diff --git a/app/Http/Controllers/Index.php b/app/Http/Controllers/Index.php new file mode 100644 index 0000000..2fbd336 --- /dev/null +++ b/app/Http/Controllers/Index.php @@ -0,0 +1,23 @@ +withCount('chapters')->get(); + return view('index', compact(['courses'])); + } + + public function download(HtmlParser $htmlParser) + { + $service = new SymfonyCastDlService($htmlParser); + $service->getInfo(); + } +} diff --git a/app/Http/SymfonyCastDl/HtmlParser.php b/app/Http/SymfonyCastDl/HtmlParser.php index fe48d9d..02b09f4 100644 --- a/app/Http/SymfonyCastDl/HtmlParser.php +++ b/app/Http/SymfonyCastDl/HtmlParser.php @@ -2,8 +2,12 @@ namespace App\Http\SymfonyCastDl; +use App\Models\Chapter; +use App\Models\Course; +use Carbon\Carbon; use DiDom\Document; use GuzzleHttp\Psr7\Response; +use Illuminate\Support\Collection; class HtmlParser { @@ -13,36 +17,55 @@ class HtmlParser return $document->first('input[name="_csrf_token"]')->attr('value'); } - public function getCourses(Response $response): array + public function getCourses(Response $response): Collection { - $courses = []; + $courses = new Collection(); $document = new Document($response->getBody()->getContents()); foreach ($document->find('div.js-course-item') as $courseItem) { - $course = []; - $course['name'] = $courseItem->first('h3')->text(); - $course['link'] = $courseItem->first('a')->attr('href'); - $course['status'] = $courseItem->attr('data-status'); - $course['chapter-count'] = $courseItem->attr('data-chapter-count'); - $course['times-watched'] = $courseItem->attr('data-times-watched'); - - $courses[] = $course; + $courseId = $courseItem->attr('data-id'); + $course = Course::firstOrNew(['course_id' => $courseId]); + $course->name = $courseItem->first('h3')->text(); + $course->thumbnail = $courseItem->first('img.course-list-item-img')->attr('src'); + $course->link = $courseItem->first('a')->attr('href'); + $course->status = $courseItem->attr('data-status'); + $course->course_id = $courseItem->attr('data-id'); + $course->numberofchapters = $courseItem->attr('data-chapter-count'); + $course->timeswatched = $courseItem->attr('data-times-watched'); + $course->published_at = $courseItem->attr('data-date') > 0 ? Carbon::createFromTimestamp( + $courseItem->attr('data-date') + ) : null; + $courses->add($course); } return $courses; } - public function getCourseDetails(Response $response): array + public function getCourseDetails(Response $response, int $courseId): Collection { $document = new Document($response->getBody()->getContents()); - $info = ['chapters' => []]; - foreach ($document->find('ul.chapter-list li') as $chapter) { - $info['chapters'][] = [ - 'link' => $chapter->first('a')->attr('href'), - 'title' => preg_replace('/\v(?:[\v\h]+)/', '', $chapter->first('.col')->text()), - 'duration' => $chapter->first('.length-styling')->text(), - ]; + $chapters = new Collection(); + $chapterId = 0; + foreach ($document->find('ul.chapter-list li') as $chapterItem) { + if ($chapterItem->first('.col')) { + $chapterId++; + +// if(!$chapterItem->first('.length-styling')){ +// dd($chapterItem->html()); +// } + $chapter = Chapter::firstOrNew(['course_id' => $courseId, 'order' => $chapterId]); + $chapter->duration = $chapterItem->first('.length-styling')?->text(); + $chapter->order = $chapterId; + $chapter->course_id = $courseId; + $chapter->link = config('symfonycast.base_url') . $chapterItem->first('a')->attr('href'); + $chapter->video_link = config('symfonycast.base_url') . $chapterItem->first('a')->attr( + 'href' + ) . '/download/video'; + $chapter->title = preg_replace('/\v(?:[\v\h]+)/', '', $chapterItem->first('.col')->text()); + $chapter->video_size = 0; + $chapters->add($chapter); + } } - return $info; + return $chapters; } } diff --git a/app/Http/SymfonyCastDl/SymfonyCastDlService.php b/app/Http/SymfonyCastDl/SymfonyCastDlService.php index ad21569..fd0bab4 100644 --- a/app/Http/SymfonyCastDl/SymfonyCastDlService.php +++ b/app/Http/SymfonyCastDl/SymfonyCastDlService.php @@ -2,39 +2,64 @@ namespace App\Http\SymfonyCastDl; +use App\Models\Chapter; +use App\Models\Course; use GuzzleHttp\TransferStats; -use DiDom\Document; use GuzzleHttp\Client; class SymfonyCastDlService { - public function __construct(HtmlParser $htmlParser) + public Client $client; + + public function __construct(public HtmlParser $htmlParser) { - $client = new Client([ - 'base_uri' => "https://symfonycasts.com", + $this->client = new Client([ + 'base_uri' => config('symfonycast.base_url'), 'cookies' => true ]); - $response = $client->get('login'); + $response = $this->client->get('login'); $token = $htmlParser->getCsrfToken($response); - $response = $client->post('login', [ + $response = $this->client->post('login', [ 'form_params' => [ -// 'email' => 'krzysiej@gmail.com', -// 'password' => '', + 'email' => config('symfonycast.login'), + 'password' => config('symfonycast.password'), '_csrf_token' => $token ], 'on_stats' => function (TransferStats $stats) use (&$currentUrl) { $currentUrl = $stats->getEffectiveUri(); } ]); + } - $coursePage = $client->get('courses/filtering'); -// dump($htmlParser->getCourses($coursePage)); + public function getInfo() + { + $coursePage = $this->client->get('courses/filtering'); - $singleCoursePage = $client->get('screencast/api-platform'); - dd($htmlParser->getCourseDetails($singleCoursePage)); + $courses = $this->htmlParser->getCourses($coursePage); + $courses->each(fn($course) => $course->save()); + $singleCoursePage = $this->client->get($courses[3]->link); + /** @var Course $course */ + foreach ($courses as $course) { + $singleCoursePage = $this->client->get($course->link); + $chapters = $this->htmlParser->getCourseDetails($singleCoursePage, $course->id); + $chapters->each(fn($chapter) => $this->videoSize($chapter)->save()); + } + } + + public function videoSize(Chapter $chapter): Chapter + { + try { + $response = $this->client->head($chapter->video_link); + if ($response->hasHeader('Content-Length')) { + $chapter->video_size = $response->getHeader('Content-Length')[0]; + } + } catch (\GuzzleHttp\Exception\ClientException $exception) { + + } + return $chapter; } } diff --git a/app/Models/Chapter.php b/app/Models/Chapter.php new file mode 100644 index 0000000..092e0a5 --- /dev/null +++ b/app/Models/Chapter.php @@ -0,0 +1,36 @@ +belongsTo(Course::class); + } +} diff --git a/app/Models/Course.php b/app/Models/Course.php new file mode 100644 index 0000000..5a9b779 --- /dev/null +++ b/app/Models/Course.php @@ -0,0 +1,42 @@ +hasMany(Chapter::class); + } +} diff --git a/app/Models/User.php b/app/Models/User.php deleted file mode 100644 index 23b4063..0000000 --- a/app/Models/User.php +++ /dev/null @@ -1,44 +0,0 @@ - - */ - protected $fillable = [ - 'name', - 'email', - 'password', - ]; - - /** - * The attributes that should be hidden for serialization. - * - * @var array - */ - protected $hidden = [ - 'password', - 'remember_token', - ]; - - /** - * The attributes that should be cast. - * - * @var array - */ - protected $casts = [ - 'email_verified_at' => 'datetime', - ]; -} diff --git a/composer.json b/composer.json index 61a932b..82c48fd 100644 --- a/composer.json +++ b/composer.json @@ -13,6 +13,7 @@ "laravel/tinker": "^2.7" }, "require-dev": { + "barryvdh/laravel-debugbar": "^3.7", "fakerphp/faker": "^1.9.1", "laravel/pint": "^1.0", "laravel/sail": "^1.0.1", diff --git a/composer.lock b/composer.lock index abd3a20..929aa10 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "6969489719f565e06eb44575a4396934", + "content-hash": "e81b030ec4951ada3e6f11c989d631ea", "packages": [ { "name": "brick/math", @@ -5244,6 +5244,90 @@ } ], "packages-dev": [ + { + "name": "barryvdh/laravel-debugbar", + "version": "v3.7.0", + "source": { + "type": "git", + "url": "https://github.com/barryvdh/laravel-debugbar.git", + "reference": "3372ed65e6d2039d663ed19aa699956f9d346271" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/barryvdh/laravel-debugbar/zipball/3372ed65e6d2039d663ed19aa699956f9d346271", + "reference": "3372ed65e6d2039d663ed19aa699956f9d346271", + "shasum": "" + }, + "require": { + "illuminate/routing": "^7|^8|^9", + "illuminate/session": "^7|^8|^9", + "illuminate/support": "^7|^8|^9", + "maximebf/debugbar": "^1.17.2", + "php": ">=7.2.5", + "symfony/finder": "^5|^6" + }, + "require-dev": { + "mockery/mockery": "^1.3.3", + "orchestra/testbench-dusk": "^5|^6|^7", + "phpunit/phpunit": "^8.5|^9.0", + "squizlabs/php_codesniffer": "^3.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.6-dev" + }, + "laravel": { + "providers": [ + "Barryvdh\\Debugbar\\ServiceProvider" + ], + "aliases": { + "Debugbar": "Barryvdh\\Debugbar\\Facades\\Debugbar" + } + } + }, + "autoload": { + "files": [ + "src/helpers.php" + ], + "psr-4": { + "Barryvdh\\Debugbar\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Barry vd. Heuvel", + "email": "barryvdh@gmail.com" + } + ], + "description": "PHP Debugbar integration for Laravel", + "keywords": [ + "debug", + "debugbar", + "laravel", + "profiler", + "webprofiler" + ], + "support": { + "issues": "https://github.com/barryvdh/laravel-debugbar/issues", + "source": "https://github.com/barryvdh/laravel-debugbar/tree/v3.7.0" + }, + "funding": [ + { + "url": "https://fruitcake.nl", + "type": "custom" + }, + { + "url": "https://github.com/barryvdh", + "type": "github" + } + ], + "time": "2022-07-11T09:26:42+00:00" + }, { "name": "doctrine/instantiator", "version": "1.4.1", @@ -5682,6 +5766,72 @@ }, "time": "2022-07-21T14:33:56+00:00" }, + { + "name": "maximebf/debugbar", + "version": "v1.18.0", + "source": { + "type": "git", + "url": "https://github.com/maximebf/php-debugbar.git", + "reference": "0d44b75f3b5d6d41ae83b79c7a4bceae7fbc78b6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/maximebf/php-debugbar/zipball/0d44b75f3b5d6d41ae83b79c7a4bceae7fbc78b6", + "reference": "0d44b75f3b5d6d41ae83b79c7a4bceae7fbc78b6", + "shasum": "" + }, + "require": { + "php": "^7.1|^8", + "psr/log": "^1|^2|^3", + "symfony/var-dumper": "^2.6|^3|^4|^5|^6" + }, + "require-dev": { + "phpunit/phpunit": "^7.5.20 || ^9.4.2", + "twig/twig": "^1.38|^2.7|^3.0" + }, + "suggest": { + "kriswallsmith/assetic": "The best way to manage assets", + "monolog/monolog": "Log using Monolog", + "predis/predis": "Redis storage" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.17-dev" + } + }, + "autoload": { + "psr-4": { + "DebugBar\\": "src/DebugBar/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Maxime Bouroumeau-Fuseau", + "email": "maxime.bouroumeau@gmail.com", + "homepage": "http://maximebf.com" + }, + { + "name": "Barry vd. Heuvel", + "email": "barryvdh@gmail.com" + } + ], + "description": "Debug bar in the browser for php application", + "homepage": "https://github.com/maximebf/php-debugbar", + "keywords": [ + "debug", + "debugbar" + ], + "support": { + "issues": "https://github.com/maximebf/php-debugbar/issues", + "source": "https://github.com/maximebf/php-debugbar/tree/v1.18.0" + }, + "time": "2021-12-27T18:49:48+00:00" + }, { "name": "mockery/mockery", "version": "1.5.0", diff --git a/config/symfonycast.php b/config/symfonycast.php new file mode 100644 index 0000000..44db737 --- /dev/null +++ b/config/symfonycast.php @@ -0,0 +1,9 @@ + env('SYMFONY_CAST_BASE_URL'), + 'login' => env('SYMFONY_CAST_LOGIN'), + 'password' => env('SYMFONY_CAST_PASSWORD'), +]; diff --git a/database/migrations/2014_10_12_000000_create_users_table.php b/database/migrations/2014_10_12_000000_create_users_table.php deleted file mode 100644 index cf6b776..0000000 --- a/database/migrations/2014_10_12_000000_create_users_table.php +++ /dev/null @@ -1,36 +0,0 @@ -id(); - $table->string('name'); - $table->string('email')->unique(); - $table->timestamp('email_verified_at')->nullable(); - $table->string('password'); - $table->rememberToken(); - $table->timestamps(); - }); - } - - /** - * Reverse the migrations. - * - * @return void - */ - public function down() - { - Schema::dropIfExists('users'); - } -}; diff --git a/database/migrations/2014_10_12_100000_create_password_resets_table.php b/database/migrations/2014_10_12_100000_create_password_resets_table.php deleted file mode 100644 index fcacb80..0000000 --- a/database/migrations/2014_10_12_100000_create_password_resets_table.php +++ /dev/null @@ -1,32 +0,0 @@ -string('email')->index(); - $table->string('token'); - $table->timestamp('created_at')->nullable(); - }); - } - - /** - * Reverse the migrations. - * - * @return void - */ - public function down() - { - Schema::dropIfExists('password_resets'); - } -}; diff --git a/database/migrations/2019_08_19_000000_create_failed_jobs_table.php b/database/migrations/2019_08_19_000000_create_failed_jobs_table.php deleted file mode 100644 index 1719198..0000000 --- a/database/migrations/2019_08_19_000000_create_failed_jobs_table.php +++ /dev/null @@ -1,36 +0,0 @@ -id(); - $table->string('uuid')->unique(); - $table->text('connection'); - $table->text('queue'); - $table->longText('payload'); - $table->longText('exception'); - $table->timestamp('failed_at')->useCurrent(); - }); - } - - /** - * Reverse the migrations. - * - * @return void - */ - public function down() - { - Schema::dropIfExists('failed_jobs'); - } -}; diff --git a/database/migrations/2019_12_14_000001_create_personal_access_tokens_table.php b/database/migrations/2019_12_14_000001_create_personal_access_tokens_table.php deleted file mode 100644 index 6c81fd2..0000000 --- a/database/migrations/2019_12_14_000001_create_personal_access_tokens_table.php +++ /dev/null @@ -1,37 +0,0 @@ -id(); - $table->morphs('tokenable'); - $table->string('name'); - $table->string('token', 64)->unique(); - $table->text('abilities')->nullable(); - $table->timestamp('last_used_at')->nullable(); - $table->timestamp('expires_at')->nullable(); - $table->timestamps(); - }); - } - - /** - * Reverse the migrations. - * - * @return void - */ - public function down() - { - Schema::dropIfExists('personal_access_tokens'); - } -}; diff --git a/database/migrations/2022_08_18_094159_create_course_table.php b/database/migrations/2022_08_18_094159_create_course_table.php new file mode 100644 index 0000000..e3f9729 --- /dev/null +++ b/database/migrations/2022_08_18_094159_create_course_table.php @@ -0,0 +1,38 @@ +id(); + $table->integer('course_id')->unique(); + $table->text('name'); + $table->text('thumbnail'); + $table->text('link'); + $table->enum('status', ['unfinished', 'upcoming', 'complete']); + $table->integer('numberofchapters'); + $table->integer('timeswatched'); + $table->dateTime('published_at')->nullable(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('courses'); + } +}; diff --git a/database/migrations/2022_08_18_094237_create_chapter_table.php b/database/migrations/2022_08_18_094237_create_chapter_table.php new file mode 100644 index 0000000..4cc0280 --- /dev/null +++ b/database/migrations/2022_08_18_094237_create_chapter_table.php @@ -0,0 +1,28 @@ +id(); + $table->foreignIdFor(Course::class); + $table->integer('order'); + $table->text('title'); + $table->text('duration')->nullable(); + $table->text('link'); + $table->text('video_link'); + $table->integer('video_size'); + $table->timestamps(); + }); + } + + public function down(): void + { + Schema::dropIfExists('chapters'); + } +}; diff --git a/resources/views/components/layout.blade.php b/resources/views/components/layout.blade.php new file mode 100644 index 0000000..bbab0df --- /dev/null +++ b/resources/views/components/layout.blade.php @@ -0,0 +1,12 @@ + + + + {{ $title ?? 'Symfonycast.local' }} + + + + +{{ $slot }} + + diff --git a/resources/views/course/index.blade.php b/resources/views/course/index.blade.php new file mode 100644 index 0000000..84b247f --- /dev/null +++ b/resources/views/course/index.blade.php @@ -0,0 +1,14 @@ + +

+ {{ $course->name }} +

+ + + @foreach($course->chapters()->get() as $chapter) + + + + + @endforeach +
{{ $chapter->title }}{{ $chapter->duration }}
+
diff --git a/resources/views/index.blade.php b/resources/views/index.blade.php new file mode 100644 index 0000000..08d3f54 --- /dev/null +++ b/resources/views/index.blade.php @@ -0,0 +1,24 @@ + + + + + + + + + + + + + @foreach($courses as $course) + + + + + + + + + @endforeach +
IdNamestatusChaptersPublished at
{{ $course->id }}{{ $course->name }} {{ $course->name }}{{ $course->status }}{{ $course->chapters_count }} / {{ $course->numberofchapters }}{{ $course->published_at?->diffForHumans() }}
+
diff --git a/routes/web.php b/routes/web.php index d0c41d6..619edc8 100644 --- a/routes/web.php +++ b/routes/web.php @@ -13,6 +13,9 @@ use Illuminate\Support\Facades\Route; | */ -Route::get('/', [\App\Http\Controllers\Controller::class, 'index']); +Route::get('/download', [\App\Http\Controllers\Index::class, 'download']); +Route::get('/', [\App\Http\Controllers\Index::class, 'index']); +Route::get('/course/{course}', [\App\Http\Controllers\CourseController::class, 'index'])->name('course.index'); +Route::get('/chapter/{chapter}', [\App\Http\Controllers\CourseController::class, 'chapter'])->name('course.chapter'); diff --git a/storage/debugbar/.gitignore b/storage/debugbar/.gitignore new file mode 100644 index 0000000..d6b7ef3 --- /dev/null +++ b/storage/debugbar/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore