├── .browserslistrc ├── .dockerignore ├── .github └── workflows │ └── rspec.yml ├── .gitignore ├── .rspec ├── .tool-versions ├── Dockerfile ├── Gemfile ├── Gemfile.lock ├── Procfile ├── README.md ├── Rakefile ├── app ├── assets │ ├── builds │ │ └── .keep │ ├── config │ │ └── manifest.js │ ├── images │ │ ├── .keep │ │ ├── javbus.png │ │ └── javlibrary.gif │ └── stylesheets │ │ └── application.scss ├── controllers │ ├── application_controller.rb │ ├── fanza_actresses_controller.rb │ ├── fanza_items_controller.rb │ ├── fc2_items_controller.rb │ ├── fc2_pages_controller.rb │ ├── feeds_controller.rb │ ├── javlibrary_items_controller.rb │ ├── javlibrary_pages_controller.rb │ ├── mgstage_items_controller.rb │ ├── mgstage_pages_controller.rb │ ├── movies_controller.rb │ ├── pages_controller.rb │ ├── sod_items_controller.rb │ └── sod_pages_controller.rb ├── helpers │ ├── application_helper.rb │ ├── fanza_actresses_helper.rb │ ├── fanza_items_helper.rb │ ├── feeds_helper.rb │ ├── javlibrary_items_helper.rb │ ├── javlibrary_pages_helper.rb │ ├── mgstage_items_helper.rb │ ├── mgstage_pages_helper.rb │ ├── movies_helper.rb │ └── pages_helper.rb ├── javascript │ ├── application.js │ └── controllers │ │ ├── application.js │ │ └── index.js ├── lib │ ├── fanza │ │ ├── api.rb │ │ └── id.rb │ ├── fc2 │ │ └── api.rb │ ├── javlibrary │ │ └── api.rb │ ├── mgstage │ │ └── api.rb │ └── sod │ │ └── api.rb ├── mailers │ └── application_mailer.rb ├── models │ ├── application_record.rb │ ├── concerns │ │ ├── .keep │ │ ├── cover_image_overridable.rb │ │ ├── derivable.rb │ │ └── generic_item.rb │ ├── fanza_actress.rb │ ├── fanza_item.rb │ ├── fc2_item.rb │ ├── fc2_page.rb │ ├── feed.rb │ ├── feed_item.rb │ ├── javlibrary_item.rb │ ├── javlibrary_page.rb │ ├── mgstage_item.rb │ ├── mgstage_page.rb │ ├── movie.rb │ ├── sod_item.rb │ ├── sod_page.rb │ └── user.rb ├── views │ ├── clearance_mailer │ │ ├── change_password.html.erb │ │ └── change_password.text.erb │ ├── fanza_actresses │ │ ├── _cards.html.erb │ │ ├── index.html.erb │ │ └── show.html.erb │ ├── feeds │ │ └── index.html.erb │ ├── generic_pages │ │ └── index.html.erb │ ├── javlibrary_pages │ │ ├── index.html.erb │ │ └── new.html.erb │ ├── kaminari │ │ ├── _first_page.html.erb │ │ ├── _gap.html.erb │ │ ├── _last_page.html.erb │ │ ├── _next_page.html.erb │ │ ├── _page.html.erb │ │ ├── _paginator.html.erb │ │ └── _prev_page.html.erb │ ├── layouts │ │ ├── _pager.html.erb │ │ ├── application.html.erb │ │ ├── mailer.html.erb │ │ └── mailer.text.erb │ ├── movies │ │ ├── _cards.html.erb │ │ ├── _search.html.erb │ │ ├── index.html.erb │ │ ├── items.html.erb │ │ ├── not_found.html.erb │ │ ├── show.html.erb │ │ └── show.json.jbuilder │ ├── pages │ │ └── index.html.erb │ ├── passwords │ │ ├── create.html.erb │ │ ├── edit.html.erb │ │ └── new.html.erb │ ├── sessions │ │ └── new.html.erb │ └── users │ │ ├── _email_field.html.erb │ │ ├── _form.html.erb │ │ ├── _password_field.html.erb │ │ ├── _submit_field.html.erb │ │ └── new.html.erb └── workers │ ├── actress_crawler.rb │ ├── fanza_item_crawler.rb │ ├── fanza_searcher.rb │ ├── feed_refresher.rb │ ├── house_keeper.rb │ └── mgstage_crawler.rb ├── bin ├── bundle ├── docker-entrypoint ├── importmap ├── puma ├── pumactl ├── rails ├── rake ├── rspec ├── setup ├── sidekiq └── sidekiqmon ├── config.ru ├── config ├── application.rb ├── boot.rb ├── credentials.yml.enc ├── database.yml ├── environment.rb ├── environments │ ├── development.rb │ ├── production.rb │ └── test.rb ├── importmap.rb ├── initializers │ ├── application_controller_renderer.rb │ ├── assets.rb │ ├── backtrace_silencers.rb │ ├── clearance.rb │ ├── content_security_policy.rb │ ├── cookies_serializer.rb │ ├── filter_parameter_logging.rb │ ├── inflections.rb │ ├── mime_types.rb │ ├── permissions_policy.rb │ ├── sidekiq.rb │ ├── unicode_strip.rb │ └── wrap_parameters.rb ├── locales │ ├── clearance.en.yml │ └── en.yml ├── puma.rb ├── routes.rb ├── sidekiq.yml └── storage.yml ├── db ├── migrate │ ├── 20200518175706_create_fanza_items.rb │ ├── 20200518210337_add_normalized_id_to_fanza_items.rb │ ├── 20200519040138_add_date_to_fanza_items.rb │ ├── 20200519042227_index_fanza_items_on_normalized_id.rb │ ├── 20200519165056_create_javlibrary_pages.rb │ ├── 20200519190110_create_javlibrary_items.rb │ ├── 20200519225221_index_javlibrary_items_on_normalized_id.rb │ ├── 20200521050347_add_floor_code_to_fanza_items.rb │ ├── 20200522004625_create_fanza_actresses.rb │ ├── 20200523005310_index_fanza_item_on_raw_json.rb │ ├── 20200524183145_create_users.rb │ ├── 20200524213828_reindex_fanza_items.rb │ ├── 20200524214145_reindex_javlibrary_items.rb │ ├── 20200524214252_index_fanza_actress_on_name.rb │ ├── 20200524220821_add_is_admin_to_users.rb │ ├── 20200525003855_add_raw_html_to_fanza_items.rb │ ├── 20200525005337_add_service_code_to_fanza_items.rb │ ├── 20200526013230_create_mgstage_pages.rb │ ├── 20200526022151_create_mgstage_items.rb │ ├── 20200527060116_index_fanza_items_on_date.rb │ ├── 20200527172233_add_actresses_to_javlibrary_items_and_mgstage_items.rb │ ├── 20200529173336_create_movies.rb │ ├── 20200531074405_add_actress_ids_to_names_to_movies.rb │ ├── 20200531200252_reindex_fanza_actresses.rb │ ├── 20200531201955_reindex_movies.rb │ ├── 20200531235219_add_timestamp_to_movies.rb │ ├── 20201113195144_add_is_hidden_to_movies.rb │ ├── 20201113201050_add_is_hidden_to_fanza_actresses.rb │ ├── 20210629214501_add_cover_image_url_to_movies.rb │ ├── 20211021205018_add_description_to_fanza_items.rb │ ├── 20211022175250_remove_raw_html_from_fanza_items.rb │ ├── 20211022185418_create_sod_pages.rb │ ├── 20211022185506_create_sod_items.rb │ ├── 20211112220153_add_priority_to_fanza_item.rb │ ├── 20230206233129_add_service_name_to_active_storage_blobs.active_storage.rb │ ├── 20230206233130_create_active_storage_variant_records.active_storage.rb │ ├── 20230206233131_remove_not_null_on_active_storage_blobs_checksum.active_storage.rb │ ├── 20240106233325_create_feeds.rb │ ├── 20240108183945_add_accessed_at_to_feeds.rb │ ├── 20240109214432_add_tag_to_feeds.rb │ ├── 20240415185222_create_feed_items.rb │ ├── 20240627220304_create_fc2_pages.rb │ └── 20240627220434_create_fc2_items.rb ├── schema.rb └── seeds.rb ├── docker-compose.yml ├── lib ├── assets │ └── .keep └── tasks │ ├── dump_fixtures.rake │ ├── house_keep.rake │ └── refresh_feeds.rake ├── log └── .keep ├── public ├── 404.html ├── 422.html ├── 500.html ├── apple-touch-icon-precomposed.png ├── apple-touch-icon.png ├── favicon.ico └── robots.txt ├── spec ├── factories │ ├── fanza_actresses.rb │ ├── fanza_items.rb │ ├── fc2_items.rb │ ├── fc2_pages.rb │ ├── fc2_pages │ │ ├── 2024-06-27 │ │ │ └── aHR0cHM6Ly9hZHVsdC5jb250ZW50cy5mYzIuY29tL2FydGljbGUvMzA2OTMwOS8 │ │ └── 2024-08-05 │ │ │ └── aHR0cDovL2RvY2t5YXJkOjMwMDIvZ2V0L2h0dHBzL2FkdWx0LmNvbnRlbnRzLmZjMi5jb20vYXJ0aWNsZS80NTA5OTg4Lw │ ├── feed_items.rb │ ├── feeds.rb │ ├── javlibrary_items.rb │ ├── javlibrary_pages.rb │ ├── javlibrary_pages │ │ └── 2020-05-20 │ │ │ ├── aHR0cDovL3d3dy5qYXZsaWJyYXJ5LmNvbS9qYS8_dj1qYXZsaW8zNTR5 │ │ │ └── aHR0cDovL3d3dy5qYXZsaWJyYXJ5LmNvbS9qYS92bF9zZWFyY2hieWlkLnBocD9rZXl3b3JkPUFCUC0wMDE │ ├── mgstage_items.rb │ ├── mgstage_pages.rb │ ├── mgstage_pages │ │ └── 2020-05-25 │ │ │ ├── aHR0cHM6Ly93d3cubWdzdGFnZS5jb20vc2VhcmNoL3NlYXJjaC5waHA_c2VhcmNoX3dvcmQ9QVJBLTE2OA │ │ │ └── aHR0cHM6Ly93d3cubWdzdGFnZS5jb20vcHJvZHVjdC9wcm9kdWN0X2RldGFpbC8yNjFBUkEtMTY4Lw │ ├── movie.rb │ ├── sod_items.rb │ ├── sod_pages.rb │ ├── sod_pages │ │ └── 2021-10-22 │ │ │ └── aHR0cHM6Ly9lYy5zb2QuY28uanAvcHJpbWUvdmlkZW9zLz9pZD1TVEFSUy00NTU │ └── users.rb ├── features │ ├── fanza_actresses_spec.rb │ └── movies_spec.rb ├── lib │ ├── fanza │ │ ├── api_spec.rb │ │ └── id_spec.rb │ ├── fc2 │ │ └── api_spec.rb │ ├── javlibrary │ │ └── api_spec.rb │ ├── mgstage │ │ └── api_spec.rb │ └── sod │ │ └── api_spec.rb ├── models │ ├── fanza_actress_spec.rb │ ├── fanza_item_spec.rb │ ├── fc2_item_spec.rb │ ├── fc2_page_spec.rb │ ├── feed_item_spec.rb │ ├── feed_spec.rb │ ├── javlibrary_item_spec.rb │ ├── javlibrary_page_spec.rb │ ├── mgstage_item_spec.rb │ ├── mgstage_page_spec.rb │ ├── movie_spec.rb │ ├── sod_item_spec.rb │ ├── sod_page_spec.rb │ └── user_spec.rb ├── rails_helper.rb ├── requests │ ├── fanza_actresses_spec.rb │ ├── fanza_items_spec.rb │ ├── fc2_items_spec.rb │ ├── fc2_pages_spec.rb │ ├── feeds_spec.rb │ ├── javlibrary_items_spec.rb │ ├── javlibrary_pages_spec.rb │ ├── mgstage_items_spec.rb │ ├── mgstage_pages_spec.rb │ ├── movies_spec.rb │ ├── pages_spec.rb │ ├── sod_items_spec.rb │ └── sod_pages_spec.rb ├── routing │ └── fanza_actresses_routing_spec.rb ├── spec_helper.rb ├── support │ ├── factory_bot.rb │ └── generic_item.rb ├── views │ ├── fanza_actresses │ │ └── index.html.erb_spec.rb │ └── generic_pages │ │ └── index.html.erb_spec.rb └── workers │ ├── actress_crawler_spec.rb │ ├── fanza_item_crawler_spec.rb │ ├── fanza_searcher_spec.rb │ ├── feed_refresher_spec.rb │ ├── house_keeper_spec.rb │ └── mgstage_crawler_spec.rb ├── storage └── .keep ├── tmp ├── .keep └── pids │ └── .keep └── vendor └── .keep /.browserslistrc: -------------------------------------------------------------------------------- 1 | defaults 2 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | # See https://docs.docker.com/engine/reference/builder/#dockerignore-file for more about ignoring files. 2 | 3 | # Ignore git directory. 4 | /.git/ 5 | 6 | # Ignore bundler config. 7 | /.bundle 8 | 9 | # Ignore all environment files (except templates). 10 | /.env* 11 | !/.env*.erb 12 | 13 | # Ignore all default key files. 14 | /config/master.key 15 | /config/credentials/*.key 16 | 17 | # Ignore all logfiles and tempfiles. 18 | /log/* 19 | /tmp/* 20 | !/log/.keep 21 | !/tmp/.keep 22 | 23 | # Ignore pidfiles, but keep the directory. 24 | /tmp/pids/* 25 | !/tmp/pids/.keep 26 | 27 | # Ignore storage (uploaded files in development and any SQLite databases). 28 | /storage/* 29 | !/storage/.keep 30 | /tmp/storage/* 31 | !/tmp/storage/.keep 32 | 33 | # Ignore assets. 34 | /node_modules/ 35 | /app/assets/builds/* 36 | !/app/assets/builds/.keep 37 | /public/assets 38 | -------------------------------------------------------------------------------- /.github/workflows/rspec.yml: -------------------------------------------------------------------------------- 1 | # This workflow uses actions that are not certified by GitHub. 2 | # They are provided by a third-party and are governed by 3 | # separate terms of service, privacy policy, and support 4 | # documentation. 5 | # This workflow will download a prebuilt Ruby version, install dependencies and run tests with Rake 6 | # For more information see: https://github.com/marketplace/actions/setup-ruby-jruby-and-truffleruby 7 | 8 | name: Rspec 9 | 10 | on: push 11 | 12 | env: 13 | RAILS_ENV: test 14 | PGHOST: localhost 15 | PGPORT: 5432 16 | PGDATABASE: postgres 17 | PGUSER: postgres 18 | PGPASSWORD: postgres 19 | 20 | jobs: 21 | test: 22 | runs-on: ubuntu-20.04 23 | 24 | services: 25 | postgres: 26 | image: postgres:12.3 27 | env: 28 | POSTGRES_USER: postgres 29 | POSTGRES_PASSWORD: postgres 30 | POSTGRES_DB: postgres 31 | ports: 32 | - 5432:5432 33 | # Set health checks to wait until postgres has started 34 | options: >- 35 | --health-cmd pg_isready 36 | --health-interval 10s 37 | --health-timeout 5s 38 | --health-retries 5 39 | 40 | steps: 41 | - uses: actions/checkout@v3 42 | - name: Set up Ruby 43 | uses: ruby/setup-ruby@v1 44 | with: 45 | bundler-cache: true # runs 'bundle install' and caches installed gems automatically 46 | - name: Prepare database 47 | run: bundle exec rails db:prepare 48 | - name: Run tests 49 | run: bundle exec rails spec 50 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files for more about ignoring files. 2 | # 3 | # If you find yourself ignoring temporary files generated by your text editor 4 | # or operating system, you probably want to add a global ignore instead: 5 | # git config --global core.excludesfile '~/.gitignore_global' 6 | 7 | # Ignore bundler config. 8 | /.bundle 9 | 10 | # Ignore the default SQLite database. 11 | /db/*.sqlite3 12 | /db/*.sqlite3-* 13 | 14 | # Ignore all logfiles and tempfiles. 15 | /log/* 16 | /tmp/* 17 | !/log/.keep 18 | !/tmp/.keep 19 | 20 | # Ignore pidfiles, but keep the directory. 21 | /tmp/pids/* 22 | !/tmp/pids/ 23 | !/tmp/pids/.keep 24 | 25 | # Ignore uploaded files in development. 26 | /storage/* 27 | !/storage/.keep 28 | /tmp/storage/* 29 | !/tmp/storage/ 30 | !/tmp/storage/.keep 31 | 32 | # Ignore master key for decrypting credentials and more. 33 | /config/master.key 34 | 35 | # Ignore compiled assets 36 | /public/assets 37 | /app/assets/builds/* 38 | !/app/assets/builds/.keep 39 | 40 | # Ignore rspec examples & coverage 41 | /spec/examples.txt 42 | /coverage 43 | 44 | # Ignore vendored assets 45 | /vendor 46 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --require spec_helper 2 | -------------------------------------------------------------------------------- /.tool-versions: -------------------------------------------------------------------------------- 1 | ruby 3.3.0 2 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # syntax = docker/dockerfile:1 2 | 3 | # Make sure RUBY_VERSION matches the Ruby version in .ruby-version and Gemfile 4 | ARG RUBY_VERSION=3.3.0 5 | FROM ruby:$RUBY_VERSION-slim as base 6 | 7 | # Rails app lives here 8 | WORKDIR /rails 9 | 10 | # Set production environment 11 | ENV BUNDLE_DEPLOYMENT="1" \ 12 | BUNDLE_PATH="/usr/local/bundle" \ 13 | BUNDLE_WITHOUT="development:test" \ 14 | RAILS_ENV="production" 15 | 16 | # Update gems and bundler 17 | RUN gem update --system --no-document && \ 18 | gem install -N bundler 19 | 20 | # Install packages needed to install nodejs 21 | RUN apt-get update -qq && \ 22 | apt-get install --no-install-recommends -y curl && \ 23 | rm -rf /var/lib/apt/lists /var/cache/apt/archives 24 | 25 | # Install Node.js 26 | ARG NODE_VERSION=22.0.0 27 | ENV PATH=/usr/local/node/bin:$PATH 28 | RUN curl -sL https://github.com/nodenv/node-build/archive/master.tar.gz | tar xz -C /tmp/ && \ 29 | /tmp/node-build-master/bin/node-build "${NODE_VERSION}" /usr/local/node && \ 30 | rm -rf /tmp/node-build-master 31 | 32 | 33 | # Throw-away build stage to reduce size of final image 34 | FROM base as build 35 | 36 | # Install packages needed to build gems 37 | RUN apt-get update -qq && \ 38 | apt-get install --no-install-recommends -y build-essential libpq-dev 39 | 40 | # Build options 41 | ENV PATH="/usr/local/node/bin:$PATH" 42 | 43 | # Install application gems 44 | COPY --link Gemfile Gemfile.lock ./ 45 | RUN bundle install && \ 46 | bundle exec bootsnap precompile --gemfile && \ 47 | rm -rf ~/.bundle/ "${BUNDLE_PATH}"/ruby/*/cache "${BUNDLE_PATH}"/ruby/*/bundler/gems/*/.git 48 | 49 | # Copy application code 50 | COPY --link . . 51 | 52 | # Precompile bootsnap code for faster boot times 53 | RUN bundle exec bootsnap precompile app/ lib/ 54 | 55 | # Precompiling assets for production without requiring secret RAILS_MASTER_KEY 56 | RUN SECRET_KEY_BASE_DUMMY=1 ./bin/rails assets:precompile 57 | 58 | 59 | # Final stage for app image 60 | FROM base 61 | 62 | # Install packages needed for deployment 63 | RUN apt-get update -qq && \ 64 | apt-get install --no-install-recommends -y curl postgresql-client && \ 65 | rm -rf /var/lib/apt/lists /var/cache/apt/archives 66 | 67 | # Copy built artifacts: gems, application 68 | COPY --from=build "${BUNDLE_PATH}" "${BUNDLE_PATH}" 69 | COPY --from=build /rails /rails 70 | 71 | # Run and own only the runtime files as a non-root user for security 72 | RUN groupadd --system --gid 1000 rails && \ 73 | useradd rails --uid 1000 --gid 1000 --create-home --shell /bin/bash && \ 74 | chown -R 1000:1000 db log storage tmp 75 | USER 1000:1000 76 | 77 | # Entrypoint prepares the database. 78 | ENTRYPOINT ["/rails/bin/docker-entrypoint"] 79 | 80 | # Start the server by default, this can be overwritten at runtime 81 | EXPOSE 3000 82 | CMD ["./bin/rails", "server"] 83 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | git_source(:github) { |repo| "https://github.com/#{repo}.git" } 3 | 4 | ruby "3.3.0" 5 | 6 | # Bundle edge Rails instead: gem 'rails', github: 'rails/rails' 7 | gem "rails", "~>7.2" 8 | # Use postgresql as the database for Active Record 9 | gem "pg" 10 | # Use Puma as the app server 11 | gem "puma" 12 | # Use SCSS for stylesheets 13 | gem "sass-rails" 14 | 15 | # The original asset pipeline for Rails [https://github.com/rails/sprockets-rails] 16 | gem "sprockets-rails" 17 | # Hotwire's SPA-like page accelerator [https://turbo.hotwired.dev] 18 | gem "turbo-rails" 19 | # Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder 20 | gem "jbuilder" 21 | # Use Active Model has_secure_password 22 | # gem 'bcrypt', '~> 3.1.7' 23 | 24 | gem "importmap-rails" 25 | 26 | # Use Active Storage variant 27 | # gem 'image_processing', '~> 1.2' 28 | 29 | # Reduces boot times through caching; required in config/boot.rb 30 | gem "bootsnap", require: false 31 | 32 | group :development, :test do 33 | # Call 'byebug' anywhere in the code to stop execution and get a debugger console 34 | gem "byebug", platforms: [:mri, :mingw, :x64_mingw] 35 | gem "database_cleaner-active_record" 36 | gem "factory_bot_rails" 37 | gem "rspec-rails" 38 | gem "warning" 39 | end 40 | 41 | group :development do 42 | # Access an interactive console on exception pages or by calling 'console' anywhere in the code. 43 | gem "web-console" 44 | 45 | gem "dockerfile-rails" 46 | gem "dotenv-rails" 47 | gem "listen" 48 | end 49 | 50 | group :test do 51 | # Adds support for Capybara system testing and selenium driver 52 | gem "capybara" 53 | gem "rspec-sidekiq" 54 | # Easy installation and use of web drivers to run system tests with browsers 55 | gem "simplecov", require: false 56 | gem "webmock" 57 | end 58 | 59 | # Windows does not include zoneinfo files, so bundle the tzinfo-data gem 60 | gem "tzinfo-data", platforms: [:mingw, :mswin, :x64_mingw, :jruby] 61 | 62 | gem "faraday" 63 | gem "jquery-rails" 64 | gem "recursive-open-struct" 65 | gem "nokogiri" 66 | gem "kaminari" 67 | gem "sidekiq", "<7" 68 | gem "sidekiq-scheduler", "~> 5.0" 69 | gem "sidekiq-unique-jobs", "~> 7.1" 70 | gem "redis", "~> 4" 71 | gem "clearance" 72 | gem "rack-mini-profiler" 73 | gem "faraday_middleware" 74 | gem "faraday-encoding" 75 | gem "rexml" 76 | 77 | # These gems will no longer be part of the default gems since Ruby 3.4.0 78 | gem "mutex_m" 79 | gem "bigdecimal" 80 | gem "base64" 81 | 82 | gem "bootstrap", "~> 5.3" 83 | gem "bootstrap_form", "~> 5.4" 84 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: bundle exec rails server -p $PORT 2 | sidekiq: RAILS_MAX_THREADS=${SIDEKIQ_RAILS_MAX_THREADS:-10} bundle exec sidekiq -t 25 -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Rspec](https://github.com/libredmm/librefanza/workflows/Rspec/badge.svg?branch=master)](https://github.com/libredmm/librefanza/actions/workflows/rspec.yml) 2 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # Add your own tasks in files placed in lib/tasks ending in .rake, 2 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. 3 | 4 | require_relative "config/application" 5 | 6 | Rails.application.load_tasks 7 | -------------------------------------------------------------------------------- /app/assets/builds/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libredmm/librefanza/945319cf3d65ffaf27bff32e1ff52d0ec1f5b5be/app/assets/builds/.keep -------------------------------------------------------------------------------- /app/assets/config/manifest.js: -------------------------------------------------------------------------------- 1 | //= link_tree ../images 2 | //= link_tree ../builds 3 | //= link_tree ../../javascript .js 4 | //= link application.css 5 | -------------------------------------------------------------------------------- /app/assets/images/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libredmm/librefanza/945319cf3d65ffaf27bff32e1ff52d0ec1f5b5be/app/assets/images/.keep -------------------------------------------------------------------------------- /app/assets/images/javbus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libredmm/librefanza/945319cf3d65ffaf27bff32e1ff52d0ec1f5b5be/app/assets/images/javbus.png -------------------------------------------------------------------------------- /app/assets/images/javlibrary.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libredmm/librefanza/945319cf3d65ffaf27bff32e1ff52d0ec1f5b5be/app/assets/images/javlibrary.gif -------------------------------------------------------------------------------- /app/assets/stylesheets/application.scss: -------------------------------------------------------------------------------- 1 | // Bootstrap 2 | @import 'bootstrap'; 3 | @import 'rails_bootstrap_forms'; 4 | @import url("https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css"); 5 | @import url('https://fonts.googleapis.com/css?family=Special+Elite'); 6 | 7 | body { 8 | padding-top: 4.5rem; 9 | } 10 | 11 | .navbar { 12 | font-family: 'Special Elite', cursive; 13 | } 14 | 15 | .logo { 16 | font-family: 'Special Elite', cursive; 17 | font-size: 4.5rem; 18 | margin-top: 4.5rem; 19 | margin-bottom: 1rem; 20 | text-align: center; 21 | } 22 | 23 | html { 24 | position: relative; 25 | min-height: 100%; 26 | } 27 | 28 | body { 29 | margin-bottom: 90px; 30 | /* Margin bottom by footer height */ 31 | } 32 | 33 | .footer { 34 | position: absolute; 35 | bottom: 0; 36 | width: 100%; 37 | height: 60px; 38 | /* Set the fixed height of the footer here */ 39 | line-height: 60px; 40 | /* Vertically center the text there */ 41 | background-color: #f5f5f5; 42 | } 43 | 44 | ul.external-links img { 45 | height: 1.5em; 46 | } 47 | 48 | #sample-images { 49 | column-count: 6; 50 | column-gap: 1rem; 51 | } 52 | 53 | #sample-images img { 54 | width: 100%; 55 | height: auto; 56 | margin-bottom: 1rem; 57 | } 58 | -------------------------------------------------------------------------------- /app/controllers/application_controller.rb: -------------------------------------------------------------------------------- 1 | class ApplicationController < ActionController::Base 2 | include Clearance::Controller 3 | 4 | before_action do 5 | Rack::MiniProfiler.authorize_request if signed_in_as_admin? 6 | end 7 | 8 | helper_method :signed_in_as_admin? 9 | 10 | def signed_in_as_admin? 11 | signed_in? && current_user.is_admin? 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /app/controllers/fanza_actresses_controller.rb: -------------------------------------------------------------------------------- 1 | class FanzaActressesController < ApplicationController 2 | def index 3 | @actresses = FanzaActress.all 4 | 5 | @order = params[:order] 6 | case params[:order] 7 | when /name/i 8 | @actresses = @actresses.order(:name) 9 | else 10 | @order = "New" 11 | @actresses = @actresses.order(fanza_id: :desc) 12 | end 13 | 14 | if params[:fuzzy] 15 | @actresses = @actresses.where("name ILIKE ?", "%#{params[:fuzzy]}%") 16 | end 17 | @actresses = @actresses.page(params[:page]) 18 | end 19 | 20 | def show 21 | @actress = FanzaActress.find_by(fanza_id: params[:id]) || 22 | FanzaActress.order(:fanza_id).find_by(name: params[:id]) || 23 | FanzaActress.new(name: params[:id]) 24 | 25 | raise ActiveRecord::RecordNotFound if @actress.is_hidden? 26 | 27 | @movies = @actress.movies.order(:normalized_id) 28 | @movies = @movies.solo if params[:solo] 29 | 30 | respond_to do |format| 31 | format.html { 32 | @movies = @movies.page(params[:page]) 33 | render 34 | } 35 | format.json { 36 | render json: { 37 | name: @actress.name, 38 | fanza_id: @actress.fanza_id, 39 | image_url: @actress.image_url, 40 | movies: @movies.pluck(:normalized_id), 41 | } 42 | } 43 | end 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /app/controllers/fanza_items_controller.rb: -------------------------------------------------------------------------------- 1 | require "open-uri" 2 | 3 | class FanzaItemsController < ApplicationController 4 | # GET /fanza_items 5 | # GET /fanza_items.json 6 | def index 7 | @items = FanzaItem.order(:normalized_id).all 8 | @items = @items.page(params[:page]) 9 | render "movies/items" 10 | end 11 | 12 | # GET /fanza_items/1 13 | # GET /fanza_items/1.json 14 | def show 15 | @item = FanzaItem.find(params[:id]) 16 | respond_to do |format| 17 | format.html { 18 | render "movies/show" 19 | } 20 | format.json { 21 | if params[:raw] 22 | render json: JSON.pretty_generate(@item.safe_json) 23 | else 24 | render "movies/show" 25 | end 26 | } 27 | end 28 | end 29 | 30 | # DELETE /fanza_items/1 31 | # DELETE /fanza_items/1.json 32 | def destroy 33 | @item = FanzaItem.find(params[:id]) 34 | @item.derive_fields! 35 | render "movies/show" 36 | end 37 | 38 | # PUT /fanza_items/1 39 | # PUT /fanza_items/1.json 40 | def update 41 | @item = FanzaItem.find(params[:id]) 42 | @item.update(priority: @item.priority + params[:priority_inc].to_i) if params[:priority_inc] 43 | if @item.preferred? 44 | redirect_to @item.movie 45 | else 46 | redirect_to @item 47 | end 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /app/controllers/fc2_items_controller.rb: -------------------------------------------------------------------------------- 1 | class Fc2ItemsController < ApplicationController 2 | # GET /fc2_items 3 | # GET /fc2_items.json 4 | def index 5 | @items = Fc2Item.order(:normalized_id).all 6 | @items = @items.page(params[:page]) 7 | render "movies/items" 8 | end 9 | 10 | # GET /fc2_items/1 11 | # GET /fc2_items/1.json 12 | def show 13 | @item = Fc2Item.find(params[:id]) 14 | render "movies/show" 15 | end 16 | 17 | # DELETE /fc2_items/1 18 | # DELETE /fc2_items/1.json 19 | def destroy 20 | @item = Fc2Item.find(params[:id]) 21 | @item.derive_fields! 22 | render "movies/show" 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /app/controllers/fc2_pages_controller.rb: -------------------------------------------------------------------------------- 1 | class Fc2PagesController < ApplicationController 2 | def index 3 | @pages = Fc2Page.order(id: :desc).all 4 | @pages = @pages.page(params[:page]) 5 | render "generic_pages/index" 6 | end 7 | 8 | def show 9 | @page = Fc2Page.find(params[:id]) 10 | render html: @page.raw_html.html_safe 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /app/controllers/javlibrary_items_controller.rb: -------------------------------------------------------------------------------- 1 | class JavlibraryItemsController < ApplicationController 2 | # GET /javlibrary_items 3 | # GET /javlibrary_items.json 4 | def index 5 | @items = JavlibraryItem.order(:normalized_id).all 6 | @items = @items.page(params[:page]) 7 | render "movies/items" 8 | end 9 | 10 | # GET /javlibrary_items/1 11 | # GET /javlibrary_items/1.json 12 | def show 13 | @item = JavlibraryItem.find(params[:id]) 14 | render "movies/show" 15 | end 16 | 17 | # DELETE /javlibrary_items/1 18 | # DELETE /javlibrary_items/1.json 19 | def destroy 20 | @item = JavlibraryItem.find(params[:id]) 21 | @item.derive_fields! 22 | render "movies/show" 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /app/controllers/javlibrary_pages_controller.rb: -------------------------------------------------------------------------------- 1 | class JavlibraryPagesController < ApplicationController 2 | def index 3 | @pages = JavlibraryPage.order(id: :desc).all 4 | @pages = @pages.page(params[:page]) 5 | end 6 | 7 | def show 8 | @page = JavlibraryPage.find(params[:id]) 9 | render html: @page.raw_html.html_safe 10 | end 11 | 12 | def new 13 | @page = JavlibraryPage.new 14 | end 15 | 16 | def create 17 | attrs = params.require(:javlibrary_page).permit(:url, :raw_html) 18 | @page = JavlibraryPage.create(attrs) 19 | if @page.persisted? 20 | redirect_to (@page.javlibrary_item || @page) 21 | else 22 | render :new 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /app/controllers/mgstage_items_controller.rb: -------------------------------------------------------------------------------- 1 | class MgstageItemsController < ApplicationController 2 | # GET /mgstage_items 3 | # GET /mgstage_items.json 4 | def index 5 | @items = MgstageItem.order(:normalized_id).all 6 | @items = @items.page(params[:page]) 7 | render "movies/items" 8 | end 9 | 10 | # GET /mgstage_items/1 11 | # GET /mgstage_items/1.json 12 | def show 13 | @item = MgstageItem.find(params[:id]) 14 | render "movies/show" 15 | end 16 | 17 | # DELETE /mgstage_items/1 18 | # DELETE /mgstage_items/1.json 19 | def destroy 20 | @item = MgstageItem.find(params[:id]) 21 | @item.derive_fields! 22 | render "movies/show" 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /app/controllers/mgstage_pages_controller.rb: -------------------------------------------------------------------------------- 1 | class MgstagePagesController < ApplicationController 2 | def index 3 | @pages = MgstagePage.order(id: :desc).all 4 | @pages = @pages.page(params[:page]) 5 | render "generic_pages/index" 6 | end 7 | 8 | def show 9 | @page = MgstagePage.find(params[:id]) 10 | render html: @page.raw_html.html_safe 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /app/controllers/movies_controller.rb: -------------------------------------------------------------------------------- 1 | class MoviesController < ApplicationController 2 | def index 3 | @movies = Movie.all 4 | 5 | @order = params[:order]&.downcase&.to_sym 6 | case @order 7 | when :release_date 8 | @movies = @movies.order(date: :desc, normalized_id: :desc) 9 | when :date_added 10 | @movies = @movies.order(id: :desc) 11 | else 12 | @order = :title 13 | @movies = @movies.order(:normalized_id) 14 | end 15 | 16 | if params[:q] 17 | @style = params[:style]&.downcase&.to_sym 18 | case @style 19 | when :prefix 20 | @movies = @movies.with_prefix params[:q] 21 | else 22 | @style = :fuzzy 23 | @movies = @movies.fuzzy_match params[:q] 24 | end 25 | end 26 | @movies = @movies.page(params[:page]) 27 | end 28 | 29 | def show 30 | id = params[:id].upcase 31 | @movie = Movie.find_by(normalized_id: id) 32 | @item = @movie&.preferred_item 33 | unless @item 34 | @searching = FanzaSearcher.perform_async id if signed_in? or request.format.json? 35 | @related_movies = Movie.fuzzy_match(id).order(:normalized_id).page(params[:page]) 36 | end 37 | 38 | respond_to do |format| 39 | format.html { 40 | if @item && !@movie.is_hidden? 41 | render 42 | else 43 | render :not_found, status: :not_found 44 | end 45 | } 46 | format.json { 47 | if @item 48 | render 49 | elsif @searching 50 | render json: { err: "processing" }, status: :accepted 51 | else 52 | render json: { err: "not_found" }, status: :not_found 53 | end 54 | } 55 | end 56 | end 57 | 58 | def update 59 | id = params[:id].upcase 60 | @movie = Movie.find_by(normalized_id: id) 61 | @movie.update params.permit(:cover_image_url) 62 | redirect_to @movie 63 | end 64 | 65 | def destroy 66 | id = params[:id].upcase 67 | @movie = Movie.find_by(normalized_id: id) 68 | @searching = FanzaSearcher.perform_async id 69 | redirect_to @movie 70 | end 71 | end 72 | -------------------------------------------------------------------------------- /app/controllers/pages_controller.rb: -------------------------------------------------------------------------------- 1 | class PagesController < ApplicationController 2 | def index 3 | end 4 | 5 | def search 6 | keyword = Fanza::Id.normalize(params[:q]) 7 | redirect_to Fanza::Id.normalized?(keyword) ? 8 | movie_path(keyword, format: params[:format]) : 9 | movies_path(q: keyword, style: "prefix", order: "release_date") 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /app/controllers/sod_items_controller.rb: -------------------------------------------------------------------------------- 1 | class SodItemsController < ApplicationController 2 | # GET /sod_items 3 | # GET /sod_items.json 4 | def index 5 | @items = SodItem.order(:normalized_id).all 6 | @items = @items.page(params[:page]) 7 | render "movies/items" 8 | end 9 | 10 | # GET /sod_items/1 11 | # GET /sod_items/1.json 12 | def show 13 | @item = SodItem.find(params[:id]) 14 | render "movies/show" 15 | end 16 | 17 | # DELETE /sod_items/1 18 | # DELETE /sod_items/1.json 19 | def destroy 20 | @item = SodItem.find(params[:id]) 21 | @item.derive_fields! 22 | render "movies/show" 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /app/controllers/sod_pages_controller.rb: -------------------------------------------------------------------------------- 1 | class SodPagesController < ApplicationController 2 | def index 3 | @pages = SodPage.order(id: :desc).all 4 | @pages = @pages.page(params[:page]) 5 | render "generic_pages/index" 6 | end 7 | 8 | def show 9 | @page = SodPage.find(params[:id]) 10 | render html: @page.raw_html.html_safe 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /app/helpers/application_helper.rb: -------------------------------------------------------------------------------- 1 | module ApplicationHelper 2 | end 3 | -------------------------------------------------------------------------------- /app/helpers/fanza_actresses_helper.rb: -------------------------------------------------------------------------------- 1 | module FanzaActressesHelper 2 | def fake_image_url(actress) 3 | "https://dummyimage.com/125x125&text=#{actress.name}" 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /app/helpers/fanza_items_helper.rb: -------------------------------------------------------------------------------- 1 | module FanzaItemsHelper 2 | end 3 | -------------------------------------------------------------------------------- /app/helpers/feeds_helper.rb: -------------------------------------------------------------------------------- 1 | module FeedsHelper 2 | end 3 | -------------------------------------------------------------------------------- /app/helpers/javlibrary_items_helper.rb: -------------------------------------------------------------------------------- 1 | module JavlibraryItemsHelper 2 | end 3 | -------------------------------------------------------------------------------- /app/helpers/javlibrary_pages_helper.rb: -------------------------------------------------------------------------------- 1 | module JavlibraryPagesHelper 2 | end 3 | -------------------------------------------------------------------------------- /app/helpers/mgstage_items_helper.rb: -------------------------------------------------------------------------------- 1 | module MgstageItemsHelper 2 | end 3 | -------------------------------------------------------------------------------- /app/helpers/mgstage_pages_helper.rb: -------------------------------------------------------------------------------- 1 | module MgstagePagesHelper 2 | end 3 | -------------------------------------------------------------------------------- /app/helpers/movies_helper.rb: -------------------------------------------------------------------------------- 1 | module MoviesHelper 2 | def to_items(movies_or_items) 3 | case movies_or_items.first 4 | when Movie 5 | movies_or_items.reject(&:is_hidden?).map(&:preferred_item) 6 | else 7 | movies_or_items.reject { |item| item.movie.is_hidden? } 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /app/helpers/pages_helper.rb: -------------------------------------------------------------------------------- 1 | module PagesHelper 2 | end 3 | -------------------------------------------------------------------------------- /app/javascript/application.js: -------------------------------------------------------------------------------- 1 | // Configure your import map in config/importmap.rb. Read more: https://github.com/rails/importmap-rails 2 | 3 | import "@hotwired/turbo-rails" 4 | import 'controllers' 5 | -------------------------------------------------------------------------------- /app/javascript/controllers/application.js: -------------------------------------------------------------------------------- 1 | import { Application } from "@hotwired/stimulus" 2 | 3 | const application = Application.start() 4 | 5 | // Configure Stimulus development experience 6 | application.debug = false 7 | window.Stimulus = application 8 | 9 | export { application } 10 | -------------------------------------------------------------------------------- /app/javascript/controllers/index.js: -------------------------------------------------------------------------------- 1 | // This file is auto-generated by ./bin/rails stimulus:manifest:update 2 | // Run that command whenever you add a new controller or create them with 3 | // ./bin/rails generate stimulus controllerName 4 | 5 | import { application } from "../controllers/application" 6 | -------------------------------------------------------------------------------- /app/lib/fanza/id.rb: -------------------------------------------------------------------------------- 1 | module Fanza 2 | class Id 3 | def self.normalize(id) 4 | Id.new(id).normalized || id 5 | end 6 | 7 | def self.normalized?(id) 8 | Id.new(id).normalized != nil 9 | end 10 | 11 | def self.variations(id) 12 | Id.new(id).variations || Set[id] 13 | end 14 | 15 | def self.compress(id) 16 | Id.new(id).compressed || id 17 | end 18 | 19 | attr_reader :compressed, :normalized, :variations 20 | 21 | def initialize(original) 22 | @compressed = nil 23 | @normalized = nil 24 | @variations = nil 25 | 26 | return unless original.present? 27 | 28 | alphas_regex = /(3dsvr|\d\did|t\d8|r18|4k|fc2ppv|fc2|\D+)/i 29 | groups = original.gsub("-", "").gsub(/^._/i, "").gsub(/[^a-z0-9]/i, "").split(alphas_regex).reject(&:empty?) 30 | groups.shift if groups.first =~ /^\d+$/ 31 | groups.pop if groups.last =~ alphas_regex 32 | 33 | digit_idx = groups.each_with_index.select { |g, i| 34 | g =~ /^\d+$/ 35 | }.map { |g, i| 36 | [g.length, i] 37 | }.max&.last 38 | return unless digit_idx 39 | 40 | alpha_idx = groups.each_with_index.select { |g, i| 41 | (i < digit_idx) && (g[alphas_regex] == g) 42 | }.map { |g, i| 43 | [g.length, i] 44 | }.max&.last 45 | return unless alpha_idx 46 | 47 | alphas = groups[alpha_idx...digit_idx].join.upcase 48 | digits = groups[digit_idx] 49 | 50 | case alphas 51 | when "DGCEAD", "DGCEMD", "DGCESD" 52 | alphas = alphas[2..-1] 53 | when "FC2PPV" 54 | alphas = "FC2" 55 | end 56 | 57 | @compressed = "#{alphas}-#{digits.to_i}" 58 | digits = "%03d" % digits.to_i if digits.length != 2 59 | 60 | if alphas == "T" && digits.length == 5 61 | alphas = "T#{digits[0..1]}" 62 | digits = digits[2..-1] 63 | end 64 | @normalized = "#{alphas}-#{digits}" 65 | 66 | @variations = Set[ 67 | @normalized, 68 | ("%s-%04d" % [alphas, digits.to_i]).upcase, 69 | ("%s%03d" % [alphas, digits.to_i]).downcase, 70 | ("%s%05d" % [alphas, digits.to_i]).downcase, 71 | ] 72 | end 73 | end 74 | end 75 | -------------------------------------------------------------------------------- /app/lib/fc2/api.rb: -------------------------------------------------------------------------------- 1 | module Fc2 2 | class Api 3 | def self.search(keyword) 4 | return to_enum(:search, keyword) unless block_given? 5 | return unless keyword.starts_with?("FC2-") 6 | 7 | url = URI.join( 8 | ENV.fetch("FC2_BASE_URL", "https://adult.contents.fc2.com/"), 9 | "article/#{keyword.split("-").last}/", 10 | ) 11 | yield url.to_s, self.get(url) 12 | end 13 | 14 | private 15 | 16 | def self.get(url) 17 | Faraday.new(proxy: ENV["PROXY_URL"]).get(url).body 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /app/lib/javlibrary/api.rb: -------------------------------------------------------------------------------- 1 | require "faraday_middleware" 2 | 3 | module Javlibrary 4 | class Api 5 | def self.search(keyword) 6 | return to_enum(:search, keyword) unless block_given? 7 | return if keyword.starts_with?("FC2-") 8 | 9 | search_url = URI::join( 10 | ENV.fetch("JAVLIBRARY_BASE_URL", "https://www.javlibrary.com/"), 11 | "ja/vl_searchbyid.php?keyword=#{keyword}" 12 | ) 13 | resp = self.get(search_url) 14 | return if resp.status != 200 15 | yield search_url.to_s, resp.body 16 | 17 | html = Nokogiri::HTML(resp.body) 18 | html.css("div.video > a").map { |a| 19 | url = URI::join(search_url, a.attr(:href)).to_s 20 | yield url.to_s, self.get(url).body 21 | } 22 | end 23 | 24 | def self.get(url) 25 | Faraday.new(proxy: ENV["PROXY_URL"]).get(url) 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /app/lib/mgstage/api.rb: -------------------------------------------------------------------------------- 1 | module Mgstage 2 | class Api 3 | def self.search(keyword) 4 | return to_enum(:search, keyword) unless block_given? 5 | return if keyword.starts_with?("FC2-") 6 | 7 | Fanza::Id.variations(keyword).each do |variation| 8 | self.search_raw(variation) do |url| 9 | yield url, self.get(url).body 10 | end 11 | end 12 | end 13 | 14 | def self.search_raw(raw_keyword) 15 | 1.upto(100) do |page| 16 | search_url = "https://www.mgstage.com/search/cSearch.php?search_word=#{raw_keyword}&page=#{page}" 17 | Rails.logger.info("[MGSTAGE] #{search_url}") 18 | search_resp = self.get(search_url) 19 | empty_page = true 20 | Nokogiri::HTML(search_resp.body).css("div.search_list h5 a").each do |a| 21 | url = URI::join(search_url, a.attr(:href)).to_s 22 | empty_page = false 23 | yield url 24 | end 25 | break if empty_page 26 | end 27 | end 28 | 29 | def self.get(url) 30 | Faraday.new(proxy: ENV["PROXY_URL"]).get(url) { |req| 31 | req.headers = { "Cookie" => "adc=1" } 32 | } 33 | end 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /app/lib/sod/api.rb: -------------------------------------------------------------------------------- 1 | module Sod 2 | class Api 3 | def self.search(keyword) 4 | return to_enum(:search, keyword) unless block_given? 5 | return if keyword.starts_with?("FC2-") 6 | 7 | Fanza::Id.variations(keyword).each do |variation| 8 | product_url = "https://ec.sod.co.jp/prime/videos/?id=#{variation}" 9 | yield product_url, self.get(product_url) 10 | end 11 | end 12 | 13 | private 14 | 15 | def self.get(url) 16 | Faraday.new(proxy: ENV["PROXY_URL"]) { |conn| 17 | conn.use FaradayMiddleware::FollowRedirects 18 | }.get("https://ec.sod.co.jp/prime/_ontime.php") { |req| 19 | req.headers = { 20 | "Cookie" => "PHPSESSID=dummy_session", 21 | "Referer" => url, 22 | } 23 | }.body 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /app/mailers/application_mailer.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class ApplicationMailer < ActionMailer::Base 4 | default from: "admin@libredmm.com" 5 | layout "mailer" 6 | end 7 | -------------------------------------------------------------------------------- /app/models/application_record.rb: -------------------------------------------------------------------------------- 1 | class ApplicationRecord < ActiveRecord::Base 2 | self.abstract_class = true 3 | end 4 | -------------------------------------------------------------------------------- /app/models/concerns/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libredmm/librefanza/945319cf3d65ffaf27bff32e1ff52d0ec1f5b5be/app/models/concerns/.keep -------------------------------------------------------------------------------- /app/models/concerns/cover_image_overridable.rb: -------------------------------------------------------------------------------- 1 | module CoverImageOverridable 2 | extend ActiveSupport::Concern 3 | 4 | def cover_image_url 5 | movie.cover_image_url ? "https://imageproxy.libredmm.com/#{movie.cover_image_url}" : super 6 | end 7 | 8 | def thumbnail_image_url 9 | movie.cover_image_url ? "https://imageproxy.libredmm.com/cx.53/#{movie.cover_image_url}" : super 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /app/models/concerns/derivable.rb: -------------------------------------------------------------------------------- 1 | module Derivable 2 | extend ActiveSupport::Concern 3 | 4 | included do 5 | before_validation :derive_fields, on: :create 6 | after_touch :derive_fields! 7 | end 8 | 9 | def derive_fields! 10 | derive_fields 11 | if changed? 12 | save || destroy 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /app/models/concerns/generic_item.rb: -------------------------------------------------------------------------------- 1 | module GenericItem 2 | extend ActiveSupport::Concern 3 | 4 | included do 5 | include Derivable 6 | prepend CoverImageOverridable 7 | 8 | belongs_to :movie, foreign_key: :normalized_id, primary_key: :normalized_id, optional: true 9 | after_save :create_or_update_movie 10 | after_destroy :destroy_obsolete_movie 11 | end 12 | 13 | def create_or_update_movie 14 | if normalized_id_previously_changed? 15 | old_movie = Movie.find_by(normalized_id: normalized_id_before_last_save) 16 | old_movie.destroy if old_movie && !old_movie.preferred_item 17 | end 18 | 19 | if movie 20 | movie.touch 21 | else 22 | create_movie!(normalized_id: self.normalized_id) 23 | end 24 | end 25 | 26 | def destroy_obsolete_movie 27 | movie = Movie.find_by(normalized_id: normalized_id) 28 | movie.destroy if movie && !movie.preferred_item 29 | end 30 | 31 | def attribute_names_for_serialization 32 | %w(actresses cover_image_url date description directors genres labels makers normalized_id review subtitle thumbnail_image_url title url volume sample_image_urls) 33 | end 34 | 35 | def preferred? 36 | movie.preferred_item == self 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /app/models/fanza_actress.rb: -------------------------------------------------------------------------------- 1 | class FanzaActress < ApplicationRecord 2 | include Derivable 3 | 4 | validates :fanza_id, presence: true, uniqueness: true 5 | validates :name, presence: true 6 | validates :raw_json, presence: true 7 | 8 | paginates_per 45 9 | 10 | def movies 11 | query = Movie.where("actress_names @> ARRAY[?]::varchar[]", name) 12 | query = query.or(Movie.where("actress_fanza_ids @> ARRAY[?]::integer[]", fanza_id.to_i)) if fanza_id 13 | query 14 | end 15 | 16 | def derive_fields 17 | self.fanza_id = self.as_struct.id 18 | self.name = self.as_struct.name.strip 19 | end 20 | 21 | def as_struct 22 | RecursiveOpenStruct.new(raw_json, recurse_over_arrays: true) 23 | end 24 | 25 | def image_url 26 | self.as_struct.imageURL&.large || self.as_struct.imageURL&.small 27 | end 28 | 29 | def to_param 30 | self.fanza_id.to_s 31 | end 32 | 33 | def attribute_names_for_serialization 34 | %w(name image_url) 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /app/models/fanza_item.rb: -------------------------------------------------------------------------------- 1 | class FanzaItem < ApplicationRecord 2 | include GenericItem 3 | 4 | validates :raw_json, presence: true 5 | validates :content_id, presence: true, uniqueness: true 6 | validates :normalized_id, presence: true 7 | validates :floor_code, inclusion: { in: %w[dvd nikkatsu video videoa videoc] } 8 | validates :service_code, inclusion: { in: %w[digital mono] } 9 | 10 | paginates_per 30 11 | 12 | def derive_fields 13 | self.content_id = self.as_struct.content_id.strip 14 | self.normalized_id = Fanza::Id.normalize(self.content_id) 15 | if self.as_struct.maker_product && self.normalized_id == self.content_id 16 | self.normalized_id = Fanza::Id.normalize(self.as_struct.maker_product) 17 | end 18 | 19 | self.date = DateTime.parse(self.as_struct.date) 20 | self.floor_code = self.as_struct.floor_code.strip 21 | self.service_code = self.as_struct.service_code.strip 22 | 23 | if self.description.blank? 24 | begin 25 | raw_html = Faraday.new(proxy: ENV["PROXY_URL"]) { |conn| 26 | conn.use FaradayMiddleware::FollowRedirects 27 | conn.response :encoding 28 | conn.adapter Faraday.default_adapter 29 | }.get(self.url) { |req| 30 | req.headers = { 31 | "Cookie" => "age_check_done=1", 32 | } 33 | }.body.encode("UTF-8", invalid: :replace, undef: :replace).gsub("\u0000", "") 34 | 35 | self.description = Nokogiri::HTML(raw_html).css(".mg-b20.lh4")&.text&.strip 36 | rescue Exception 37 | end 38 | end 39 | end 40 | 41 | def as_struct 42 | RecursiveOpenStruct.new(safe_json, recurse_over_arrays: true) 43 | end 44 | 45 | def safe_json 46 | raw_json.except("affiliateURL", "affiliateURLsp") 47 | end 48 | 49 | ################### 50 | # Items interface # 51 | ################### 52 | 53 | def title 54 | as_struct.title.strip 55 | end 56 | 57 | def subtitle 58 | content_id 59 | end 60 | 61 | def cover_image_url 62 | if normalized_id =~ /^DANDY-/ && sample_image_urls.any? 63 | return sample_image_urls.first 64 | end 65 | as_struct.imageURL&.large 66 | end 67 | 68 | def thumbnail_image_url 69 | as_struct.imageURL&.small 70 | end 71 | 72 | def url 73 | as_struct.URL 74 | end 75 | 76 | def actresses 77 | as_struct.iteminfo&.actress&.map { |info| 78 | FanzaActress.find_by(fanza_id: info.id) || FanzaActress.new(name: info.name&.strip) 79 | } || [] 80 | end 81 | 82 | def genres 83 | as_struct.iteminfo&.genre&.map(&:name)&.map(&:strip) || [] 84 | end 85 | 86 | def review 87 | (as_struct.review&.average&.to_f || 0) * 2.0 88 | end 89 | 90 | def labels 91 | as_struct.iteminfo&.label&.map(&:name)&.map(&:strip) || [] 92 | end 93 | 94 | def makers 95 | as_struct.iteminfo&.maker&.map(&:name)&.map(&:strip) || [] 96 | end 97 | 98 | def directors 99 | as_struct.iteminfo&.director&.map(&:name)&.map(&:strip) || [] 100 | end 101 | 102 | def volume 103 | as_struct.volume&.to_i&.minutes 104 | end 105 | 106 | def logo_url 107 | "https://p.dmm.co.jp/p/affiliate/web_service/r18_88_35.gif" 108 | end 109 | 110 | def sample_image_urls 111 | as_struct.sampleImageURL&.sample_l&.image || 112 | as_struct.sampleImageURL&.sample_m&.image || 113 | as_struct.sampleImageURL&.sample_s&.image || 114 | [] 115 | end 116 | end 117 | -------------------------------------------------------------------------------- /app/models/fc2_item.rb: -------------------------------------------------------------------------------- 1 | class Fc2Item < ApplicationRecord 2 | include GenericItem 3 | 4 | belongs_to :fc2_page 5 | 6 | validates :normalized_id, presence: true, uniqueness: true, format: { with: /\AFC2-\d+\z/ } 7 | 8 | paginates_per 30 9 | 10 | def derive_fields 11 | self.normalized_id = "FC2-" + fc2_page.url.split("/").last 12 | self.actress_names = [] 13 | end 14 | 15 | def html 16 | Nokogiri.HTML(fc2_page.raw_html) 17 | end 18 | 19 | ################### 20 | # Items interface # 21 | ################### 22 | 23 | def title 24 | html.at_css(".items_article_headerInfo > h3").text.strip 25 | end 26 | 27 | def subtitle 28 | "" 29 | end 30 | 31 | def cover_image_url 32 | html.at_css(".items_article_SampleImages li a")&.attr("href")&.strip&.gsub(/^\/\//, "https://") 33 | end 34 | 35 | def thumbnail_image_url 36 | html.at_css(".items_article_MainitemThumb img")&.attr("src")&.strip&.gsub(/^\/\//, "https://") 37 | end 38 | 39 | def url 40 | fc2_page.url 41 | end 42 | 43 | def date 44 | DateTime.parse(html.at_css("div.items_article_Releasedate")) 45 | rescue TypeError 46 | nil 47 | end 48 | 49 | def actresses 50 | [] 51 | end 52 | 53 | def description 54 | html.css(".items_article_Contents iframe").text 55 | end 56 | 57 | def genres 58 | [] 59 | end 60 | 61 | def review 62 | 0 63 | end 64 | 65 | def labels 66 | html.css(".items_article_TagArea a.tag")&.map(&:text)&.map(&:strip) || [] 67 | end 68 | 69 | def makers 70 | [] 71 | end 72 | 73 | def directors 74 | [] 75 | end 76 | 77 | def volume 78 | nil 79 | end 80 | 81 | def logo_url 82 | "https://static.fc2.com/contents/images/header/main_logo_new.png" 83 | end 84 | 85 | def sample_image_urls 86 | [] 87 | end 88 | end 89 | -------------------------------------------------------------------------------- /app/models/fc2_page.rb: -------------------------------------------------------------------------------- 1 | class Fc2Page < ApplicationRecord 2 | has_one :fc2_item, dependent: :destroy 3 | 4 | validates :url, presence: true, uniqueness: true 5 | validates :raw_html, presence: true 6 | 7 | validate :html_should_contain_header 8 | 9 | def html_should_contain_header 10 | unless raw_html.downcase.include? "items_article_header" 11 | errors.add(:raw_html, "does not contain item header") 12 | end 13 | end 14 | 15 | after_save :create_or_update_item 16 | 17 | def create_or_update_item 18 | return unless raw_html_previously_changed? 19 | 20 | begin 21 | self.create_fc2_item! 22 | rescue ActiveRecord::RecordInvalid => e 23 | self.fc2_item = nil 24 | end 25 | end 26 | 27 | def item 28 | fc2_item 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /app/models/feed.rb: -------------------------------------------------------------------------------- 1 | require "open-uri" 2 | 3 | class Feed < ApplicationRecord 4 | validates :uri, presence: true, uniqueness: true 5 | validates :host, presence: true 6 | validates :content, presence: true 7 | 8 | before_validation :parse_host 9 | before_validation :fetch_content, on: :create 10 | 11 | validate :content_should_contain_at_least_one_item 12 | 13 | def content_should_contain_at_least_one_item 14 | if Nokogiri::XML(content).xpath("//channel/item").empty? 15 | errors.add(:content, "should contain at least one item") 16 | end 17 | end 18 | 19 | def parse_host 20 | self.host = URI.parse(uri).host 21 | end 22 | 23 | def fetch_content 24 | self.content = Faraday.get(uri).body 25 | rescue OpenURI::HTTPError, WebMock::NetConnectNotAllowedError => e 26 | self.content = nil 27 | errors.add(:uri, e.message) 28 | end 29 | 30 | def refresh! 31 | fetch_content 32 | save! 33 | end 34 | 35 | def self.by_uri(uri) 36 | feed = find_or_create_by(uri: uri) 37 | feed.update_columns accessed_at: Time.current 38 | feed 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /app/models/feed_item.rb: -------------------------------------------------------------------------------- 1 | class FeedItem < ApplicationRecord 2 | end 3 | -------------------------------------------------------------------------------- /app/models/javlibrary_item.rb: -------------------------------------------------------------------------------- 1 | class JavlibraryItem < ApplicationRecord 2 | include GenericItem 3 | 4 | belongs_to :javlibrary_page 5 | 6 | validates :normalized_id, presence: true, uniqueness: true 7 | 8 | paginates_per 30 9 | 10 | def derive_fields 11 | self.normalized_id = Fanza::Id.normalize(html.at_css("#video_id td.text")&.text) 12 | self.actress_names = html.css(".cast .star").map(&:text).map(&:strip) 13 | end 14 | 15 | def html 16 | Nokogiri.HTML(javlibrary_page.raw_html) 17 | end 18 | 19 | ################### 20 | # Items interface # 21 | ################### 22 | 23 | def title 24 | html.at_css("#video_title > h3")&.text&.gsub( 25 | html.at_css("#video_id td.text")&.text, "" 26 | )&.strip 27 | end 28 | 29 | def subtitle 30 | URI.parse(url).query 31 | end 32 | 33 | def cover_image_url 34 | URI.join( 35 | javlibrary_page.url, 36 | html.at_css("#video_jacket_img").attr("src").strip 37 | ).to_s 38 | end 39 | 40 | def thumbnail_image_url 41 | if cover_image_url =~ /\/\/pics\.dmm\.co\.jp.*pl.jpg$/ 42 | cover_image_url.gsub(/pl\.jpg$/, "ps.jpg") 43 | else 44 | "https://imageproxy.libredmm.com/cx.53/" + cover_image_url 45 | end 46 | end 47 | 48 | def url 49 | javlibrary_page.url 50 | end 51 | 52 | def date 53 | DateTime.parse(html.at_css("#video_date td.text")&.text&.strip) 54 | end 55 | 56 | def actresses 57 | actress_names.map do |name| 58 | FanzaActress.find_by(name: name) || FanzaActress.new(name: name) 59 | end 60 | end 61 | 62 | def description 63 | "" 64 | end 65 | 66 | def genres 67 | html.css("span.genre").map(&:text).map(&:strip) || [] 68 | end 69 | 70 | def review 71 | html.at_css("span.score")&.text&.[](/\d+(\.\d+)?/)&.to_f 72 | end 73 | 74 | def labels 75 | html.css("span.label").map(&:text).map(&:strip) || [] 76 | end 77 | 78 | def makers 79 | html.css("span.maker").map(&:text).map(&:strip) || [] 80 | end 81 | 82 | def directors 83 | html.css("span.director").map(&:text).map(&:strip) || [] 84 | end 85 | 86 | def volume 87 | html.css("#video_length span.text").text.to_i.minutes 88 | end 89 | 90 | def logo_url 91 | "javlibrary.gif" 92 | end 93 | 94 | def sample_image_urls 95 | [] 96 | end 97 | end 98 | -------------------------------------------------------------------------------- /app/models/javlibrary_page.rb: -------------------------------------------------------------------------------- 1 | class JavlibraryPage < ApplicationRecord 2 | has_one :javlibrary_item, dependent: :destroy 3 | 4 | validates :url, presence: true, uniqueness: true 5 | validates :raw_html, presence: true 6 | 7 | validate :html_should_not_be_challenged 8 | validate :html_should_not_be_denied_access 9 | 10 | def html_should_not_be_challenged 11 | if raw_html.downcase.include? "challenge-form" 12 | errors.add(:raw_html, "is challenged") 13 | end 14 | end 15 | 16 | def html_should_not_be_denied_access 17 | if raw_html.downcase.include? "access denied" 18 | errors.add(:raw_html, "is denied access") 19 | end 20 | end 21 | 22 | after_save :create_or_update_item 23 | 24 | def create_or_update_item 25 | return unless raw_html_previously_changed? 26 | 27 | begin 28 | self.create_javlibrary_item! 29 | rescue ActiveRecord::RecordInvalid => e 30 | self.javlibrary_item = nil 31 | end 32 | end 33 | 34 | def item 35 | javlibrary_item 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /app/models/mgstage_item.rb: -------------------------------------------------------------------------------- 1 | class MgstageItem < ApplicationRecord 2 | include GenericItem 3 | 4 | belongs_to :mgstage_page 5 | 6 | validates :normalized_id, presence: true, uniqueness: true 7 | 8 | paginates_per 30 9 | 10 | def derive_fields 11 | html.css(".detail_data table tr").each do |tr| 12 | th = tr.at_css("th")&.text&.strip 13 | if th&.start_with? "品番" 14 | self.normalized_id = Fanza::Id.normalize(tr.at_css("td").text.strip) 15 | elsif th&.start_with? "出演" 16 | self.actress_names = tr.css("td a").map(&:text).map(&:strip) 17 | end 18 | end 19 | 20 | self.actress_names ||= [] 21 | end 22 | 23 | def html 24 | Nokogiri.HTML(mgstage_page.raw_html) 25 | end 26 | 27 | ################### 28 | # Items interface # 29 | ################### 30 | 31 | def title 32 | html.at_css("h1.tag").text.strip 33 | end 34 | 35 | def subtitle 36 | url.split("/").reject(&:empty?).last 37 | end 38 | 39 | def cover_image_url 40 | html.at_css("#EnlargeImage")&.attr("href")&.strip 41 | end 42 | 43 | def thumbnail_image_url 44 | html.at_css(".enlarge_image")&.attr("src")&.strip 45 | end 46 | 47 | def url 48 | mgstage_page.url 49 | end 50 | 51 | def date 52 | html.css(".detail_data table tr").find { |tr| 53 | tr.at_css("th")&.text&.strip&.start_with? "配信開始日" 54 | }&.then { |tr| 55 | DateTime.parse(tr.at_css("td")&.text&.strip) 56 | } 57 | end 58 | 59 | def actresses 60 | actress_names.map { |name| 61 | FanzaActress.find_by(name: name) || FanzaActress.new(name: name) 62 | } 63 | end 64 | 65 | def description 66 | html.css("div.introduction").text 67 | end 68 | 69 | def genres 70 | html.css(".detail_data table tr").find { |tr| 71 | tr.at_css("th")&.text&.strip&.start_with? "ジャンル" 72 | }&.css("td a")&.map(&:text)&.map(&:strip) || [] 73 | end 74 | 75 | def review 76 | html.at_css("td.review")&.inner_text&.to_f 77 | end 78 | 79 | def labels 80 | html.css(".detail_data table tr").find { |tr| 81 | tr.at_css("th")&.text&.strip&.start_with? "レーベル" 82 | }&.css("td a")&.map(&:text)&.map(&:strip) || [] 83 | end 84 | 85 | def makers 86 | html.css(".detail_data table tr").find { |tr| 87 | tr.at_css("th")&.text&.strip&.start_with? "メーカー" 88 | }&.css("td a")&.map(&:text)&.map(&:strip) || [] 89 | end 90 | 91 | def directors 92 | [] 93 | end 94 | 95 | def volume 96 | html.css(".detail_data table tr").find { |tr| 97 | tr.at_css("th")&.text&.strip&.start_with? "収録時間" 98 | }&.css("td")&.text&.to_i&.minutes 99 | end 100 | 101 | def logo_url 102 | "https://static.mgstage.com/mgs/img/pc/top_logo.jpg" 103 | end 104 | 105 | def sample_image_urls 106 | [] 107 | end 108 | end 109 | -------------------------------------------------------------------------------- /app/models/mgstage_page.rb: -------------------------------------------------------------------------------- 1 | class MgstagePage < ApplicationRecord 2 | has_one :mgstage_item, dependent: :destroy 3 | 4 | validates :url, presence: true, uniqueness: true 5 | validates :raw_html, presence: true 6 | 7 | after_save :create_or_update_item 8 | 9 | def create_or_update_item 10 | return unless raw_html_previously_changed? 11 | 12 | begin 13 | self.create_mgstage_item! 14 | rescue ActiveRecord::RecordInvalid => e 15 | self.mgstage_item = nil 16 | end 17 | end 18 | 19 | def item 20 | mgstage_item 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /app/models/movie.rb: -------------------------------------------------------------------------------- 1 | class Movie < ApplicationRecord 2 | include Derivable 3 | 4 | self.primary_key = :normalized_id 5 | 6 | has_many :fanza_items, foreign_key: :normalized_id, primary_key: :normalized_id 7 | has_many :sod_items, foreign_key: :normalized_id, primary_key: :normalized_id 8 | has_many :mgstage_items, foreign_key: :normalized_id, primary_key: :normalized_id 9 | has_many :javlibrary_items, foreign_key: :normalized_id, primary_key: :normalized_id 10 | has_many :fc2_items, foreign_key: :normalized_id, primary_key: :normalized_id 11 | 12 | validates :normalized_id, presence: true, uniqueness: true 13 | validates :compressed_id, presence: true 14 | validates :date, presence: true 15 | 16 | paginates_per 30 17 | 18 | scope :solo, -> { where("array_length(actress_fanza_ids, 1) = 1").or(where("array_length(actress_names, 1) = 1")) } 19 | scope :with_prefix, ->(q) { where("normalized_id ILIKE ?", "#{q}%") } 20 | scope :fuzzy_match, ->(q) { where("normalized_id ILIKE ?", "%#{q}%") } 21 | 22 | def derive_fields 23 | self.compressed_id = Fanza::Id.compress(self.normalized_id) 24 | self.date = self.preferred_item&.date 25 | self.actress_fanza_ids = self.preferred_item&.actresses&.map(&:fanza_id)&.reject(&:nil?) 26 | self.actress_names = self.preferred_item&.actresses&.map(&:name)&.reject(&:nil?) 27 | end 28 | 29 | def preferred_item 30 | self.fanza_items.order(priority: :desc).order(date: :desc).first || 31 | self.sod_items.first || 32 | self.mgstage_items.first || 33 | self.javlibrary_items.first || 34 | self.fc2_items.first 35 | end 36 | 37 | def items 38 | self.fanza_items + self.sod_items + self.mgstage_items + self.javlibrary_items + self.fc2_items 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /app/models/sod_item.rb: -------------------------------------------------------------------------------- 1 | class SodItem < ApplicationRecord 2 | include GenericItem 3 | 4 | belongs_to :sod_page 5 | 6 | validates :normalized_id, presence: true, uniqueness: true 7 | 8 | paginates_per 30 9 | 10 | def derive_fields 11 | html.css("table#v_introduction tr").each do |tr| 12 | ti = tr.at_css("td.v_intr_ti")&.text&.strip 13 | if ti&.start_with? "品番" 14 | self.normalized_id = Fanza::Id.normalize(tr.at_css("td.v_intr_tx").text.strip) 15 | elsif ti&.start_with? "出演者" 16 | self.actress_names = tr.css("td.v_intr_tx a").map(&:text).map(&:strip) 17 | end 18 | end 19 | 20 | self.actress_names ||= [] 21 | end 22 | 23 | def html 24 | Nokogiri.HTML(sod_page.raw_html) 25 | end 26 | 27 | def proxy_with_referer(image_url) 28 | "https://imageproxy.libredmm.com/_ref#{Base64.strict_encode64(url)}/#{image_url}" 29 | end 30 | 31 | ################### 32 | # Items interface # 33 | ################### 34 | 35 | def title 36 | html.at_css("#videos_head h1:not([style*='none'])").text.strip 37 | end 38 | 39 | def subtitle 40 | "" 41 | end 42 | 43 | def cover_image_url 44 | proxy_with_referer html.at_css("div.videos_samimg > a")&.attr("href")&.strip 45 | end 46 | 47 | def thumbnail_image_url 48 | proxy_with_referer html.at_css("div.videos_samimg > a > img")&.attr("src")&.strip 49 | end 50 | 51 | def url 52 | sod_page.url 53 | end 54 | 55 | def date 56 | html.css("table#v_introduction tr").find { |tr| 57 | tr.at_css("td.v_intr_ti")&.text&.strip&.start_with? "発売年月日" 58 | }&.then { |tr| 59 | DateTime.parse(tr.at_css("td.v_intr_tx")&.text&.strip.gsub(/[年月日]/, "").gsub(/\s+/, "-")) 60 | } 61 | end 62 | 63 | def actresses 64 | actress_names.map { |name| 65 | FanzaActress.find_by(name: name) || FanzaActress.new(name: name) 66 | } 67 | end 68 | 69 | def description 70 | html.css("div.videos_textli > article").text 71 | end 72 | 73 | def genres 74 | html.css("table#v_introduction tr").find { |tr| 75 | tr.at_css("td.v_intr_ti")&.text&.strip&.start_with? "ジャンル" 76 | }&.css("td.v_intr_tx a")&.map(&:text)&.map(&:strip) || [] 77 | end 78 | 79 | def review 80 | html.at_css("#review_toptd > div.imagestar > i")&.inner_text&.to_f 81 | end 82 | 83 | def labels 84 | html.css("table#v_introduction tr").find { |tr| 85 | tr.at_css("td.v_intr_ti")&.text&.strip&.start_with? "レーベル" 86 | }&.css("td.v_intr_tx")&.map(&:text)&.map(&:strip) || [] 87 | end 88 | 89 | def makers 90 | html.css("table#v_introduction tr").find { |tr| 91 | tr.at_css("td.v_intr_ti")&.text&.strip&.start_with? "メーカー" 92 | }&.css("td.v_intr_tx a")&.map(&:text)&.map(&:strip) || [] 93 | end 94 | 95 | def directors 96 | html.css("table#v_introduction tr").find { |tr| 97 | tr.at_css("td.v_intr_ti")&.text&.strip&.start_with? "監督" 98 | }&.css("td.v_intr_tx a")&.map(&:text)&.map(&:strip) || [] 99 | end 100 | 101 | def volume 102 | html.css("table#v_introduction tr").find { |tr| 103 | tr.at_css("td.v_intr_ti")&.text&.strip&.start_with? "再生時間" 104 | }&.css("td.v_intr_tx")&.text&.to_i&.minutes 105 | end 106 | 107 | def logo_url 108 | "https://ec.sod.co.jp/prime/image/logo-b.png" 109 | end 110 | 111 | def sample_image_urls 112 | [] 113 | end 114 | end 115 | -------------------------------------------------------------------------------- /app/models/sod_page.rb: -------------------------------------------------------------------------------- 1 | class SodPage < ApplicationRecord 2 | has_one :sod_item, dependent: :destroy 3 | 4 | validates :url, presence: true, uniqueness: true 5 | validates :raw_html, presence: true 6 | 7 | validate :html_should_contain_videos 8 | 9 | def html_should_contain_videos 10 | unless raw_html.downcase.include? "middle_videos" 11 | errors.add(:raw_html, "does not contain videos") 12 | end 13 | end 14 | 15 | after_save :create_or_update_item 16 | 17 | def create_or_update_item 18 | return unless raw_html_previously_changed? 19 | 20 | begin 21 | self.create_sod_item! 22 | rescue ActiveRecord::RecordInvalid => e 23 | self.sod_item = nil 24 | end 25 | end 26 | 27 | def item 28 | sod_item 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /app/models/user.rb: -------------------------------------------------------------------------------- 1 | class User < ApplicationRecord 2 | include Clearance::User 3 | 4 | before_validation :generate_api_token, on: :create 5 | validates :api_token, presence: true, uniqueness: true 6 | 7 | def generate_api_token 8 | self.api_token = Clearance::Token.new 9 | end 10 | 11 | def reset_api_token! 12 | generate_api_token 13 | save! 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /app/views/clearance_mailer/change_password.html.erb: -------------------------------------------------------------------------------- 1 |

