├── .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 | [](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 |
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 | ID |
10 | Tag |
11 | Created |
12 | Updated |
13 | Accessed |
14 | |
15 |
16 |
17 |
18 | <% @feeds.each do |feed| %>
19 |
20 |
21 | <%= link_to feed.id, feed, class: "btn" %>
22 | |
23 |
24 | <%= link_to feed.uri, feed.uri %>
25 | |
26 |
27 |
30 | |
31 |
32 |
33 |
34 | |
35 |
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 | |
47 | <%= time_ago_in_words(feed.created_at) %> ago |
48 | <%= time_ago_in_words(feed.updated_at) %> ago |
49 | <%= time_ago_in_words(feed.accessed_at) %> ago |
50 |
51 | <%= button_to feed, method: :delete, class: "btn btn-danger" do %>
52 |
53 | <% end %>
54 | |
55 |
56 | <% end %>
57 |
58 |
59 |
60 |
--------------------------------------------------------------------------------
/app/views/generic_pages/index.html.erb:
--------------------------------------------------------------------------------
1 | <%= render partial: "layouts/pager", locals: { items: @pages, name: nil } %>
2 |
3 |
4 |
5 |
6 | ID |
7 | Url |
8 | Created At |
9 | Item |
10 |
11 |
12 |
13 |
14 | <% @pages.each do |page| %>
15 |
16 | <%= link_to page.id, page %> |
17 | <%= link_to page.url, page.url %> |
18 | <%= page.created_at.in_time_zone("Pacific Time (US & Canada)") %> |
19 |
20 | <% if page.item %>
21 | <%= link_to page.item.normalized_id, page.item %>
22 | <% end %>
23 | |
24 |
25 | <% end %>
26 |
27 |
--------------------------------------------------------------------------------
/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 |
10 | <% if Clearance.configuration.allow_sign_up? %>
11 | <%= link_to t(".sign_up"), sign_up_path %>
12 | <% end %>
13 |
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 |
10 | <%= link_to t(".sign_in"), sign_in_path %>
11 |
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
--------------------------------------------------------------------------------