<%= t(".opening") %>

2 | 3 |

4 | <%= link_to t(".link_text", default: "Change my password"), 5 | edit_user_password_url(@user, token: @user.confirmation_token.html_safe) %> 6 |

7 | 8 |

<%= raw t(".closing") %>

9 | -------------------------------------------------------------------------------- /app/views/clearance_mailer/change_password.text.erb: -------------------------------------------------------------------------------- 1 | <%= t(".opening") %> 2 | 3 | <%= edit_user_password_url(@user, token: @user.confirmation_token.html_safe) %> 4 | 5 | <%= raw t(".closing") %> 6 | -------------------------------------------------------------------------------- /app/views/fanza_actresses/_cards.html.erb: -------------------------------------------------------------------------------- 1 | <% cards.reject(&:is_hidden?).in_groups_of(9).each do |group| %> 2 |
3 | <% group.each do |actress| %> 4 | <% if actress %> 5 |
6 | <%= link_to( 7 | image_tag(actress.image_url || fake_image_url(actress), class: "card-image-top img-fluid mx-auto d-block"), 8 | actress.persisted? ? actress : fanza_actress_path(actress.name) 9 | ) %> 10 |
11 |
12 | <%= link_to( 13 | actress.name, 14 | actress.persisted? ? actress : fanza_actress_path(actress.name) 15 | ) %>
16 |
17 |
18 | <% else %> 19 |
20 | <% end %> 21 | <% end %> 22 |
23 | <% end %> -------------------------------------------------------------------------------- /app/views/fanza_actresses/index.html.erb: -------------------------------------------------------------------------------- 1 | <% content_for :title do %> 2 | <%= params[:fuzzy] %> 3 | <% end %> 4 | 5 | 41 | 42 | 43 | <%= render partial: "layouts/pager", locals: { items: @actresses, name: "actress" } %> 44 | <%= render partial: "fanza_actresses/cards", object: @actresses %> 45 | <%= render partial: "layouts/pager", locals: { items: @actresses, name: "actress" } %> -------------------------------------------------------------------------------- /app/views/fanza_actresses/show.html.erb: -------------------------------------------------------------------------------- 1 | <% content_for :title do %> 2 | <%= @actress.name %> 3 | <% end %> 4 | 5 |
6 | <%= image_tag(@actress.image_url || fake_image_url(@actress), class: "align-self-center mr-3")%> 7 |
8 | 13 |
14 |
15 | 16 | <% if @movies %> 17 | <%= render partial: "movies/cards", object: @movies %> 18 | <% end %> 19 | -------------------------------------------------------------------------------- /app/views/feeds/index.html.erb: -------------------------------------------------------------------------------- 1 |

<%= notice %>

2 | 3 |

Feeds

4 | 5 |
6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | <% @feeds.each do |feed| %> 19 | 20 | 23 | 26 | 31 | 32 | 33 | 35 | 47 | 48 | 49 | 50 | 55 | 56 | <% end %> 57 | 58 |
IDTagCreatedUpdatedAccessed
21 | <%= link_to feed.id, feed, class: "btn" %> 22 | 24 | <%= link_to feed.uri, feed.uri %> 25 | 27 | 30 |
34 | 36 | <%= bootstrap_form_for feed, class: "mt-3" do |f| %> 37 | <%= f.text_field( 38 | :tag, 39 | label_as_placeholder: true, 40 | append: f.primary { 41 | content_tag(:i, nil, class: "bi bi-tag") 42 | }, 43 | wrapper: { class: '' }, 44 | ) %> 45 | <% end %> 46 | <%= time_ago_in_words(feed.created_at) %> ago<%= time_ago_in_words(feed.updated_at) %> ago<%= time_ago_in_words(feed.accessed_at) %> ago 51 | <%= button_to feed, method: :delete, class: "btn btn-danger" do %> 52 | 53 | <% end %> 54 |
59 |
60 | -------------------------------------------------------------------------------- /app/views/generic_pages/index.html.erb: -------------------------------------------------------------------------------- 1 | <%= render partial: "layouts/pager", locals: { items: @pages, name: nil } %> 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | <% @pages.each do |page| %> 15 | 16 | 17 | 18 | 19 | 24 | 25 | <% end %> 26 | 27 |
IDUrlCreated AtItem
<%= link_to page.id, page %><%= link_to page.url, page.url %><%= page.created_at.in_time_zone("Pacific Time (US & Canada)") %> 20 | <% if page.item %> 21 | <%= link_to page.item.normalized_id, page.item %> 22 | <% end %> 23 |
-------------------------------------------------------------------------------- /app/views/javlibrary_pages/index.html.erb: -------------------------------------------------------------------------------- 1 | <%= link_to "Create Page", new_javlibrary_page_path, class: "btn btn-success" %> 2 | 3 | <%= render(template: 'generic_pages/index') %> -------------------------------------------------------------------------------- /app/views/javlibrary_pages/new.html.erb: -------------------------------------------------------------------------------- 1 |

New Javlibrary Page

2 | 3 | <%= bootstrap_form_for @page do |f| %> 4 | <%= f.text_field :url %> 5 | <%= f.text_area :raw_html, rows: 10 %> 6 | <%= f.submit "Create", class: "btn btn-success" %> 7 | <%= link_to "Back", javlibrary_pages_path, class: "btn btn-secondary" %> 8 | <% end %> -------------------------------------------------------------------------------- /app/views/kaminari/_first_page.html.erb: -------------------------------------------------------------------------------- 1 |
  • 2 | <%= link_to_unless current_page.first?, raw(t 'views.pagination.first'), url, remote: remote, class: 'page-link' %> 3 |
  • 4 | -------------------------------------------------------------------------------- /app/views/kaminari/_gap.html.erb: -------------------------------------------------------------------------------- 1 |
  • 2 | <%= link_to raw(t 'views.pagination.truncate'), '#', class: 'page-link' %> 3 |
  • 4 | -------------------------------------------------------------------------------- /app/views/kaminari/_last_page.html.erb: -------------------------------------------------------------------------------- 1 |
  • 2 | <%= link_to_unless current_page.last?, raw(t 'views.pagination.last'), url, remote: remote, class: 'page-link' %> 3 |
  • 4 | -------------------------------------------------------------------------------- /app/views/kaminari/_next_page.html.erb: -------------------------------------------------------------------------------- 1 |
  • 2 | <%= link_to_unless current_page.last?, raw(t 'views.pagination.next'), url, rel: 'next', remote: remote, class: 'page-link' %> 3 |
  • 4 | -------------------------------------------------------------------------------- /app/views/kaminari/_page.html.erb: -------------------------------------------------------------------------------- 1 | <% if page.current? %> 2 |
  • 3 | <%= content_tag :a, page, data: { remote: remote }, rel: page.rel, class: 'page-link' %> 4 |
  • 5 | <% else %> 6 |
  • 7 | <%= link_to page, url, remote: remote, rel: page.rel, class: 'page-link' %> 8 |
  • 9 | <% end %> 10 | -------------------------------------------------------------------------------- /app/views/kaminari/_paginator.html.erb: -------------------------------------------------------------------------------- 1 | <%= paginator.render do %> 2 | 17 | <% end %> 18 | -------------------------------------------------------------------------------- /app/views/kaminari/_prev_page.html.erb: -------------------------------------------------------------------------------- 1 |
  • 2 | <%= link_to_unless current_page.first?, raw(t 'views.pagination.previous'), url, rel: 'prev', remote: remote, class: 'page-link' %> 3 |
  • 4 | -------------------------------------------------------------------------------- /app/views/layouts/_pager.html.erb: -------------------------------------------------------------------------------- 1 |
    2 | <%= paginate items, window: 2 %> 3 |
    4 |

    5 | <%= page_entries_info items, entry_name: name %> 6 | <% if !items.is_a?(Kaminari::PaginatableArray) and items.exists? and items.has_attribute? :date %> 7 |
    8 | 9 | Released between 10 | <%= items.except(:limit, :offset).minimum(:date).to_date %> and 11 | <%= items.except(:limit, :offset).maximum(:date).to_date %> 12 | 13 | <% end %> 14 |

    15 |
    16 |
    -------------------------------------------------------------------------------- /app/views/layouts/mailer.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | 9 | 10 | 11 | <%= yield %> 12 | 13 | 14 | -------------------------------------------------------------------------------- /app/views/layouts/mailer.text.erb: -------------------------------------------------------------------------------- 1 | <%= yield %> 2 | -------------------------------------------------------------------------------- /app/views/movies/_cards.html.erb: -------------------------------------------------------------------------------- 1 | <% render_pager = local_assigns.fetch(:render_pager, true) %> 2 | <% always_link_to_movie = local_assigns.fetch(:link_to_movie, true) %> 3 | 4 | <% if render_pager %> 5 | <%= render partial: "layouts/pager", locals: { items: cards, name: "movie" } %> 6 | <% end %> 7 | 8 | <% to_items(cards).in_groups_of(6).each do |group| %> 9 |
    10 | <% group.each do |item| %> 11 |
    12 | <% if item %> 13 | <% link_dst = (always_link_to_movie || item.preferred? )? item.movie : item %> 14 | <%= link_to( 15 | image_tag(item.thumbnail_image_url, class: "card-image-top img-fluid mx-auto d-block"), 16 | link_dst 17 | )%> 18 |
    19 | <%= link_to link_dst, class: "card-title" do %> 20 |
    21 | <%= item.normalized_id %> 22 |
    23 |
    24 | 25 | <%= item.title %> 26 | 27 |
    28 | <% end %> 29 |
    30 | <% end %> 31 |
    32 | <% end %> 33 |
    34 | <% end %> 35 | 36 | <% if render_pager and cards.present? %> 37 | <%= render partial: "layouts/pager", locals: { items: cards, name: "movie" } %> 38 | <% end %> -------------------------------------------------------------------------------- /app/views/movies/_search.html.erb: -------------------------------------------------------------------------------- 1 | <%= form_tag search_path, method: :get, class: "form-inline" do %> 2 |
    3 | <%= text_field_tag("q", nil, class: "form-control") %> 4 | <%= submit_tag "Search", class: "btn btn-success" %> 5 |
    6 | <% end %> -------------------------------------------------------------------------------- /app/views/movies/index.html.erb: -------------------------------------------------------------------------------- 1 | <% content_for :title do %> 2 | <%= params[:q] %> 3 | <% end %> 4 | 5 | 47 | 48 | <%= render partial: "movies/cards", object: @movies %> -------------------------------------------------------------------------------- /app/views/movies/items.html.erb: -------------------------------------------------------------------------------- 1 | <%= render partial: "movies/cards", object: @items, locals: { link_to_movie: false } %> -------------------------------------------------------------------------------- /app/views/movies/not_found.html.erb: -------------------------------------------------------------------------------- 1 | <% if @searching %> 2 | 3 |
    4 |
    5 |
    6 | Loading... 7 |
    8 |
    9 |
    10 | Loading in background, try come back later. 11 |
    12 |
    13 | <% else %> 14 | 15 |
    16 |

    Not found yet

    17 |
    18 | 19 | <% end %> 20 | 21 | <%if @related_movies.present? %> 22 | 23 |

    You might be looking for

    24 | <%= render partial: "movies/cards", object: @related_movies %> 25 | 26 | <% end %> -------------------------------------------------------------------------------- /app/views/movies/show.json.jbuilder: -------------------------------------------------------------------------------- 1 | json.merge! @item.as_json 2 | -------------------------------------------------------------------------------- /app/views/pages/index.html.erb: -------------------------------------------------------------------------------- 1 |

    LibreFanza

    2 | 3 |
    4 |
    5 | <%= render "movies/search" %> 6 |
    7 |
    -------------------------------------------------------------------------------- /app/views/passwords/create.html.erb: -------------------------------------------------------------------------------- 1 |
    2 |

    <%= t(".description") %>

    3 |
    4 | -------------------------------------------------------------------------------- /app/views/passwords/edit.html.erb: -------------------------------------------------------------------------------- 1 |
    2 |

    <%= t(".title") %>

    3 | 4 |

    <%= t(".description") %>

    5 | 6 | <%= form_for :password_reset, 7 | url: user_password_path(@user, token: @user.confirmation_token), 8 | html: { method: :put } do |form| %> 9 |
    10 | <%= form.label :password %> 11 | <%= form.password_field :password %> 12 |
    13 | 14 |
    15 | <%= form.submit %> 16 |
    17 | <% end %> 18 |
    19 | -------------------------------------------------------------------------------- /app/views/passwords/new.html.erb: -------------------------------------------------------------------------------- 1 |
    2 |

    <%= t(".title") %>

    3 | 4 |

    <%= t(".description") %>

    5 | 6 | <%= form_for :password, url: passwords_path do |form| %> 7 |
    8 | <%= form.label :email %> 9 | <%= form.text_field :email, type: 'email' %> 10 |
    11 | 12 |
    13 | <%= form.submit %> 14 |
    15 | <% end %> 16 |
    17 | -------------------------------------------------------------------------------- /app/views/sessions/new.html.erb: -------------------------------------------------------------------------------- 1 |
    2 |

    <%= t(".title") %>

    3 | 4 | <%= form_for :session, url: session_path do |form| %> 5 | <%= render partial: "/users/email_field", locals: { form: form } %> 6 | <%= render partial: "/users/password_field", locals: { form: form } %> 7 | <%= render partial: "/users/submit_field", locals: { form: form } %> 8 | 9 | 14 | <% end %> 15 |
    -------------------------------------------------------------------------------- /app/views/users/_email_field.html.erb: -------------------------------------------------------------------------------- 1 |
    2 | <%= form.label :email, class: "col-md-2 col-form-label" %> 3 | <%= form.text_field :email, type: "email", class: "form-control col-md-4" %> 4 |
    -------------------------------------------------------------------------------- /app/views/users/_form.html.erb: -------------------------------------------------------------------------------- 1 |
    2 | <%= form.label :email %> 3 | <%= form.text_field :email, type: 'email' %> 4 |
    5 | 6 |
    7 | <%= form.label :password %> 8 | <%= form.password_field :password %> 9 |
    10 | -------------------------------------------------------------------------------- /app/views/users/_password_field.html.erb: -------------------------------------------------------------------------------- 1 |
    2 | <%= form.label :password, class: "col-md-2 col-form-label" %> 3 | <%= form.password_field :password, class: "form-control col-md-4" %> 4 |
    -------------------------------------------------------------------------------- /app/views/users/_submit_field.html.erb: -------------------------------------------------------------------------------- 1 |
    2 | <%= form.submit class: "btn btn-primary" %> 3 |
    -------------------------------------------------------------------------------- /app/views/users/new.html.erb: -------------------------------------------------------------------------------- 1 |
    2 |

    <%= t(".title") %>

    3 | 4 | <%= form_for @user do |form| %> 5 | <%= render partial: "/users/email_field", locals: { form: form } %> 6 | <%= render partial: "/users/password_field", locals: { form: form } %> 7 | <%= render partial: "/users/submit_field", locals: { form: form } %> 8 | 9 | 12 | <% end %> 13 |
    -------------------------------------------------------------------------------- /app/workers/actress_crawler.rb: -------------------------------------------------------------------------------- 1 | class ActressCrawler 2 | include Sidekiq::Worker 3 | 4 | sidekiq_options( 5 | queue: :critical, 6 | lock: :until_and_while_executing, 7 | on_conflict: { client: :log, server: :reject }, 8 | ) 9 | 10 | def perform(overlap = 1000) 11 | offset = FanzaActress.count - overlap + 1 12 | offset = 1 if offset < 1 13 | Fanza::Api.actress_search(offset: offset) do |json| 14 | actress = FanzaActress.create(raw_json: json) 15 | unless actress.persisted? 16 | actress = FanzaActress.find_by(fanza_id: actress.fanza_id) 17 | actress.raw_json = json 18 | actress.derive_fields 19 | actress.save 20 | end 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /app/workers/fanza_item_crawler.rb: -------------------------------------------------------------------------------- 1 | class FanzaItemCrawler 2 | include Sidekiq::Worker 3 | 4 | sidekiq_options( 5 | queue: :critical, 6 | lock: :until_and_while_executing, 7 | on_conflict: { client: :log, server: :reject }, 8 | ) 9 | 10 | def perform(days_to_crawl = 3) 11 | 1.upto(days_to_crawl) do |i| 12 | start_date = i.days.ago.to_date 13 | end_date = (i - 1).days.ago.to_date 14 | logger.info "Crawling Fanza from #{start_date} to #{end_date}" 15 | Fanza::Api.search(start_date: start_date, end_date: end_date, sort: "date") do |json| 16 | FanzaItem.create(raw_json: json) 17 | end 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /app/workers/fanza_searcher.rb: -------------------------------------------------------------------------------- 1 | class FanzaSearcher 2 | include Sidekiq::Worker 3 | 4 | sidekiq_options( 5 | queue: :default, 6 | lock: :until_expired, 7 | lock_ttl: 1.day.to_i, 8 | on_conflict: :log, 9 | ) 10 | 11 | def perform(keyword) 12 | unless keyword =~ /^[[:ascii:]]+$/ 13 | logger.info "[NON_ASCII] #{keyword}" 14 | return 15 | end 16 | 17 | if keyword =~ Regexp.new(ENV.fetch("BLACKHOLE_PATTERN", "^$"), Regexp::IGNORECASE) 18 | logger.info "[BLACKHOLED] #{keyword}" 19 | return 20 | end 21 | 22 | id = Fanza::Id.new(keyword) 23 | unless id.normalized 24 | logger.info "[UNNORMALIZED] #{keyword}" 25 | return 26 | end 27 | 28 | found = search_on_fanza(id.normalized) || 29 | search_on_mgstage(id.normalized) || 30 | search_on_javlibrary(id.normalized) || 31 | search_on_fc2(id.normalized) 32 | end 33 | 34 | def search_on_fanza(keyword) 35 | logger.info "[FANZA] [SEARCHING] #{keyword}" 36 | Fanza::Api.search(keyword: keyword) do |json| 37 | item = FanzaItem.create(raw_json: json) 38 | end 39 | 40 | if FanzaItem.where(normalized_id: keyword).exists? 41 | logger.info "[FANZA] [FOUND] #{keyword}" 42 | true 43 | else 44 | logger.info "[FANZA] [NOT_FOUND] #{keyword}" 45 | false 46 | end 47 | end 48 | 49 | def search_on_mgstage(keyword) 50 | if MgstageItem.where(normalized_id: keyword).exists? 51 | logger.info "[MGSTAGE] [ALREADY_FOUND] #{keyword}" 52 | return true 53 | end 54 | logger.info "[MGSTAGE] [SEARCHING] #{keyword}" 55 | 56 | Mgstage::Api.search(keyword) do |url, raw_html| 57 | page = MgstagePage.create(url: url, raw_html: raw_html) 58 | end 59 | 60 | if MgstageItem.where(normalized_id: keyword).exists? 61 | logger.info "[MGSTAGE] [FOUND] #{keyword}" 62 | true 63 | else 64 | logger.info "[MGSTAGE] [NOT_FOUND] #{keyword}" 65 | false 66 | end 67 | end 68 | 69 | def search_on_javlibrary(keyword) 70 | if JavlibraryItem.where(normalized_id: keyword).exists? 71 | logger.info "[JAVLIBRARY] [ALREADY_FOUND] #{keyword}" 72 | return true 73 | end 74 | logger.info "[JAVLIBRARY] [SEARCHING] #{keyword}" 75 | 76 | Javlibrary::Api.search(keyword) do |url, raw_html| 77 | page = JavlibraryPage.find_or_initialize_by(url: url) 78 | page.raw_html = raw_html 79 | page.save! 80 | break if page.javlibrary_item&.normalized_id == keyword 81 | end 82 | 83 | if JavlibraryItem.where(normalized_id: keyword).exists? 84 | logger.info "[JAVLIBRARY] [FOUND] #{keyword}" 85 | true 86 | else 87 | logger.info "[JAVLIBRARY] [NOT_FOUND] #{keyword}" 88 | false 89 | end 90 | end 91 | 92 | def search_on_fc2(keyword) 93 | if Fc2Item.where(normalized_id: keyword).exists? 94 | logger.info "[FC2] [ALREADY_FOUND] #{keyword}" 95 | return true 96 | end 97 | logger.info "[FC2] [SEARCHING] #{keyword}" 98 | 99 | Fc2::Api.search(keyword) do |url, raw_html| 100 | page = Fc2Page.create(url: url, raw_html: raw_html) 101 | end 102 | 103 | if Fc2Item.where(normalized_id: keyword).exists? 104 | logger.info "[FC2] [FOUND] #{keyword}" 105 | true 106 | else 107 | logger.info "[FC2] [NOT_FOUND] #{keyword}" 108 | false 109 | end 110 | end 111 | end 112 | -------------------------------------------------------------------------------- /app/workers/feed_refresher.rb: -------------------------------------------------------------------------------- 1 | class FeedRefresher 2 | include Sidekiq::Worker 3 | 4 | sidekiq_options( 5 | queue: :critical, 6 | lock: :until_and_while_executing, 7 | on_conflict: { client: :log, server: :reject }, 8 | ) 9 | 10 | def perform(interval = 30, max_failures = 3) 11 | fail_cnt = 0 12 | Feed.order(updated_at: :asc).each do |feed| 13 | begin 14 | feed.refresh! 15 | rescue ActiveRecord::RecordInvalid => e 16 | fail_cnt += 1 17 | end 18 | break if fail_cnt >= max_failures 19 | sleep interval 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /app/workers/house_keeper.rb: -------------------------------------------------------------------------------- 1 | class HouseKeeper 2 | include Sidekiq::Worker 3 | 4 | sidekiq_options( 5 | queue: :low, 6 | lock: :until_and_while_executing, 7 | on_conflict: { client: :log, server: :reject }, 8 | ) 9 | 10 | def perform(task) 11 | case task.to_sym 12 | when :derive_fields 13 | logger.info "Re-deriving fields" 14 | Movie.find_each(batch_size: 100, &:derive_fields!) 15 | FanzaItem.find_each(batch_size: 100, &:derive_fields!) 16 | MgstageItem.find_each(batch_size: 100, &:derive_fields!) 17 | JavlibraryItem.find_each(batch_size: 100, &:derive_fields!) 18 | else 19 | logger.warn "Invalid task #{task}" 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /app/workers/mgstage_crawler.rb: -------------------------------------------------------------------------------- 1 | require "open-uri" 2 | 3 | class MgstageCrawler 4 | include Sidekiq::Worker 5 | 6 | sidekiq_options( 7 | queue: :critical, 8 | lock: :until_and_while_executing, 9 | on_conflict: { client: :log, server: :reject }, 10 | ) 11 | 12 | def perform 13 | Faraday.get(ENV["MGSTAGE_SERIES_URL"]).body.split.each do |series| 14 | logger.info "[MGS] crawling #{series}" 15 | Mgstage::Api.search_raw(series.upcase) do |url| 16 | self.crawl_page url 17 | end 18 | end 19 | end 20 | 21 | private 22 | 23 | def crawl_page(url) 24 | return if MgstagePage.exists? url: url 25 | resp = Mgstage::Api.get url 26 | return unless resp.status == 200 27 | logger.info "[MGSTAGE] [CRAWLED] #{url}" 28 | MgstagePage.create(url: url, raw_html: resp.body) 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /bin/docker-entrypoint: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | # If running the rails server then create or migrate existing database 4 | if [ "${1}" == "./bin/puma" ]; then 5 | ./bin/rails db:prepare 6 | fi 7 | 8 | exec "${@}" 9 | -------------------------------------------------------------------------------- /bin/importmap: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require_relative "../config/application" 4 | require "importmap/commands" 5 | -------------------------------------------------------------------------------- /bin/puma: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | # 5 | # This file was generated by Bundler. 6 | # 7 | # The application 'puma' is installed as part of a gem, and 8 | # this file is here to facilitate running it. 9 | # 10 | 11 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) 12 | 13 | bundle_binstub = File.expand_path("bundle", __dir__) 14 | 15 | if File.file?(bundle_binstub) 16 | if File.read(bundle_binstub, 300).include?("This file was generated by Bundler") 17 | load(bundle_binstub) 18 | else 19 | abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. 20 | Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.") 21 | end 22 | end 23 | 24 | require "rubygems" 25 | require "bundler/setup" 26 | 27 | load Gem.bin_path("puma", "puma") 28 | -------------------------------------------------------------------------------- /bin/pumactl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | # 5 | # This file was generated by Bundler. 6 | # 7 | # The application 'pumactl' is installed as part of a gem, and 8 | # this file is here to facilitate running it. 9 | # 10 | 11 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) 12 | 13 | bundle_binstub = File.expand_path("bundle", __dir__) 14 | 15 | if File.file?(bundle_binstub) 16 | if File.read(bundle_binstub, 300).include?("This file was generated by Bundler") 17 | load(bundle_binstub) 18 | else 19 | abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. 20 | Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.") 21 | end 22 | end 23 | 24 | require "rubygems" 25 | require "bundler/setup" 26 | 27 | load Gem.bin_path("puma", "pumactl") 28 | -------------------------------------------------------------------------------- /bin/rails: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | APP_PATH = File.expand_path("../config/application", __dir__) 3 | require_relative "../config/boot" 4 | require "rails/commands" 5 | -------------------------------------------------------------------------------- /bin/rake: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require_relative "../config/boot" 3 | require "rake" 4 | Rake.application.run 5 | -------------------------------------------------------------------------------- /bin/rspec: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | # 5 | # This file was generated by Bundler. 6 | # 7 | # The application 'rspec' is installed as part of a gem, and 8 | # this file is here to facilitate running it. 9 | # 10 | 11 | require "pathname" 12 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", 13 | Pathname.new(__FILE__).realpath) 14 | 15 | bundle_binstub = File.expand_path("../bundle", __FILE__) 16 | 17 | if File.file?(bundle_binstub) 18 | if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/ 19 | load(bundle_binstub) 20 | else 21 | abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. 22 | Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.") 23 | end 24 | end 25 | 26 | require "rubygems" 27 | require "bundler/setup" 28 | 29 | load Gem.bin_path("rspec-core", "rspec") 30 | -------------------------------------------------------------------------------- /bin/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require "fileutils" 3 | 4 | # path to your application root. 5 | APP_ROOT = File.expand_path("..", __dir__) 6 | 7 | def system!(*args) 8 | system(*args) || abort("\n== Command #{args} failed ==") 9 | end 10 | 11 | FileUtils.chdir APP_ROOT do 12 | # This script is a way to set up or update your development environment automatically. 13 | # This script is idempotent, so that you can run it at any time and get an expectable outcome. 14 | # Add necessary setup steps to this file. 15 | 16 | puts "== Installing dependencies ==" 17 | system! "gem install bundler --conservative" 18 | system("bundle check") || system!("bundle install") 19 | 20 | # puts "\n== Copying sample files ==" 21 | # unless File.exist?("config/database.yml") 22 | # FileUtils.cp "config/database.yml.sample", "config/database.yml" 23 | # end 24 | 25 | puts "\n== Preparing database ==" 26 | system! "bin/rails db:prepare" 27 | 28 | puts "\n== Removing old logs and tempfiles ==" 29 | system! "bin/rails log:clear tmp:clear" 30 | 31 | puts "\n== Restarting application server ==" 32 | system! "bin/rails restart" 33 | end 34 | -------------------------------------------------------------------------------- /bin/sidekiq: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | # 5 | # This file was generated by Bundler. 6 | # 7 | # The application 'sidekiq' is installed as part of a gem, and 8 | # this file is here to facilitate running it. 9 | # 10 | 11 | require "pathname" 12 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", 13 | Pathname.new(__FILE__).realpath) 14 | 15 | bundle_binstub = File.expand_path("../bundle", __FILE__) 16 | 17 | if File.file?(bundle_binstub) 18 | if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/ 19 | load(bundle_binstub) 20 | else 21 | abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. 22 | Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.") 23 | end 24 | end 25 | 26 | require "rubygems" 27 | require "bundler/setup" 28 | 29 | load Gem.bin_path("sidekiq", "sidekiq") 30 | -------------------------------------------------------------------------------- /bin/sidekiqmon: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | # 5 | # This file was generated by Bundler. 6 | # 7 | # The application 'sidekiqmon' is installed as part of a gem, and 8 | # this file is here to facilitate running it. 9 | # 10 | 11 | require "pathname" 12 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", 13 | Pathname.new(__FILE__).realpath) 14 | 15 | bundle_binstub = File.expand_path("../bundle", __FILE__) 16 | 17 | if File.file?(bundle_binstub) 18 | if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/ 19 | load(bundle_binstub) 20 | else 21 | abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. 22 | Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.") 23 | end 24 | end 25 | 26 | require "rubygems" 27 | require "bundler/setup" 28 | 29 | load Gem.bin_path("sidekiq", "sidekiqmon") 30 | -------------------------------------------------------------------------------- /config.ru: -------------------------------------------------------------------------------- 1 | # This file is used by Rack-based servers to start the application. 2 | 3 | require_relative "config/environment" 4 | 5 | run Rails.application 6 | -------------------------------------------------------------------------------- /config/application.rb: -------------------------------------------------------------------------------- 1 | require_relative "boot" 2 | 3 | require "rails" 4 | # Pick the frameworks you want: 5 | require "active_model/railtie" 6 | require "active_job/railtie" 7 | require "active_record/railtie" 8 | require "active_storage/engine" 9 | require "action_controller/railtie" 10 | require "action_mailer/railtie" 11 | require "action_mailbox/engine" 12 | require "action_text/engine" 13 | require "action_view/railtie" 14 | require "action_cable/engine" 15 | require "rails/test_unit/railtie" 16 | 17 | # Require the gems listed in Gemfile, including any gems 18 | # you've limited to :test, :development, or :production. 19 | Bundler.require(*Rails.groups) 20 | 21 | module Librefanza 22 | class Application < Rails::Application 23 | # Initialize configuration defaults for originally generated Rails version. 24 | config.load_defaults 7.0 25 | 26 | # Configuration for the application, engines, and railties goes here. 27 | # 28 | # These settings can be overridden in specific environments using the files 29 | # in config/environments, which are processed later. 30 | # 31 | # config.time_zone = "Central Time (US & Canada)" 32 | # config.eager_load_paths << Rails.root.join("extras") 33 | 34 | config.active_job.queue_adapter = :sidekiq 35 | 36 | config.cache_store = :memory_store 37 | 38 | config.time_zone = "Pacific Time (US & Canada)" 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /config/boot.rb: -------------------------------------------------------------------------------- 1 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) 2 | 3 | require "bundler/setup" # Set up gems listed in the Gemfile. 4 | require "bootsnap/setup" # Speed up boot time by caching expensive operations. 5 | -------------------------------------------------------------------------------- /config/credentials.yml.enc: -------------------------------------------------------------------------------- 1 | IqFpYM657Zr+fhl3/0S8p6T/Y2naMHyTR9gZEkLCLID1oYsaS0qO+9WcpnGH4sSS4Gq+vHtWshmbWbOtIx9bwXkUCd08YFsFqU/B2g6uCCqpqYGS9N0elystluPfTSxd7RXiQbeN5LsswOPtIBEAhckE263s2+PAJdfoW+Ihmgcyvw/MHLuzYaxAKELyKP5/fSvzdPwewQGbPXrF9pS1YgeCJBqwbTQNreLSJN8PwvF6X2Rdms8XJ46BFL0pbaRBLtY9rTyCqjzghVN4fnnB9JKMUO1iIh9BYsfXUOD93ttU9ebs2NRajpXrKx79tzXA9L6svfeQT92qu3TN4s9qpbG82t7dqZTH85d5sFsmszecdfz948fxo+oWOJiJK3zkr0aZXAaP1XCMvf/mrjJb1voSf+t0W9AfvLsZ--SDo8gxBbvr5dDqyg--UIhy6QvQ0f2VcCvrzSXz7Q== -------------------------------------------------------------------------------- /config/database.yml: -------------------------------------------------------------------------------- 1 | # PostgreSQL. Versions 9.3 and up are supported. 2 | # 3 | # Install the pg driver: 4 | # gem install pg 5 | # On macOS with Homebrew: 6 | # gem install pg -- --with-pg-config=/usr/local/bin/pg_config 7 | # On macOS with MacPorts: 8 | # gem install pg -- --with-pg-config=/opt/local/lib/postgresql84/bin/pg_config 9 | # On Windows: 10 | # gem install pg 11 | # Choose the win32 build. 12 | # Install PostgreSQL and put its /bin directory on your path. 13 | # 14 | # Configure Using Gemfile 15 | # gem 'pg' 16 | # 17 | default: &default 18 | adapter: postgresql 19 | encoding: unicode 20 | # For details on connection pooling, see Rails configuration guide 21 | # https://guides.rubyonrails.org/configuring.html#database-pooling 22 | pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> 23 | 24 | development: 25 | <<: *default 26 | database: librefanza_development 27 | 28 | # The specified database role being used to connect to postgres. 29 | # To create additional roles in postgres see `$ createuser --help`. 30 | # When left blank, postgres will use the default role. This is 31 | # the same name as the operating system user that initialized the database. 32 | #username: librefanza 33 | 34 | # The password associated with the postgres role (username). 35 | #password: 36 | 37 | # Connect on a TCP socket. Omitted by default since the client uses a 38 | # domain socket that doesn't need configuration. Windows does not have 39 | # domain sockets, so uncomment these lines. 40 | #host: localhost 41 | 42 | # The TCP port the server listens on. Defaults to 5432. 43 | # If your server runs on a different port number, change accordingly. 44 | #port: 5432 45 | 46 | # Schema search path. The server defaults to $user,public 47 | #schema_search_path: myapp,sharedapp,public 48 | 49 | # Minimum log levels, in increasing order: 50 | # debug5, debug4, debug3, debug2, debug1, 51 | # log, notice, warning, error, fatal, and panic 52 | # Defaults to warning. 53 | #min_messages: notice 54 | 55 | # Warning: The database defined as "test" will be erased and 56 | # re-generated from your development database when you run "rake". 57 | # Do not set this db to the same as development or production. 58 | test: 59 | <<: *default 60 | database: librefanza_test 61 | 62 | # As with config/credentials.yml, you never want to store sensitive information, 63 | # like your database password, in your source code. If your source code is 64 | # ever seen by anyone, they now have access to your database. 65 | # 66 | # Instead, provide the password as a unix environment variable when you boot 67 | # the app. Read https://guides.rubyonrails.org/configuring.html#configuring-a-database 68 | # for a full rundown on how to provide these environment variables in a 69 | # production deployment. 70 | # 71 | # On Heroku and other platform providers, you may have a full connection URL 72 | # available as an environment variable. For example: 73 | # 74 | # DATABASE_URL="postgres://myuser:mypass@localhost/somedatabase" 75 | # 76 | # You can use this database configuration with: 77 | # 78 | # production: 79 | # url: <%= ENV['DATABASE_URL'] %> 80 | # 81 | production: 82 | <<: *default 83 | database: librefanza_production 84 | pool: 20 85 | -------------------------------------------------------------------------------- /config/environment.rb: -------------------------------------------------------------------------------- 1 | # Load the Rails application. 2 | require_relative "application" 3 | 4 | # Initialize the Rails application. 5 | Rails.application.initialize! 6 | -------------------------------------------------------------------------------- /config/environments/development.rb: -------------------------------------------------------------------------------- 1 | require "active_support/core_ext/integer/time" 2 | 3 | Rails.application.configure do 4 | # Settings specified here will take precedence over those in config/application.rb. 5 | 6 | # In the development environment your application's code is reloaded any time 7 | # it changes. This slows down response time but is perfect for development 8 | # since you don't have to restart the web server when you make code changes. 9 | config.cache_classes = false 10 | 11 | # Do not eager load code on boot. 12 | config.eager_load = false 13 | 14 | # Show full error reports. 15 | config.consider_all_requests_local = true 16 | 17 | # Enable server timing 18 | config.server_timing = true 19 | 20 | # Enable/disable caching. By default caching is disabled. 21 | # Run rails dev:cache to toggle caching. 22 | if Rails.root.join("tmp/caching-dev.txt").exist? 23 | config.action_controller.perform_caching = true 24 | config.action_controller.enable_fragment_cache_logging = true 25 | 26 | config.cache_store = :memory_store 27 | config.public_file_server.headers = { 28 | "Cache-Control" => "public, max-age=#{2.days.to_i}" 29 | } 30 | else 31 | config.action_controller.perform_caching = false 32 | 33 | config.cache_store = :null_store 34 | end 35 | 36 | # Store uploaded files on the local file system (see config/storage.yml for options). 37 | config.active_storage.service = :local 38 | 39 | # Don't care if the mailer can't send. 40 | config.action_mailer.raise_delivery_errors = false 41 | 42 | config.action_mailer.perform_caching = false 43 | 44 | # Print deprecation notices to the Rails logger. 45 | config.active_support.deprecation = :log 46 | 47 | # Raise exceptions for disallowed deprecations. 48 | config.active_support.disallowed_deprecation = :raise 49 | 50 | # Tell Active Support which deprecation messages to disallow. 51 | config.active_support.disallowed_deprecation_warnings = [] 52 | 53 | # Raise an error on page load if there are pending migrations. 54 | config.active_record.migration_error = :page_load 55 | 56 | # Highlight code that triggered database queries in logs. 57 | config.active_record.verbose_query_logs = true 58 | 59 | # Suppress logger output for asset requests. 60 | config.assets.quiet = true 61 | 62 | # Raises error for missing translations. 63 | # config.i18n.raise_on_missing_translations = true 64 | 65 | # Annotate rendered view with file names. 66 | # config.action_view.annotate_rendered_view_with_filenames = true 67 | 68 | # Uncomment if you wish to allow Action Cable access from any origin. 69 | # config.action_cable.disable_request_forgery_protection = true 70 | end 71 | -------------------------------------------------------------------------------- /config/environments/test.rb: -------------------------------------------------------------------------------- 1 | require "active_support/core_ext/integer/time" 2 | 3 | # The test environment is used exclusively to run your application's 4 | # test suite. You never need to work with it otherwise. Remember that 5 | # your test database is "scratch space" for the test suite and is wiped 6 | # and recreated between test runs. Don't rely on the data there! 7 | 8 | Rails.application.configure do 9 | # Settings specified here will take precedence over those in config/application.rb. 10 | 11 | # Turn false under Spring and add config.action_view.cache_template_loading = true. 12 | config.cache_classes = true 13 | 14 | # Eager loading loads your whole application. When running a single test locally, 15 | # this probably isn't necessary. It's a good idea to do in a continuous integration 16 | # system, or in some way before deploying your code. 17 | config.eager_load = ENV["CI"].present? 18 | 19 | # Configure public file server for tests with Cache-Control for performance. 20 | config.public_file_server.enabled = true 21 | config.public_file_server.headers = { 22 | "Cache-Control" => "public, max-age=#{1.hour.to_i}", 23 | } 24 | 25 | # Show full error reports and disable caching. 26 | config.consider_all_requests_local = true 27 | config.action_controller.perform_caching = false 28 | config.cache_store = :null_store 29 | 30 | # Raise exceptions instead of rendering exception templates. 31 | config.action_dispatch.show_exceptions = :none 32 | 33 | # Disable request forgery protection in test environment. 34 | config.action_controller.allow_forgery_protection = false 35 | 36 | # Store uploaded files on the local file system in a temporary directory. 37 | config.active_storage.service = :test 38 | 39 | config.action_mailer.perform_caching = false 40 | 41 | # Tell Action Mailer not to deliver emails to the real world. 42 | # The :test delivery method accumulates sent emails in the 43 | # ActionMailer::Base.deliveries array. 44 | config.action_mailer.delivery_method = :test 45 | 46 | # Print deprecation notices to the stderr. 47 | config.active_support.deprecation = :stderr 48 | 49 | # Raise exceptions for disallowed deprecations. 50 | config.active_support.disallowed_deprecation = :raise 51 | 52 | # Tell Active Support which deprecation messages to disallow. 53 | config.active_support.disallowed_deprecation_warnings = [] 54 | 55 | # Raises error for missing translations. 56 | # config.i18n.raise_on_missing_translations = true 57 | 58 | # Annotate rendered view with file names. 59 | # config.action_view.annotate_rendered_view_with_filenames = true 60 | 61 | config.middleware.use Clearance::BackDoor 62 | end 63 | -------------------------------------------------------------------------------- /config/importmap.rb: -------------------------------------------------------------------------------- 1 | # Pin npm packages by running ./bin/importmap 2 | 3 | pin "application" 4 | pin "@hotwired/turbo-rails", to: "turbo.min.js", preload: true 5 | pin_all_from "app/javascript/controllers", under: "controllers", to: "controllers" 6 | -------------------------------------------------------------------------------- /config/initializers/application_controller_renderer.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # ActiveSupport::Reloader.to_prepare do 4 | # ApplicationController.renderer.defaults.merge!( 5 | # http_host: 'example.org', 6 | # https: false 7 | # ) 8 | # end 9 | -------------------------------------------------------------------------------- /config/initializers/assets.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Version of your assets, change this if you want to expire all your assets. 4 | Rails.application.config.assets.version = "1.0" 5 | 6 | # Add additional assets to the asset load path. 7 | # Rails.application.config.assets.paths << Emoji.images_path 8 | # Rails.application.config.assets.paths << Rails.root.join("node_modules/bootstrap-icons/font") 9 | 10 | # Precompile additional assets. 11 | # application.js, application.css, and all non-JS/CSS in the app/assets 12 | # folder are already added. 13 | # Rails.application.config.assets.precompile += %w( admin.js admin.css ) 14 | -------------------------------------------------------------------------------- /config/initializers/backtrace_silencers.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces. 4 | # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ } 5 | 6 | # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code. 7 | # Rails.backtrace_cleaner.remove_silencers! 8 | -------------------------------------------------------------------------------- /config/initializers/clearance.rb: -------------------------------------------------------------------------------- 1 | Clearance.configure do |config| 2 | config.routes = true # We want to set our own 3 | config.mailer_sender = "reply@example.com" 4 | config.rotate_csrf_on_sign_in = true 5 | end 6 | -------------------------------------------------------------------------------- /config/initializers/content_security_policy.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Define an application-wide content security policy. 4 | # See the Securing Rails Applications Guide for more information: 5 | # https://guides.rubyonrails.org/security.html#content-security-policy-header 6 | 7 | # Rails.application.configure do 8 | # config.content_security_policy do |policy| 9 | # policy.default_src :self, :https 10 | # policy.font_src :self, :https, :data 11 | # policy.img_src :self, :https, :data 12 | # policy.object_src :none 13 | # policy.script_src :self, :https 14 | # policy.style_src :self, :https 15 | # # Specify URI for violation reports 16 | # # policy.report_uri "/csp-violation-report-endpoint" 17 | # end 18 | # 19 | # # Generate session nonces for permitted importmap and inline scripts 20 | # config.content_security_policy_nonce_generator = ->(request) { request.session.id.to_s } 21 | # config.content_security_policy_nonce_directives = %w(script-src) 22 | # 23 | # # Report violations without enforcing the policy. 24 | # # config.content_security_policy_report_only = true 25 | # end 26 | -------------------------------------------------------------------------------- /config/initializers/cookies_serializer.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Specify a serializer for the signed and encrypted cookie jars. 4 | # Valid options are :json, :marshal, and :hybrid. 5 | Rails.application.config.action_dispatch.cookies_serializer = :json 6 | -------------------------------------------------------------------------------- /config/initializers/filter_parameter_logging.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Configure parameters to be filtered from the log file. Use this to limit dissemination of 4 | # sensitive information. See the ActiveSupport::ParameterFilter documentation for supported 5 | # notations and behaviors. 6 | Rails.application.config.filter_parameters += [ 7 | :passw, :secret, :token, :_key, :crypt, :salt, :certificate, :otp, :ssn 8 | ] 9 | -------------------------------------------------------------------------------- /config/initializers/inflections.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new inflection rules using the following format. Inflections 4 | # are locale specific, and you may define rules for as many different 5 | # locales as you wish. All of these examples are active by default: 6 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 7 | # inflect.plural /^(ox)$/i, "\\1en" 8 | # inflect.singular /^(ox)en/i, "\\1" 9 | # inflect.irregular "person", "people" 10 | # inflect.uncountable %w( fish sheep ) 11 | # end 12 | 13 | # These inflection rules are supported but not enabled by default: 14 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 15 | # inflect.acronym "RESTful" 16 | # end 17 | -------------------------------------------------------------------------------- /config/initializers/mime_types.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new mime types for use in respond_to blocks: 4 | # Mime::Type.register "text/richtext", :rtf 5 | -------------------------------------------------------------------------------- /config/initializers/permissions_policy.rb: -------------------------------------------------------------------------------- 1 | # Define an application-wide HTTP permissions policy. For further 2 | # information see https://developers.google.com/web/updates/2018/06/feature-policy 3 | # 4 | # Rails.application.config.permissions_policy do |f| 5 | # f.camera :none 6 | # f.gyroscope :none 7 | # f.microphone :none 8 | # f.usb :none 9 | # f.fullscreen :self 10 | # f.payment :self, "https://secure.example.com" 11 | # end 12 | -------------------------------------------------------------------------------- /config/initializers/sidekiq.rb: -------------------------------------------------------------------------------- 1 | SidekiqUniqueJobs.configure do |config| 2 | config.enabled = Rails.env.production? 3 | config.lock_prefix = ENV["SIDEKIQ_UNIQUE_PREFIX"] || "uniquejobs" 4 | end 5 | 6 | Sidekiq.configure_server do |config| 7 | config.client_middleware do |chain| 8 | chain.add SidekiqUniqueJobs::Middleware::Client 9 | end 10 | 11 | config.server_middleware do |chain| 12 | chain.add SidekiqUniqueJobs::Middleware::Server 13 | end 14 | 15 | SidekiqUniqueJobs::Server.configure(config) 16 | end 17 | 18 | Sidekiq.configure_client do |config| 19 | config.client_middleware do |chain| 20 | chain.add SidekiqUniqueJobs::Middleware::Client 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /config/initializers/unicode_strip.rb: -------------------------------------------------------------------------------- 1 | class String 2 | def strip 3 | self&.gsub(/^[[:space:]]+|[[:space:]]+$/, '') 4 | end 5 | end -------------------------------------------------------------------------------- /config/initializers/wrap_parameters.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # This file contains settings for ActionController::ParamsWrapper which 4 | # is enabled by default. 5 | 6 | # Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array. 7 | ActiveSupport.on_load(:action_controller) do 8 | wrap_parameters format: [:json] 9 | end 10 | 11 | # To enable root element in JSON for ActiveRecord objects. 12 | # ActiveSupport.on_load(:active_record) do 13 | # self.include_root_in_json = true 14 | # end 15 | -------------------------------------------------------------------------------- /config/locales/clearance.en.yml: -------------------------------------------------------------------------------- 1 | --- 2 | en: 3 | clearance: 4 | models: 5 | clearance_mailer: 6 | change_password: Change your password 7 | clearance_mailer: 8 | change_password: 9 | closing: If you didn't request this, ignore this email. Your password has 10 | not been changed. 11 | link_text: Change my password 12 | opening: "Someone, hopefully you, requested we send you a link to change 13 | your password:" 14 | flashes: 15 | failure_after_create: Bad email or password. 16 | failure_after_update: Password can't be blank. 17 | failure_when_forbidden: Please double check the URL or try submitting 18 | the form again. 19 | failure_when_not_signed_in: Please sign in to continue. 20 | failure_when_missing_email: Email can't be blank. 21 | helpers: 22 | label: 23 | password: 24 | email: Email address 25 | password_reset: 26 | password: Choose password 27 | session: 28 | password: Password 29 | user: 30 | password: Password 31 | submit: 32 | password: 33 | submit: Reset password 34 | password_reset: 35 | submit: Save this password 36 | session: 37 | submit: Sign in 38 | user: 39 | create: Sign up 40 | layouts: 41 | application: 42 | sign_in: Sign in 43 | sign_out: Sign out 44 | passwords: 45 | create: 46 | description: You will receive an email within the next few minutes. It 47 | contains instructions for changing your password. 48 | edit: 49 | description: Your password has been reset. Choose a new password below. 50 | title: Change your password 51 | new: 52 | description: To be emailed a link to reset your password, please enter 53 | your email address. 54 | title: Reset your password 55 | sessions: 56 | form: 57 | forgot_password: Forgot password? 58 | sign_up: Sign up 59 | new: 60 | title: Sign in 61 | users: 62 | new: 63 | sign_in: Sign in 64 | title: Sign up 65 | -------------------------------------------------------------------------------- /config/locales/en.yml: -------------------------------------------------------------------------------- 1 | # Files in the config/locales directory are used for internationalization 2 | # and are automatically loaded by Rails. If you want to use locales other 3 | # than English, add the necessary files in this directory. 4 | # 5 | # To use the locales, use `I18n.t`: 6 | # 7 | # I18n.t 'hello' 8 | # 9 | # In views, this is aliased to just `t`: 10 | # 11 | # <%= t('hello') %> 12 | # 13 | # To use a different locale, set it with `I18n.locale`: 14 | # 15 | # I18n.locale = :es 16 | # 17 | # This would use the information in config/locales/es.yml. 18 | # 19 | # The following keys must be escaped otherwise they will not be retrieved by 20 | # the default I18n backend: 21 | # 22 | # true, false, on, off, yes, no 23 | # 24 | # Instead, surround them with single quotes. 25 | # 26 | # en: 27 | # 'true': 'foo' 28 | # 29 | # To learn more, please read the Rails Internationalization guide 30 | # available at https://guides.rubyonrails.org/i18n.html. 31 | 32 | en: 33 | hello: "Hello world" 34 | -------------------------------------------------------------------------------- /config/puma.rb: -------------------------------------------------------------------------------- 1 | # Puma can serve each request in a thread from an internal thread pool. 2 | # The `threads` method setting takes two numbers: a minimum and maximum. 3 | # Any libraries that use thread pools should be configured to match 4 | # the maximum value specified for Puma. Default is set to 5 threads for minimum 5 | # and maximum; this matches the default thread size of Active Record. 6 | # 7 | max_threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 } 8 | min_threads_count = ENV.fetch("RAILS_MIN_THREADS") { max_threads_count } 9 | threads min_threads_count, max_threads_count 10 | 11 | # Specifies the `port` that Puma will listen on to receive requests; default is 3000. 12 | # 13 | port ENV.fetch("PORT") { 3000 } 14 | 15 | # Specifies the `environment` that Puma will run in. 16 | # 17 | environment ENV.fetch("RAILS_ENV") { "development" } 18 | 19 | # Specifies the `pidfile` that Puma will use. 20 | pidfile ENV.fetch("PIDFILE") { "tmp/pids/server.pid" } 21 | 22 | # Specifies the number of `workers` to boot in clustered mode. 23 | # Workers are forked web server processes. If using threads and workers together 24 | # the concurrency of the application would be max `threads` * `workers`. 25 | # Workers do not work on JRuby or Windows (both of which do not support 26 | # processes). 27 | # 28 | # workers ENV.fetch("WEB_CONCURRENCY") { 2 } 29 | 30 | # Use the `preload_app!` method when specifying a `workers` number. 31 | # This directive tells Puma to first boot the application and load code 32 | # before forking the application. This takes advantage of Copy On Write 33 | # process behavior so workers use less memory. 34 | # 35 | # preload_app! 36 | 37 | # Allow puma to be restarted by `rails restart` command. 38 | plugin :tmp_restart 39 | -------------------------------------------------------------------------------- /config/routes.rb: -------------------------------------------------------------------------------- 1 | require "sidekiq-scheduler/web" 2 | require "sidekiq_unique_jobs/web" 3 | 4 | Rails.application.routes.draw do 5 | root "pages#index" 6 | get "/search", to: "pages#search" 7 | get "feeds/pipe" 8 | 9 | resources :movies, only: %i[index show] 10 | resources :fanza_actresses, only: %i[index show], path: "actresses" 11 | resources :fanza_items, only: %i[show] 12 | resources :javlibrary_items, only: %i[show] 13 | resources :mgstage_items, only: %i[show] 14 | resources :sod_items, only: %i[show] 15 | resources :fc2_items, only: %i[show] 16 | 17 | constraints Clearance::Constraints::SignedIn.new { |user| user.is_admin? } do 18 | resources :movies, only: %i[update destroy] 19 | resources :fanza_items, only: %i[index destroy update] 20 | resources :javlibrary_items, only: %i[index destroy] 21 | resources :javlibrary_pages, only: %i[index show new create] 22 | resources :mgstage_items, only: %i[index destroy] 23 | resources :mgstage_pages, only: %i[index show] 24 | resources :sod_items, only: %i[index destroy] 25 | resources :sod_pages, only: %i[index show] 26 | resources :fc2_items, only: %i[index destroy] 27 | resources :fc2_pages, only: %i[index show] 28 | resources :feeds, only: %i[index show destroy update] 29 | mount Sidekiq::Web => "/sidekiq" 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /config/sidekiq.yml: -------------------------------------------------------------------------------- 1 | --- 2 | :queues: 3 | - critical 4 | - default 5 | - low 6 | 7 | :scheduler: 8 | :schedule: 9 | ActressCrawler: 10 | cron: '0 0 * * *' 11 | FanzaItemCrawler: 12 | cron: '0 1 * * *' 13 | MgstageCrawler: 14 | cron: '0 2 * * *' 15 | FeedRefresher: 16 | cron: '*/10 * * * *' 17 | -------------------------------------------------------------------------------- /config/storage.yml: -------------------------------------------------------------------------------- 1 | test: 2 | service: Disk 3 | root: <%= Rails.root.join("tmp/storage") %> 4 | 5 | local: 6 | service: Disk 7 | root: <%= Rails.root.join("storage") %> 8 | 9 | # Use rails credentials:edit to set the AWS secrets (as aws:access_key_id|secret_access_key) 10 | # amazon: 11 | # service: S3 12 | # access_key_id: <%= Rails.application.credentials.dig(:aws, :access_key_id) %> 13 | # secret_access_key: <%= Rails.application.credentials.dig(:aws, :secret_access_key) %> 14 | # region: us-east-1 15 | # bucket: your_own_bucket 16 | 17 | # Remember not to checkin your GCS keyfile to a repository 18 | # google: 19 | # service: GCS 20 | # project: your_project 21 | # credentials: <%= Rails.root.join("path/to/gcs.keyfile") %> 22 | # bucket: your_own_bucket 23 | 24 | # Use rails credentials:edit to set the Azure Storage secret (as azure_storage:storage_access_key) 25 | # microsoft: 26 | # service: AzureStorage 27 | # storage_account_name: your_account_name 28 | # storage_access_key: <%= Rails.application.credentials.dig(:azure_storage, :storage_access_key) %> 29 | # container: your_container_name 30 | 31 | # mirror: 32 | # service: Mirror 33 | # primary: local 34 | # mirrors: [ amazon, google, microsoft ] 35 | -------------------------------------------------------------------------------- /db/migrate/20200518175706_create_fanza_items.rb: -------------------------------------------------------------------------------- 1 | class CreateFanzaItems < ActiveRecord::Migration[6.0] 2 | def change 3 | create_table :fanza_items do |t| 4 | t.string :content_id 5 | t.jsonb :raw_json 6 | 7 | t.timestamps 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /db/migrate/20200518210337_add_normalized_id_to_fanza_items.rb: -------------------------------------------------------------------------------- 1 | class AddNormalizedIdToFanzaItems < ActiveRecord::Migration[6.0] 2 | def change 3 | add_column :fanza_items, :normalized_id, :string 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20200519040138_add_date_to_fanza_items.rb: -------------------------------------------------------------------------------- 1 | class AddDateToFanzaItems < ActiveRecord::Migration[6.0] 2 | def change 3 | add_column :fanza_items, :date, :datetime 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20200519042227_index_fanza_items_on_normalized_id.rb: -------------------------------------------------------------------------------- 1 | class IndexFanzaItemsOnNormalizedId < ActiveRecord::Migration[6.0] 2 | def change 3 | enable_extension "pg_trgm" 4 | add_index( 5 | :fanza_items, 6 | [:normalized_id, :content_id], 7 | using: "gin", 8 | opclass: :gin_trgm_ops, 9 | ) 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /db/migrate/20200519165056_create_javlibrary_pages.rb: -------------------------------------------------------------------------------- 1 | class CreateJavlibraryPages < ActiveRecord::Migration[6.0] 2 | def change 3 | create_table :javlibrary_pages do |t| 4 | t.string :url 5 | t.text :raw_html 6 | 7 | t.timestamps 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /db/migrate/20200519190110_create_javlibrary_items.rb: -------------------------------------------------------------------------------- 1 | class CreateJavlibraryItems < ActiveRecord::Migration[6.0] 2 | def change 3 | create_table :javlibrary_items do |t| 4 | t.string :normalized_id 5 | t.references :javlibrary_page, null: false, foreign_key: true 6 | 7 | t.timestamps 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /db/migrate/20200519225221_index_javlibrary_items_on_normalized_id.rb: -------------------------------------------------------------------------------- 1 | class IndexJavlibraryItemsOnNormalizedId < ActiveRecord::Migration[6.0] 2 | def change 3 | add_index( 4 | :fanza_items, 5 | :normalized_id, 6 | using: "gin", 7 | opclass: :gin_trgm_ops, 8 | ) 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /db/migrate/20200521050347_add_floor_code_to_fanza_items.rb: -------------------------------------------------------------------------------- 1 | class AddFloorCodeToFanzaItems < ActiveRecord::Migration[6.0] 2 | def change 3 | add_column :fanza_items, :floor_code, :string 4 | add_index :fanza_items, :floor_code 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /db/migrate/20200522004625_create_fanza_actresses.rb: -------------------------------------------------------------------------------- 1 | class CreateFanzaActresses < ActiveRecord::Migration[6.0] 2 | def change 3 | create_table :fanza_actresses do |t| 4 | t.integer :fanza_id 5 | t.string :name 6 | t.jsonb :raw_json 7 | 8 | t.timestamps 9 | end 10 | add_index :fanza_actresses, :fanza_id, unique: true 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /db/migrate/20200523005310_index_fanza_item_on_raw_json.rb: -------------------------------------------------------------------------------- 1 | class IndexFanzaItemOnRawJson < ActiveRecord::Migration[6.0] 2 | def change 3 | add_index( 4 | :fanza_items, 5 | :raw_json, 6 | using: "gin", 7 | opclass: :jsonb_path_ops, 8 | ) 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /db/migrate/20200524183145_create_users.rb: -------------------------------------------------------------------------------- 1 | class CreateUsers < ActiveRecord::Migration[6.0] 2 | def change 3 | create_table :users do |t| 4 | t.timestamps null: false 5 | t.string :email, null: false 6 | t.string :encrypted_password, limit: 128, null: false 7 | t.string :confirmation_token, limit: 128 8 | t.string :remember_token, limit: 128, null: false 9 | t.string :api_token, limit: 128, null: false 10 | end 11 | 12 | add_index :users, :email 13 | add_index :users, :remember_token 14 | add_index :users, :api_token 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /db/migrate/20200524213828_reindex_fanza_items.rb: -------------------------------------------------------------------------------- 1 | class ReindexFanzaItems < ActiveRecord::Migration[6.0] 2 | def change 3 | remove_index :fanza_items, :normalized_id 4 | add_index :fanza_items, :normalized_id 5 | add_index :fanza_items, :content_id 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /db/migrate/20200524214145_reindex_javlibrary_items.rb: -------------------------------------------------------------------------------- 1 | class ReindexJavlibraryItems < ActiveRecord::Migration[6.0] 2 | def change 3 | add_index :javlibrary_items, :normalized_id 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20200524214252_index_fanza_actress_on_name.rb: -------------------------------------------------------------------------------- 1 | class IndexFanzaActressOnName < ActiveRecord::Migration[6.0] 2 | def change 3 | add_index :fanza_actresses, :name 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20200524220821_add_is_admin_to_users.rb: -------------------------------------------------------------------------------- 1 | class AddIsAdminToUsers < ActiveRecord::Migration[6.0] 2 | def change 3 | add_column :users, :is_admin, :boolean, null: false, default: false 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20200525003855_add_raw_html_to_fanza_items.rb: -------------------------------------------------------------------------------- 1 | class AddRawHtmlToFanzaItems < ActiveRecord::Migration[6.0] 2 | def change 3 | add_column :fanza_items, :raw_html, :text 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20200525005337_add_service_code_to_fanza_items.rb: -------------------------------------------------------------------------------- 1 | class AddServiceCodeToFanzaItems < ActiveRecord::Migration[6.0] 2 | def change 3 | add_column :fanza_items, :service_code, :string 4 | add_index :fanza_items, :service_code 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /db/migrate/20200526013230_create_mgstage_pages.rb: -------------------------------------------------------------------------------- 1 | class CreateMgstagePages < ActiveRecord::Migration[6.0] 2 | def change 3 | create_table :mgstage_pages do |t| 4 | t.string :url 5 | t.text :raw_html 6 | 7 | t.timestamps 8 | end 9 | 10 | add_index :mgstage_pages, :url, unique: true 11 | add_index :javlibrary_pages, :url, unique: true 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /db/migrate/20200526022151_create_mgstage_items.rb: -------------------------------------------------------------------------------- 1 | class CreateMgstageItems < ActiveRecord::Migration[6.0] 2 | def change 3 | create_table :mgstage_items do |t| 4 | t.string :normalized_id 5 | t.references :mgstage_page, null: false, foreign_key: true 6 | 7 | t.timestamps 8 | end 9 | 10 | add_index :mgstage_items, :normalized_id, unique: true 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /db/migrate/20200527060116_index_fanza_items_on_date.rb: -------------------------------------------------------------------------------- 1 | class IndexFanzaItemsOnDate < ActiveRecord::Migration[6.0] 2 | def change 3 | add_index :fanza_items, :date 4 | add_index :fanza_items, [:normalized_id, :date] 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /db/migrate/20200527172233_add_actresses_to_javlibrary_items_and_mgstage_items.rb: -------------------------------------------------------------------------------- 1 | class AddActressesToJavlibraryItemsAndMgstageItems < ActiveRecord::Migration[6.0] 2 | def change 3 | add_column :javlibrary_items, :actress_names, :string, array: true 4 | add_index :javlibrary_items, :actress_names, using: :gin 5 | add_column :mgstage_items, :actress_names, :string, array: true 6 | add_index :mgstage_items, :actress_names, using: :gin 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /db/migrate/20200529173336_create_movies.rb: -------------------------------------------------------------------------------- 1 | class CreateMovies < ActiveRecord::Migration[6.0] 2 | def change 3 | create_table :movies do |t| 4 | t.string :normalized_id, null: false 5 | t.string :compressed_id, null: false 6 | t.datetime :date 7 | end 8 | 9 | add_index :movies, :normalized_id, unique: true 10 | add_index :movies, :compressed_id 11 | add_index :movies, :date 12 | add_index :movies, [:date, :normalized_id] 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /db/migrate/20200531074405_add_actress_ids_to_names_to_movies.rb: -------------------------------------------------------------------------------- 1 | class AddActressIdsToNamesToMovies < ActiveRecord::Migration[6.0] 2 | def change 3 | add_column :movies, :actress_fanza_ids, :integer, array: true 4 | add_index :movies, :actress_fanza_ids, using: :gin 5 | add_column :movies, :actress_names, :string, array: true 6 | add_index :movies, :actress_names, using: :gin 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /db/migrate/20200531200252_reindex_fanza_actresses.rb: -------------------------------------------------------------------------------- 1 | class ReindexFanzaActresses < ActiveRecord::Migration[6.0] 2 | def change 3 | add_index :fanza_actresses, :name, using: "gin", opclass: :gin_trgm_ops, name: "fuzzy_name" 4 | add_index :fanza_actresses, :raw_json, using: "gin", opclass: :jsonb_path_ops 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /db/migrate/20200531201955_reindex_movies.rb: -------------------------------------------------------------------------------- 1 | class ReindexMovies < ActiveRecord::Migration[6.0] 2 | def change 3 | add_index :movies, [:normalized_id, :compressed_id], using: "gin", opclass: :gin_trgm_ops 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20200531235219_add_timestamp_to_movies.rb: -------------------------------------------------------------------------------- 1 | class AddTimestampToMovies < ActiveRecord::Migration[6.0] 2 | def change 3 | add_timestamps :movies, null: true 4 | long_ago = DateTime.new(2020, 1, 1) 5 | Movie.update_all(created_at: long_ago, updated_at: long_ago) 6 | 7 | change_column_null :movies, :created_at, false 8 | change_column_null :movies, :updated_at, false 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /db/migrate/20201113195144_add_is_hidden_to_movies.rb: -------------------------------------------------------------------------------- 1 | class AddIsHiddenToMovies < ActiveRecord::Migration[6.0] 2 | def change 3 | add_column :movies, :is_hidden, :boolean, default: false, null: false 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20201113201050_add_is_hidden_to_fanza_actresses.rb: -------------------------------------------------------------------------------- 1 | class AddIsHiddenToFanzaActresses < ActiveRecord::Migration[6.0] 2 | def change 3 | add_column :fanza_actresses, :is_hidden, :boolean, default: false, null: false 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20210629214501_add_cover_image_url_to_movies.rb: -------------------------------------------------------------------------------- 1 | class AddCoverImageUrlToMovies < ActiveRecord::Migration[6.1] 2 | def change 3 | add_column :movies, :cover_image_url, :string 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20211021205018_add_description_to_fanza_items.rb: -------------------------------------------------------------------------------- 1 | class AddDescriptionToFanzaItems < ActiveRecord::Migration[6.1] 2 | def change 3 | add_column :fanza_items, :description, :text 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20211022175250_remove_raw_html_from_fanza_items.rb: -------------------------------------------------------------------------------- 1 | class RemoveRawHtmlFromFanzaItems < ActiveRecord::Migration[6.1] 2 | def change 3 | remove_column :fanza_items, :raw_html, :text 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20211022185418_create_sod_pages.rb: -------------------------------------------------------------------------------- 1 | class CreateSodPages < ActiveRecord::Migration[6.1] 2 | def change 3 | create_table :sod_pages do |t| 4 | t.string :url 5 | t.text :raw_html 6 | 7 | t.timestamps 8 | end 9 | 10 | add_index :sod_pages, :url, unique: true 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /db/migrate/20211022185506_create_sod_items.rb: -------------------------------------------------------------------------------- 1 | class CreateSodItems < ActiveRecord::Migration[6.1] 2 | def change 3 | create_table :sod_items do |t| 4 | t.string :normalized_id 5 | t.references :sod_page, null: false, foreign_key: true 6 | t.string :actress_names, array: true 7 | 8 | t.timestamps 9 | end 10 | 11 | add_index :sod_items, :actress_names, using: :gin 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /db/migrate/20211112220153_add_priority_to_fanza_item.rb: -------------------------------------------------------------------------------- 1 | class AddPriorityToFanzaItem < ActiveRecord::Migration[6.1] 2 | def change 3 | add_column :fanza_items, :priority, :int, default: 0, null: false 4 | add_index :fanza_items, :priority 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /db/migrate/20230206233129_add_service_name_to_active_storage_blobs.active_storage.rb: -------------------------------------------------------------------------------- 1 | # This migration comes from active_storage (originally 20190112182829) 2 | class AddServiceNameToActiveStorageBlobs < ActiveRecord::Migration[6.0] 3 | def up 4 | return unless table_exists?(:active_storage_blobs) 5 | 6 | unless column_exists?(:active_storage_blobs, :service_name) 7 | add_column :active_storage_blobs, :service_name, :string 8 | 9 | if configured_service = ActiveStorage::Blob.service.name 10 | ActiveStorage::Blob.unscoped.update_all(service_name: configured_service) 11 | end 12 | 13 | change_column :active_storage_blobs, :service_name, :string, null: false 14 | end 15 | end 16 | 17 | def down 18 | return unless table_exists?(:active_storage_blobs) 19 | 20 | remove_column :active_storage_blobs, :service_name 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /db/migrate/20230206233130_create_active_storage_variant_records.active_storage.rb: -------------------------------------------------------------------------------- 1 | # This migration comes from active_storage (originally 20191206030411) 2 | class CreateActiveStorageVariantRecords < ActiveRecord::Migration[6.0] 3 | def change 4 | return unless table_exists?(:active_storage_blobs) 5 | 6 | # Use Active Record's configured type for primary key 7 | create_table :active_storage_variant_records, id: primary_key_type, if_not_exists: true do |t| 8 | t.belongs_to :blob, null: false, index: false, type: blobs_primary_key_type 9 | t.string :variation_digest, null: false 10 | 11 | t.index %i[ blob_id variation_digest ], name: "index_active_storage_variant_records_uniqueness", unique: true 12 | t.foreign_key :active_storage_blobs, column: :blob_id 13 | end 14 | end 15 | 16 | private 17 | def primary_key_type 18 | config = Rails.configuration.generators 19 | config.options[config.orm][:primary_key_type] || :primary_key 20 | end 21 | 22 | def blobs_primary_key_type 23 | pkey_name = connection.primary_key(:active_storage_blobs) 24 | pkey_column = connection.columns(:active_storage_blobs).find { |c| c.name == pkey_name } 25 | pkey_column.bigint? ? :bigint : pkey_column.type 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /db/migrate/20230206233131_remove_not_null_on_active_storage_blobs_checksum.active_storage.rb: -------------------------------------------------------------------------------- 1 | # This migration comes from active_storage (originally 20211119233751) 2 | class RemoveNotNullOnActiveStorageBlobsChecksum < ActiveRecord::Migration[6.0] 3 | def change 4 | return unless table_exists?(:active_storage_blobs) 5 | 6 | change_column_null(:active_storage_blobs, :checksum, true) 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /db/migrate/20240106233325_create_feeds.rb: -------------------------------------------------------------------------------- 1 | class CreateFeeds < ActiveRecord::Migration[7.1] 2 | def change 3 | create_table :feeds do |t| 4 | t.string :uri 5 | t.string :host 6 | t.text :content 7 | 8 | t.timestamps 9 | 10 | t.index :uri, unique: true 11 | t.index :host 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /db/migrate/20240108183945_add_accessed_at_to_feeds.rb: -------------------------------------------------------------------------------- 1 | class AddAccessedAtToFeeds < ActiveRecord::Migration[7.1] 2 | def change 3 | add_column :feeds, :accessed_at, :datetime, null: false, default: -> { 'NOW()' } 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20240109214432_add_tag_to_feeds.rb: -------------------------------------------------------------------------------- 1 | class AddTagToFeeds < ActiveRecord::Migration[7.1] 2 | def change 3 | add_column :feeds, :tag, :string 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20240415185222_create_feed_items.rb: -------------------------------------------------------------------------------- 1 | class CreateFeedItems < ActiveRecord::Migration[7.1] 2 | def change 3 | create_table :feed_items do |t| 4 | t.string :guid 5 | t.text :content 6 | 7 | t.timestamps 8 | end 9 | add_index :feed_items, :guid, unique: true 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /db/migrate/20240627220304_create_fc2_pages.rb: -------------------------------------------------------------------------------- 1 | class CreateFc2Pages < ActiveRecord::Migration[7.1] 2 | def change 3 | create_table :fc2_pages do |t| 4 | t.string :url 5 | t.text :raw_html 6 | 7 | t.timestamps 8 | end 9 | add_index :fc2_pages, :url 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /db/migrate/20240627220434_create_fc2_items.rb: -------------------------------------------------------------------------------- 1 | class CreateFc2Items < ActiveRecord::Migration[7.1] 2 | def change 3 | create_table :fc2_items do |t| 4 | t.string :normalized_id 5 | t.references :fc2_page, null: false, foreign_key: true 6 | t.string :actress_names, array: true 7 | 8 | t.timestamps 9 | end 10 | add_index :fc2_items, :actress_names, using: :gin 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /db/seeds.rb: -------------------------------------------------------------------------------- 1 | # This file should contain all the record creation needed to seed the database with its default values. 2 | # The data can then be loaded with the rails db:seed command (or created alongside the database with db:setup). 3 | # 4 | # Examples: 5 | # 6 | # movies = Movie.create([{ name: 'Star Wars' }, { name: 'Lord of the Rings' }]) 7 | # Character.create(name: 'Luke', movie: movies.first) 8 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | web: 3 | env_file: 4 | - .env 5 | - .env.production 6 | restart: always 7 | build: . 8 | ports: 9 | - 3000:3000 10 | links: 11 | - redis 12 | environment: 13 | REDIS_URL: redis://redis:6379/ 14 | command: ./bin/puma -w 2 15 | sidekiq: 16 | env_file: 17 | - .env 18 | - .env.production 19 | restart: always 20 | build: . 21 | links: 22 | - redis 23 | environment: 24 | REDIS_URL: redis://redis:6379/ 25 | command: bundle exec sidekiq 26 | redis: 27 | image: redis 28 | restart: always 29 | expose: 30 | - 6379 31 | 32 | -------------------------------------------------------------------------------- /lib/assets/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libredmm/librefanza/945319cf3d65ffaf27bff32e1ff52d0ec1f5b5be/lib/assets/.keep -------------------------------------------------------------------------------- /lib/tasks/dump_fixtures.rake: -------------------------------------------------------------------------------- 1 | require "open-uri" 2 | 3 | def dump_fixture(url, page, dir_name) 4 | dir = File.absolute_path( 5 | File.join( 6 | Rails.root, 7 | "spec/factories", 8 | dir_name, 9 | DateTime.now.strftime("%Y-%m-%d"), 10 | ) 11 | ) 12 | path = File.join( 13 | dir, 14 | Base64.urlsafe_encode64(url, padding: false), 15 | ) 16 | puts "#{url}\n=> #{path}" 17 | mkdir_p(dir) 18 | File.open(path, "w") do |f| 19 | f.write(page.raw_html) 20 | end 21 | end 22 | 23 | namespace :dump do 24 | desc "Dump javlibrary page from DB to fixtures" 25 | task :javlibrary, %i[url] => :environment do |_, args| 26 | url = args[:url] 27 | page = JavlibraryPage.find_by!(url: url) 28 | dump_fixture(url, page, "javlibrary_pages") 29 | end 30 | 31 | desc "Dump mgstage page from DB to fixtures" 32 | task :mgstage, %i[url] => :environment do |_, args| 33 | url = args[:url] 34 | page = MgstagePage.find_by!(url: url) 35 | dump_fixture(url, page, "mgstage_pages") 36 | end 37 | 38 | desc "Dump fc2 page from DB to fixtures" 39 | task :fc2, %i[url] => :environment do |_, args| 40 | url = args[:url] 41 | page = Fc2Page.find_by!(url: url) 42 | dump_fixture(url, page, "fc2_pages") 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /lib/tasks/house_keep.rake: -------------------------------------------------------------------------------- 1 | require "open-uri" 2 | 3 | desc "Do some house keeping" 4 | task :house_keep, %i[task] => :environment do |_, args| 5 | puts HouseKeeper.perform_async(args[:task].to_sym) 6 | end 7 | -------------------------------------------------------------------------------- /lib/tasks/refresh_feeds.rake: -------------------------------------------------------------------------------- 1 | desc "Refresh feeds" 2 | task :refresh_feeds, %i[interval max_failures] => :environment do |_, args| 3 | puts FeedRefresher.perform_async(args[:interval].to_i, args[:max_failures].to_i) 4 | end 5 | -------------------------------------------------------------------------------- /log/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libredmm/librefanza/945319cf3d65ffaf27bff32e1ff52d0ec1f5b5be/log/.keep -------------------------------------------------------------------------------- /public/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The page you were looking for doesn't exist (404) 5 | 6 | 55 | 56 | 57 | 58 | 59 |
    60 |
    61 |

    The page you were looking for doesn't exist.

    62 |

    You may have mistyped the address or the page may have moved.

    63 |
    64 |

    If you are the application owner check the logs for more information.

    65 |
    66 | 67 | 68 | -------------------------------------------------------------------------------- /public/422.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The change you wanted was rejected (422) 5 | 6 | 55 | 56 | 57 | 58 | 59 |
    60 |
    61 |

    The change you wanted was rejected.

    62 |

    Maybe you tried to change something you didn't have access to.

    63 |
    64 |

    If you are the application owner check the logs for more information.

    65 |
    66 | 67 | 68 | -------------------------------------------------------------------------------- /public/500.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | We're sorry, but something went wrong (500) 5 | 6 | 55 | 56 | 57 | 58 | 59 |
    60 |
    61 |

    We're sorry, but something went wrong.

    62 |
    63 |

    If you are the application owner check the logs for more information.

    64 |
    65 | 66 | 67 | -------------------------------------------------------------------------------- /public/apple-touch-icon-precomposed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libredmm/librefanza/945319cf3d65ffaf27bff32e1ff52d0ec1f5b5be/public/apple-touch-icon-precomposed.png -------------------------------------------------------------------------------- /public/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libredmm/librefanza/945319cf3d65ffaf27bff32e1ff52d0ec1f5b5be/public/apple-touch-icon.png -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libredmm/librefanza/945319cf3d65ffaf27bff32e1ff52d0ec1f5b5be/public/favicon.ico -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # See https://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file 2 | -------------------------------------------------------------------------------- /spec/factories/fanza_actresses.rb: -------------------------------------------------------------------------------- 1 | FactoryBot.define do 2 | sequence(:fanza_id) 3 | 4 | sequence(:name) do |i| 5 | "Actress #{i}" 6 | end 7 | 8 | factory :fanza_actress do 9 | transient { 10 | fanza_id 11 | name 12 | } 13 | 14 | raw_json { 15 | { 16 | id: fanza_id, 17 | name: name, 18 | imageURL: { 19 | large: generate(:url), 20 | small: generate(:url), 21 | }, 22 | } 23 | } 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /spec/factories/fanza_items.rb: -------------------------------------------------------------------------------- 1 | FactoryBot.define do 2 | sequence :content_id do |i| 3 | "content#{format("%05d", i)}" 4 | end 5 | 6 | sequence :normalized_id do |i| 7 | "NORMALIZED-#{format("%03d", i)}" 8 | end 9 | 10 | sequence :title do |i| 11 | "Item #{i}" 12 | end 13 | 14 | sequence :url do |i| 15 | "http://example.com/#{i}" 16 | end 17 | 18 | factory :fanza_item do 19 | transient do 20 | service_code { "mono" } 21 | floor_code { "dvd" } 22 | content_id 23 | title 24 | date { DateTime.now } 25 | actress { create(:fanza_actress) } 26 | end 27 | 28 | raw_json { 29 | { 30 | service_code: service_code, 31 | floor_code: floor_code, 32 | content_id: content_id, 33 | title: title, 34 | URL: generate(:url), 35 | imageURL: { 36 | large: generate(:url), 37 | small: generate(:url), 38 | }, 39 | affiliateURL: generate(:url), 40 | affiliateURLsp: generate(:url), 41 | date: date.to_s, 42 | review: { 43 | count: 10, 44 | average: 4.0, 45 | }, 46 | iteminfo: { 47 | actress: [{ 48 | id: actress.fanza_id, 49 | name: actress.name, 50 | }], 51 | }, 52 | } 53 | } 54 | end 55 | end 56 | -------------------------------------------------------------------------------- /spec/factories/fc2_items.rb: -------------------------------------------------------------------------------- 1 | FactoryBot.define do 2 | sequence :fc2_id do |i| 3 | "FC2-#{format("%03d", i)}" 4 | end 5 | 6 | factory :fc2_item do 7 | initialize_with { 8 | create(:fc2_page).fc2_item 9 | } 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /spec/factories/fc2_pages.rb: -------------------------------------------------------------------------------- 1 | FactoryBot.define do 2 | factory :fc2_page do 3 | transient do 4 | fixture_date { "2024-08-05" } 5 | end 6 | 7 | url { 8 | "http://dockyard:3002/get/https/adult.contents.fc2.com/article/4509988/" 9 | } 10 | 11 | raw_html { 12 | File.open( 13 | File.absolute_path( 14 | File.join( 15 | Rails.root, 16 | "spec/factories/fc2_pages", 17 | fixture_date, 18 | Base64.urlsafe_encode64(url, padding: false), 19 | ) 20 | ) 21 | ).read 22 | } 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /spec/factories/feed_items.rb: -------------------------------------------------------------------------------- 1 | FactoryBot.define do 2 | sequence(:guid) 3 | 4 | factory :feed_item do 5 | guid 6 | content { "" } 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /spec/factories/feeds.rb: -------------------------------------------------------------------------------- 1 | FactoryBot.define do 2 | sequence :feed_uri do |i| 3 | "http://rss.example.com/#{i}" 4 | end 5 | 6 | factory :feed do 7 | uri { generate(:feed_uri) } 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /spec/factories/javlibrary_items.rb: -------------------------------------------------------------------------------- 1 | FactoryBot.define do 2 | factory :javlibrary_item do 3 | initialize_with { 4 | create(:javlibrary_product_page).javlibrary_item 5 | } 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /spec/factories/javlibrary_pages.rb: -------------------------------------------------------------------------------- 1 | FactoryBot.define do 2 | factory :javlibrary_page do 3 | transient do 4 | fixture_date { "2020-05-20" } 5 | end 6 | 7 | factory :javlibrary_search_page do 8 | url { 9 | "http://www.javlibrary.com/ja/vl_searchbyid.php?keyword=ABP-001" 10 | } 11 | end 12 | 13 | factory :javlibrary_product_page do 14 | url { 15 | "http://www.javlibrary.com/ja/?v=javlio354y" 16 | } 17 | end 18 | 19 | raw_html { 20 | File.open( 21 | File.absolute_path( 22 | File.join( 23 | Rails.root, 24 | "spec/factories/javlibrary_pages", 25 | fixture_date, 26 | Base64.urlsafe_encode64(url, padding: false), 27 | ) 28 | ) 29 | ).read 30 | } 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /spec/factories/mgstage_items.rb: -------------------------------------------------------------------------------- 1 | FactoryBot.define do 2 | factory :mgstage_item do 3 | initialize_with { 4 | create(:mgstage_product_page).mgstage_item 5 | } 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /spec/factories/mgstage_pages.rb: -------------------------------------------------------------------------------- 1 | FactoryBot.define do 2 | factory :mgstage_page do 3 | transient do 4 | fixture_date { "2020-05-25" } 5 | end 6 | 7 | factory :mgstage_search_page do 8 | url { 9 | "https://www.mgstage.com/search/search.php?search_word=ARA-168" 10 | } 11 | end 12 | 13 | factory :mgstage_product_page do 14 | url { 15 | "https://www.mgstage.com/product/product_detail/261ARA-168/" 16 | } 17 | end 18 | 19 | raw_html { 20 | File.open( 21 | File.absolute_path( 22 | File.join( 23 | Rails.root, 24 | "spec/factories/mgstage_pages", 25 | fixture_date, 26 | Base64.urlsafe_encode64(url, padding: false), 27 | ) 28 | ) 29 | ).read 30 | } 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /spec/factories/movie.rb: -------------------------------------------------------------------------------- 1 | FactoryBot.define do 2 | factory :movie do 3 | initialize_with { 4 | create(:fanza_item).movie 5 | } 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /spec/factories/sod_items.rb: -------------------------------------------------------------------------------- 1 | FactoryBot.define do 2 | factory :sod_item do 3 | initialize_with { 4 | create(:sod_page).sod_item 5 | } 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /spec/factories/sod_pages.rb: -------------------------------------------------------------------------------- 1 | FactoryBot.define do 2 | factory :sod_page do 3 | transient do 4 | fixture_date { "2021-10-22" } 5 | end 6 | 7 | url { 8 | "https://ec.sod.co.jp/prime/videos/?id=STARS-455" 9 | } 10 | 11 | raw_html { 12 | File.open( 13 | File.absolute_path( 14 | File.join( 15 | Rails.root, 16 | "spec/factories/sod_pages", 17 | fixture_date, 18 | Base64.urlsafe_encode64(url, padding: false), 19 | ) 20 | ) 21 | ).read 22 | } 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /spec/factories/users.rb: -------------------------------------------------------------------------------- 1 | FactoryBot.define do 2 | sequence :email do |n| 3 | "user#{n}@example.com" 4 | end 5 | 6 | factory :user do 7 | email 8 | password { "password" } 9 | is_admin { false } 10 | 11 | factory :admin do 12 | is_admin { true } 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /spec/features/fanza_actresses_spec.rb: -------------------------------------------------------------------------------- 1 | require "rails_helper" 2 | 3 | RSpec.feature "FanzaActresses", type: :feature do 4 | scenario "actress has movies" do 5 | item = create :fanza_item 6 | actress = item.actresses.first 7 | visit fanza_actress_path(actress) 8 | expect(page).to have_text(item.normalized_id) 9 | end 10 | 11 | scenario "fuzzy searching" do 12 | actress = create :fanza_actress, name: "SPECIAL" 13 | create_list :fanza_actress, 3 14 | visit fanza_actresses_path(fuzzy: "special") 15 | expect(page).to have_selector(".card.actress", count: 1) 16 | end 17 | 18 | scenario "with hidden actresses" do 19 | actress = create :fanza_actress 20 | hidden = create :fanza_actress, is_hidden: true 21 | visit fanza_actresses_path 22 | expect(page).to have_text(actress.name) 23 | expect(page).not_to have_text(hidden.name) 24 | end 25 | 26 | context "sorting" do 27 | before(:each) do 28 | create :fanza_actress, fanza_id: 1, name: "Actress A" 29 | create :fanza_actress, fanza_id: 2, name: "Actress B" 30 | create :fanza_actress, fanza_id: 3, name: "Actress C" 31 | end 32 | 33 | scenario "by new" do 34 | visit fanza_actresses_path(order: "new") 35 | expect(page).to have_text(/Actress C.+Actress B.+Actress A/m) 36 | end 37 | 38 | scenario "by name" do 39 | visit fanza_actresses_path(order: "name") 40 | expect(page).to have_text(/Actress A.+Actress B.+Actress C/m) 41 | end 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /spec/features/movies_spec.rb: -------------------------------------------------------------------------------- 1 | require "rails_helper" 2 | 3 | RSpec.feature "Movies", type: :feature do 4 | scenario "fuzzy search" do 5 | abp = create :fanza_item, content_id: "abp123" 6 | sdde = create :fanza_item, content_id: "sdde234" 7 | visit movies_path(q: "abp") 8 | expect(page).to have_text(abp.normalized_id) 9 | expect(page).not_to have_text(sdde.normalized_id) 10 | end 11 | 12 | scenario "prefix search" do 13 | bp = create :fanza_item, content_id: "bp456" 14 | abp = create :fanza_item, content_id: "abp123" 15 | visit movies_path(q: "bp", style: "prefix") 16 | expect(page).to have_text(bp.normalized_id) 17 | expect(page).not_to have_text(abp.normalized_id) 18 | end 19 | 20 | scenario "with hidden movies" do 21 | movie = create :movie 22 | hidden = create :movie, is_hidden: true 23 | visit movies_path 24 | expect(page).to have_text(movie.normalized_id) 25 | expect(page).not_to have_text(hidden.normalized_id) 26 | end 27 | 28 | context "sorting" do 29 | before(:each) do 30 | create :fanza_item, content_id: "AA", date: 3.day.ago 31 | create :fanza_item, content_id: "BB", date: 1.day.ago 32 | create :fanza_item, content_id: "CC", date: 2.day.ago 33 | end 34 | 35 | scenario "by title" do 36 | visit movies_path(order: "title") 37 | expect(page).to have_text(/AA.+BB.+CC/m) 38 | end 39 | 40 | scenario "by release date" do 41 | visit movies_path(order: "release_date") 42 | expect(page).to have_text(/BB.+CC.+AA/m) 43 | end 44 | 45 | scenario "by date added" do 46 | visit movies_path(order: "date_added") 47 | expect(page).to have_text(/CC.+BB.+AA/m) 48 | end 49 | end 50 | end 51 | -------------------------------------------------------------------------------- /spec/lib/fanza/api_spec.rb: -------------------------------------------------------------------------------- 1 | require "rails_helper" 2 | 3 | RSpec.describe Fanza::Api do 4 | describe "#search" do 5 | it "calls fanza lazily" do 6 | Fanza::Api.search(keyword: generate(:normalized_id)) { break } 7 | expect(@item_list_stub).to have_been_requested 8 | end 9 | 10 | it "keeps calling until all results fetched" do 11 | Fanza::Api.search(keyword: generate(:normalized_id)) { next } 12 | expect(@item_list_stub).to have_been_requested.at_least_times(10) 13 | end 14 | 15 | it "is no-op on fc2 ids" do 16 | Fanza::Api.search(keyword: generate(:fc2_id)) { break } 17 | expect(@item_list_stub).not_to have_been_requested 18 | end 19 | end 20 | 21 | describe "#actress_search" do 22 | it "calls fanza lazily" do 23 | Fanza::Api.actress_search { break } 24 | expect(@actress_search_stub).to have_been_requested 25 | end 26 | 27 | it "keeps calling until all results fetched" do 28 | Fanza::Api.actress_search { next } 29 | expect(@actress_search_stub).to have_been_requested.times(5) 30 | end 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /spec/lib/fc2/api_spec.rb: -------------------------------------------------------------------------------- 1 | require "rails_helper" 2 | 3 | RSpec.describe Fc2::Api do 4 | describe "#search" do 5 | it "requests fc2" do 6 | Fc2::Api.search(generate(:fc2_id)) { next } 7 | expect(@fc2_stub).to have_been_requested.at_least_once 8 | end 9 | 10 | it "is no-op on non-fc2 ids" do 11 | Fc2::Api.search(generate(:normalized_id)) { break } 12 | expect(@fc2_stub).not_to have_been_requested 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /spec/lib/javlibrary/api_spec.rb: -------------------------------------------------------------------------------- 1 | require "rails_helper" 2 | 3 | RSpec.describe Javlibrary::Api do 4 | describe "#search" do 5 | it "requests javlibrary" do 6 | id = generate :normalized_id 7 | Javlibrary::Api.search(id) { next } 8 | expect(@javlibrary_stub).to have_been_requested 9 | end 10 | 11 | it "requests all product page" do 12 | html = double(:html) 13 | expect(Nokogiri).to receive(:HTML).and_return(html) 14 | a = double(:a) 15 | expect(html).to receive(:css).and_return([a]) 16 | url = "./elsewhere" 17 | expect(a).to receive(:attr).and_return(url) 18 | 19 | Javlibrary::Api.search(generate(:normalized_id)) { next } 20 | expect(@javlibrary_stub).to have_been_requested.twice 21 | end 22 | 23 | it "is no-op on fc2 ids" do 24 | Javlibrary::Api.search(generate(:fc2_id)) { break } 25 | expect(@javlibrary_stub).not_to have_been_requested 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /spec/lib/mgstage/api_spec.rb: -------------------------------------------------------------------------------- 1 | require "rails_helper" 2 | 3 | RSpec.describe Mgstage::Api do 4 | describe "#search" do 5 | it "requests mgstage" do 6 | Mgstage::Api.search(generate(:normalized_id)) { next } 7 | expect(@mgstage_stub).to have_been_requested.at_least_once 8 | end 9 | 10 | it "requests all product page" do 11 | html = double(:html) 12 | expect(Nokogiri).to receive(:HTML).and_return(html).at_least(:once) 13 | a = double(:a) 14 | expect(html).to receive(:css).and_return([a]).at_least(:once) 15 | url = "/elsewhere" 16 | expect(a).to receive(:attr).and_return(url).at_least(:once) 17 | 18 | Mgstage::Api.search(generate(:normalized_id)) { next } 19 | expect(@mgstage_stub).to have_been_requested.at_least_twice 20 | end 21 | 22 | it "is no-op on fc2 ids" do 23 | Mgstage::Api.search(generate(:fc2_id)) { break } 24 | expect(@mgstage_stub).not_to have_been_requested 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /spec/lib/sod/api_spec.rb: -------------------------------------------------------------------------------- 1 | require "rails_helper" 2 | 3 | RSpec.describe Sod::Api do 4 | describe "#search" do 5 | it "requests sod" do 6 | Sod::Api.search(generate(:normalized_id)) { next } 7 | expect(@sod_stub).to have_been_requested.at_least_once 8 | end 9 | 10 | it "is no-op on fc2 ids" do 11 | Sod::Api.search(generate(:fc2_id)) { break } 12 | expect(@sod_stub).not_to have_been_requested 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /spec/models/fanza_actress_spec.rb: -------------------------------------------------------------------------------- 1 | require "rails_helper" 2 | 3 | RSpec.describe FanzaActress, type: :model do 4 | let(:actress) { create :fanza_actress } 5 | 6 | describe ".image_url" do 7 | it "prefers large image" do 8 | expect(actress.image_url).to eq(actress.as_struct.imageURL.large) 9 | end 10 | 11 | it "falls back to small image" do 12 | actress.raw_json["imageURL"].delete("large") 13 | expect(actress.image_url).to eq(actress.as_struct.imageURL.small) 14 | end 15 | end 16 | 17 | describe ".as_json" do 18 | it "contains image url" do 19 | expect(actress.as_json).to include("image_url") 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /spec/models/fanza_item_spec.rb: -------------------------------------------------------------------------------- 1 | require "rails_helper" 2 | 3 | RSpec.describe FanzaItem, type: :model do 4 | subject { create :fanza_item } 5 | 6 | it_behaves_like "generic item" 7 | 8 | %i[floor_code].each do |key| 9 | it "has #{key}" do 10 | expect(subject.send(key)).to be_present 11 | end 12 | end 13 | 14 | it "falls back to maker product when content id can not be normalized" do 15 | expect { 16 | subject.raw_json["content_id"] = "123abc" 17 | subject.raw_json["maker_product"] = "MAKER-123" 18 | subject.derive_fields 19 | subject.save 20 | }.to change { 21 | subject.normalized_id 22 | }.to("MAKER-123") 23 | end 24 | 25 | describe "derive_fields" do 26 | it "works with invalid url" do 27 | allow(subject).to receive(:url).and_return("http://bad.url/") 28 | subject.derive_fields 29 | end 30 | end 31 | 32 | describe ".actresses" do 33 | it "returns empty array when no provided" do 34 | expect { 35 | subject.raw_json["iteminfo"].delete("actress") 36 | }.to change { 37 | subject.actresses 38 | }.to([]) 39 | end 40 | end 41 | 42 | describe ".cover_image_url" do 43 | context "for DANDY series" do 44 | subject { create :fanza_item, content_id: "dandy00123" } 45 | 46 | context "when sample image urls are present" do 47 | it "returns first sample image url" do 48 | subject.raw_json["sampleImageURL"] = { "sample_l": { "image": ["dummy_url"] } } 49 | expect(subject.cover_image_url).to eq("dummy_url") 50 | end 51 | end 52 | 53 | context "when no sample image urls" do 54 | it "returns cover image url" do 55 | subject.raw_json["imageURL"]["large"] = "dummy_url" 56 | subject.raw_json["sampleImageURL"] = {} 57 | expect(subject.cover_image_url).to eq("dummy_url") 58 | end 59 | end 60 | end 61 | end 62 | 63 | describe ".as_json" do 64 | it "includes title" do 65 | expect(subject.as_json).to include("title") 66 | end 67 | 68 | it "filters out affiliate urls" do 69 | expect(subject.as_json).not_to include("affiliateURL") 70 | expect(subject.as_json).not_to include("affiliateURLsp") 71 | end 72 | end 73 | end 74 | -------------------------------------------------------------------------------- /spec/models/fc2_item_spec.rb: -------------------------------------------------------------------------------- 1 | require "rails_helper" 2 | 3 | RSpec.describe Fc2Item, type: :model do 4 | subject { create(:fc2_item) } 5 | 6 | it_behaves_like "generic item" 7 | end 8 | -------------------------------------------------------------------------------- /spec/models/fc2_page_spec.rb: -------------------------------------------------------------------------------- 1 | require "rails_helper" 2 | 3 | RSpec.describe Fc2Page, type: :model do 4 | let(:page) { create :fc2_page } 5 | 6 | describe "product page" do 7 | it "has item" do 8 | expect(page.fc2_item).to be_present 9 | end 10 | end 11 | 12 | describe "home page" do 13 | it "gets rejected" do 14 | url = generate :url 15 | html = "" 16 | expect { 17 | Fc2Page.create!(url: url, raw_html: html) 18 | }.to raise_error(ActiveRecord::RecordInvalid) 19 | end 20 | end 21 | 22 | describe "invalid page" do 23 | it "does not have item" do 24 | url = generate :url 25 | html = "items_article_header" 26 | page = Fc2Page.create!(url: url, raw_html: html) 27 | expect(page.fc2_item).not_to be_present 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /spec/models/feed_item_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | RSpec.describe FeedItem, type: :model do 4 | end 5 | -------------------------------------------------------------------------------- /spec/models/feed_spec.rb: -------------------------------------------------------------------------------- 1 | require "rails_helper" 2 | 3 | RSpec.describe Feed, type: :model do 4 | subject { create :feed } 5 | 6 | context "on create" do 7 | it "parses host" do 8 | expect(subject.host).not_to be_nil 9 | end 10 | 11 | it "fetches content" do 12 | expect(subject.content).not_to be_nil 13 | end 14 | 15 | it "rejects invalid uri" do 16 | expect { 17 | create :feed, uri: "http://bad.url" 18 | }.to raise_error ActiveRecord::RecordInvalid 19 | end 20 | 21 | it "rejects empty feed" do 22 | stub_request(:any, %r{rss.example.com}).to_return( 23 | body: %q{ 24 | 25 | 26 | 27 | RSS Title 28 | https://example.com 29 | 30 | 31 | }, 32 | ) 33 | expect { 34 | create :feed 35 | }.to raise_error ActiveRecord::RecordInvalid 36 | end 37 | end 38 | 39 | describe "refresh!" do 40 | it "updates content" do 41 | subject 42 | stub_request(:any, %r{rss.example.com}).to_return( 43 | body: %q{ 44 | 45 | 46 | 47 | RSS Title 48 | https://example.com 49 | 50 | Item 1 51 | https://example.com/items/1 52 | 53 | 54 | Item 2 55 | https://example.com/items/2 56 | 57 | 58 | Item 3 59 | https://example.com/items/3 60 | 61 | 62 | 63 | }, 64 | ) 65 | expect { subject.refresh! }.to change { subject.content } 66 | end 67 | 68 | it "raises error on invalid uri" do 69 | subject.uri = "http://bad.url" 70 | expect { subject.refresh! }.to raise_error ActiveRecord::RecordInvalid 71 | end 72 | end 73 | 74 | describe "by_uri" do 75 | it "updates accessed at" do 76 | expect { 77 | Feed.by_uri(subject.uri) 78 | }.to change { subject.reload.accessed_at } 79 | end 80 | end 81 | end 82 | -------------------------------------------------------------------------------- /spec/models/javlibrary_item_spec.rb: -------------------------------------------------------------------------------- 1 | require "rails_helper" 2 | 3 | RSpec.describe JavlibraryItem, type: :model do 4 | subject { create(:javlibrary_item) } 5 | 6 | it_behaves_like "generic item" 7 | 8 | context "on destroy" do 9 | it "does not destroy movie that has other items" do 10 | id = subject.normalized_id 11 | create :fanza_item, content_id: id 12 | subject.destroy 13 | expect(Movie.where(normalized_id: id)).to exist 14 | end 15 | end 16 | 17 | it "uses cover image url of movie when available" do 18 | expect { 19 | subject.movie.cover_image_url = "dummy_url" 20 | }.to change { 21 | subject.cover_image_url 22 | }.to("https://imageproxy.libredmm.com/dummy_url") 23 | end 24 | 25 | describe "thumbnail_image_url" do 26 | context "when cover image is from dmm" do 27 | it "guesses thumbnail from dmm" do 28 | allow(subject).to receive(:cover_image_url).and_return( 29 | "http://pics.dmm.co.jp/mono/movie/adult/atad106/atad106pl.jpg" 30 | ) 31 | expect(subject.thumbnail_image_url).to eq( 32 | "http://pics.dmm.co.jp/mono/movie/adult/atad106/atad106ps.jpg" 33 | ) 34 | end 35 | end 36 | 37 | context "when cover image is not from dmm" do 38 | it "crops thumbnail from cover" do 39 | allow(subject).to receive(:cover_image_url).and_return("dummy_url") 40 | expect(subject.thumbnail_image_url).to eq("https://imageproxy.libredmm.com/cx.53/dummy_url") 41 | end 42 | end 43 | 44 | context "when cover image is overridden" do 45 | it "does not double proxy" do 46 | expect { 47 | subject.movie.cover_image_url = "dummy_url" 48 | }.to change { 49 | subject.thumbnail_image_url 50 | }.to("https://imageproxy.libredmm.com/cx.53/dummy_url") 51 | end 52 | end 53 | end 54 | end 55 | -------------------------------------------------------------------------------- /spec/models/javlibrary_page_spec.rb: -------------------------------------------------------------------------------- 1 | require "rails_helper" 2 | 3 | RSpec.describe JavlibraryPage, type: :model do 4 | describe "search page" do 5 | let(:page) { create :javlibrary_search_page } 6 | 7 | it "does not have item" do 8 | expect(page.javlibrary_item).not_to be_present 9 | end 10 | 11 | it "recreates items on save" do 12 | product_page = build :javlibrary_product_page 13 | 14 | expect { 15 | page.raw_html = product_page.raw_html 16 | page.save 17 | }.to change { 18 | page.javlibrary_item 19 | }.from(nil) 20 | end 21 | end 22 | 23 | describe "product page" do 24 | let(:page) { create :javlibrary_product_page } 25 | 26 | it "has item" do 27 | expect(page.javlibrary_item).to be_present 28 | end 29 | 30 | it "rebuilds items on save" do 31 | id = page.javlibrary_item.normalized_id 32 | new_id = generate(:normalized_id) 33 | expect { 34 | page.raw_html.gsub!(id, new_id) 35 | page.save 36 | }.to change { 37 | page.javlibrary_item.normalized_id 38 | }.from(id).to(new_id) 39 | end 40 | end 41 | 42 | describe "challenged page" do 43 | it "gets rejected" do 44 | url = generate :url 45 | html = "challenge-form" 46 | expect { 47 | JavlibraryPage.create!(url: url, raw_html: html) 48 | }.to raise_error(ActiveRecord::RecordInvalid) 49 | end 50 | end 51 | 52 | describe "access denied page" do 53 | it "gets rejected" do 54 | url = generate :url 55 | html = "Access denied" 56 | expect { 57 | JavlibraryPage.create!(url: url, raw_html: html) 58 | }.to raise_error(ActiveRecord::RecordInvalid) 59 | end 60 | end 61 | end 62 | -------------------------------------------------------------------------------- /spec/models/mgstage_item_spec.rb: -------------------------------------------------------------------------------- 1 | require "rails_helper" 2 | 3 | RSpec.describe MgstageItem, type: :model do 4 | subject { create(:mgstage_item) } 5 | 6 | it_behaves_like "generic item" 7 | end 8 | -------------------------------------------------------------------------------- /spec/models/mgstage_page_spec.rb: -------------------------------------------------------------------------------- 1 | require "rails_helper" 2 | 3 | RSpec.describe MgstagePage, type: :model do 4 | describe "search page" do 5 | let(:page) { create :mgstage_search_page } 6 | 7 | it "does not have item" do 8 | expect(page.mgstage_item).not_to be_present 9 | end 10 | 11 | it "recreates items on save" do 12 | product_page = build :mgstage_product_page 13 | 14 | expect { 15 | page.raw_html = product_page.raw_html 16 | page.save 17 | }.to change { 18 | page.mgstage_item 19 | }.from(nil) 20 | end 21 | end 22 | 23 | describe "product page" do 24 | let(:page) { create :mgstage_product_page } 25 | 26 | it "has item" do 27 | expect(page.mgstage_item).to be_present 28 | end 29 | 30 | it "rebuilds items on save" do 31 | id = page.mgstage_item.normalized_id 32 | new_id = generate(:normalized_id) 33 | expect { 34 | page.raw_html.gsub!(id, new_id) 35 | page.save 36 | }.to change { 37 | page.mgstage_item.normalized_id 38 | }.from(id).to(new_id) 39 | end 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /spec/models/movie_spec.rb: -------------------------------------------------------------------------------- 1 | require "rails_helper" 2 | 3 | RSpec.describe Movie, type: :model do 4 | context "on item change" do 5 | context "when no items left" do 6 | it "destroys itself" do 7 | item = create :fanza_item 8 | movie = item.movie 9 | 10 | expect { 11 | item.update(normalized_id: "CHANGED-001") 12 | }.to change { 13 | Movie.exists?(movie.id) 14 | }.from(true).to(false) 15 | end 16 | end 17 | 18 | context "when any item left" do 19 | it "keeps alive" do 20 | item = create :fanza_item 21 | movie = item.movie 22 | 23 | another_item = create :fanza_item 24 | another_item.update_column(:normalized_id, item.normalized_id) 25 | expect(another_item.movie).to eq(movie) 26 | 27 | expect { 28 | item.update(normalized_id: "CHANGED-001") 29 | }.not_to change { 30 | Movie.exists?(movie.id) 31 | } 32 | end 33 | end 34 | 35 | it "updates date" do 36 | item = create :fanza_item 37 | movie = item.movie 38 | 39 | another_item = create :fanza_item 40 | another_item.update_column(:normalized_id, item.normalized_id) 41 | 42 | expect { 43 | another_item.update(date: 1.day.from_now) 44 | }.to change { 45 | movie.reload.date 46 | } 47 | end 48 | end 49 | 50 | it "prefers fanza items" do 51 | javlibrary_item = create :javlibrary_item 52 | fanza_item = create :fanza_item, content_id: javlibrary_item.normalized_id 53 | movie = javlibrary_item.movie 54 | 55 | expect(fanza_item.movie).to eq(movie) 56 | expect(movie.preferred_item).to eq(fanza_item) 57 | expect(fanza_item.preferred?).to eq(true) 58 | expect(javlibrary_item.preferred?).to eq(false) 59 | end 60 | 61 | it "prefers fanza items with highest priority" do 62 | low_pri = create :fanza_item 63 | hi_pri = create :fanza_item, content_id: low_pri.normalized_id, priority: 1 64 | movie = low_pri.movie 65 | 66 | expect(movie.preferred_item).to eq(hi_pri) 67 | expect(hi_pri.preferred?).to eq(true) 68 | expect(low_pri.preferred?).to eq(false) 69 | end 70 | end 71 | -------------------------------------------------------------------------------- /spec/models/sod_item_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | RSpec.describe SodItem, type: :model do 4 | subject { create(:sod_item) } 5 | 6 | it_behaves_like "generic item" 7 | end 8 | -------------------------------------------------------------------------------- /spec/models/sod_page_spec.rb: -------------------------------------------------------------------------------- 1 | require "rails_helper" 2 | 3 | RSpec.describe SodPage, type: :model do 4 | let(:page) { create :sod_page } 5 | 6 | describe "product page" do 7 | it "has item" do 8 | expect(page.sod_item).to be_present 9 | end 10 | 11 | it "rebuilds items on save" do 12 | id = page.sod_item.normalized_id 13 | new_id = generate(:normalized_id) 14 | expect { 15 | page.raw_html.gsub!(id, new_id) 16 | page.save 17 | }.to change { 18 | page.sod_item.normalized_id 19 | }.from(id).to(new_id) 20 | end 21 | end 22 | 23 | describe "home page" do 24 | it "gets rejected" do 25 | url = generate :url 26 | html = "" 27 | expect { 28 | SodPage.create!(url: url, raw_html: html) 29 | }.to raise_error(ActiveRecord::RecordInvalid) 30 | end 31 | end 32 | 33 | describe "invalid page" do 34 | it "does not have item" do 35 | url = generate :url 36 | html = "middle_videos" 37 | page = SodPage.create!(url: url, raw_html: html) 38 | expect(page.sod_item).not_to be_present 39 | end 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /spec/models/user_spec.rb: -------------------------------------------------------------------------------- 1 | require "rails_helper" 2 | 3 | RSpec.describe User, type: :model do 4 | describe ".reset_api_token!" do 5 | it "works" do 6 | user = create :user 7 | expect { 8 | user.reset_api_token! 9 | }.to change { 10 | user.api_token 11 | } 12 | end 13 | end 14 | 15 | context "on create" do 16 | it "generates api token" do 17 | user = create :user 18 | expect(user.api_token).not_to be_blank 19 | end 20 | end 21 | 22 | context "on update" do 23 | it "validates api token" do 24 | user = create :user 25 | user.api_token = "" 26 | expect { 27 | user.save! 28 | }.to raise_error(ActiveRecord::RecordInvalid) 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /spec/requests/fanza_actresses_spec.rb: -------------------------------------------------------------------------------- 1 | require "rails_helper" 2 | 3 | # This spec was generated by rspec-rails when you ran the scaffold generator. 4 | # It demonstrates how one might use RSpec to test the controller code that 5 | # was generated by Rails when you ran the scaffold generator. 6 | # 7 | # It assumes that the implementation code is generated by the rails scaffold 8 | # generator. If you are using any extension libraries to generate different 9 | # controller code, this generated spec may or may not pass. 10 | # 11 | # It only uses APIs available in rails and/or rspec-rails. There are a number 12 | # of tools you can use to make these specs even more expressive, but we're 13 | # sticking to rails and rspec-rails APIs to keep things simple and stable. 14 | 15 | RSpec.describe "FanzaActresses", type: :request do 16 | let(:actress) { create :fanza_actress } 17 | 18 | describe "GET /fanza_actresses" do 19 | it "works" do 20 | get fanza_actresses_path 21 | expect(response).to have_http_status(200) 22 | end 23 | end 24 | 25 | describe "GET /fanza_actresses/:id" do 26 | it "works" do 27 | get fanza_actress_path(actress) 28 | expect(response).to have_http_status(200) 29 | end 30 | 31 | it "accept json format" do 32 | get fanza_actress_path(actress, format: :json) 33 | expect(response).to have_http_status(200) 34 | end 35 | end 36 | 37 | describe "GET /fanza_actresses/:name" do 38 | it "works with known name" do 39 | get fanza_actress_path(actress.name) 40 | expect(response).to have_http_status(200) 41 | end 42 | 43 | it "works with unknown name" do 44 | get fanza_actress_path("UNKNOWN") 45 | expect(response).to have_http_status(200) 46 | end 47 | end 48 | 49 | context "when hidden" do 50 | let(:hidden_actress) { create :fanza_actress, is_hidden: true } 51 | 52 | describe "GET /fanza_actresses/:id" do 53 | it "returns not found" do 54 | expect { 55 | get fanza_actress_path(hidden_actress) 56 | }.to raise_error(ActiveRecord::RecordNotFound) 57 | end 58 | end 59 | 60 | describe "GET /fanza_actresses/:name" do 61 | it "returns not found" do 62 | expect { 63 | get fanza_actress_path(hidden_actress.name) 64 | }.to raise_error(ActiveRecord::RecordNotFound) 65 | end 66 | end 67 | end 68 | end 69 | -------------------------------------------------------------------------------- /spec/requests/fanza_items_spec.rb: -------------------------------------------------------------------------------- 1 | require "rails_helper" 2 | 3 | RSpec.describe "FanzaItems", type: :request do 4 | let(:item) { create(:fanza_item) } 5 | let(:user) { create(:user) } 6 | let(:admin) { create(:admin) } 7 | 8 | describe "GET /fanza_items" do 9 | it "works for admin" do 10 | get fanza_items_path(as: admin) 11 | expect(response).to have_http_status(200) 12 | end 13 | 14 | it "rejects other users" do 15 | expect { 16 | get fanza_items_path(as: user) 17 | }.to raise_error(ActionController::RoutingError) 18 | end 19 | end 20 | 21 | describe "GET /fanza_items/:id" do 22 | it "works" do 23 | get fanza_item_path(item, as: user) 24 | expect(response).to have_http_status(200) 25 | end 26 | 27 | context "with format json" do 28 | it "works" do 29 | get fanza_item_path(item, format: :json, as: user) 30 | expect(response).to have_http_status(200) 31 | expect(response.body).to include("normalized_id") 32 | end 33 | 34 | context "requesting raw" do 35 | it "returns raw json" do 36 | get fanza_item_path(item, format: :json, raw: true, as: user) 37 | expect(response).to have_http_status(200) 38 | expect(response.body).to include("content_id") 39 | end 40 | 41 | it "filters out affiliate info" do 42 | get fanza_item_path(item, format: :json, raw: true, as: user) 43 | expect(response).to have_http_status(200) 44 | expect(response.body).not_to include("affiliate") 45 | end 46 | end 47 | end 48 | end 49 | 50 | describe "DELETE /fanza_items/:id" do 51 | it "works for admin" do 52 | delete fanza_item_path(item, as: admin) 53 | expect(response).to have_http_status(200) 54 | end 55 | 56 | it "rejects other users" do 57 | expect { 58 | delete fanza_item_path(item, as: user) 59 | }.to raise_error(ActionController::RoutingError) 60 | end 61 | end 62 | 63 | describe "PUT /fanza_items/:id" do 64 | context "for admin" do 65 | it "changes priority" do 66 | expect { 67 | put fanza_item_path(item, priority_inc: 2, as: admin) 68 | }.to change { 69 | item.reload.priority 70 | }.by(2) 71 | end 72 | 73 | it "redirects to movie if new item preferred" do 74 | put fanza_item_path(item, priority_inc: 2, as: admin) 75 | expect(response).to redirect_to(item.movie) 76 | end 77 | 78 | it "redirects to item if new item not preferred" do 79 | create :fanza_item, content_id: item.normalized_id 80 | put fanza_item_path(item, priority_inc: -2, as: admin) 81 | expect(response).to redirect_to(item) 82 | end 83 | end 84 | 85 | it "rejects other users" do 86 | expect { 87 | put fanza_item_path(item, as: user) 88 | }.to raise_error(ActionController::RoutingError) 89 | end 90 | end 91 | end 92 | -------------------------------------------------------------------------------- /spec/requests/fc2_items_spec.rb: -------------------------------------------------------------------------------- 1 | require "rails_helper" 2 | 3 | RSpec.describe "Fc2Items", type: :request do 4 | let(:item) { create(:fc2_item) } 5 | let(:user) { create(:user) } 6 | let(:admin) { create(:admin) } 7 | 8 | describe "GET /fc2_items" do 9 | it "works for admin" do 10 | get fc2_items_path(as: admin) 11 | expect(response).to have_http_status(200) 12 | end 13 | 14 | it "rejects other users" do 15 | expect { 16 | get fc2_items_path(as: user) 17 | }.to raise_error(ActionController::RoutingError) 18 | end 19 | end 20 | 21 | describe "GET /fc2_items/:id" do 22 | it "works" do 23 | get fc2_item_path(item, as: user) 24 | expect(response).to have_http_status(200) 25 | end 26 | end 27 | 28 | describe "DELETE /fc2_items/:id" do 29 | it "works for admin" do 30 | delete fc2_item_path(item, as: admin) 31 | expect(response).to have_http_status(200) 32 | end 33 | 34 | it "rejects other users" do 35 | expect { 36 | delete fc2_item_path(item, as: user) 37 | }.to raise_error(ActionController::RoutingError) 38 | end 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /spec/requests/fc2_pages_spec.rb: -------------------------------------------------------------------------------- 1 | require "rails_helper" 2 | 3 | RSpec.describe "Fc2Pages", type: :request do 4 | let(:page) { create(:fc2_page) } 5 | let(:user) { create(:user) } 6 | let(:admin) { create(:admin) } 7 | 8 | describe "GET /fc2_pages" do 9 | it "works for admin" do 10 | get fc2_pages_path(as: admin) 11 | expect(response).to have_http_status(200) 12 | end 13 | 14 | it "rejects other users" do 15 | expect { 16 | get fc2_pages_path(as: user) 17 | }.to raise_error(ActionController::RoutingError) 18 | end 19 | end 20 | 21 | describe "GET /fc2_pages/:id" do 22 | it "works for admin" do 23 | get fc2_page_path(page, as: admin) 24 | expect(response).to have_http_status(200) 25 | end 26 | 27 | it "rejects other users" do 28 | expect { 29 | get fc2_page_path(page, as: user) 30 | }.to raise_error(ActionController::RoutingError) 31 | end 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /spec/requests/javlibrary_items_spec.rb: -------------------------------------------------------------------------------- 1 | require "rails_helper" 2 | 3 | RSpec.describe "JavlibraryItems", type: :request do 4 | let(:item) { create(:javlibrary_item) } 5 | let(:user) { create(:user) } 6 | let(:admin) { create(:admin) } 7 | 8 | describe "GET /javlibrary_items" do 9 | it "works for admin" do 10 | get javlibrary_items_path(as: admin) 11 | expect(response).to have_http_status(200) 12 | end 13 | 14 | it "rejects other users" do 15 | expect { 16 | get javlibrary_items_path(as: user) 17 | }.to raise_error(ActionController::RoutingError) 18 | end 19 | end 20 | 21 | describe "GET /javlibrary_items/:id" do 22 | it "works" do 23 | get javlibrary_item_path(item, as: user) 24 | expect(response).to have_http_status(200) 25 | end 26 | end 27 | 28 | describe "DELETE /javlibrary_items/:id" do 29 | it "works for admin" do 30 | delete javlibrary_item_path(item, as: admin) 31 | expect(response).to have_http_status(200) 32 | end 33 | 34 | it "rejects other users" do 35 | expect { 36 | delete javlibrary_item_path(item, as: user) 37 | }.to raise_error(ActionController::RoutingError) 38 | end 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /spec/requests/javlibrary_pages_spec.rb: -------------------------------------------------------------------------------- 1 | require "rails_helper" 2 | 3 | RSpec.describe "JavlibraryPages", type: :request do 4 | let(:user) { create(:user) } 5 | let(:admin) { create(:admin) } 6 | 7 | describe "GET /javlibrary_pages" do 8 | it "works for admin" do 9 | get javlibrary_pages_path(as: admin) 10 | expect(response).to have_http_status(200) 11 | end 12 | 13 | it "rejects other users" do 14 | expect { 15 | get javlibrary_pages_path(as: user) 16 | }.to raise_error(ActionController::RoutingError) 17 | end 18 | end 19 | 20 | describe "GET /javlibrary_pages/:id" do 21 | let(:page) { create(:javlibrary_product_page) } 22 | 23 | it "works for admin" do 24 | get javlibrary_page_path(page, as: admin) 25 | expect(response).to have_http_status(200) 26 | end 27 | 28 | it "rejects other users" do 29 | expect { 30 | get javlibrary_page_path(page, as: user) 31 | }.to raise_error(ActionController::RoutingError) 32 | end 33 | end 34 | 35 | describe "GET /javlibrary_pages/new" do 36 | it "works for admin" do 37 | get new_javlibrary_page_path(as: admin) 38 | expect(response).to have_http_status(200) 39 | end 40 | 41 | it "rejects other users" do 42 | expect { 43 | get new_javlibrary_page_path(as: user) 44 | }.to raise_error(ActionController::RoutingError) 45 | end 46 | end 47 | 48 | describe "POST /javlibrary_pages/" do 49 | it "rejects non admins" do 50 | expect { 51 | post javlibrary_pages_path(as: user) 52 | }.to raise_error(ActionController::RoutingError) 53 | end 54 | 55 | describe "with invalid params" do 56 | it "renders form with errors" do 57 | post javlibrary_pages_path( 58 | javlibrary_page: { url: "", raw_html: "" }, 59 | as: admin, 60 | ) 61 | expect(response.body).to include("invalid-feedback") 62 | end 63 | end 64 | 65 | describe "when page contains item" do 66 | it "redirect to item" do 67 | attrs = attributes_for(:javlibrary_product_page) 68 | post javlibrary_pages_path(javlibrary_page: attrs, as: admin) 69 | page = JavlibraryPage.find_by(url: attrs[:url]) 70 | expect(response).to redirect_to(page.javlibrary_item) 71 | end 72 | end 73 | 74 | describe "when page does not contain item" do 75 | it "redirect to item" do 76 | url = generate :url 77 | post javlibrary_pages_path( 78 | javlibrary_page: { 79 | url: url, 80 | raw_html: "", 81 | }, 82 | as: admin, 83 | ) 84 | page = JavlibraryPage.find_by(url: url) 85 | expect(response).to redirect_to(page) 86 | end 87 | end 88 | end 89 | end 90 | -------------------------------------------------------------------------------- /spec/requests/mgstage_items_spec.rb: -------------------------------------------------------------------------------- 1 | require "rails_helper" 2 | 3 | RSpec.describe "MgstageItems", type: :request do 4 | let(:item) { create(:mgstage_item) } 5 | let(:user) { create(:user) } 6 | let(:admin) { create(:admin) } 7 | 8 | describe "GET /mgstage_items" do 9 | it "works for admin" do 10 | get mgstage_items_path(as: admin) 11 | expect(response).to have_http_status(200) 12 | end 13 | 14 | it "rejects other users" do 15 | expect { 16 | get mgstage_items_path(as: user) 17 | }.to raise_error(ActionController::RoutingError) 18 | end 19 | end 20 | 21 | describe "GET /mgstage_items/:id" do 22 | it "works" do 23 | get mgstage_item_path(item, as: user) 24 | expect(response).to have_http_status(200) 25 | end 26 | end 27 | 28 | describe "DELETE /mgstage_items/:id" do 29 | it "works for admin" do 30 | delete mgstage_item_path(item, as: admin) 31 | expect(response).to have_http_status(200) 32 | end 33 | 34 | it "rejects other users" do 35 | expect { 36 | delete mgstage_item_path(item, as: user) 37 | }.to raise_error(ActionController::RoutingError) 38 | end 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /spec/requests/mgstage_pages_spec.rb: -------------------------------------------------------------------------------- 1 | require "rails_helper" 2 | 3 | RSpec.describe "MgstagePages", type: :request do 4 | let(:page) { create(:mgstage_product_page) } 5 | let(:user) { create(:user) } 6 | let(:admin) { create(:admin) } 7 | 8 | describe "GET /mgstage_pages" do 9 | it "works for admin" do 10 | get mgstage_pages_path(as: admin) 11 | expect(response).to have_http_status(200) 12 | end 13 | 14 | it "rejects other users" do 15 | expect { 16 | get mgstage_pages_path(as: user) 17 | }.to raise_error(ActionController::RoutingError) 18 | end 19 | end 20 | 21 | describe "GET /mgstage_pages/:id" do 22 | it "works for admin" do 23 | get mgstage_page_path(page, as: admin) 24 | expect(response).to have_http_status(200) 25 | end 26 | 27 | it "rejects other users" do 28 | expect { 29 | get mgstage_page_path(page, as: user) 30 | }.to raise_error(ActionController::RoutingError) 31 | end 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /spec/requests/movies_spec.rb: -------------------------------------------------------------------------------- 1 | require "rails_helper" 2 | 3 | RSpec.describe "Movies", type: :request do 4 | let(:movie) { create :movie } 5 | let(:user) { create(:user) } 6 | let(:admin) { create(:admin) } 7 | 8 | describe "GET /movies" do 9 | it "works" do 10 | get movies_path 11 | expect(response).to have_http_status(200) 12 | end 13 | end 14 | 15 | describe "GET /movie/:id" do 16 | it "works" do 17 | get movie_path(movie) 18 | expect(response).to have_http_status(200) 19 | end 20 | 21 | context "when not found" do 22 | it "returns 404" do 23 | id = generate :normalized_id 24 | get movie_path(id) 25 | expect(response).to have_http_status(404) 26 | # expect(FanzaSearcher).to have_enqueued_sidekiq_job(id) 27 | end 28 | 29 | context "when logged in" do 30 | it "performs search" do 31 | id = generate :normalized_id 32 | get movie_path(id, as: user) 33 | expect(FanzaSearcher).to have_enqueued_sidekiq_job(id) 34 | end 35 | end 36 | 37 | context "when not logged in" do 38 | it "does not perform search" do 39 | id = generate :normalized_id 40 | get movie_path(id) 41 | expect(FanzaSearcher).not_to have_enqueued_sidekiq_job(id) 42 | end 43 | end 44 | end 45 | end 46 | 47 | describe "GET /movie/:id.json" do 48 | it "works" do 49 | get movie_path(movie, format: :json) 50 | expect(response).to have_http_status(200) 51 | end 52 | 53 | it "returns accepted when search triggered" do 54 | id = generate :normalized_id 55 | get movie_path(id, format: :json) 56 | expect(response).to have_http_status(202) 57 | end 58 | 59 | it "returns not found when not found" do 60 | id = generate :normalized_id 61 | expect(FanzaSearcher).to receive(:perform_async).and_return(nil) 62 | get movie_path(id, format: :json) 63 | expect(response).to have_http_status(404) 64 | end 65 | end 66 | 67 | describe "PUT /movie/:id" do 68 | it "works for admin" do 69 | put movie_path(movie, as: admin) 70 | expect(response).to have_http_status(302) 71 | end 72 | 73 | it "rejects other users" do 74 | expect { 75 | put movie_path(movie, as: user) 76 | }.to raise_error(ActionController::RoutingError) 77 | end 78 | end 79 | 80 | describe "DELETE /fanza_items/:id" do 81 | it "works for admin" do 82 | expect(FanzaSearcher).to receive(:perform_async).and_return(nil) 83 | delete movie_path(movie, as: admin) 84 | expect(response).to have_http_status(302) 85 | end 86 | 87 | it "rejects other users" do 88 | expect { 89 | delete movie_path(movie, as: user) 90 | }.to raise_error(ActionController::RoutingError) 91 | end 92 | end 93 | 94 | context "when hidden" do 95 | let(:hidden_movie) { create :movie, is_hidden: true } 96 | 97 | describe "GET /movie/:id" do 98 | it "returns not found" do 99 | get movie_path(hidden_movie) 100 | expect(response).to have_http_status(404) 101 | end 102 | end 103 | 104 | describe "GET /movie/:id.json" do 105 | it "works" do 106 | get movie_path(hidden_movie, format: :json) 107 | expect(response).to have_http_status(200) 108 | end 109 | end 110 | end 111 | end 112 | -------------------------------------------------------------------------------- /spec/requests/pages_spec.rb: -------------------------------------------------------------------------------- 1 | require "rails_helper" 2 | 3 | RSpec.describe "Pages", type: :request do 4 | describe "GET /search" do 5 | context "for normalizable query" do 6 | it "redirects to normalized movie page" do 7 | get search_path(q: "abc123") 8 | expect(response).to redirect_to(movie_path("ABC-123")) 9 | end 10 | end 11 | 12 | context "for un-normalizable query" do 13 | it "redirects to prefix search movie page" do 14 | get search_path(q: "abC") 15 | expect(response).to redirect_to(movies_path(q: "abC", style: "prefix", order: "release_date")) 16 | end 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /spec/requests/sod_items_spec.rb: -------------------------------------------------------------------------------- 1 | require "rails_helper" 2 | 3 | RSpec.describe "SodItems", type: :request do 4 | let(:item) { create(:sod_item) } 5 | let(:user) { create(:user) } 6 | let(:admin) { create(:admin) } 7 | 8 | describe "GET /sod_items" do 9 | it "works for admin" do 10 | get sod_items_path(as: admin) 11 | expect(response).to have_http_status(200) 12 | end 13 | 14 | it "rejects other users" do 15 | expect { 16 | get sod_items_path(as: user) 17 | }.to raise_error(ActionController::RoutingError) 18 | end 19 | end 20 | 21 | describe "GET /sod_items/:id" do 22 | it "works" do 23 | get sod_item_path(item, as: user) 24 | expect(response).to have_http_status(200) 25 | end 26 | end 27 | 28 | describe "DELETE /sod_items/:id" do 29 | it "works for admin" do 30 | delete sod_item_path(item, as: admin) 31 | expect(response).to have_http_status(200) 32 | end 33 | 34 | it "rejects other users" do 35 | expect { 36 | delete sod_item_path(item, as: user) 37 | }.to raise_error(ActionController::RoutingError) 38 | end 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /spec/requests/sod_pages_spec.rb: -------------------------------------------------------------------------------- 1 | require "rails_helper" 2 | 3 | RSpec.describe "SodPages", type: :request do 4 | let(:page) { create(:sod_page) } 5 | let(:user) { create(:user) } 6 | let(:admin) { create(:admin) } 7 | 8 | describe "GET /sod_pages" do 9 | it "works for admin" do 10 | get sod_pages_path(as: admin) 11 | expect(response).to have_http_status(200) 12 | end 13 | 14 | it "rejects other users" do 15 | expect { 16 | get sod_pages_path(as: user) 17 | }.to raise_error(ActionController::RoutingError) 18 | end 19 | end 20 | 21 | describe "GET /sod_pages/:id" do 22 | it "works for admin" do 23 | get sod_page_path(page, as: admin) 24 | expect(response).to have_http_status(200) 25 | end 26 | 27 | it "rejects other users" do 28 | expect { 29 | get sod_page_path(page, as: user) 30 | }.to raise_error(ActionController::RoutingError) 31 | end 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /spec/routing/fanza_actresses_routing_spec.rb: -------------------------------------------------------------------------------- 1 | require "rails_helper" 2 | 3 | RSpec.describe FanzaActressesController, type: :routing do 4 | describe "routing" do 5 | it "routes to #index" do 6 | expect(get: "/actresses").to route_to("fanza_actresses#index") 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /spec/support/factory_bot.rb: -------------------------------------------------------------------------------- 1 | RSpec.configure do |config| 2 | config.include FactoryBot::Syntax::Methods 3 | end 4 | -------------------------------------------------------------------------------- /spec/support/generic_item.rb: -------------------------------------------------------------------------------- 1 | RSpec.shared_examples "generic item" do 2 | %i[actresses cover_image_url date description directors genres labels logo_url makers review subtitle thumbnail_image_url title url sample_image_urls].each do |key| 3 | it "has #{key}" do 4 | expect(subject.send(key)).not_to be_nil 5 | end 6 | end 7 | 8 | it "has volume" do 9 | expect(subject.volume).to be_a(ActiveSupport::Duration).or be_nil 10 | end 11 | 12 | it "has movie" do 13 | expect(subject.movie).to be_persisted 14 | expect(subject.movie.normalized_id).to eq(subject.normalized_id) 15 | end 16 | 17 | context "when normalized id changes" do 18 | it "still has movie with same normalized id" do 19 | expect { 20 | subject.update(normalized_id: "FC2-001") 21 | }.to change { 22 | subject.movie.normalized_id 23 | }.to("FC2-001") 24 | end 25 | 26 | it "destroys old obsolete movie" do 27 | old_id = subject.normalized_id 28 | new_id = "#{old_id}1" 29 | expect { 30 | subject.update(normalized_id: new_id) 31 | }.to change { 32 | Movie.exists?(normalized_id: old_id) 33 | }.from(true).to(false) 34 | end 35 | end 36 | 37 | context "on destroy" do 38 | it "destroys old obsolete movie" do 39 | id = subject.normalized_id 40 | expect { 41 | subject.destroy 42 | }.to change { 43 | Movie.exists?(normalized_id: id) 44 | }.from(true).to(false) 45 | end 46 | end 47 | 48 | describe ".derive_fields!" do 49 | it "derives fields and saves changes" do 50 | expect(subject).to receive(:derive_fields).once do 51 | subject.normalized_id = subject.normalized_id + "0" 52 | end 53 | expect { 54 | subject.derive_fields! 55 | subject.reload 56 | }.to change { 57 | subject.normalized_id 58 | } 59 | end 60 | 61 | it "does not trigger save if no change" do 62 | expect(subject).not_to receive(:save) 63 | subject.derive_fields! 64 | end 65 | 66 | it "destroys itself when not passing validation" do 67 | expect(subject).to receive(:changed?).and_return(true) 68 | expect(subject).to receive(:save).and_return(false) 69 | expect { 70 | subject.derive_fields! 71 | }.to change { 72 | subject.destroyed? 73 | }.from(false).to(true) 74 | end 75 | end 76 | 77 | describe "cover_image_url" do 78 | it "uses cover image from movie with proxy when available" do 79 | expect { 80 | subject.movie.cover_image_url = "dummy_url" 81 | }.to change { 82 | subject.cover_image_url 83 | }.to("https://imageproxy.libredmm.com/dummy_url") 84 | end 85 | 86 | it "starts with http" do 87 | expect(subject.cover_image_url).to start_with("http") 88 | end 89 | end 90 | 91 | describe "thumbnail_image_url" do 92 | it "crops cover image from movie with proxy when available" do 93 | expect { 94 | subject.movie.cover_image_url = "dummy_url" 95 | }.to change { 96 | subject.thumbnail_image_url 97 | }.to("https://imageproxy.libredmm.com/cx.53/dummy_url") 98 | end 99 | 100 | it "starts with http" do 101 | expect(subject.thumbnail_image_url).to start_with("http") 102 | end 103 | end 104 | end 105 | -------------------------------------------------------------------------------- /spec/views/fanza_actresses/index.html.erb_spec.rb: -------------------------------------------------------------------------------- 1 | require "rails_helper" 2 | 3 | RSpec.describe "fanza_actresses/index" do 4 | before :each do 5 | create_list :fanza_actress, 5 6 | @actresses = FanzaActress.all.page(1) 7 | end 8 | 9 | it "renders actress image" do 10 | render 11 | expect(rendered).to have_selector("img[src='#{@actresses.first.image_url}']") 12 | end 13 | 14 | it "uses dummy image as place holder" do 15 | @actresses.first.raw_json["imageURL"].delete("large") 16 | @actresses.first.raw_json["imageURL"].delete("small") 17 | @actresses.first.save 18 | render 19 | expect(rendered).to have_selector("img[src*='dummyimage.com']") 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /spec/views/generic_pages/index.html.erb_spec.rb: -------------------------------------------------------------------------------- 1 | require "rails_helper" 2 | 3 | RSpec.describe "generic_pages/index" do 4 | it "works with javlibrary pages" do 5 | product_page = create :javlibrary_product_page 6 | search_page = create :javlibrary_search_page 7 | @pages = JavlibraryPage.all.page(1) 8 | 9 | render 10 | expect(rendered).to have_text(product_page.url) 11 | expect(rendered).to have_text(search_page.url) 12 | end 13 | 14 | it "works with mgstage pages" do 15 | product_page = create :mgstage_product_page 16 | search_page = create :mgstage_search_page 17 | @pages = MgstagePage.all.page(1) 18 | 19 | render 20 | expect(rendered).to have_text(product_page.url) 21 | expect(rendered).to have_text(search_page.url) 22 | end 23 | 24 | it "works with sod pages" do 25 | page = create :sod_page 26 | @pages = SodPage.all.page(1) 27 | 28 | render 29 | expect(rendered).to have_text(page.url) 30 | end 31 | 32 | it "works with fc2 pages" do 33 | page = create :fc2_page 34 | @pages = Fc2Page.all.page(1) 35 | 36 | render 37 | expect(rendered).to have_text(page.url) 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /spec/workers/actress_crawler_spec.rb: -------------------------------------------------------------------------------- 1 | require "rails_helper" 2 | 3 | RSpec.describe ActressCrawler, type: :worker do 4 | it "creates actresses" do 5 | 10.times do |i| 6 | create :fanza_actress, fanza_id: i + 1 7 | end 8 | 9 | expect { 10 | subject.perform(10) 11 | }.to change { 12 | FanzaActress.count 13 | }.by(10) 14 | end 15 | 16 | it "updates existing actresses" do 17 | 10.times do |i| 18 | create :fanza_actress, fanza_id: i + 1 19 | end 20 | expect { 21 | subject.perform(10) 22 | }.to change { 23 | FanzaActress.first.name 24 | } 25 | end 26 | 27 | it "starts at tail with overlap" do 28 | create_list :fanza_actress, 20 29 | expect(Fanza::Api).to receive(:actress_search).with(offset: 11).and_call_original 30 | subject.perform(10) 31 | end 32 | 33 | it "always starts with positive offset" do 34 | expect(Fanza::Api).to receive(:actress_search).with(offset: 1).and_call_original 35 | subject.perform(10) 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /spec/workers/fanza_item_crawler_spec.rb: -------------------------------------------------------------------------------- 1 | require "rails_helper" 2 | 3 | RSpec.describe FanzaItemCrawler, type: :worker do 4 | it "works" do 5 | expect { 6 | subject.perform 7 | }.to change { 8 | FanzaItem.count 9 | } 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /spec/workers/feed_refresher_spec.rb: -------------------------------------------------------------------------------- 1 | require "rails_helper" 2 | 3 | RSpec.describe FeedRefresher, type: :worker do 4 | before(:each) do 5 | stub_request(:any, %r{rss.example.com}).to_return( 6 | body: %q{ 7 | 8 | 9 | 10 | RSS Title 11 | https://example.com 12 | 13 | Item 1 14 | https://example.com/items/1 15 | 16 | 17 | Item 2 18 | https://example.com/items/2 19 | 20 | 21 | 22 | }, 23 | ) 24 | end 25 | 26 | it "refreshes feeds" do 27 | 10.times do |i| 28 | create :feed 29 | end 30 | 31 | new_content = %q{ 32 | 33 | 34 | 35 | RSS Title 36 | https://example.com 37 | 38 | Item 1 39 | https://example.com/items/1 40 | 41 | 42 | 43 | } 44 | stub_request(:any, %r{rss.example.com}).to_return(body: new_content) 45 | 46 | refresh_count = 0 47 | allow_any_instance_of(Feed).to receive(:refresh!).and_wrap_original do |m, *args| 48 | refresh_count += 1 49 | m.call(*args) 50 | end 51 | 52 | subject.perform(0, 10) 53 | expect(refresh_count).to eq(10) 54 | Feed.find_each do |feed| 55 | expect(feed.content).to eq(new_content) 56 | end 57 | end 58 | 59 | it "stops after reaching maximum failures" do 60 | 10.times do |i| 61 | create :feed 62 | end 63 | 64 | refresh_cnt = 0 65 | allow_any_instance_of(Feed).to receive(:refresh!) do 66 | refresh_cnt += 1 67 | raise ActiveRecord::RecordInvalid 68 | end 69 | 70 | subject.perform(0, 5) 71 | expect(refresh_cnt).to eq(5) 72 | end 73 | end 74 | -------------------------------------------------------------------------------- /spec/workers/house_keeper_spec.rb: -------------------------------------------------------------------------------- 1 | require "rails_helper" 2 | 3 | RSpec.describe HouseKeeper, type: :worker do 4 | it "can re-derive fields" do 5 | create(:fanza_item) 6 | expect_any_instance_of(FanzaItem).to receive(:derive_fields!) 7 | create(:mgstage_item) 8 | expect_any_instance_of(MgstageItem).to receive(:derive_fields!) 9 | create(:javlibrary_item) 10 | expect_any_instance_of(JavlibraryItem).to receive(:derive_fields!) 11 | subject.perform :derive_fields 12 | end 13 | 14 | it "ignores invalid task" do 15 | subject.perform :invalid_task 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /spec/workers/mgstage_crawler_spec.rb: -------------------------------------------------------------------------------- 1 | require "rails_helper" 2 | 3 | RSpec.describe MgstageCrawler, type: :worker do 4 | before(:each) do 5 | ENV["MGSTAGE_SERIES_URL"] = "http://www.example.com/mgstage_series" 6 | stub_request(:any, "http://www.example.com/mgstage_series").to_return(body: "ABC") 7 | end 8 | 9 | it "crawl product pages" do 10 | expect(Mgstage::Api).to receive(:search_raw).exactly(1).times.with("ABC").and_call_original 11 | expect(Mgstage::Api).to receive(:get).at_least(10).times.and_call_original 12 | subject.perform 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /storage/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libredmm/librefanza/945319cf3d65ffaf27bff32e1ff52d0ec1f5b5be/storage/.keep -------------------------------------------------------------------------------- /tmp/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libredmm/librefanza/945319cf3d65ffaf27bff32e1ff52d0ec1f5b5be/tmp/.keep -------------------------------------------------------------------------------- /tmp/pids/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libredmm/librefanza/945319cf3d65ffaf27bff32e1ff52d0ec1f5b5be/tmp/pids/.keep -------------------------------------------------------------------------------- /vendor/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libredmm/librefanza/945319cf3d65ffaf27bff32e1ff52d0ec1f5b5be/vendor/.keep --------------------------------------------------------------------------------