diff --git a/.gitignore b/.gitignore index c03901b1..247e0e0f 100644 --- a/.gitignore +++ b/.gitignore @@ -34,3 +34,4 @@ # Ignore master key for decrypting credentials and more. /config/master.key /.idea/railways.cache +/.idea diff --git a/.idea/.gitignore b/.idea/.gitignore deleted file mode 100644 index 13566b81..00000000 --- a/.idea/.gitignore +++ /dev/null @@ -1,8 +0,0 @@ -# Default ignored files -/shelf/ -/workspace.xml -# Editor-based HTTP Client requests -/httpRequests/ -# Datasource local storage ignored files -/dataSources/ -/dataSources.local.xml diff --git a/.idea/Walltaker.iml b/.idea/Walltaker.iml index 6479179a..7dcc8e94 100644 --- a/.idea/Walltaker.iml +++ b/.idea/Walltaker.iml @@ -26,95 +26,112 @@ - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -135,6 +152,7 @@ + + + file://$MODULE_DIR$/app + + + file://$MODULE_DIR$/app/assets + + + file://$MODULE_DIR$/app/channels + + + file://$MODULE_DIR$/app/controllers + + + file://$MODULE_DIR$/app/helpers + + + file://$MODULE_DIR$/app/mailers + + + file://$MODULE_DIR$/app/models + + + file://$MODULE_DIR$/app/views + + + file://$MODULE_DIR$/config + + + file://$MODULE_DIR$/config/cable.yml + + + file://$MODULE_DIR$/config/database.yml + + + file://$MODULE_DIR$/config/environment.rb + + + file://$MODULE_DIR$/config/environments + + + file://$MODULE_DIR$/config/initializers + + + file://$MODULE_DIR$/config/locales + + + file://$MODULE_DIR$/config/routes + + + file://$MODULE_DIR$/config/routes.rb + + + file://$MODULE_DIR$/config + + + file://$MODULE_DIR$/db + + + file://$MODULE_DIR$/db/migrate + + + file://$MODULE_DIR$/db/seeds.rb + + + file://$MODULE_DIR$/lib + + + file://$MODULE_DIR$/lib/assets + + + file://$MODULE_DIR$/lib/tasks + + + file://$MODULE_DIR$/lib/templates + + + file://$MODULE_DIR$/log/development.log + + + file://$MODULE_DIR$/public + + + file://$MODULE_DIR$/public/javascripts + + + file://$MODULE_DIR$/public/stylesheets + + + file://$MODULE_DIR$/tmp + + + file://$MODULE_DIR$/vendor + + + file://$MODULE_DIR$/vendor/assets + + diff --git a/.idea/dataSources.xml b/.idea/dataSources.xml deleted file mode 100644 index 31a768f3..00000000 --- a/.idea/dataSources.xml +++ /dev/null @@ -1,39 +0,0 @@ - - - - - postgresql - true - true - $PROJECT_DIR$/config/database.yml - org.postgresql.Driver - jdbc:postgresql://database:5432/walltaker - $ProjectFileDir$ - - - postgresql - true - org.postgresql.Driver - jdbc:postgresql://localhost:5432/walltaker - $ProjectFileDir$ - - - postgresql - true - true - $PROJECT_DIR$/config/database.yml - org.postgresql.Driver - jdbc:postgresql://127.0.0.1:5432/walltaker - $ProjectFileDir$ - - - sqlite.xerial - true - true - $PROJECT_DIR$/config/database.yml - org.sqlite.JDBC - jdbc:sqlite:$PROJECT_DIR$/db/test.sqlite3 - $ProjectFileDir$ - - - \ No newline at end of file diff --git a/.idea/deployment.xml b/.idea/deployment.xml deleted file mode 100644 index 2df68132..00000000 --- a/.idea/deployment.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml deleted file mode 100644 index 2538888d..00000000 --- a/.idea/modules.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/.idea/modules/nuttracker.iml b/.idea/modules/nuttracker.iml deleted file mode 100644 index f9743d31..00000000 --- a/.idea/modules/nuttracker.iml +++ /dev/null @@ -1,139 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/sqldialects.xml b/.idea/sqldialects.xml deleted file mode 100644 index 030c6645..00000000 --- a/.idea/sqldialects.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.idea/sshConfigs.xml b/.idea/sshConfigs.xml deleted file mode 100644 index d5d62deb..00000000 --- a/.idea/sshConfigs.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml deleted file mode 100644 index 4c6280eb..00000000 --- a/.idea/vcs.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/webServers.xml b/.idea/webServers.xml deleted file mode 100644 index 472f0659..00000000 --- a/.idea/webServers.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.ruby-version b/.ruby-version index 872e1208..9e79f6c4 100644 --- a/.ruby-version +++ b/.ruby-version @@ -1 +1 @@ -ruby-3.1.0 +ruby-3.2.2 diff --git a/Gemfile b/Gemfile index 57821e05..04e9901a 100644 --- a/Gemfile +++ b/Gemfile @@ -1,10 +1,10 @@ source "https://rubygems.org" git_source(:github) { |repo| "https://github.com/#{repo}.git" } -ruby "~> 3.1.0" +ruby "~> 3.2.2" # Bundle edge Rails instead: gem "rails", github: "rails/rails", branch: "main" -gem "rails", "~> 7.0.2", ">= 7.0.2.2" +gem "rails", "~> 7.1.2", ">= 7.0.2.2" # The original asset pipeline for Rails [https://github.com/rails/sprockets-rails] gem "sprockets-rails" @@ -28,7 +28,7 @@ gem "stimulus-rails" gem "jbuilder" # Use Redis adapter to run Action Cable in production -# gem "redis", "~> 4.0" +gem "redis" # Use Kredis to get higher-level data types in Redis [https://github.com/rails/kredis] # gem "kredis" @@ -82,4 +82,8 @@ gem 'rbs_rails', require: false gem 'ahoy_matey' gem "blazer" gem "lograge" -gem 'nuttracker', path: 'nuttracker' \ No newline at end of file +gem 'nuttracker', path: 'nuttracker' +gem "pg_search", "~> 2.3" +gem 'inline_svg' +gem 'crono' +gem 'wicked' diff --git a/Gemfile.lock b/Gemfile.lock index 5aab0f79..b9549a2b 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -7,89 +7,100 @@ PATH GEM remote: https://rubygems.org/ specs: - actioncable (7.0.2.2) - actionpack (= 7.0.2.2) - activesupport (= 7.0.2.2) + actioncable (7.1.2) + actionpack (= 7.1.2) + activesupport (= 7.1.2) nio4r (~> 2.0) websocket-driver (>= 0.6.1) - actionmailbox (7.0.2.2) - actionpack (= 7.0.2.2) - activejob (= 7.0.2.2) - activerecord (= 7.0.2.2) - activestorage (= 7.0.2.2) - activesupport (= 7.0.2.2) + zeitwerk (~> 2.6) + actionmailbox (7.1.2) + actionpack (= 7.1.2) + activejob (= 7.1.2) + activerecord (= 7.1.2) + activestorage (= 7.1.2) + activesupport (= 7.1.2) mail (>= 2.7.1) net-imap net-pop net-smtp - actionmailer (7.0.2.2) - actionpack (= 7.0.2.2) - actionview (= 7.0.2.2) - activejob (= 7.0.2.2) - activesupport (= 7.0.2.2) + actionmailer (7.1.2) + actionpack (= 7.1.2) + actionview (= 7.1.2) + activejob (= 7.1.2) + activesupport (= 7.1.2) mail (~> 2.5, >= 2.5.4) net-imap net-pop net-smtp - rails-dom-testing (~> 2.0) - actionpack (7.0.2.2) - actionview (= 7.0.2.2) - activesupport (= 7.0.2.2) - rack (~> 2.0, >= 2.2.0) + rails-dom-testing (~> 2.2) + actionpack (7.1.2) + actionview (= 7.1.2) + activesupport (= 7.1.2) + nokogiri (>= 1.8.5) + racc + rack (>= 2.2.4) + rack-session (>= 1.0.1) rack-test (>= 0.6.3) - rails-dom-testing (~> 2.0) - rails-html-sanitizer (~> 1.0, >= 1.2.0) - actiontext (7.0.2.2) - actionpack (= 7.0.2.2) - activerecord (= 7.0.2.2) - activestorage (= 7.0.2.2) - activesupport (= 7.0.2.2) + rails-dom-testing (~> 2.2) + rails-html-sanitizer (~> 1.6) + actiontext (7.1.2) + actionpack (= 7.1.2) + activerecord (= 7.1.2) + activestorage (= 7.1.2) + activesupport (= 7.1.2) globalid (>= 0.6.0) nokogiri (>= 1.8.5) - actionview (7.0.2.2) - activesupport (= 7.0.2.2) + actionview (7.1.2) + activesupport (= 7.1.2) builder (~> 3.1) - erubi (~> 1.4) - rails-dom-testing (~> 2.0) - rails-html-sanitizer (~> 1.1, >= 1.2.0) - activejob (7.0.2.2) - activesupport (= 7.0.2.2) + erubi (~> 1.11) + rails-dom-testing (~> 2.2) + rails-html-sanitizer (~> 1.6) + activejob (7.1.2) + activesupport (= 7.1.2) globalid (>= 0.3.6) - activemodel (7.0.2.2) - activesupport (= 7.0.2.2) - activerecord (7.0.2.2) - activemodel (= 7.0.2.2) - activesupport (= 7.0.2.2) - activestorage (7.0.2.2) - actionpack (= 7.0.2.2) - activejob (= 7.0.2.2) - activerecord (= 7.0.2.2) - activesupport (= 7.0.2.2) + activemodel (7.1.2) + activesupport (= 7.1.2) + activerecord (7.1.2) + activemodel (= 7.1.2) + activesupport (= 7.1.2) + timeout (>= 0.4.0) + activestorage (7.1.2) + actionpack (= 7.1.2) + activejob (= 7.1.2) + activerecord (= 7.1.2) + activesupport (= 7.1.2) marcel (~> 1.0) - mini_mime (>= 1.1.0) - activesupport (7.0.2.2) + activesupport (7.1.2) + base64 + bigdecimal concurrent-ruby (~> 1.0, >= 1.0.2) + connection_pool (>= 2.2.5) + drb i18n (>= 1.6, < 2) minitest (>= 5.1) + mutex_m tzinfo (~> 2.0) - addressable (2.8.0) - public_suffix (>= 2.0.2, < 5.0) - ahoy_matey (4.0.3) + addressable (2.8.5) + public_suffix (>= 2.0.2, < 6.0) + ahoy_matey (4.2.1) activesupport (>= 5.2) device_detector safely_block (>= 0.2.1) ast (2.4.2) - bcrypt (3.1.16) + base64 (0.2.0) + bcrypt (3.1.19) + bigdecimal (3.1.5) bindex (0.8.1) - blazer (2.5.0) + blazer (2.6.5) activerecord (>= 5) chartkick (>= 3.2) railties (>= 5) safely_block (>= 0.1.1) - bootsnap (1.10.3) + bootsnap (1.16.0) msgpack (~> 1.2) builder (3.2.4) - capybara (3.36.0) + capybara (3.39.2) addressable matrix mini_mime (>= 0.1.3) @@ -98,162 +109,199 @@ GEM rack-test (>= 0.6.3) regexp_parser (>= 1.5, < 3.0) xpath (~> 3.2) - chartkick (4.1.3) - childprocess (4.1.0) - concurrent-ruby (1.1.9) + chartkick (5.0.4) + concurrent-ruby (1.2.2) + connection_pool (2.4.1) crass (1.0.6) - debug (1.4.0) - irb (>= 1.3.6) - reline (>= 0.2.7) - device_detector (1.0.7) - digest (3.1.0) - errbase (0.2.2) - erubi (1.10.0) - excon (0.91.0) - globalid (1.0.0) - activesupport (>= 5.0) - groupdate (6.0.1) - activesupport (>= 5.2) - i18n (1.9.1) + crono (2.0.1) + rails (>= 5.2.8) + sprockets-rails + date (3.3.4) + debug (1.8.0) + irb (>= 1.5.0) + reline (>= 0.3.1) + device_detector (1.1.1) + drb (2.2.0) + ruby2_keywords + erubi (1.12.0) + excon (0.102.0) + globalid (1.2.0) + activesupport (>= 6.1) + groupdate (6.3.0) + activesupport (>= 6.1) + i18n (1.14.1) concurrent-ruby (~> 1.0) - importmap-rails (1.0.2) + importmap-rails (1.2.1) actionpack (>= 6.0.0) railties (>= 6.0.0) - io-console (0.5.11) - io-wait (0.2.1) - irb (1.4.1) - reline (>= 0.3.0) + inline_svg (1.9.0) + activesupport (>= 3.0) + nokogiri (>= 1.6) + io-console (0.6.0) + irb (1.8.0) + rdoc (~> 6.5) + reline (>= 0.3.6) jbuilder (2.11.5) actionview (>= 5.0.0) activesupport (>= 5.0.0) - lograge (0.12.0) + lograge (0.13.0) actionpack (>= 4) activesupport (>= 4) railties (>= 4) request_store (~> 1.0) - loofah (2.14.0) + loofah (2.21.3) crass (~> 1.0.2) - nokogiri (>= 1.5.9) - mail (2.7.1) + nokogiri (>= 1.12.0) + mail (2.8.1) mini_mime (>= 0.1.1) + net-imap + net-pop + net-smtp marcel (1.0.2) matrix (0.4.2) - method_source (1.0.0) - mini_mime (1.1.2) - minitest (5.15.0) - msgpack (1.4.4) - net-imap (0.2.3) - digest + mini_mime (1.1.5) + mini_portile2 (2.8.5) + minitest (5.19.0) + msgpack (1.7.2) + mutex_m (0.2.0) + net-imap (0.4.9.1) + date net-protocol - strscan - net-pop (0.1.1) - digest + net-pop (0.1.2) net-protocol + net-protocol (0.2.2) timeout - net-protocol (0.1.2) - io-wait - timeout - net-smtp (0.3.1) - digest + net-smtp (0.4.0.1) net-protocol - timeout - nio4r (2.5.8) - nokogiri (1.13.1-x86_64-darwin) + nio4r (2.5.9) + nokogiri (1.15.4) + mini_portile2 (~> 2.8.2) + racc (~> 1.4) + nokogiri (1.15.4-arm64-darwin) racc (~> 1.4) - nokogiri (1.13.1-x86_64-linux) + nokogiri (1.15.4-x86_64-linux) racc (~> 1.4) - parser (3.1.1.0) + parser (3.2.2.3) ast (~> 2.4.1) - pg (1.3.1) - public_suffix (4.0.6) - puma (5.6.2) + racc + pg (1.5.4) + pg_search (2.3.6) + activerecord (>= 5.2) + activesupport (>= 5.2) + psych (5.1.0) + stringio + public_suffix (5.0.3) + puma (5.6.7) nio4r (~> 2.0) - racc (1.6.0) - rack (2.2.3) - rack-cors (1.1.1) + racc (1.7.1) + rack (2.2.8) + rack-cors (2.0.1) rack (>= 2.0.0) - rack-test (1.1.0) - rack (>= 1.0, < 3) - rails (7.0.2.2) - actioncable (= 7.0.2.2) - actionmailbox (= 7.0.2.2) - actionmailer (= 7.0.2.2) - actionpack (= 7.0.2.2) - actiontext (= 7.0.2.2) - actionview (= 7.0.2.2) - activejob (= 7.0.2.2) - activemodel (= 7.0.2.2) - activerecord (= 7.0.2.2) - activestorage (= 7.0.2.2) - activesupport (= 7.0.2.2) + rack-session (1.0.2) + rack (< 3) + rack-test (2.1.0) + rack (>= 1.3) + rackup (1.0.0) + rack (< 3) + webrick + rails (7.1.2) + actioncable (= 7.1.2) + actionmailbox (= 7.1.2) + actionmailer (= 7.1.2) + actionpack (= 7.1.2) + actiontext (= 7.1.2) + actionview (= 7.1.2) + activejob (= 7.1.2) + activemodel (= 7.1.2) + activerecord (= 7.1.2) + activestorage (= 7.1.2) + activesupport (= 7.1.2) bundler (>= 1.15.0) - railties (= 7.0.2.2) - rails-dom-testing (2.0.3) - activesupport (>= 4.2.0) + railties (= 7.1.2) + rails-dom-testing (2.2.0) + activesupport (>= 5.0.0) + minitest nokogiri (>= 1.6) - rails-html-sanitizer (1.4.2) - loofah (~> 2.3) - railties (7.0.2.2) - actionpack (= 7.0.2.2) - activesupport (= 7.0.2.2) - method_source + rails-html-sanitizer (1.6.0) + loofah (~> 2.21) + nokogiri (~> 1.14) + railties (7.1.2) + actionpack (= 7.1.2) + activesupport (= 7.1.2) + irb + rackup (>= 1.0.0) rake (>= 12.2) - thor (~> 1.0) - zeitwerk (~> 2.5) + thor (~> 1.0, >= 1.2.2) + zeitwerk (~> 2.6) rake (13.0.6) - rbs (2.2.2) - rbs_rails (0.9.0) + rbs (3.2.1) + rbs_rails (0.12.0) parser rbs (>= 1) - regexp_parser (2.2.1) - reline (0.3.1) + rdoc (6.5.0) + psych (>= 4.0.0) + redis (5.0.8) + redis-client (>= 0.17.0) + redis-client (0.19.1) + connection_pool + regexp_parser (2.8.1) + reline (0.3.8) io-console (~> 0.5) request_store (1.5.1) rack (>= 1.4) - rexml (3.2.5) + rexml (3.2.6) + ruby2_keywords (0.0.5) rubyzip (2.3.2) - safely_block (0.3.0) - errbase (>= 0.1.1) - selenium-webdriver (4.1.0) - childprocess (>= 0.5, < 5.0) + safely_block (0.4.0) + selenium-webdriver (4.10.0) rexml (~> 3.2, >= 3.2.5) - rubyzip (>= 1.2.2) - sprockets (4.0.2) + rubyzip (>= 1.2.2, < 3.0) + websocket (~> 1.0) + sprockets (4.2.0) concurrent-ruby (~> 1.0) - rack (> 1, < 3) + rack (>= 2.2.4, < 4) sprockets-rails (3.4.2) actionpack (>= 5.2) activesupport (>= 5.2) sprockets (>= 3.0.0) - sqlite3 (1.4.2) - stimulus-rails (1.0.2) + sqlite3 (1.6.4) + mini_portile2 (~> 2.8.0) + sqlite3 (1.6.4-arm64-darwin) + sqlite3 (1.6.4-x86_64-linux) + stimulus-rails (1.2.2) railties (>= 6.0.0) - strscan (3.0.1) - thor (1.2.1) - timeout (0.2.0) - turbo-rails (1.0.1) + stringio (3.0.8) + thor (1.2.2) + timeout (0.4.1) + turbo-rails (1.4.0) actionpack (>= 6.0.0) + activejob (>= 6.0.0) railties (>= 6.0.0) - tzinfo (2.0.4) + tzinfo (2.0.6) concurrent-ruby (~> 1.0) web-console (4.2.0) actionview (>= 6.0.0) activemodel (>= 6.0.0) bindex (>= 0.4.0) railties (>= 6.0.0) - webdrivers (5.0.0) + webdrivers (5.3.1) nokogiri (~> 1.6) rubyzip (>= 1.3.0) - selenium-webdriver (~> 4.0) - websocket-driver (0.7.5) + selenium-webdriver (~> 4.0, < 4.11) + webrick (1.8.1) + websocket (1.2.9) + websocket-driver (0.7.6) websocket-extensions (>= 0.1.0) websocket-extensions (0.1.5) + wicked (2.0.0) + railties (>= 3.0.7) xpath (3.2.0) nokogiri (~> 1.8) - zeitwerk (2.5.4) + zeitwerk (2.6.11) PLATFORMS - x86_64-darwin-21 + arm64-darwin-22 + ruby x86_64-linux DEPENDENCIES @@ -263,18 +311,22 @@ DEPENDENCIES bootsnap capybara chartkick + crono debug excon groupdate importmap-rails + inline_svg jbuilder lograge nuttracker! pg + pg_search (~> 2.3) puma (~> 5.0) rack-cors - rails (~> 7.0.2, >= 7.0.2.2) + rails (~> 7.1.2, >= 7.0.2.2) rbs_rails + redis selenium-webdriver sprockets-rails sqlite3 (~> 1.4) @@ -283,9 +335,10 @@ DEPENDENCIES tzinfo-data web-console webdrivers + wicked RUBY VERSION - ruby 3.1.2p20 + ruby 3.2.2p53 BUNDLED WITH - 2.3.3 + 2.4.13 diff --git a/Procfile b/Procfile new file mode 100644 index 00000000..863fbf1c --- /dev/null +++ b/Procfile @@ -0,0 +1,2 @@ +release: rake db:migrate assets:precompile +web: bundle exec puma -C config/puma.rb \ No newline at end of file diff --git a/README.md b/README.md index 5ff83514..fb4e7a0f 100644 --- a/README.md +++ b/README.md @@ -5,19 +5,29 @@ ## What is it? Walltaker is inspired by the [WallClaimer](https://www.wallclaimer.com/) app, which allows you to set the wallpaper of -your friends phones. This however leads to some weird cases where people post stuff that'd be on your metaphorical +your friend's phones. This however leads to some weird cases where people post stuff that'd be on your metaphorical blacklist. With that in mind, I wanted it to be restricted to e621.net results, with an enforced blacklist. This keeps you in control, but not _too much_ control. ## Clients A client is required to set the wallpaper of a device. Here are some made by the awesome community! -| client | platforms | +#### Desktop + +| Client | Platforms | +|-----------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------| +| [Chewtoy's Walltaker Engine](https://github.com/dogkisser/walltaker-engine/releases) | Windows | +| [JBerliner's Walltaker.sh](https://gitlab.com/JBerliner/walltaker-client) | Linux | +| [Lycraon's Wallpaper Engine Client](https://github.com/Lycraon/Walltaker-for-WallpaperEngine) | [Wallpaper Engine](https://store.steampowered.com/app/431960/Wallpaper_Engine/) (Windows) | +| [MacOS Client](https://github.com/PawCorp/walltaker-macos/releases/latest) | MacOS | + +#### Mobile + +| Client | Platforms | |-----------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------| -| [walltaker-desktop-client](https://github.com/PawCorp/walltaker-desktop-client) | windows/mac/linux | -| [walltaker-android-client](https://github.com/PawCorp/walltaker-android-client) | android | -| [Lycraon's Wallpaper Engine Client](https://github.com/Lycraon/Walltaker-for-WallpaperEngine) | [Wallpaper Engine](https://store.steampowered.com/app/431960/Wallpaper_Engine/) (windows) | -| [Deanskond's Automate Client](https://github.com/Deanskond/walltaker_automate) | [Automate App](https://llamalab.com/automate/) (android) | +| [Deanskond's Automate Client](https://github.com/Deanskond/walltaker_automate) | [Automate App](https://llamalab.com/automate/) (Android) | +| [Gios' Client](https://github.com/gios2/Walltaker-Changer/releases/latest) | Android | +| [iOS Widget](https://github.com/PawCorp/walltaker/blob/main/ios.md#ios-widget) | iOS | ## API Guide _Make your own client!_ @@ -40,7 +50,7 @@ Get the current post details for a given link. "id": 1, // The ID, you already know this "expires": "2025-03-05T00:00:00.000Z", // Expiry timestamp, will be inaccessible after this time "username": "gray", // The user this refers to, queryable at the /api/user/[username] endpoint. - "terms": "I'm trying out something new, break this please! :)", // Open text feild for user to describe terms of posting + "terms": "I'm trying out something new, break this please! :)", // Open text field for user to describe terms of posting "blacklist": "feet blood", // e621 style blacklist "post_url": "https://static1.e621.net/data/5d/87/5d87428c4839b0dc7d585b87a25af61a.png", // Full size post image "post_thumbnail_url": "https://static1.e621.net/data/preview/5d/87/5d87428c4839b0dc7d585b87a25af61a.jpg", // Thumnail size post image @@ -60,7 +70,7 @@ Get the current post details for a given link. 🔑 Requires user's API Key -Set a reponse for a given link. There are 3 kinds of responses. +Set a response for a given link. There are 3 kinds of responses. | `type` | Shown In UI as | Effect | |-------------|----------------|---------------------------------------------------------------------------------------| diff --git a/app/assets/images/mascot/Ki.png b/app/assets/images/mascot/Ki.png new file mode 100644 index 00000000..7ad74c1c Binary files /dev/null and b/app/assets/images/mascot/Ki.png differ diff --git a/app/assets/images/mascot/KiHead.png b/app/assets/images/mascot/KiHead.png new file mode 100644 index 00000000..f4f1e1be Binary files /dev/null and b/app/assets/images/mascot/KiHead.png differ diff --git a/app/assets/images/mascot/KiNSFW.png b/app/assets/images/mascot/KiNSFW.png new file mode 100644 index 00000000..1572660b Binary files /dev/null and b/app/assets/images/mascot/KiNSFW.png differ diff --git a/app/assets/images/mascot/KiShrug.png b/app/assets/images/mascot/KiShrug.png new file mode 100644 index 00000000..f03a009f Binary files /dev/null and b/app/assets/images/mascot/KiShrug.png differ diff --git a/app/assets/images/mascot/KiWorking.png b/app/assets/images/mascot/KiWorking.png new file mode 100644 index 00000000..9de5b19a Binary files /dev/null and b/app/assets/images/mascot/KiWorking.png differ diff --git a/app/assets/images/mascot/TaylorBadEnd.png b/app/assets/images/mascot/TaylorBadEnd.png new file mode 100644 index 00000000..c9a09fc2 Binary files /dev/null and b/app/assets/images/mascot/TaylorBadEnd.png differ diff --git a/app/assets/images/mascot/TaylorEducator.png b/app/assets/images/mascot/TaylorEducator.png new file mode 100644 index 00000000..a3eed9cb Binary files /dev/null and b/app/assets/images/mascot/TaylorEducator.png differ diff --git a/app/assets/images/mascot/TaylorHead.png b/app/assets/images/mascot/TaylorHead.png new file mode 100644 index 00000000..54c1ceab Binary files /dev/null and b/app/assets/images/mascot/TaylorHead.png differ diff --git a/app/assets/images/mascot/TaylorNSFW.png b/app/assets/images/mascot/TaylorNSFW.png new file mode 100644 index 00000000..b918433c Binary files /dev/null and b/app/assets/images/mascot/TaylorNSFW.png differ diff --git a/app/assets/images/mascot/TaylorSFW.png b/app/assets/images/mascot/TaylorSFW.png new file mode 100644 index 00000000..6a3d9ee0 Binary files /dev/null and b/app/assets/images/mascot/TaylorSFW.png differ diff --git a/app/assets/images/mascot/TaylorTits.png b/app/assets/images/mascot/TaylorTits.png new file mode 100644 index 00000000..6bb5762d Binary files /dev/null and b/app/assets/images/mascot/TaylorTits.png differ diff --git a/app/assets/images/mascot/TaylorTitsSFW.png b/app/assets/images/mascot/TaylorTitsSFW.png new file mode 100644 index 00000000..f4ed59a3 Binary files /dev/null and b/app/assets/images/mascot/TaylorTitsSFW.png differ diff --git a/app/assets/images/mascot/WarrenAPIExtrodinaire.png b/app/assets/images/mascot/WarrenAPIExtrodinaire.png new file mode 100644 index 00000000..34b60cce Binary files /dev/null and b/app/assets/images/mascot/WarrenAPIExtrodinaire.png differ diff --git a/app/assets/images/mascot/WarrenBadEnd.png b/app/assets/images/mascot/WarrenBadEnd.png new file mode 100644 index 00000000..123495e8 Binary files /dev/null and b/app/assets/images/mascot/WarrenBadEnd.png differ diff --git a/app/assets/images/mascot/WarrenHead.png b/app/assets/images/mascot/WarrenHead.png new file mode 100644 index 00000000..5faddc87 Binary files /dev/null and b/app/assets/images/mascot/WarrenHead.png differ diff --git a/app/assets/images/mascot/WarrenHeadHorny.png b/app/assets/images/mascot/WarrenHeadHorny.png new file mode 100644 index 00000000..730ef080 Binary files /dev/null and b/app/assets/images/mascot/WarrenHeadHorny.png differ diff --git a/app/assets/images/mascot/WarrenJacking.png b/app/assets/images/mascot/WarrenJacking.png new file mode 100644 index 00000000..8faf206c Binary files /dev/null and b/app/assets/images/mascot/WarrenJacking.png differ diff --git a/app/assets/images/mascot/WarrenNSFW.png b/app/assets/images/mascot/WarrenNSFW.png new file mode 100644 index 00000000..6069dd42 Binary files /dev/null and b/app/assets/images/mascot/WarrenNSFW.png differ diff --git a/app/assets/images/mascot/WarrenSFW.png b/app/assets/images/mascot/WarrenSFW.png new file mode 100644 index 00000000..5ad94f0f Binary files /dev/null and b/app/assets/images/mascot/WarrenSFW.png differ diff --git a/app/assets/images/mascot/Wizards.png b/app/assets/images/mascot/Wizards.png new file mode 100644 index 00000000..4fe557a6 Binary files /dev/null and b/app/assets/images/mascot/Wizards.png differ diff --git a/app/assets/images/wt.svg b/app/assets/images/wt.svg new file mode 100644 index 00000000..ea2df8a5 --- /dev/null +++ b/app/assets/images/wt.svg @@ -0,0 +1,113 @@ + + + + + + + + + + + + Canevas 1 + + Calque 1 + + Link Icon + + + + + + + + + Client Icon + + + + + + + + 420 + 7 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Client Icon + + + + + + + + + + + + People choose wallpapers on your + + + + Link Icon + + + + + + + + + Client Icon + + + + + …which are pushed to client apps + + + + + links… + + + + + , running on your devices. + + + + + diff --git a/app/channels/application_cable/connection.rb b/app/channels/application_cable/connection.rb index 2db5d6a2..ea630b6e 100644 --- a/app/channels/application_cable/connection.rb +++ b/app/channels/application_cable/connection.rb @@ -1,20 +1,20 @@ module ApplicationCable class Connection < ActionCable::Connection::Base - attr_accessor :watched_link, :current_user + attr_accessor :watched_links, :current_user def connect - @watched_link = nil + @watched_links = [] @current_user = connection_current_user end - def disconnect - if self.watched_link - self.watched_link.live_client_started_at = nil - self.watched_link.save - end - if self.current_user - self.current_user.leave_link + def disconnect + self.watched_links.for do |link| + link.live_client_started_at = nil + link.save end + + self.watched_links = [] + self.current_user&.leave_link end private diff --git a/app/channels/link_channel.rb b/app/channels/link_channel.rb index 3a77ddab..da58ac39 100644 --- a/app/channels/link_channel.rb +++ b/app/channels/link_channel.rb @@ -3,22 +3,50 @@ def subscribed if params[:id].present? link = Link.find(params[:id]) if link - if connection&.watched_link - connection.watched_link.live_client_started_at = nil - connection.watched_link.save - end - connection.watched_link = link link.live_client_started_at = Time.now link.save + + connection&.watched_links.push(link) stream_from "Link::#{params[:id]}" end end end def unsubscribed - if connection&.watched_link - connection.watched_link.live_client_started_at = nil - connection.watched_link.save + if params[:id].present? + link = Link.find(params[:id]) + if link + stop_stream_from "Link::#{params[:id]}" + link.live_client_started_at = nil + link.save + + connection&.watched_links.delete(link) + end + end + end + + def check + if params[:id].present? + link = Link.find(params[:id]) + if link + connection.watched_links.push(link) + link.live_client_started_at = Time.now + link.save + end + end + end + + def announce_client(data) + if params[:id].present? && data['client'] + link = Link.find(params[:id]) + if link + begin + link.last_ping_user_agent = data['client'] + link.save + rescue + {success: false, why: 'bad client name'} + end + end end end end diff --git a/app/controllers/api_controller.rb b/app/controllers/api_controller.rb index 24cfeb1f..f9298f23 100644 --- a/app/controllers/api_controller.rb +++ b/app/controllers/api_controller.rb @@ -1,6 +1,40 @@ class ApiController < ApplicationController after_action :log_presence, only: %i[show_link show_link_widget] skip_before_action :verify_authenticity_token, only: :set_link_response + before_action :authorize_for_surrenderd_accounts, only: %i[update_mascot update_perviness] + + def update_mascot + next_mascot = current_user&.mascot || 'ki' + + case next_mascot + when 'warren' + next_mascot = 'taylor' + + when 'taylor' + next_mascot = 'ki' + + when 'ki' + next_mascot = 'warren' + end + + current_user.mascot = next_mascot + current_user.save + + render turbo_stream: [ + turbo_stream.replace("title", partial: 'layouts/title', locals: { mascot: next_mascot, pervert: current_user.pervert }), + turbo_stream.replace("mascot_picker", partial: 'layouts/mascot_picker') + ] + end + + def update_perviness + current_user.pervert = !current_user.pervert + current_user.save + + render turbo_stream: [ + turbo_stream.replace("title", partial: 'layouts/title', locals: { mascot: current_user.mascot, pervert: current_user.pervert }), + turbo_stream.replace("mascot_picker", partial: 'layouts/mascot_picker'), + ] + end # GET /api/links/:id.json def show_link @@ -10,6 +44,12 @@ def show_link render json: { message: 'This link does not exist.' }, status: 404 end + def all_links + @force_online = params.has_key? :online + @links = Link.all.where(friends_only: false).and(Link.where('expires > ?', Time.now).or(Link.where(never_expires: true))) unless @force_online + @links = Link.all.where(friends_only: false).is_online.and(Link.where('expires > ?', Time.now).or(Link.where(never_expires: true))) if @force_online + end + def show_link_widget @link = Link.find(params[:id]) @set_by = User.find(@link.set_by_id) if @link.set_by_id diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 466dffad..e02bf2c6 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -1,4 +1,18 @@ class ApplicationController < ActionController::Base + before_action :broadcast_flash_message + + def broadcast_flash_message + return unless request.format.turbo_stream? + return if response.status == 301 || response.status == 302 + return unless current_user + + flash.each do |type, msg| + Turbo::StreamsChannel.broadcast_append_to("#{current_user.username}-flashes", + target: 'flashes', partial: 'application/flash', + locals: { msg:, type: }) + end + end + def get_tag_results(tag_string, after, before, link, limit = 15) if link.nil? append_to_tags = '' @@ -8,39 +22,56 @@ def get_tag_results(tag_string, after, before, link, limit = 15) link_can_show_videos = link.check_ability 'can_show_videos' sanitized_blacklist = make_blacklist(link) - append_to_tags = make_tag_suffix(link, sanitized_blacklist) + blacklist_tags = sanitized_blacklist.split(' ') + deduped_tag_string = tag_string.split(' ').filter { |tag| blacklist_tags.none? tag }.uniq.join(' ') - padded_tag_string = tag_string + append_to_tags = make_tag_suffix(link, sanitized_blacklist, deduped_tag_string) + + padded_tag_string = deduped_tag_string end unless append_to_tags.nil? || append_to_tags.empty? padded_tag_string += " #{append_to_tags.to_s}" end + tags = CGI.escape padded_tag_string - url = "https://e621.net/posts.json?tags=#{tags}&limit=15" - after_id = after.gsub(/\D/, '') if after - url = "#{url}&page=b#{after_id}" if after_id - before_id = before.gsub(/\D/, '') if before - url = "#{url}&page=a#{before_id}" if before_id - url = "#{url}&limit=#{limit}" if limit - response = Excon.get(url, headers: { 'User-Agent': 'walltaker.joi.how (by ailurus on e621)' }) - if response.status != 200 - track :error, :e621_posts_api_fail, response: response - return nil - end - results = JSON.parse(response.body)['posts'] + key = "v1/tagresults/#{tags}/#{after}/#{before}/#{limit}/#{link_can_show_videos}" + cache_hit = Rails.cache.read(key) - if results.present? && results.class == Array - unless link_can_show_videos - results.filter do |post| - %w[png jpg bmp webp].include? post['file']['ext'] + unless cache_hit.nil? + cache_hit + else + url = "https://e621.net/posts.json?tags=#{tags}" + after_id = after.gsub(/\D/, '') if after + url = "#{url}&page=b#{after_id}" if after_id + before_id = before.gsub(/\D/, '') if before + url = "#{url}&page=a#{before_id}" if before_id + url = "#{url}&limit=#{limit}" + response = Excon.get(url, headers: { 'User-Agent': 'walltaker.joi.how (by ailurus on e621)' }) + if response.status != 200 + track :error, :e621_posts_api_fail, response: response + return nil + end + + results = JSON.parse(response.body)['posts'] + if results.present? && results.class == Array + if /order:random/i =~ padded_tag_string + Rails.cache.write(key, results, expires_in: 1.minute) + else + Rails.cache.write(key, results, expires_in: 45.minutes) + end + + unless link_can_show_videos + results.filter do |post| + %w[png jpg bmp webp].include? post['file']['ext'] + end + else + results end else - results + [] end - else - [] end end @@ -54,28 +85,36 @@ def get_possible_post_count(link) helper_method :get_possible_post_count - def make_tag_suffix(link, sanitized_blacklist) - append_to_tags = '' + def make_tag_suffix(link, sanitized_blacklist, query) + kink_in_query = false + if link.check_ability 'is_kink_aligned' + kinks = link.user.kinks.pluck(:name) + + kink_in_query = query.split(' ').any? { |tag| kinks.any? tag } + end + append_to_tags = '-flash ' append_to_tags += link.theme if (link.theme) append_to_tags += ' ' + ((sanitized_blacklist.split.map { |tag| "-#{tag}" }).join ' ') unless (sanitized_blacklist.empty?) append_to_tags += ' score:>' + link.min_score.to_s if link.min_score.present? && link.min_score != 0 append_to_tags += ' -animated' unless link.check_ability 'can_show_videos' + append_to_tags += ' ' + link.user.kinks.pluck(:name).map { |name| "~#{name}" }.join(' ') if link.check_ability('is_kink_aligned') && !kink_in_query append_to_tags end def make_blacklist(link) - link.blacklist.downcase.gsub(/[^a-z_\(\)\d\: ]/, '') + link&.blacklist&.downcase&.gsub(/[^a-z_\(\)\d\: ]/, '') || '' end def get_search_base(link) sanitized_blacklist = make_blacklist(link) - make_tag_suffix(link, sanitized_blacklist) + make_tag_suffix(link, sanitized_blacklist, '') end helper_method :get_search_base def get_post(id, link) result = get_tag_results "id:#{id}", nil, nil, link, 1 + result&.count&.positive? ? result[0] : nil end @@ -149,7 +188,7 @@ def on_link_react (link) Comment.create user_id: link.user.id, link_id: link.id, content: link.response_text unless link.response_type.nil? # If a came reaction, log an orgasm - Nuttracker::Orgasm.create rating: 3, is_ruined: false, user_id: link.user.id if link.response_type == 'came' + Nuttracker::Orgasm.create rating: 3, is_ruined: false, user: link.user, caused_by: link.set_by if link.response_type == 'came' # If a disgust reaction, revert to old wallpaper if link.response_type == 'disgust' @@ -165,11 +204,74 @@ def on_link_react (link) link end + # @param [User] user + # @param [Surrender] surrender + def log_in_as(user, surrender = nil) + session[:user_id] = user.id + cookies.signed[:permanent_session_id] = nil + + if surrender + cookies.signed[:surrender_id] = surrender.id + Notification.create user:, notification_type: :surrender_event, link: root_path, text: "#{surrender.controller.username} logged in as you." + redirect_to root_path, notice: "#{surrender.controller.username} has logged in as #{ user.username }" + else + cookies.signed[:surrender_id] = nil + redirect_to root_path + end + + end + + def kick_bad_surrender_controllers + if cookies.signed[:surrender_id].present? + begin + surrender = Surrender.find(cookies.signed[:surrender_id]) + if !surrender || surrender.expired? + session[:user_id] = nil + cookies.signed[:surrender_id] = nil + surrender.destroy if surrender + return redirect_to new_session_url, alert: 'Account surrender for user is over.' + end + rescue + session[:user_id] = nil + cookies.signed[:surrender_id] = nil + return redirect_to new_session_url, alert: 'Account surrender for user is over.' + end + end + end + + def authorize_for_surrenderd_accounts + return redirect_to new_session_url, alert: 'Error 500, service/E621 down?' if current_visit&.banned_ip.present? + + return redirect_to new_session_url, alert: 'Not authorized' if current_user.nil? + + return kick_bad_surrender_controllers + end + def authorize - redirect_to new_session_url, alert: 'Not authorized' if current_user.nil? + result = authorize_for_surrenderd_accounts + return result if result + + result_two = disallow_surrendered_accounts + return result_two if result_two + end + + def disallow_surrendered_accounts + current_surrender = current_user&.current_surrender + if cookies.signed[:surrender_id].nil? && current_surrender && !current_surrender.expired? + return redirect_to current_surrender, alert: "#{current_surrender.user.username} attempted to use walltaker while their account is surrendered." + end + + return kick_bad_surrender_controllers end def authorize_with_admin redirect_to '/', alert: 'Not authorized' unless current_user && current_user.admin end + + def authorize_with_admin_or_lizard + redirect_to '/', alert: 'Not authorized' unless current_user + + is_lizard = %w[PornLizardWarren PornLizardKi PornLizardTaylor].include?(current_user.username) + redirect_to '/', alert: 'Not authorized' unless current_user && (current_user.admin || is_lizard) + end end diff --git a/app/controllers/comments_controller.rb b/app/controllers/comments_controller.rb index a4e419b1..c9e46138 100644 --- a/app/controllers/comments_controller.rb +++ b/app/controllers/comments_controller.rb @@ -18,6 +18,9 @@ def create @comment.user_id = current_user.id if @comment.save + if @link.user != current_user + Notification.create user: @link.user, notification_type: :comment_on_your_link, text: "#{current_user.username} said \"#{@comment.content&.truncate(100)}\"", link: link_path(@link, anchor: 'comments') + end redirect_to new_link_comment_url(@link) else render :new, status: :unprocessable_entity @@ -25,12 +28,13 @@ def create end private - def set_link - @link = Link.find(params[:link_id]) - end - # Only allow a list of trusted parameters through. - def comment_params - params.require(:comment).permit(:content) - end + def set_link + @link = Link.find(params[:link_id]) + end + + # Only allow a list of trusted parameters through. + def comment_params + params.require(:comment).permit(:content) + end end diff --git a/app/controllers/dashboard_controller.rb b/app/controllers/dashboard_controller.rb index a34409e3..fb0735c8 100644 --- a/app/controllers/dashboard_controller.rb +++ b/app/controllers/dashboard_controller.rb @@ -4,23 +4,27 @@ class DashboardController < ApplicationController def index unless current_user.nil? @recent_posts = PastLink.order(id: :desc).take 6 - wallpapers_changed_today = PastLink.where(created_at: Time.now.beginning_of_day..Time.now.end_of_day) - @total_wallpapers_changed_today_by_user = wallpapers_changed_today - .joins(:user) - .select('COUNT(DISTINCT past_links.id) as total, users.username') - .group('users.username') - .order(total: :desc) - @total_wallpapers_changed_today = wallpapers_changed_today.count - @total_wallpapers_changed_all = PastLink.count + wallpapers_changed_today = Rails.cache.fetch("v1/totalchangedtoday", expires_in: 2.minutes) { PastLink.where(created_at: Time.now.beginning_of_day..Time.now.end_of_day) } + @total_wallpapers_changed_today_by_user = Rails.cache.fetch("v1/changedtodaychart", expires_in: 5.minutes) do + wallpapers_changed_today + .joins(:user) + .select('COUNT(DISTINCT past_links.id) as total, users.username') + .group('users.username') + .order(total: :desc) + .limit(20) + end + @total_wallpapers_changed_all = Rails.cache.fetch("v1/totallinkchanges", expires_in: 45.minutes) { PastLink.count } @your_total_wallpapers_changed_all = PastLink.where(user_id: current_user.id).count if current_user - @total_wallpapers_changed_grouped_by_day = PastLink.group_by_day(:created_at, range: 1.weeks.ago.midnight..Time.now).count + @total_wallpapers_changed_grouped_by_day = Rails.cache.fetch('v1/totalwallpapersgroupedbydaychart', expires_in: 10.minutes) { PastLink.group_by_day(:created_at, range: 1.weeks.ago.midnight..Time.now).count } @newest_user = User.last - @online_links_count = Link.all - .where(friends_only: false) - .is_online - .and( - Link.all.where('expires > ?', Time.now).or(Link.all.where(never_expires: true)) - ).count + @online_links_count = Rails.cache.fetch("v1/onlinelinkcount", expires_in: 5.minutes) do + Link.all + .where(friends_only: false) + .is_online + .and( + Link.all.where('expires > ?', Time.now).or(Link.all.where(never_expires: true)) + ).count + end @users_viewing_links = User.where.not(viewing_link_id: nil) end diff --git a/app/controllers/errors_controller.rb b/app/controllers/errors_controller.rb new file mode 100644 index 00000000..a8b0928d --- /dev/null +++ b/app/controllers/errors_controller.rb @@ -0,0 +1,9 @@ +class ErrorsController < ApplicationController + def not_found + render status: :not_found + end + + def server_error + render status: :internal_server_error + end +end diff --git a/app/controllers/friendships_controller.rb b/app/controllers/friendships_controller.rb index 2ccda562..fe167602 100644 --- a/app/controllers/friendships_controller.rb +++ b/app/controllers/friendships_controller.rb @@ -5,17 +5,13 @@ class FriendshipsController < ApplicationController # GET /friendships or /friendships.json def index - @friendships = Friendship.all.where(sender: current_user) - .or(Friendship.all.where(receiver: current_user)) - .where(confirmed: true) - @pending_friendship_requests = Friendship.all.where(sender: current_user) - .where(confirmed: false) + @friendships = Friendship.involving(current_user).is_confirmed + @pending_friendship_requests = Friendship.where(sender: current_user).is_request end # GET /friendship/requests def requests - @friendships = Friendship.all.where(receiver: current_user) - .where(confirmed: [nil, false]) + @friendships = Friendship.where(receiver: current_user).is_request render :index end @@ -60,7 +56,7 @@ def create })) unless @friendship.valid? - redirect_back_or_to root_path, alert: 'You\'re already friends, or have a pending request.' + redirect_back_or_to root_path, alert: @friendship.errors.first.message return end diff --git a/app/controllers/help_controller.rb b/app/controllers/help_controller.rb index b22280d2..65697f3e 100644 --- a/app/controllers/help_controller.rb +++ b/app/controllers/help_controller.rb @@ -1,4 +1,9 @@ class HelpController < ApplicationController def index end + def faq + end + + def client_guide + end end diff --git a/app/controllers/kink_controller.rb b/app/controllers/kink_controller.rb new file mode 100644 index 00000000..5cd82bbb --- /dev/null +++ b/app/controllers/kink_controller.rb @@ -0,0 +1,108 @@ +class KinkController < ApplicationController + include ActionView::RecordIdentifier + before_action :authorize, only: %i[add remove toggle_star] + + def users_kinks + @user = User.find_by_username(params['user_id']) + @kinks = @user.kinks if @user.present? + @kinks = [] unless @user.present? + + @is_current_user = false + @is_current_user = current_user.id == @user.id if current_user && @user + @is_current_user = false unless current_user + end + + def show + @kink = Kink.find(params['id']) + @users = @kink.users.order(updated_at: :desc).where.not(id: current_user&.id) + end + + def search_kinks + @link = Link.find(params[:link_id]) + return redirect_to root_path unless @link + + @user = @link.user + @kinks = @user.kinks if @user.present? + @kinks = [] unless @user.present? + end + + def test_on_e621 + kink = Kink.find(params['id']) + kink.test_on_e621 if kink + + if kink.works_on_e621? + render turbo_stream: turbo_stream.update((dom_id(kink) + '_e621_status'), partial: 'kink/e621_status', locals: { kink: }) + else + render turbo_stream: turbo_stream.update((dom_id(kink) + '_e621_status'), partial: 'kink/e621_status', locals: { kink:, failed: true }) + end + end + + def new + @kink = current_user.kinks.new + end + + def update + kink_name = kink_params['name'] + + kink = Kink.find_by_name kink_name + if kink + begin + current_user.kinks << kink + + @kink = kink + render 'update' + rescue + kink.valid? + redirect_to user_kinks_path(current_user.username), alert: kink.errors.full_messages.first + end + else + kink = current_user.kinks.build + kink.name = kink_name + + begin + if kink.save + @kink = kink + render 'update' + else + redirect_to user_kinks_path(current_user.username), alert: kink.errors.full_messages.first + end + rescue + redirect_to user_kinks_path(current_user.username), alert: kink.errors.full_messages.first + end + end + end + + def toggle_star + @kink = Kink.find(params['id']) + + if @kink + kink_haver = @kink.had_by(current_user) + + if @kink.had_by(current_user) + result = kink_haver.toggle_star + redirect_to user_kinks_path(current_user.username), alert: kink_haver.errors.full_messages.join("\n") unless result + else + redirect_to user_kinks_path(current_user.username), alert: 'Something went wrong' + end + else + redirect_to user_kinks_path(current_user.username), alert: 'Kink missing' + end + end + + def remove + kink = Kink.find(params['id']) + + if kink + @kink = kink + current_user.kinks.delete kink + else + redirect_to user_kinks_path(current_user.username), alert: 'Something went wrong' + end + end + + private + + def kink_params + params.require(:kink).permit(:name) + end +end diff --git a/app/controllers/leaderboard_controller.rb b/app/controllers/leaderboard_controller.rb index d0b9e895..a44d45d5 100644 --- a/app/controllers/leaderboard_controller.rb +++ b/app/controllers/leaderboard_controller.rb @@ -1,5 +1,5 @@ class LeaderboardController < ApplicationController def index - @top_setters = User.order(set_count: :desc).limit(10) + @top_setters = User.where.not(username: %w[PornLizardKi PornLizardWarren PornLizardTaylor PornBot DeletedAccount28]).order(set_count: :desc).limit(10) end end diff --git a/app/controllers/link_wizard_controller.rb b/app/controllers/link_wizard_controller.rb new file mode 100644 index 00000000..ffacba47 --- /dev/null +++ b/app/controllers/link_wizard_controller.rb @@ -0,0 +1,139 @@ +class LinkWizardController < ApplicationController + include Wicked::Wizard + + before_action :set_progress, only: %i[show] + before_action :set_link, only: %i[show apply] + before_action :authorize + + steps :intro, :summon, :control, :kinks, :gender, :surprise, :no_fun, :orgasmic_conclusion + + def show + case step + when :summon + @link + end + render_wizard + end + + def apply + case step + when :intro + if params[:gooner] == 'true' + @link.min_score = 10 + current_user.pervert = true + current_user.mascot = :taylor + current_user.save + else + @link.min_score = 50 + end + + when :summon + if params[:impatient] == 'true' + @link.min_score -= 10 + @link.set_ability :can_be_set_by_lizard, true + end + + when :control + protection_level = (params[:protect] || '3').to_i + @link.blacklist ||= '' + + @link.blacklist += ' nightmare_fuel nazi why' if protection_level > 1 + @link.blacklist += ' meme death body_horror gore scat' if protection_level > 2 + @link.blacklist += ' urine fart rape messy_diaper cursed_image vore pregnant' if protection_level > 3 + @link.blacklist += ' diapers humor joke icon blood pain sadism crossover obese what_has_science_done' if protection_level > 4 + + @link.min_score += 5 * protection_level + + when :kinks + @link.terms = params[:terms] || '' + + when :gender + bl_genders = params[:blacklisted_genders].filter &:present? + bl_parts = params[:blacklisted_parts].filter &:present? + bl_sex = params[:blacklisted_sexualities].filter &:present? + + @link.blacklist += ' male_focus manly muscular' if bl_genders.include? 'male' + @link.blacklist += ' female_focus girly' if bl_genders.include? 'female' + @link.blacklist += ' genderfluid ambiguous_gender' if bl_genders.include? 'nonbinary' + @link.blacklist += ' genderswap' if bl_genders.include? 'genderbends' + @link.blacklist += ' penis' if bl_parts.include? 'cocks' + @link.blacklist += ' pussy' if bl_parts.include? 'pussies' + @link.blacklist += ' breasts teats' if bl_parts.include? 'breasts' + @link.blacklist += ' anus anal_penetration' if bl_parts.include? 'assholes' + @link.blacklist += ' cloaca' if bl_parts.include? 'cloaca' + + @link.blacklist += ' male/male' if bl_sex.include?('gay') && bl_genders.include?('male') + @link.blacklist += ' female/female' if bl_sex.include?('gay') && bl_genders.include?('female') + @link.blacklist += ' female/female male/male' if bl_sex.include?('gay') && !bl_genders.include?('female') && !bl_genders.include?('male') + + @link.blacklist += ' male/female' if bl_sex.include?('straight') + + if !bl_genders.include?('male') && bl_genders.include?('female') + current_user.mascot = :warren + elsif bl_genders.include?('male') && !bl_genders.include?('female') + current_user.mascot = :taylor + elsif bl_genders.include?('male') && bl_genders.include?('female') + current_user.mascot = :ki + end + + current_user.save + + when :surprise + @link.theme = params[:theme] || nil + + when :no_fun + @link.friends_only = params[:friends_only] == 'true' + end + + @link.wizard_page = next_step unless step === :no_fun + @link.wizard_page = nil if step === :no_fun + + @link.save + redirect_to next_wizard_path + end + + def spawn_link + link = Link.new + link.user = current_user + link.wizard_page = :intro + link.never_expires = true + link.min_score = 0 + link.save + redirect_to link_wizard_path(link_id: link.id, id: :intro) + end + + def update + @link.unfinished = true + + if @link.errors.count == 0 + @link.save + redirect_to link_path @link + else + redirect_back_or_to link_wizard_path(link_id: link.id, id: :intro), alert: 'Something went wrong' + end + end + + private + + def set_link + @link = Link.find(params['link_id']) + + if current_user&.id != @link.user.id + redirect_to link_url(@link), alert: 'Not authorized.' + track :nefarious, :use_wizard_on_others_link + return + end + + unless @link + redirect_to spawn_link_links_path + end + end + + def set_progress + if wizard_steps.any? && wizard_steps.index(step).present? + @progress = ((wizard_steps.index(step) + 1).to_d / wizard_steps.count.to_d) * 100 + else + @progress = 0 + end + end +end diff --git a/app/controllers/links_controller.rb b/app/controllers/links_controller.rb index 2b23a398..2b297ea9 100644 --- a/app/controllers/links_controller.rb +++ b/app/controllers/links_controller.rb @@ -15,6 +15,7 @@ class LinksController < ApplicationController before_action :prevent_public_expired, only: %i[show update] before_action :protect_friends_only_links, only: %i[show update] before_action :skip_unauthorized_requests, only: %i[update toggle_ability], if: -> { update_request_unsafe? } + before_action :disallow_surrendered_accounts, only: %i[update] # 4. save presence + analytics after_action :log_presence, only: %i[show] @@ -28,20 +29,22 @@ def index # GET /browse (all online links) def browse # FUCK YOU, I join what I want, get ready for the query from hell - @links = Link.all - .where(friends_only: false) - .is_online - .and( - Link.all.where('expires > ?', Time.now).or(Link.all.where(never_expires: true)) - ) - .joins(:user) - .joins(:past_links) - .where('past_links.created_at = (SELECT MAX(created_at) FROM past_links WHERE past_links.link_id = links.id)') - .order(Arel.sql(%q{ - CASE WHEN users.created_at > now() - interval '8 hours' THEN 1 ELSE 0 END DESC, + science_links = Rails.cache.fetch("v2/browselinks", expires_in: 4.minutes) do + Link.all + .is_public + .is_online + .joins(:user) + .joins(:past_links) + .where('past_links.created_at = (SELECT MAX(created_at) FROM past_links WHERE past_links.link_id = links.id)') + .order(Arel.sql(%q{ past_links.created_at - make_interval(secs := users.set_count * 6) ASC })) + .limit(18) + .pluck(:id) + end + @new_user_links = Link.joins(:user).is_public.is_online.where('users.created_at': 12.hours.ago..Time.now).order('RANDOM()').limit(3) + @links = Link.where(id: science_links) end # GET /links/1 or /links/1.json @@ -116,6 +119,13 @@ def update track :regular, :update_link_details did_save_successfully else + if current_user&.quarantined || current_visit&.banned_ip.present? + redirect_to new_session_url, alert: 'Error 500, service/E621 down?' + return + end + if current_user&.current_surrender + Notification.create user: current_user, notification_type: :surrender_event, link: link_path(@link), text: "#{current_user.current_surrender.controller.username} set a new wallpaper for #{@link.user.username}" + end assign_e621_post_to_self e621_post, @link end @@ -154,14 +164,7 @@ def export end def toggle_ability - able_to = @link.check_ability params['ability'] - if able_to - track :regular, :disabled_ability, ability_name: params['ability'] - @link.abilities.delete_by ability: params['ability'] - else - track :regular, :enabled_ability, ability_name: params['ability'] - @link.abilities.create ability: params['ability'] - end + @link.toggle_ability params['ability'] redirect_to edit_link_path @link end @@ -252,7 +255,7 @@ def prevent_public_expired @is_expired = @link.never_expires ? false : @link.expires <= Time.now.utc current_user_is_not_owner = current_user && current_user.id != @link.user.id not_logged_in = current_user.nil? - redirect_to root_url, alert: 'That link was expired!' if @is_expired && (current_user_is_not_owner || not_logged_in) + redirect_to root_url, alert: 'That link has expired!' if @is_expired && (current_user_is_not_owner || not_logged_in) end def do_link_request_test @@ -291,7 +294,8 @@ def assign_e621_post_to_self(e621_post, link) current_user.set_count = current_user.set_count.to_i + 1 current_user.save end - track :regular, :update_link_post, attempted_post_id: params['link'][:post_id] - PastLink.log_link(link).save + past_link = PastLink.log_link(link) + past_link.save + track :regular, :update_link_post, attempted_post_id: params['link'][:post_id], past_link_id: past_link.id end end diff --git a/app/controllers/lizard_tools_controller.rb b/app/controllers/lizard_tools_controller.rb new file mode 100644 index 00000000..287e2db6 --- /dev/null +++ b/app/controllers/lizard_tools_controller.rb @@ -0,0 +1,23 @@ +class LizardToolsController < ApplicationController + before_action :authorize_with_admin_or_lizard + def index + end + + def warren + @links = Link.is_online.joins(:user).where(user: {mascot: 'warren'}).group_by {|link| !!link.user.pervert} + @name = 'Warren' + render 'lizard_browse' + end + + def ki + @links = Link.is_online.joins(:user).where(user: {mascot: 'ki'}).group_by {|link| !!link.user.pervert} + @name = 'Ki' + render 'lizard_browse' + end + + def taylor + @links = Link.is_online.joins(:user).where(user: {mascot: 'taylor'}).group_by {|link| !!link.user.pervert} + @name = 'Taylor' + render 'lizard_browse' + end +end diff --git a/app/controllers/message_thread_controller.rb b/app/controllers/message_thread_controller.rb index 08e7d7cc..e9e427e0 100644 --- a/app/controllers/message_thread_controller.rb +++ b/app/controllers/message_thread_controller.rb @@ -11,6 +11,10 @@ def show end def send_message + if current_visit&.banned_ip.present? + return + end + message = params['message'] if @message_thread && (message['content'].class == String) && (message['content'].length > 0) @new_message = @message_thread.messages.new @@ -26,6 +30,10 @@ def send_message Notification.create user: user, notification_type: :new_message, text: "#{current_user.username}: #{message['content'].truncate 24}", link: message_thread_path(@message_thread) end end + + if current_user&.current_surrender + Notification.create user: current_user, notification_type: :surrender_event, link: message_thread_path(@message_thread), text: "#{current_user.current_surrender.controller.username} said '#{@new_message.content}' in a message thread." + end redirect_to message_thread_path @message_thread end end diff --git a/app/controllers/mod_tools_controller.rb b/app/controllers/mod_tools_controller.rb new file mode 100644 index 00000000..841843a5 --- /dev/null +++ b/app/controllers/mod_tools_controller.rb @@ -0,0 +1,136 @@ +class ModToolsController < ApplicationController + before_action :authorize_with_admin + + def index + end + + def show_password_reset + end + + def update_password_reset + email = params.permit(:email)['email'] + + if !email || email.length < 2 + return redirect_to mod_tools_passwords_index_path(fail: 'Email segment too short. Ideally at least 6 letters. They need to provide more of their email.', email:) + end + + # Prepared statement, rails escapes and wraps template var here. + matches = User.where('lower(email) LIKE ?', "%#{email.downcase}%").all if params['commit'] == 'Try to generate a reset link (case insensitive)' + matches = User.where('email LIKE ?', "%#{email}%").all if params['commit'] == 'Try to generate a reset link (case sensitive)' + + if matches.length > 1 + if matches.length == 2 && (matches[0].email.downcase === matches[1].email.downcase) + return redirect_to mod_tools_passwords_index_path(fail: "Found 2 accounts only differing by capitalization, #{matches[0].email} and #{matches[1].email}. Use case sensitive search.", email:) + else + return redirect_to mod_tools_passwords_index_path(fail: 'Matches more than 1 account, can they be more specific?', email:) + end + end + + if !matches || matches.length == 0 + return redirect_to mod_tools_passwords_index_path(fail: 'Email does not exist in database, did they make a typo?', email:) + end + + matches[0].password_reset_token = SecureRandom.uuid + '-' + current_user.id.to_s + matches[0].save + + track :regular, :mod_password_reset, by: current_user.username, for: matches[0].username + + if email.length < 6 + return redirect_to mod_tools_passwords_index_path(link: matches[0].password_reset_token, username: matches[0].username, fail: "Search provided was short. (Only #{email.length} letters!) Are you sure you aren\'t being manipulated? Someone may be able to guess a small portion of an email.", email: matches[0].email) + end + + redirect_to mod_tools_passwords_index_path(link: matches[0].password_reset_token, username: matches[0].username, email: matches[0].email) + end + + def show_user + if params['email'] + email = params.permit(:email)['email'] + user = User.where('lower(email) LIKE ?', "%#{email.downcase}%").first + + render 'show_user', locals: { user: } + end + end + + def assume_user + user = User.find(params['user']) + + unless user + redirect_to mod_tools_users_index_url, alert: 'Failed to assume that user.' + return + end + + log_in_as(user) + end + + def update_user + begin + safe_params = params.require(:user).permit(:id, :email, :username, :details, :api_key, :set_count, :viewing_link, :password_reset_token, :quarantined, :pervert, :mascot) + user = User.find(safe_params['id']) + + if user + user.update(safe_params) + return render turbo_stream: turbo_stream.replace("mod_tools_edit_user_form", partial: 'mod_tools/edit_user_form', locals: { user: }) + end + rescue + return render turbo_stream: turbo_stream.replace("mod_tools_edit_user_form", partial: 'mod_tools/edit_user_form', locals: { fail: 'something went wrong' }) + end + + render turbo_stream: turbo_stream.replace("mod_tools_edit_user_form", partial: 'mod_tools/edit_user_form', locals: { fail: 'that user does not exist' }) + end + + def show_quarantine + @users = User.where(admin: false).order(id: :desc).limit 100 + end + + def update_quarantine + user = User.find(params['user']) + + unless user + redirect_to mod_tools_quarantine_index_path, alert: 'Failed to find that user.' + return + end + + user.quarantined = user.quarantined ? false : true + result = user.save + + redirect_to mod_tools_quarantine_index_path(anchor: helpers.dom_id(user)) + end + + def update_ipban + user = User.find(params['user']) + + unless user + redirect_to mod_tools_quarantine_index_path, alert: 'Failed to find that user.' + return + end + + ips = user.ahoy_visits.all.map { |visit| visit.ip } + ips.each do |ip| + BannedIp.create(ip_address: ip, banned_by: current_user) + end + + redirect_to mod_tools_quarantine_index_path(anchor: helpers.dom_id(user)) + end + + def show_recent_events + @events = Ahoy::Event.where(name: 'regular:update_link_post') + .or(Ahoy::Event.where("name like 'nefarious:%'")) + .order(id: :desc) + .limit 100 + end + + def update_ipban_by_event + event = Ahoy::Event.find(params['event']) + + unless event + redirect_to mod_tools_events_index_url, alert: 'Failed to find that event.' + return + end + + BannedIp.create(ip_address: event.visit.ip, banned_by: current_user) + + redirect_to mod_tools_events_index_url, notice: "IP banned #{event.visit.ip}" + end + + +end diff --git a/app/controllers/notification_controller.rb b/app/controllers/notification_controller.rb index 999e9e2b..fb7e4144 100644 --- a/app/controllers/notification_controller.rb +++ b/app/controllers/notification_controller.rb @@ -1,6 +1,6 @@ class NotificationController < ApplicationController before_action :set_notification, only: %i[show] - before_action :authorize + before_action :authorize_for_surrenderd_accounts def show if @notification && (@notification.user.id == current_user.id) diff --git a/app/controllers/porn_search_controller.rb b/app/controllers/porn_search_controller.rb index fe79feb8..40fdb28d 100644 --- a/app/controllers/porn_search_controller.rb +++ b/app/controllers/porn_search_controller.rb @@ -1,9 +1,13 @@ class PornSearchController < ApplicationController def index - + @link = Link.find(params[:link]) if params[:link] end def search + if current_visit&.banned_ip.present? + return + end + @page_number = porn_search_params[:page_number].to_i @page_number = 1 if @page_number == 0 @@ -18,7 +22,15 @@ def search track :regular, :search_e621_on_link, search: porn_search_params[:tags] - redirect_to :index if @posts.nil? + # I'm sorry lol + if porn_search_params[:full_rerender].present? + return render turbo_stream: [ + turbo_stream.replace('pornsearch_input', partial: 'porn_search/input'), + turbo_stream.replace('pornsearch_results', template: 'porn_search/search') + ] + end + + return render :index if @posts.nil? elsif porn_search_params[:message_thread].present? @message_thread = MessageThread.find(porn_search_params[:message_thread]) @@ -43,6 +55,6 @@ def send_message_and_return # Only allow a list of trusted parameters through. def porn_search_params - params.permit(:tags, :after, :before, :page_number, :link, :message_thread, :message, :return_to_path) + params.permit(:tags, :after, :before, :page_number, :link, :message_thread, :message, :return_to_path, :full_rerender) end end diff --git a/app/controllers/search_controller.rb b/app/controllers/search_controller.rb new file mode 100644 index 00000000..08674a12 --- /dev/null +++ b/app/controllers/search_controller.rb @@ -0,0 +1,31 @@ +class SearchController < ApplicationController + PAGE_SIZE = 5 + + def index + end + + def results + @query = params[:q].gsub(/\![\w\d_\(\)]+/, '') || '' + @only_online = params[:only_online] == '1' + @page = (params[:page] || '1').to_i + + merged_results = Rails.cache.fetch("v2/searchall/#{params[:q]}/#{@only_online ? 'online' : 'anystate'}", expires_in: 10.minutes) { + kinks = params[:q].scan(/\![\w\d_\(\)]+/).map { |query| query.gsub('!', '') } + + all_results = Link.search_positive(@query).is_public.is_online if @only_online + all_results = Link.search_positive(@query).is_public unless @only_online + + all_results_negative = all_results.search_negative(@query).pluck(:id) + + kink_results = Link.is_public.is_online.joins(user: :kinks).where(user: { kinks: { name: kinks } }).or(Link.is_public.is_online.where(theme: kinks)) if @only_online + kink_results = Link.is_public.joins(user: :kinks).where(user: { kinks: { name: kinks } }).or(Link.is_public.where(theme: kinks)) unless @only_online + + (kink_results.pluck(:id) + all_results.pluck(:id)).uniq.filter {|id| all_results_negative.none? { |neg_id| neg_id == id }} + } + + result_link_ids = merged_results[((@page - 1) * PAGE_SIZE)..PAGE_SIZE] + @total = merged_results.length + @has_next_page = (@total - (@page * PAGE_SIZE)) > 0 + @links = Link.where(id: result_link_ids) + end +end diff --git a/app/controllers/session_controller.rb b/app/controllers/session_controller.rb index 24ff09cb..7a377e4e 100644 --- a/app/controllers/session_controller.rb +++ b/app/controllers/session_controller.rb @@ -4,7 +4,11 @@ class SessionController < ApplicationController def new; end def create - user = User.find_by_email(login_params[:email]) + if current_visit&.banned_ip.present? + return + end + + user = User.where("lower(email) = ?",login_params[:email]&.downcase).first if !user.nil? if user.username == 'PornBot' @error = 'You don\'t look like a robot... Your IP address has been flagged.' @@ -12,6 +16,7 @@ def create render 'new', status: :unprocessable_entity else if user.authenticate(login_params[:password]) + cookies.signed[:surrender_id] = nil session[:user_id] = user.id unless params[:keep_me_logged_in] cookies.signed[:permanent_session_id] = { value: user.id, expires: 14.days.from_now } if params[:keep_me_logged_in] ahoy.authenticate(user) @@ -33,6 +38,7 @@ def create def destroy track :regular, :logged_out session[:user_id] = nil + cookies.signed[:surrender_id] = nil cookies.delete :permanent_session_id if cookies.signed[:permanent_session_id] redirect_to root_path, notice: 'Logged out!' end diff --git a/app/controllers/settings_controller.rb b/app/controllers/settings_controller.rb new file mode 100644 index 00000000..22e2c166 --- /dev/null +++ b/app/controllers/settings_controller.rb @@ -0,0 +1,24 @@ +class SettingsController < ApplicationController + before_action :authorize + + def index + @user = current_user + end + + def save + current_user.colour_preference = user_params['colour_preference'] + + if current_user.save + if current_user&.current_surrender + Notification.create user: current_user, notification_type: :surrender_event, link: user_path(current_user), text: "#{current_user.current_surrender.controller.username} set your colour scheme to #{current_user.colour_preference}" + end + redirect_to user_path(current_user.username), notice: "Settings changed successfully!" + else + redirect_to settings_path, notice: "Unknown error occurred" + end + end + + def user_params + params.require(:user).permit(:colour_preference) + end +end diff --git a/app/controllers/surrenders_controller.rb b/app/controllers/surrenders_controller.rb new file mode 100644 index 00000000..80e47a54 --- /dev/null +++ b/app/controllers/surrenders_controller.rb @@ -0,0 +1,69 @@ +class SurrendersController < ApplicationController + before_action :authorize, except: %i[show destroy] + before_action :authorize_for_surrenderd_accounts, only: %i[show destroy] + before_action :set_friendship_options, only: %i[new edit] + before_action :set_surrender, only: %i[show edit destroy assume] + before_action :protect_own_surrender, only: %i[show destroy] + + def index + @current_surrender = current_user.current_surrender + end + + def show + end + + def create + friendship = Friendship.involving(current_user).is_confirmed.find(surrender_params[:friendship]) + + if friendship.present? + surrender = current_user.create_current_surrender(expires_at: Time.now + 24.hours, friendship:) + + if surrender.save + Notification.create user: surrender.controller, notification_type: :surrender_event, link: friendships_path, text: "#{surrender.user.username} has allowed you to log into their account. You can do so in the friends menu." + redirect_to surrender_path(surrender), notice: 'Surrender was successfully created. Now just wait...' + else + redirect_to new_surrender_path, alert: surrender.errors.full_messages.first + end + else + redirect_to new_surrender_path, alert: 'Friendship does not exist.' + end + end + + def new + @surrender = Surrender.new + end + + def destroy + if @surrender.destroy + redirect_to surrenders_path, notice: 'Surrender was successfully destroyed.' + else + redirect_to surrender_path(@surrender), alert: 'Surrender could not be destroyed.' + end + end + + def assume + return redirect_to root_path, alert: 'Not allowed.' unless Friendship.where(id: @surrender.friendship.id).involving(current_user).is_confirmed.exists? + return redirect_to surrender_path(surrender), alert: '... what? How does that even make sense? You chose to surrender you account, then assume your own account?' unless @surrender.user != current_user + + log_in_as(@surrender.user, @surrender) + end + + private + + def set_friendship_options + friendships = Friendship.involving(current_user).is_confirmed + @friendship_options = friendships.map { |f| [f.other_user(current_user).username, f.id] } + end + + def set_surrender + @surrender = Surrender.find(params[:id]) + end + + def surrender_params + params.require(:surrender).permit(:friendship) + end + + def protect_own_surrender + redirect_to surrenders_path, alert: 'You cannot configure this surrender.' if @surrender.user != current_user + end +end diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 483ed2ec..05c33b58 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -1,5 +1,6 @@ class UsersController < ApplicationController after_action :track_visit, only: %i[new show edit] + before_action :authorize, only: %i[update] def new @user = User.new @@ -7,7 +8,14 @@ def new def show set_user_vars - @total_orgasms_by_day = Nuttracker::Orgasm.all.where(user: @user).where('created_at > ?', 1.weeks.ago.midnight).group_by_day(:created_at, range: 1.weeks.ago.midnight..Time.now).count + @total_orgasms_by_day = @user.orgasms.where('created_at > ?', 1.weeks.ago.midnight).group_by_day(:created_at, range: 1.weeks.ago.midnight..Time.now).count + @total_orgasms_caused = @user.caused_orgasms.where('caused_by_user_id <> user_id').count unless @user.username == 'gray' + @total_orgasms_caused = Nuttracker::Orgasm.count if @user.username == 'gray' + end + + def sets + @user = User.find_by(username: params[:username]) + @past_links = PastLink.where(set_by_id: @user.id).order(id: :desc).limit(50) end def edit @@ -25,6 +33,10 @@ def update @user.details = user_params[:details] if @user.save track :regular, :updated_details + + if @user.current_surrender + Notification.create user: @user, notification_type: :surrender_event, link: user_path(@user.username), text: "#{@user.current_surrender.controller.username} changed a profile setting." + end redirect_to user_path(@user.username), { notice: 'Successfully updated user.' } else track :error, :updating_details_went_wrong @@ -33,10 +45,15 @@ def update end def create + if current_visit&.banned_ip.present? + return + end + @user = User.new(user_params) if @user.save session[:user_id] = @user.id track :regular, :signed_up_and_first_log_in + ahoy.authenticate(@user) redirect_to url_for(controller: :links, action: :index), notice: 'Thank you for signing up!' else track :error, :failed_to_sign_up, errors: @user.errors @@ -49,7 +66,7 @@ def request_password_reset end def password_reset begin - user = User.find_by(email: params['email']) + user = User.where("lower(email) = ?", params[:email]&.downcase).first unless user throw :nefarious @@ -58,7 +75,7 @@ def password_reset PasswordResetMailer.reset_password(user).deliver redirect_to login_path, notice: 'A password reset email has been sent to that user' rescue - track :nefarious, :failed_to_reset_password, tried_email: params['email'] + track :nefarious, :failed_to_reset_password, tried_email: params['email'], exception: $! redirect_to forgor_path, alert: 'User was not found with that email' end end diff --git a/app/helpers/errors_helper.rb b/app/helpers/errors_helper.rb new file mode 100644 index 00000000..8e3b415c --- /dev/null +++ b/app/helpers/errors_helper.rb @@ -0,0 +1,2 @@ +module ErrorsHelper +end diff --git a/app/helpers/kink_helper.rb b/app/helpers/kink_helper.rb new file mode 100644 index 00000000..a0cbc2d5 --- /dev/null +++ b/app/helpers/kink_helper.rb @@ -0,0 +1,2 @@ +module KinkHelper +end diff --git a/app/helpers/link_wizard_helper.rb b/app/helpers/link_wizard_helper.rb new file mode 100644 index 00000000..f8fb53b4 --- /dev/null +++ b/app/helpers/link_wizard_helper.rb @@ -0,0 +1,2 @@ +module LinkWizardHelper +end diff --git a/app/helpers/links_helper.rb b/app/helpers/links_helper.rb index fb775afe..06611063 100644 --- a/app/helpers/links_helper.rb +++ b/app/helpers/links_helper.rb @@ -1,9 +1,13 @@ module LinksHelper def link_id_for_decoration(link_id) return '🐇' if link_id == 69 - return '🐺' if link_id == 666 + return '🐺' if link_id == 666 || link_id == 343 return '🐕' if link_id == 1 return '🐈' if link_id == 658 || link_id == 656 + return '🥎' if link_id == 348 + return '⚙️' if link_id == 581 + return '🏳️‍🌈' if link_id == 346 + return '🦊' if link_id == 1964 link_id end @@ -15,11 +19,31 @@ def link_agent_to_icon(link_agent) return :joihow if link_agent.include? 'joihow' return :wallpaper_engine if link_agent.include? 'Wallpaper-Engine-Client' return :automate if link_agent.include? 'walltaker-android-automate' + return :arson_automate if link_agent.include? 'arson-walltaker-automate' return :ioswidget if link_agent.include? 'widgetExtension' return :swift if link_agent.include? 'CFNetwork/' return :android_changer if link_agent.include? 'Walltaker-Changer/' return :jberliner if link_agent.include? 'JBerliner' - + return :chewtoy if link_agent.include? 'WalltakerEngine-chewtoy/' + return :kemkem_userscript if link_agent.include? 'Walltaker for Walltaker (kemkem)' + return :xenofluff if link_agent.include? 'Walltaker_eXPerience' :unknown end + + def lizard_to_description(lizard_name, is_perverted) + case [lizard_name, is_perverted] + when ['warren', false] + return 'He loves everything big. Big tits. Big dicks. Fat asses. All he cares about it your bits, just to feel the heft of it, what it smells like, and to taste anything dripping from them. Expect him to give you lots of larger than life close ups, and a little bit of light musk.' + when ['warren', true] + return 'He loves everything big. Really big. Huge udders. Horse dicks. Fat haunches. All he cares about it your bits, just to feel the sheer size of it, what it reeks like, and to taste what ever gross fluid is leaking out of it. Expect him to give you lots of hyper content and heavy musk.' + when ['taylor', false] + return 'She\'s a party girl, she\'ll fuck anyone that looks at her the right way. She especially loves showing off and she\'s maybe a bit competitive about it. Seeing anyone walking around with a fresh load dripping out their ass or cunt gets her looking for the closest cock she can milk. Expect public sex, creampies and facials.' + when ['taylor', true] + return 'She\'s a party girl, she\'ll fuck anything really. She especially loves showing off and she\'s very competitive about it. Seeing anyone walking around with a fresh load dripping out their ass or cunt gets her looking for the closest cock get bred by. She\'s never minded flashing her cunt to make that happen. Expect lots of public sex with flashing, excessive cum and impregnation.' + when ['ki', false] + return 'They\'re demisexual. That means they need to sense a connection with someone before they can do anything in bed. (And just talking about it makes them blush!) They love cuddling, rubbing, and any sensual touch. Expect to see lots of very cozy posts. Not all posts may be explicit, sometimes they just like a cozy wallpaper you know?' + when ['ki', true] + return 'They\'re demisexual... and they like you. You should be scared. They love touch, and nothing is more sensual to them than raw textural experiences. Latex, rubber, neoprene, slime, they love it all. They might even tickle you, it\'s hard to know what to expect! Just know, you\'re going to feel it.' + end + end end diff --git a/app/helpers/lizard_tools_helper.rb b/app/helpers/lizard_tools_helper.rb new file mode 100644 index 00000000..3af34ece --- /dev/null +++ b/app/helpers/lizard_tools_helper.rb @@ -0,0 +1,2 @@ +module LizardToolsHelper +end diff --git a/app/helpers/mod_tools_helper.rb b/app/helpers/mod_tools_helper.rb new file mode 100644 index 00000000..631076ef --- /dev/null +++ b/app/helpers/mod_tools_helper.rb @@ -0,0 +1,2 @@ +module ModToolsHelper +end diff --git a/app/helpers/search_helper.rb b/app/helpers/search_helper.rb new file mode 100644 index 00000000..b3ce20ac --- /dev/null +++ b/app/helpers/search_helper.rb @@ -0,0 +1,2 @@ +module SearchHelper +end diff --git a/app/helpers/settings_helper.rb b/app/helpers/settings_helper.rb new file mode 100644 index 00000000..ffbedbaf --- /dev/null +++ b/app/helpers/settings_helper.rb @@ -0,0 +1,2 @@ +module SettingsHelper +end diff --git a/app/helpers/surrenders_helper.rb b/app/helpers/surrenders_helper.rb new file mode 100644 index 00000000..37fa5379 --- /dev/null +++ b/app/helpers/surrenders_helper.rb @@ -0,0 +1,2 @@ +module SurrendersHelper +end diff --git a/app/javascript/controllers/api_key_form_controller.js b/app/javascript/controllers/api_key_form_controller.js index a47b7db7..47b92d5a 100644 --- a/app/javascript/controllers/api_key_form_controller.js +++ b/app/javascript/controllers/api_key_form_controller.js @@ -1,5 +1,5 @@ import {Controller} from "@hotwired/stimulus" -import {WithModal} from '../modules/Modal' +import {WithModal} from 'modules/modal' export default class ApiKeyFormController extends Controller { static targets = ['key'] diff --git a/app/javascript/controllers/comments_sidebar_controller.js b/app/javascript/controllers/comments_sidebar_controller.js index 83cb035c..1792c427 100644 --- a/app/javascript/controllers/comments_sidebar_controller.js +++ b/app/javascript/controllers/comments_sidebar_controller.js @@ -7,6 +7,10 @@ export default class CommentsSidebarController extends Controller { past_first_load = false connect() { + if (window.location.hash.includes('comments')) { + this.setHidden(false) + } + document.body.addEventListener('click', (e) => { if (!this.hidden && !this.element.contains(e.target)) { this.setHidden(true) @@ -18,6 +22,7 @@ export default class CommentsSidebarController extends Controller { this.setHidden(true) } }) + setTimeout(() => this.past_first_load = true, 500) } diff --git a/app/javascript/controllers/discord_invite_controller.js b/app/javascript/controllers/discord_invite_controller.js new file mode 100644 index 00000000..13c1e319 --- /dev/null +++ b/app/javascript/controllers/discord_invite_controller.js @@ -0,0 +1,16 @@ +import {Controller} from "@hotwired/stimulus" +import {WithModal} from 'modules/modal' + +class DiscordInviteController extends Controller { + connect () { + if (this.element) { + this.element.addEventListener('click', this.handleClick.bind(this)) + } + } + + handleClick (e) { + if (!this.modal.isOpen()) this.modal.open() + } +} + +export default WithModal(DiscordInviteController) \ No newline at end of file diff --git a/app/javascript/controllers/friendship_controller.js b/app/javascript/controllers/friendship_controller.js index 2933c5f2..84488691 100644 --- a/app/javascript/controllers/friendship_controller.js +++ b/app/javascript/controllers/friendship_controller.js @@ -1,5 +1,5 @@ import {Controller} from "@hotwired/stimulus" -import {WithModal} from '../modules/Modal' +import {WithModal} from 'modules/modal' class FriendshipController extends Controller { static targets = ['cancel'] diff --git a/app/javascript/controllers/link_controller.js b/app/javascript/controllers/link_controller.js index 285419de..6a13dee7 100644 --- a/app/javascript/controllers/link_controller.js +++ b/app/javascript/controllers/link_controller.js @@ -1,5 +1,5 @@ import {Controller} from "@hotwired/stimulus" -import {WithModal} from '../modules/Modal' +import {WithModal} from 'modules/modal' function copy(str) { if (navigator?.clipboard?.writeText) { diff --git a/app/javascript/controllers/porn_search_controller.js b/app/javascript/controllers/porn_search_controller.js index 5256b943..b5b3d4e4 100644 --- a/app/javascript/controllers/porn_search_controller.js +++ b/app/javascript/controllers/porn_search_controller.js @@ -1,5 +1,5 @@ import {Controller} from "@hotwired/stimulus" -import {WithModal} from '../modules/Modal' +import {WithModal} from 'modules/modal' class PornSearchController extends Controller { static targets = ['thumbnail'] diff --git a/app/javascript/controllers/range_controller.js b/app/javascript/controllers/range_controller.js index 056ebe4d..f7e747be 100644 --- a/app/javascript/controllers/range_controller.js +++ b/app/javascript/controllers/range_controller.js @@ -1,5 +1,5 @@ import {Controller} from "@hotwired/stimulus" -import {WithModal} from '../modules/Modal' +import {WithModal} from 'modules/modal' export default class RangeController extends Controller { static targets = ['input', 'value'] diff --git a/app/javascript/controllers/tabs_controller.js b/app/javascript/controllers/tabs_controller.js index 30029926..5b968688 100644 --- a/app/javascript/controllers/tabs_controller.js +++ b/app/javascript/controllers/tabs_controller.js @@ -1,5 +1,5 @@ import {Controller} from "@hotwired/stimulus" -import {WithModal} from '../modules/Modal' +import {WithModal} from 'modules/modal' export default class TabsController extends Controller { static targets = ['tab', 'label'] diff --git a/app/javascript/controllers/widget_installer_controller.js b/app/javascript/controllers/widget_installer_controller.js index 1f022f12..6826d2fa 100644 --- a/app/javascript/controllers/widget_installer_controller.js +++ b/app/javascript/controllers/widget_installer_controller.js @@ -1,5 +1,5 @@ import {Controller} from "@hotwired/stimulus" -import {WithModal} from '../modules/Modal' +import {WithModal} from 'modules/modal' class WidgetInstallerController extends Controller { connect () { diff --git a/app/javascript/modules/Modal.js b/app/javascript/modules/modal.js similarity index 93% rename from app/javascript/modules/Modal.js rename to app/javascript/modules/modal.js index 84bf7725..bce1dbf1 100644 --- a/app/javascript/modules/Modal.js +++ b/app/javascript/modules/modal.js @@ -12,7 +12,8 @@ export class Modal { this.#tick(); } - close() { + close(e) { + if (e) e.stopPropagation(); this.#visible = false; this.#tick(); } @@ -24,6 +25,10 @@ export class Modal { } } + isOpen () { + return this.#visible + } + #appendModalHeader() { if (this.target) { const header = document.createElement('div'); diff --git a/app/mailers/password_reset_mailer.rb b/app/mailers/password_reset_mailer.rb index 67e4cd8c..99eda180 100644 --- a/app/mailers/password_reset_mailer.rb +++ b/app/mailers/password_reset_mailer.rb @@ -1,5 +1,5 @@ class PasswordResetMailer < ApplicationMailer - default :from => 'gray.pup@pawcorp.org' + default :from => 'mailgun@walltaker.joi.how' # @param [User] user def reset_password(user) diff --git a/app/models/ahoy/visit.rb b/app/models/ahoy/visit.rb index 66f89e5d..43c3071d 100644 --- a/app/models/ahoy/visit.rb +++ b/app/models/ahoy/visit.rb @@ -2,5 +2,6 @@ class Ahoy::Visit < ApplicationRecord self.table_name = "ahoy_visits" has_many :events, class_name: "Ahoy::Event" + has_one :banned_ip, foreign_key: :ip_address, primary_key: :ip belongs_to :user, optional: true end diff --git a/app/models/banned_ip.rb b/app/models/banned_ip.rb new file mode 100644 index 00000000..e2d3284d --- /dev/null +++ b/app/models/banned_ip.rb @@ -0,0 +1,3 @@ +class BannedIp < ApplicationRecord + belongs_to :banned_by, foreign_key: :banned_by_id, class_name: 'User' +end diff --git a/app/models/friendship.rb b/app/models/friendship.rb index 60787f70..52a21912 100644 --- a/app/models/friendship.rb +++ b/app/models/friendship.rb @@ -1,9 +1,46 @@ class Friendship < ApplicationRecord + has_many :surrenders, dependent: :destroy belongs_to :sender, foreign_key: :sender_id, class_name: 'User' belongs_to :receiver, foreign_key: :receiver_id, class_name: 'User', optional: true - validates :sender_id, uniqueness: { scope: [:receiver_id] } + + validate :friendship_does_not_already_exist + validates :receiver_id, comparison: { other_than: :sender_id, message: ->(friendship, data) { "You can\'t be friends with yourself, on walltaker at least." } } + + scope :involving, ->(user) { where(sender_id: user.id).or(where(receiver_id: user.id)) } + scope :is_confirmed, ->() { where(confirmed: true) } + scope :is_request, ->() { where(confirmed: [nil, false]) } def self.find_friendship(person_a, person_b) - self.where(sender_id: [person_a.id, person_b.id], receiver_id: [person_b.id, person_a.id], confirmed: true) + self.find_friendship_request(person_a, person_b).is_confirmed + end + + def self.find_friendship_request(person_a, person_b) + self.where(sender_id: [person_a.id, person_b.id], receiver_id: [person_b.id, person_a.id]) + end + + def friendship_does_not_already_exist + existing_friendship = Friendship.find_friendship_request(sender, receiver).first + if existing_friendship.present? + if existing_friendship.confirmed? + errors.add(:receiver_id, "You're already friends!") + else + if existing_friendship.id != self.id + if existing_friendship.sender_id == self.sender_id + errors.add(:receiver_id, "#{existing_friendship.receiver.username} has not accepted a friend request you already sent. You can cancel it and resend it in the 'Friends' tab. They will get another notification. Don't be a dick.") + else + errors.add(:receiver_id, "You already have a pending friend request from #{existing_friendship.sender.username}! You can accept it in the 'Friends' tab.") + end + end + end + end + end + + def other_user(user) + return sender if sender.id != user.id + receiver if receiver.id != user.id + end + + def controllable(user) + surrenders.where(user: user).any? end end diff --git a/app/models/kink.rb b/app/models/kink.rb new file mode 100644 index 00000000..911e051d --- /dev/null +++ b/app/models/kink.rb @@ -0,0 +1,37 @@ +class Kink < ApplicationRecord + validates_uniqueness_of :name + validates_length_of :name, maximum: 30, minimum: 1 + validates_associated :kink_havers, message: 'must only have one of each kink.' + + normalizes :name, with: -> name { name.gsub(/[^\w\d_\-\(\)\/\s]/, '').strip.squish.downcase.gsub(/\s/, '_') } + + has_many :kink_havers + has_many :users, through: :kink_havers + + def test_on_e621 + begin + result = ApplicationController.new.get_tag_results(name, nil, nil, nil, 1); + rescue + result = nil + end + + if result && result.length > 0 + self.works_on_e621 = true + else + self.works_on_e621 = false + end + + save + end + + # @param [User] by + def had_by (by) + self.kink_havers.find_by(user_id: by.id) + end + + # @param [User] by + def is_starred? (by) + kink_haver = had_by(by) + kink_haver&.is_starred? || false + end +end diff --git a/app/models/kink_haver.rb b/app/models/kink_haver.rb new file mode 100644 index 00000000..e198a424 --- /dev/null +++ b/app/models/kink_haver.rb @@ -0,0 +1,26 @@ +class KinkHaver < ApplicationRecord + MAXIMUM_STARRED_KINKS = 3 + + validates_uniqueness_of :kink_id, scope: :user_id, message: 'must only appear once' + validate :user_has_less_than_the_maximum_number_of_starred_kinks, on: :starring + + belongs_to :user + belongs_to :kink, inverse_of: :kink_havers + + scope :are_starred, -> { where(is_starred: true) } + + def user_has_less_than_the_maximum_number_of_starred_kinks + if user.kink_havers.are_starred.length >= MAXIMUM_STARRED_KINKS + errors.add(:user, "must can only have a maximum of #{MAXIMUM_STARRED_KINKS} starred kinks") + end + end + + def toggle_star + self.is_starred = !is_starred? + if is_starred + save(context: :starring) + else + save + end + end +end diff --git a/app/models/link.rb b/app/models/link.rb index d223e0e3..f14ef06c 100644 --- a/app/models/link.rb +++ b/app/models/link.rb @@ -1,7 +1,9 @@ class Link < ApplicationRecord + include PgSearch::Model belongs_to :user + belongs_to :set_by, foreign_key: :set_by_id, class_name: 'User', optional: true belongs_to :forked_from, foreign_key: :forked_from_id, class_name: 'Link', inverse_of: :forks, optional: true - has_many :forks, foreign_key: :forked_from_id, class_name: 'Link', inverse_of: :forked_from + has_many :forks, foreign_key: :forked_from_id, class_name: 'Link', inverse_of: :forked_from, dependent: :nullify has_many :viewing_users, foreign_key: :viewing_link_id, class_name: 'User' has_many :past_links has_many :comments, dependent: :destroy @@ -16,6 +18,12 @@ class Link < ApplicationRecord validates_uniqueness_of :custom_url, allow_nil: true, unless: ->(l) { l.custom_url.blank? } visitable :ahoy_visit + pg_search_scope :search_positive, against: %i[terms theme custom_url response_text post_description], associated_against: { + user: %i[username details] + }, using: { tsearch: { dictionary: 'english', prefix: true, any_word: true } } + + pg_search_scope :search_negative, against: :blacklist, using: { tsearch: { dictionary: 'english', any_word: true } } + scope :is_online, -> { where('last_ping > ?', Time.now - 1.minute) .or(where('live_client_started_at > ?', Time.now - 7.days)) @@ -24,8 +32,19 @@ class Link < ApplicationRecord scope :with_ability_to, ->(ability_name) { joins(:abilities).where('link_abilities.ability': ability_name) } + scope :is_public, -> { + ( + where(friends_only: false) + ).and( + where('expires > ?', Time.now).or(where(never_expires: true)) + ) + } + def is_online? - last_ping_online = last_ping > Time.now - 1.minute + is_ios = last_ping_user_agent&.match(/widgetExtension/) || false + + last_ping_online = last_ping > Time.now - 20.minutes if is_ios && last_ping_user_agent && last_ping + last_ping_online = last_ping > Time.now - 1.minute if !is_ios && last_ping_user_agent && last_ping live_client_online = live_client_started_at && (live_client_started_at > Time.now - 7.days) last_ping_online || live_client_online end @@ -35,6 +54,19 @@ def check_ability(ability) abilities.any? { |edge| edge.ability == ability } end + def toggle_ability(ability_name) + set_ability(ability_name, !check_ability(ability_name)) + end + + def set_ability(ability_name, value) + able_to = check_ability ability_name + if able_to && !value + abilities.delete_by ability: ability_name + elsif !able_to && value + abilities.create ability: ability_name + end + end + # @return [User | nil] def get_set_by_user return User.find(self.set_by_id) if self.set_by_id diff --git a/app/models/link_ability.rb b/app/models/link_ability.rb index 47b10e22..feced38d 100644 --- a/app/models/link_ability.rb +++ b/app/models/link_ability.rb @@ -2,6 +2,8 @@ class LinkAbility < ApplicationRecord belongs_to :link enum :ability, { can_show_videos: 'can_show_videos', - can_be_set_by_porn_bot: 'can_be_set_by_porn_bot' + can_be_set_by_porn_bot: 'can_be_set_by_porn_bot', + can_be_set_by_lizard: 'can_be_set_by_lizard', + is_kink_aligned: 'is_kink_aligned' } end diff --git a/app/models/notification.rb b/app/models/notification.rb index 2f3965ff..72905690 100644 --- a/app/models/notification.rb +++ b/app/models/notification.rb @@ -1,6 +1,6 @@ class Notification < ApplicationRecord belongs_to :user - enum notification_type: %i[friend_request_sent friend_request_received friend_request_they_accepted post_response added_to_message_thread new_message] + enum notification_type: %i[friend_request_sent friend_request_received friend_request_they_accepted post_response added_to_message_thread new_message orgasm_credited_to_you comment_on_your_link surrender_event] after_commit do broadcast_replace_to "header_notifications_#{user.id}", target: "header_notifications", partial: 'notifications', locals: { notifications: Notification.all.where(user: user).order(id: :desc).limit(5) } diff --git a/app/models/past_link.rb b/app/models/past_link.rb index 89411921..da2b1bc0 100644 --- a/app/models/past_link.rb +++ b/app/models/past_link.rb @@ -1,7 +1,7 @@ class PastLink < ApplicationRecord belongs_to :link belongs_to :user - #belongs_to :set_by_user, foreign_key: 'set_by_id', class_name: 'User' + belongs_to :set_by, foreign_key: :set_by_id, class_name: 'User', optional: true visitable :ahoy_visit def self.log_link(link) diff --git a/app/models/surrender.rb b/app/models/surrender.rb new file mode 100644 index 00000000..c52a2e20 --- /dev/null +++ b/app/models/surrender.rb @@ -0,0 +1,18 @@ +class Surrender < ApplicationRecord + belongs_to :user, inverse_of: :current_surrender, required: true + belongs_to :friendship, required: true + + validates :user, uniqueness: { scope: :friendship } + validates :expires_at, comparison: { greater_than: Time.now }, on: :create + + scope :not_for_user, ->(user) { where.not(user: user) } + scope :for_user, ->(user) { find_by(user: user) } + + def controller + friendship.other_user(user) + end + + def expired? + expires_at.before? Time.now + end +end diff --git a/app/models/user.rb b/app/models/user.rb index 8779981f..190cc756 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -2,16 +2,51 @@ class User < ApplicationRecord include ActiveModel::SecurePassword has_secure_password has_many :link + has_many :past_links, foreign_key: :set_by_id + has_many :orgasms, foreign_key: :user_id, class_name: 'Nuttracker::Orgasm' + has_many :caused_orgasms, foreign_key: :caused_by_user_id, class_name: 'Nuttracker::Orgasm' has_many :notifications + has_many :ahoy_visits, :class_name => 'Ahoy::Visit' + has_many :kink_havers + has_many :kinks, -> { order(is_starred: :desc, id: :desc) }, through: :kink_havers + attribute :colour_preference, :integer belongs_to :viewing_link, foreign_key: :viewing_link_id, class_name: 'Link', optional: true - validates_uniqueness_of :email, :username + has_one :current_surrender, class_name: 'Surrender', dependent: :destroy + + validates_uniqueness_of :username validates :email, format: { with: /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i, message: 'must be a valid email address' } + validates_uniqueness_of :email, :case_sensitive => false validates :password, confirmation: true validates :username, presence: true, format: { with: /\A[a-zA-Z0-9]+\Z/ } + enum colour_preference: %i[auto light dark] + + scope :has_friendship_with, ->(other) { + Friendship.find_friendship(other, self) + } + + scope :controllable_by, ->(other) { + controllable_user_ids = other.controllable_surrenders.pluck(:user_id).uniq + where(id: controllable_user_ids) + } + + # This was implemented so bad lol, should've been a relation. + def find_pornlizard + case mascot + when 'taylor' + User.find_by_username('PornLizardTaylor') + when 'warren' + User.find_by_username('PornLizardWarren') + when 'ki' + User.find_by_username('PornLizardKi') + else + User.find_by_username('PornLizardKi') + end + end + def assign_new_api_key self.api_key = SecureRandom.base64(6).slice 0..7 save @@ -27,6 +62,11 @@ def leave_link save end + def controllable_surrenders + friendship_ids = Friendship.involving(self).is_confirmed.pluck(:id) + Surrender.not_for_user(self).where(id: friendship_ids) + end + after_commit do if viewing_link_id viewed_link = Link.find(viewing_link_id) diff --git a/app/views/api/all_links.json.jbuilder b/app/views/api/all_links.json.jbuilder new file mode 100644 index 00000000..7e0cd41c --- /dev/null +++ b/app/views/api/all_links.json.jbuilder @@ -0,0 +1,5 @@ +json.cache! ['v1', 'all_links api', @force_online], expires_in: 5.minutes do + json.array! @links do |link| + json.partial! 'link', link: link + end +end \ No newline at end of file diff --git a/app/views/application/_flash.html.erb b/app/views/application/_flash.html.erb new file mode 100644 index 00000000..c5097f52 --- /dev/null +++ b/app/views/application/_flash.html.erb @@ -0,0 +1,7 @@ +<%# locals: (type:, msg:) -%> + +<%= turbo_frame_tag [type, msg].map(&:parameterize).join('_') do %> + <%= tag.div class: "flash flash--#{type == 'notice' ? 'success' : 'danger'}" do %> + <%= msg %> + <% end %> +<% end %> diff --git a/app/views/application/_flashes.html.erb b/app/views/application/_flashes.html.erb new file mode 100644 index 00000000..df0954cc --- /dev/null +++ b/app/views/application/_flashes.html.erb @@ -0,0 +1,6 @@ +<%= turbo_stream_from "#{current_user&.username || 'anon'}-flashes", target: :flashes %> +<%= turbo_frame_tag :flashes, class: 'flashes' do %> + <% flash.each do |type, msg| %> + <%= render "application/flash", type:, msg: %> + <% end %> +<% end %> diff --git a/app/views/application/_notifications.html.erb b/app/views/application/_notifications.html.erb index ffe7d26e..55af5492 100644 --- a/app/views/application/_notifications.html.erb +++ b/app/views/application/_notifications.html.erb @@ -11,7 +11,7 @@ <% else %> <% notifications.each do |notification| %> - <%= link_to notification_path(notification.id), class: 'notification' do %> + <%= link_to notification_path(notification.id), class: 'notification', data: { turbo: false } do %> <% case notification.notification_type %> <% when 'friend_request_sent' %> @@ -25,6 +25,12 @@ <% when 'new_message' %> + <% when 'orgasm_credited_to_you' %> + + <% when 'comment_on_your_link' %> + + <% when 'surrender_event' %> + <% end %>

<%= notification.text %>

diff --git a/app/views/dashboard/index.html.erb b/app/views/dashboard/index.html.erb index 36f2f923..a41a754c 100644 --- a/app/views/dashboard/index.html.erb +++ b/app/views/dashboard/index.html.erb @@ -30,7 +30,6 @@

Changes

-
<%= area_chart @total_wallpapers_changed_grouped_by_day, colors: ["#606575"], legend: false, height: "60px", library: { scales: { @@ -94,87 +93,104 @@

First off: Walltaker is an app built by adults, for adults. You must be over the age of 18 to use it. - Walltaker is - inspired by the WallClaimer app, which allows you to set the - wallpaper of your friends phones. This however leads to some weird cases where people post stuff that'd be on - your - metaphorical blacklist. With that in mind, I wanted it to be restricted to - e621.net + Walltaker is inspired by the WallClaimer app, which allows you to set the + wallpaper of your friend's phones. This however leads to some weird cases where people post stuff that'd be on + your metaphorical blacklist. With that in mind, I wanted it to be restricted to e621.net results, with an enforced blacklist. This keeps you in control, but not too much control.

What do I need?

- Walltaker requires you to make an account on the website (here!) first. Once you have an - account, you - can generate - links to send to friends/enemies, which will enqueue new wallpapers for your account. When you want to start - seeing - these new wallpapers, then you can download the companion apps listed below which is able to - listen - to wallpaper changes to any + Walltaker requires you to make an account on the website (here!) first. Once you have an account, you can generate + links to send to friends/enemies, which will enqueue new wallpapers for your account. When you want to start seeing + these new wallpapers, then you can download the companion apps listed below which is able to listen to wallpaper changes to any account. Enter your username, and then go about your day!

+<%= inline_svg_tag "wt.svg", class: "explainer_graphic", aria: true, title: "A flow chart, showing links pushing updates to devices running walltaker clients.", width: '100%' %> +

Clients

-
    -
  • - Desktop - Client - (Windows/Linux) -
  • -
  • - Android + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    ClientPlatform
    Lycraon's Wallpaper Engine + ClientWallpaper Engine for Windows
    Chewtoy's Walltaker Engine + Windows
    Gios' ClientAndroid 7+
    Arson's Automate ClientAutomate App for Android
    Deanskond's Automate ClientAutomate App for Android
    JBerliner's Walltaker.shLinux
    macOS ClientmacOS
    iOS WidgetiOS
    Android Client - (Android 12+) - -
  • - Gio's Client - (Android) -
  • -
  • - Lycraon's Wallpaper Engine + (Unmaintained) +
  • Android 12+
    Desktop Client - (Wallpaper Engine for Windows) - -
  • - Deanskond's Automate Client - (Automate App for Android) -
  • -
  • - JBerliner's Walltaker.sh - (Linux) -
  • -
  • - MacOS Client - (MacOS) -
  • -
  • - iOS Widget - (iOS) -
  • -
  • - JOI.how (Web-based) -
  • - + (Unmaintained) +
    Windows / Linux
    +Please try using clients that are still being maintained before trying unmaintained clients.

    Companion/Explorer Apps

    Extra tools that tie into Walltaker's site. These should help to integrate walltaker better or make the user experience cleaner.

    -
    + + + + + + + + + + + +
    ClientPlatform
    Gios' Explorer + Android 7+

    Help us out!

    - We'll be upfront! This site very cheap to operate. We have very little in running costs, and I am personally OK with - paying the $3-5 a month it costs in bandwidth and compute that it takes to keep this service online; however, a few - folks have asked if they can compensate our time as a tip, so we set up a kofi! -

    -

    - Know that this is - just for donating to us, because we like money. The site is cheap to run, interesting to work on, and is - not in danger of being shut down due - to expenses. It's just a nice gesture that's very much appreciated. + <%= image_tag 'mascot/TaylorNSFW.png', alt: 'Taylor, a gray and yellow lizard with tits, shaking them at you', class: 'mascot float-right', width: 195 %> + We'll be upfront! The site pretty cheap to operate. We have very little in running costs, and I am somewhat OK with + paying the ~$40 a month it costs in bandwidth and compute that it takes to keep this service online; however, a few + folks have asked if they can help out or compensate our time as a tip, so we set up a Ko-Fi!

    @@ -182,4 +198,4 @@ Just the tip! - + \ No newline at end of file diff --git a/app/views/errors/not_found.html.erb b/app/views/errors/not_found.html.erb new file mode 100644 index 00000000..36306379 --- /dev/null +++ b/app/views/errors/not_found.html.erb @@ -0,0 +1,18 @@ +<% + mascot = current_user&.mascot || 'ki' +%> + +

    + 404: Page missing or hidden +

    + +

    + ...but you can use the links above to continue browsing the site. If you are 100% sure this is a mistake, or an error + has occured, you can contact <%= link_to 'Gray', users_path('gray') %> with the URL you used to get here. +

    + +
    + <%= image_tag 'mascot/WarrenBadEnd.png', width: 320 if mascot == 'warren' %> + <%= image_tag 'mascot/TaylorBadEnd.png', width: 320 if mascot == 'taylor' %> + <%= image_tag 'mascot/KiShrug.png', width: 320 if mascot == 'ki' %> +
    diff --git a/app/views/errors/server_error.html.erb b/app/views/errors/server_error.html.erb new file mode 100644 index 00000000..c24479d8 --- /dev/null +++ b/app/views/errors/server_error.html.erb @@ -0,0 +1,27 @@ +<% + mascot = current_user&.mascot || 'ki' + + if request.env['action_dispatch.exception'].present? + exception = request.env['action_dispatch.exception'] + trace = Rails.backtrace_cleaner.clean(exception.backtrace) + end +%> + +

    + 500: Oopsie whoopsie +

    + +

    + The site has shit the bed in a new and unique way! Gray will want to see the error shown below. Send it to him on + discord please! +

    + + +
    <%= trace.join("\n") %>
    +
    + +
    + <%= image_tag 'mascot/WarrenBadEnd.png', width: 320 if mascot == 'warren' %> + <%= image_tag 'mascot/TaylorBadEnd.png', width: 320 if mascot == 'taylor' %> + <%= image_tag 'mascot/KiShrug.png', width: 320 if mascot == 'ki' %> +
    diff --git a/app/views/friendships/_friendship.html.erb b/app/views/friendships/_friendship.html.erb index 27c1b332..586ae683 100644 --- a/app/views/friendships/_friendship.html.erb +++ b/app/views/friendships/_friendship.html.erb @@ -1,7 +1,4 @@ -<% - other_user = friendship.sender if friendship.sender.id != current_user.id - other_user = friendship.receiver if friendship.receiver.id != current_user.id -%> +<% other_user = friendship.other_user(current_user) %>
    @@ -29,9 +26,15 @@
    <% if action_name == 'index' %> + <% if friendship.controllable(other_user) %> + <%= button_to assume_surrender_path(friendship.surrenders.for_user(other_user)), method: :post, class: 'no-underline primary' do %> + + Take control + <% end %> + <% end %> <%= button_tag class: 'no-underline secondary', data: { action: 'click->friendship#confirm' } do %> - Unfriend + Delete <% end %> <% end %> <% if action_name == 'requests' %> diff --git a/app/views/friendships/index.html.erb b/app/views/friendships/index.html.erb index aab37943..95f772ef 100644 --- a/app/views/friendships/index.html.erb +++ b/app/views/friendships/index.html.erb @@ -8,6 +8,10 @@
    <% if action_name == 'index' %> + <%= link_to surrenders_path, class: 'no-underline' do %> + + Surrender + <% end %> <% if has_requests? %> New Requests <% end %> diff --git a/app/views/help/client_guide.html.erb b/app/views/help/client_guide.html.erb new file mode 100644 index 00000000..cdd3683f --- /dev/null +++ b/app/views/help/client_guide.html.erb @@ -0,0 +1,155 @@ +<% content_for :stylesheets do %> + + + +<% end %> + +

    + + Client Guide +

    +

    + <%= image_tag 'mascot/WarrenAPIExtrodinaire.png', alt: "Warren making a client with great difficulty", width: 240, style: "float: right" %> + You can make your own Walltaker client! A user should be able to supply any link ID, + that you can then use to pull down the latest wallpaper for that instance. I suggest you poll the endpoint ~10 seconds + and cache the last post url, so you can skip downloading if it hasn't changed since the last one. +

    +

    + Something to know, this part of the docs is still being worked on! There are two ways to write clients. This only covers the simple way. It's still preferred you experiment with this version of the API, but a live web-socket based API is available, and will appear here soon™ +

    + +
    + +
    GET https://walltaker.joi.how/api/links/[id].json
    +

    Gets the current link status. This is what you could poll every 10 seconds to check if the wallpaper has changed. Use + post_url as the current wallpaper's URL. This is always an image by default, but users can enable + videos/gifs on a link, so please handle error checking if you see a file type you can't display.

    +

    🔓 No API key required.

    + +
    +
    + + Response +
    +
    {
    +  "id": 1,
    +  "expires": "2025-03-05T00:00:00.000Z",
    +  "username": "gray",
    +  "terms": "I'm trying out something new, break this please! :)",
    +  "blacklist": "feet blood",
    +  "post_url": "https://static1.e621.net/data/5d/87/5d87428c4839b0dc7d585b87a25af61a.png",
    +  "post_thumbnail_url": "https://static1.e621.net/data/preview/5d/87/5d87428c4839b0dc7d585b87a25af61a.jpg",
    +  "post_description": "",
    +  "created_at": "2022-03-08T01:01:50.142Z",
    +  "updated_at": "2022-03-13T21:39:01.828Z",
    +  "set_by": "name",
    +  "response_type": "horny",
    +  "response_text": "HUFF wow",
    +  "online": true
    +}
    +
    + +
    + +
    POST https://walltaker.joi.how/api/links/[id]/response.json
    +

    Set a response for a given link. There are 3 kinds of responses.

    +

    🔑 Requires user's API Key.

    + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    typeShown In UI asEffect
    "horny"Love itUser who set wallpaper gets notification, response text displayed on link in webapp
    "disgust"Hate itUser who set wallpaper gets notification, wallpaper rolled back to previous image
    "came"CameUser who set wallpaper gets notification, response text displayed on link in webapp
    + +

    ⚠️ I suggest re-pinging the link after sending a "disgust" response, so you can reset the user's + wallpaper to the previous version as soon as possible.

    + +
    +
    + + Request +
    +
    POST https://walltaker.joi.how/api/links/[id]/response.json HTTP/1.1
    +Content-Type: application/json;
    +{
    +  "api_key": "23unFe3i",
    +  "type": "horny",
    +  "text": "mmph nice"
    +}
    +
    +
    + +
    +
    + + Response +
    +
    {
    +  "id": 1,
    +  "expires": "2025-03-05T00:00:00.000Z",
    +  "username": "gray",
    +  "terms": "I'm trying out something new, break this please! :)",
    +  "blacklist": "feet blood",
    +  "post_url": "https://static1.e621.net/data/5d/87/5d87428c4839b0dc7d585b87a25af61a.png",
    +  "post_thumbnail_url": "https://static1.e621.net/data/preview/5d/87/5d87428c4839b0dc7d585b87a25af61a.jpg",
    +  "post_description": "",
    +  "created_at": "2022-03-08T01:01:50.142Z",
    +  "updated_at": "2022-03-13T21:39:01.828Z",
    +  "set_by": "name",
    +  "response_type": "horny",
    +  "response_text": "HUFF wow",
    +  "online": true
    +}
    +
    + +
    + +
    GET https://walltaker.joi.how/api/users/[username].json?api_key=xxxxxxxx
    +

    Get details about this user's status such as if they're online, a friend, or the currently authenticated user for a + given session.

    +

    🔓/🔐 API key required for some, but not all data.

    + +

    ℹ️ To ensure this query is processed quickly, set_by has been excluded from the links array.

    + +
    +
    + + Response +
    +
    
    +{
    +  "username": "apple",
    +  "id": 24,
    +  "set_count": 540,
    +  "online": true,
    +  "links": [... see link response ...],
    +  "authenticated": true,
    +  "friend": true,
    +  "self": false
    +}
    +
    +
    + + \ No newline at end of file diff --git a/app/views/help/faq.html.erb b/app/views/help/faq.html.erb new file mode 100644 index 00000000..2deaca1a --- /dev/null +++ b/app/views/help/faq.html.erb @@ -0,0 +1,146 @@ +

    + + FAQs +

    + +
    +

    I got a wallpaper I don't like.

    +
    +
    +
    + Walltaker is an app for letting others change your wallpaper, without your + approval. While I provide as many tools as possible to ensure you only get wallpapers you like, know that the + point + of this app is to be out of control to some extent. Here are some tips to use Walltaker's + filtering + features to their fullest: +

    +
      +
    • Add people you trust as friends, this allows you to use + + friends-only links, which stops anon spam. This is by far the most effective route. +
    • +
    • Use a theme tag. A theme tag allows you to force a tag to be present on wallpapers people + pick + for you. This can normally be good at filtering out things like genitalia (penis) you don't want to see or + forcing a + specific sexuality. (male/female) +
    • +
    • Fill out your blacklist honestly. If you don't like most foot-fetish art, then it + should be in your blacklist, even if there's some art you do like. People don't read your link terms enough to + understand nuance. +
    • +
    • + Be clear in your terms. A lot of bad wallpapers are simply mistakes. Use as few words as + possible to express what you want. +
    • +
    +

    + As always, if you think you're being harassed, contact an admin on the Discord (See link in the header) who can + look into it for you, and will IP ban an anon user if they're trying to be malicious. +

    +
    +
    +
    + +
    +

    Can I make my own client?

    +
    +
    +

    + Yes! I have some API documentation on our + GitHub. You can contact + an admin on discord to get it listed on the site. +
    + Here are some tips for what makes a good client: +

    +
      +
    • + It doesn't just have to be wallpapers! iOS has no native APIs for setting lock screen or home + screen wallpapers. Maybe a good idea would be a home screen widget that could show the current wallpaper + instead? +
    • +
    • + Use native APIs to set the wallpaper if available. A good example of why this is a good idea + is + the MacOS client. It uses the Cocoa APIs to affect the wallpaper instead of using the more-universal + AppleScript + solution common in apps not made with swift/obj-c. This is a FAR less buggy way to assign wallpapers, + and doesn't + require as many permissions. Research if there's an SDK/language you should use to better take advantage of + native APIs. +
    • +
    • + Just because we're short-polling, doesn't mean you can ping us as fast as you want! Please + keep + requests at around 1 request per 10 seconds. This service is hosted at my expensive, and heavy load will mean + I have + to vertically scale up the server it lives on. Don't be an asshole. +
    • +
    +
    +
    +
    + +
    +

    How do I change my username / email?

    +
    +
    +
    + Please message a staff member on our Discord to change your username/email. +
    + ⚠️Do NOT post your email address in any public channel +
    +
  • Username change: Provide your full email address +
  • Email change: Provide both your old and new email addresses +
  • +
    +
    +
    + +
    +

    Why do I keep getting kicked from the Discord server?

    +
    +
    +
    + Ensure that your account is at least 1 week old and has a profile picture before attempting to join the server. +
    +
    +
    +
    + +
    +

    Help! The client I am using doesn't work

    +
    +
    +
    + Check if there are any known issues with the client you are using. If you require further support, please join the + Discord server and use the appropriate forum to report bugs with the client. + Forums can be found at the bottom of the channel list. +
    +
    +
    +
    + +
    +

    I would like to merge two accounts.

    +
    +
    +
    + Message Gray on our Discord and provide them with both email addresses and let them know which account you want to + keep using. +
    +
    +
    +
    + +
    +

    I have feedback/suggestion

    +
    +
    +
    + Join our Discord and post your feedback/suggestion in the #walltaker-site forum. +
    +
    +
    +
    \ No newline at end of file diff --git a/app/views/help/index.html.erb b/app/views/help/index.html.erb index d4b066c9..86aa2d15 100644 --- a/app/views/help/index.html.erb +++ b/app/views/help/index.html.erb @@ -3,20 +3,22 @@ Help! +<%= image_tag 'mascot/Wizards.png', alt: 'A suave little lizard welcoming you to the website.', class: 'mascot float-right', width: 300 %> +

    Q: How do I set this up?

    A: Thanks for being willing to give Walltaker a try! It's pretty easy to get started, but it might seem a bit weird at - first since there's some hoops we have to jump thru to be able to update your wallpaper. + first since there are some hoops we have to jump through to be able to update your wallpaper.

    0. Log in

    Firstly, you will need an account. You can make an account by pressing the - Sign Up link in the top-right-hand side of the screen, in the header. Once you have - an account (which should only take 1 minute) return to this page. You must be logged in to continue. + Sign Up link on the top-right-hand side of the screen. Once you have created + an account (which should only take a minute) return to this page. You must be logged in to continue.

    @@ -44,96 +46,85 @@ <%= image_tag '/help/new-link-button-location.png' %>
  • - ...then confire your new link. + ...then configure your new link.

    - There are 6 options to change here that affect who can set your wallpaper, and what + There are 8 options to change here that affect who can set your wallpaper, and what restrictions they have to work within. Set these as you see fit, using the key below to explain what each option does.

    <%= image_tag '/help/new-link-form.png', class: 'small' %> -
    -
    #1 Expires
    +
    #1 Terms

    - This is the date at which this link will not accept more wallpaper changes. Clicking - it will open a date picker. You could use this for example if you want to use walltaker for a full - weekend, - but - want it to stop on monday for work. + Your link terms are simply an area to express what you'd like to see as wallpapers more generally. Nothing + here is enforced, it's just a text field for you to leave a note that appears on the link. I suggest you + outline what things turn you on here.

    - -
    #2 Never Expires
    +
    #2 Custom URL
    +

    + Once you have achieved the silver trophy (by having set 300 wallpapers to other users), then you will be + able to set a custom URL. + This means that in addition to your regular walltaker link URL + (https://walltaker.joi.how/links/1), you can provide your own + text for a custom url. It will also be accessible as https://walltaker.joi.how/links/ + your-text-here + +

    +
    #3 Never Expires

    Ensures that the link will never expire, and people you allow to set the link will be able to do so until - you - delete it. + you delete it.

    - When checked, - this link will never expire, and the #1 Expires option will be ignored.
    + When checked, this link will never expire, and the #4 Expires option will be ignored.
    - When unchecked, - this link will expire at the date indicated by the #1 Expires option. + When unchecked, this link will expire at the date indicated by the #4 Expires option.
    - -
    #3 Friends Only
    + ⚠️ With an expired link, Pornbot and the Pornlizards will still be able to set your wallpaper and you will + still be able to connect a client. +
    #5 Friends Only

    This restricts who can set wallpapers on this link.

    - When checked, - ONLY people you have accepted friend requests with will be able to see and set your wallpaper.
    + When checked, ONLY people you have accepted friend requests with will be able to see and set + your wallpaper.
    - When unchecked, - ANYONE (including anon users) will be able to see and set your wallpaper. + When unchecked, ANYONE (including anon users) will be able to see and set your wallpaper.
    - -
    #4 Blacklist
    +
    #6 Blacklist

    A list of E621 tags that must NOT appear on wallpapers people submit. - Tags are separated by spaces or commas, and cannot be special tags. (such as rating:e) + Tags are separated by spaces or commas.

    female feral feline will prevent any posts with females presenting characters, feral characters, or felines.
    male/male group_sex solo will mean you only get extremely boring, straight, 1-on-1 porn. - You - weirdo. + You weirdo.
    - -
    #5 Terms
    -

    - Your link terms are simply an area to express what you'd like to see as wallpapers more generally. Nothing - here - is enforced, it's just a text field for you to leave a note that appears on the link. I suggest you - outline - what - things turn you on here. -

    - -
    #6 Theme Tag
    +
    #7 Theme Tag

    Theme tags are special! This is a way to enforce all wallpapers submitted to this link to be tagged with a single, specific tag of your choosing. Use this when you're looking for something specific, like a specific species or sex act. You're horny, you'll come up with something fun. Be careful about how you type your tag in here. If you misspell this theme tag, no one - will - be + will be able to submit anything until you remove it or correct the spelling. Confirm spelling on E621 if you have - to. + to. Filter tags cannot be used.

    @@ -141,12 +132,15 @@ loimu_(character) all posts will feature Loimu 🦌. Any other characters are blocked.
    - pokeemon a missspelled tag! No one will be able to submit anything.
    + pokémon a misspelled tag! No one will be able to submit anything.
    tits an alias tag! While this works in e621 search, the real tag is "breasts", so this is the - same - as misspelling a tag. + same as misspelling a tag.
    + + + rating:e a filter tag! While this works in e621 search, filter tags are unsupported when + setting a theme.
    @@ -166,45 +160,81 @@

    Congratulations! You should have a fresh link ready to share with the world. However, it currently doesn't change - the wallpaper on anything. This is because, as simply a website, Walltaker can not change your wallpaper for you. + the wallpaper on anything. This is because, as simply a website, Walltaker cannot change your wallpaper for you. To allow us to do this, we need a Walltaker Client, which is an app you can install on which ever devices you'd - like to get wallpaper updates on. It will silently check your link every 10 seconds, and update your - wallpaper if - there's a new one, automatically. + like to get wallpaper updates on. It will silently check your link every few seconds, and update your + wallpaper if there's someone has updated your wallpaper, automatically.

    You can download a client for which ever device you'd like to use. Follow the instructions for each one for how to install it, then return to this tutorial.

    - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    ClientPlatform
    + Lycraon's Wallpaper Engine + ClientWallpaper Engine for Windows
    Chewtoy's Walltaker + Engine + Windows
    Gios' ClientAndroid 7+
    Arson's Automate Client + Automate App for Android
    Deanskond's Automate Client + Automate App for Android
    JBerliner's Walltaker.shLinux
    macOS ClientmacOS
    iOS Widget + iOS
    + Android + Client + (Unmaintained) + Android 12+
    + Desktop + Client + (Unmaintained) + Windows / Linux
    + Please try using clients that are still being maintained before trying unmaintained clients.
    Then...
    @@ -253,7 +283,6 @@
Then...
-

Follow the instructions for your client to add this link to it. Normally it's in a menu where you can simply type in the link ID. @@ -304,7 +333,7 @@

  • Some phone operating systems kill background apps, this can sometimes affect clients. Refer to client documentation.
  • -
  • Did you change the frequency of checks? It must be under 1minute to be +
  • Did you change the frequency of checks? It must be under 1 minute to appear
    Online
    @@ -330,62 +359,13 @@
  • -
    -

    Q: I got a wallpaper I don't like.

    -

    - A: - Walltaker is an app for letting others change your wallpaper, without your - approval. While I provide as many tools as possible to ensure you only get wallpapers you like, know that the point - of this app is to be out of control to some extent. Here are some tips to use walltaker's filtering - features to their fullest: -

    -
      -
    • Add people you trust as friends, this allows you to use - - friends-only links, which stops anon spam. This is by far the most effective route. -
    • -
    • Use a theme tag. A theme tag allows you to force a tag to be present on wallpapers people pick - for you. This can normally be good at filtering out things like genitalia (gay) you don't want to see or forcing a - specific sexuality. (male/female) -
    • -
    • Fill out your blacklist honestly. If you don't like most foot-fetish art, then it - should be in your - blacklist, even if there's some art you do like. People don't read your link terms enough to understand nuance. -
    • -
    • Be clear in your terms. A lot of bad wallpapers are simply mistakes. Use as few words as - possible to express - what you want. -
    • -
    -

    - As always, if you think you're being harassed, contact an admin on the discord (See link in the header) who can - look into it for you, and IP ban an anon user if they're trying to be malicious. -

    -
    - -
    -

    Q: Can I make my own client?

    -

    - A: - Yes! I have some API documentation on our github. You can contact - an admin on discord to get it listed on the site. Here's some tips for what makes a good client: -

    -
      -
    • - It doesn't just have to be wallpapers! iOS has no native APIs for setting lock screen or home - screen wallpapers. Maybe a good idea would be a home screen widget that could show the current wallpaper instead? -
    • -
    • - Just because we're short-polling, doesn't mean you can ping us as fast as you want! Please keep - requests at around 1request per 10seconds. This service is hosted at my expensive, and heavy load will mean I have - to vertically scale up the server it lives on. Don't be an asshole. -
    • -
    • - Use native APIs to set the wallpaper if available. A good example of why this is a good idea is - the MacOS client. It uses the Cocoa APIs to affect the wallpaper instead of using the more-universal apple-script - solution common in apps not made with swift/obj-c. This is MUCH less buggy way to assign wallpapers, and doesn't - require as many permissions. Research if there's an SDK/language you should use to better take advantage of native - APIs. -
    • -
    +
    +

    FAQs

    +
    +
    +
    + To view some recently asked questions, check out <%= link_to 'FAQs', faq_path %> +
    +
    +
    \ No newline at end of file diff --git a/app/views/kink/_e621_status.html.erb b/app/views/kink/_e621_status.html.erb new file mode 100644 index 00000000..4ae5662e --- /dev/null +++ b/app/views/kink/_e621_status.html.erb @@ -0,0 +1,19 @@ +<%# locals: (kink:, failed: false) -%> + +<%= turbo_frame_tag dom_id(kink) + '_e621_status', class: 'kink__status' do %> + <% if kink.works_on_e621? %> + <%= link_to "https://e621.net/posts?tags=#{kink.name}", target: '_blanks', class: 'no-underline' do %> + + e621 + <% end %> + <% else %> + <% if failed %> + Invalid tag, are you sure it's spelt right? + <% else %> + <%= button_to kink_e621_path(kink), method: :post do %> + + Test on e621 + <% end %> + <% end %> + <% end %> +<% end %> \ No newline at end of file diff --git a/app/views/kink/_form.html.erb b/app/views/kink/_form.html.erb new file mode 100644 index 00000000..13edbd17 --- /dev/null +++ b/app/views/kink/_form.html.erb @@ -0,0 +1,6 @@ +<%# locals: (kink:) -%> + +<%= form_with model: kink, url: kink_path(kink.id), class: 'kink_form' do |f| %> + <%= f.text_field :name, placeholder: 'Enter a kink' %> + <%= f.submit '+' %> +<% end %> \ No newline at end of file diff --git a/app/views/kink/_kink.html.erb b/app/views/kink/_kink.html.erb new file mode 100644 index 00000000..039c523e --- /dev/null +++ b/app/views/kink/_kink.html.erb @@ -0,0 +1,21 @@ +<%# locals: (kink:, is_current_user: false, destination: :kink_show, link: nil, user: nil) -%> + +<%= turbo_frame_tag kink, class: ("kink #{kink.works_on_e621? ? 'valid' : 'untested'}") do %> + <%= link_to destination == :search ? porn_search_search_path(tags: kink.name, link:, full_rerender: true) : kink_show_path(kink), target: destination == :search ? 'pornsearch' : '_top' do %> + <%= kink.name %> + <% unless is_current_user %> + <% if (link && kink.is_starred?(link.user)) || (user && kink.is_starred?(user)) %> + + <% end %> + <% end %> + <% end %> + + <% if is_current_user %> + <%= button_to kink_toggle_star_path(kink) do %> + + <% end %> + <%= button_to kink_remove_path(kink), method: :delete do %> + + <% end %> + <% end %> +<% end %> \ No newline at end of file diff --git a/app/views/kink/_kinks.html.erb b/app/views/kink/_kinks.html.erb new file mode 100644 index 00000000..45db7055 --- /dev/null +++ b/app/views/kink/_kinks.html.erb @@ -0,0 +1,7 @@ +<%# locals: (kinks:, is_current_user: false, user: nil) -%> + +<%= turbo_frame_tag :kink_list do %> + <% kinks.each do |kink| %> + <%= render 'kink/kink', kink:, is_current_user:, user: %> + <% end %> +<% end %> \ No newline at end of file diff --git a/app/views/kink/_kinks_for_search.html.erb b/app/views/kink/_kinks_for_search.html.erb new file mode 100644 index 00000000..e7f160bc --- /dev/null +++ b/app/views/kink/_kinks_for_search.html.erb @@ -0,0 +1,7 @@ +<%# locals: (kinks:, link:) -%> + +<%= turbo_frame_tag :kink_list, target: 'pornsearch' do %> + <% kinks.each do |kink| %> + <%= render 'kink/kink', kink:, is_current_user: false, destination: :search, link: %> + <% end %> +<% end %> \ No newline at end of file diff --git a/app/views/kink/add.html.erb b/app/views/kink/add.html.erb new file mode 100644 index 00000000..c3394754 --- /dev/null +++ b/app/views/kink/add.html.erb @@ -0,0 +1,2 @@ +

    Kink#add

    +

    Find me in app/views/kink/add.html.erb

    diff --git a/app/views/kink/new.html.erb b/app/views/kink/new.html.erb new file mode 100644 index 00000000..0c3ffd6d --- /dev/null +++ b/app/views/kink/new.html.erb @@ -0,0 +1 @@ +<%= render 'form', kink: @kink %> \ No newline at end of file diff --git a/app/views/kink/remove.turbo_stream.erb b/app/views/kink/remove.turbo_stream.erb new file mode 100644 index 00000000..782d3492 --- /dev/null +++ b/app/views/kink/remove.turbo_stream.erb @@ -0,0 +1,3 @@ +<%= turbo_frame_tag @kink do %> + <%= turbo_stream.remove @kink %> +<% end %> \ No newline at end of file diff --git a/app/views/kink/search_kinks.html.erb b/app/views/kink/search_kinks.html.erb new file mode 100644 index 00000000..4f75dff7 --- /dev/null +++ b/app/views/kink/search_kinks.html.erb @@ -0,0 +1,3 @@ +<%= turbo_frame_tag :kink_tools, target: 'pornsearch' do %> + <%= render 'kinks_for_search', kinks: @kinks, link: @link %> +<% end %> \ No newline at end of file diff --git a/app/views/kink/show.html.erb b/app/views/kink/show.html.erb new file mode 100644 index 00000000..d00b2749 --- /dev/null +++ b/app/views/kink/show.html.erb @@ -0,0 +1,34 @@ +<% content_for(:html_title) { "Walltaker #{@kink.name} Kink" } %> + +
    +
    + <%= link_to "Back", :back %> +
    +
    + <%= render 'kink/e621_status', kink: @kink %> + + <%= button_to search_path, method: :get, params: {q: "!#{@kink.name}"} do %> + + Find Links + <% end %> +
    +
    + +

    + + <%= @kink.name.titlecase %> +

    + +<% if @users.count > 0 %> +
    + <% @users.each do |user| %> + <%= render user %> + <% end %> +
    +<% else %> +

    No one <%= current_user ? 'else' : '' %> has this kink.

    +

    + Kinks can be set on your profile, try and get others to use this one if you want to see more of it! When set, you'll + see <%= @kink.name %> appear in their profile, and they will be listed here. +

    +<% end %> \ No newline at end of file diff --git a/app/views/kink/toggle_star.turbo_stream.erb b/app/views/kink/toggle_star.turbo_stream.erb new file mode 100644 index 00000000..addd2fbc --- /dev/null +++ b/app/views/kink/toggle_star.turbo_stream.erb @@ -0,0 +1 @@ +<%= turbo_stream.replace @kink, partial: 'kink/kink', locals: { kink: @kink, is_current_user: true } %> \ No newline at end of file diff --git a/app/views/kink/update.turbo_stream.erb b/app/views/kink/update.turbo_stream.erb new file mode 100644 index 00000000..0cdc844c --- /dev/null +++ b/app/views/kink/update.turbo_stream.erb @@ -0,0 +1 @@ +<%= turbo_stream.append :kink_list, partial: 'kink/kink', locals: {kink: @kink, is_current_user: true} %> \ No newline at end of file diff --git a/app/views/kink/users_kinks.html.erb b/app/views/kink/users_kinks.html.erb new file mode 100644 index 00000000..471e4c00 --- /dev/null +++ b/app/views/kink/users_kinks.html.erb @@ -0,0 +1,9 @@ +<%= turbo_frame_tag :kink_tools do %> +
    +
    + <%= render 'kinks', kinks: @kinks, is_current_user: @is_current_user, user: @user %> + + <%= render 'form', kink: Kink.new if @is_current_user %> +
    +
    +<% end %> \ No newline at end of file diff --git a/app/views/layouts/_mascot_picker.erb b/app/views/layouts/_mascot_picker.erb new file mode 100644 index 00000000..4278e005 --- /dev/null +++ b/app/views/layouts/_mascot_picker.erb @@ -0,0 +1,11 @@ +<%= turbo_frame_tag 'mascot_picker', class: "mascot-picker" do %> + <%= button_to api_mascot_next_path, data: {'turbo-frame': 'title'}, title: 'next mascot' do %> + + <% end %> + + <%= button_to api_pervert_toggle_path, data: {'turbo-frame': 'title'}, title: 'naughty toggle' do %> + <%= current_user&.pervert ? '>_<' : 'OwO' %> + <% end %> + + <%= current_user&.mascot&.capitalize %> +<% end %> \ No newline at end of file diff --git a/app/views/layouts/_title.erb b/app/views/layouts/_title.erb new file mode 100644 index 00000000..e1b983b0 --- /dev/null +++ b/app/views/layouts/_title.erb @@ -0,0 +1,21 @@ +<% + mascot ||= 'ki' + pervert ||= false +%> + +<%= turbo_frame_tag 'title' do %> + + Walltaker + + <%= image_tag("mascot/Ki#{pervert ? 'NSFW' : ''}.png", alt: 'A suave little lizard welcoming you to the website.', class: 'mascot', height: 190) if mascot == 'ki' %> + <%= image_tag("mascot/Warren#{pervert ? 'N' : ''}SFW.png", alt: 'A suave little lizard welcoming you to the website.', class: 'mascot', height: 190) if mascot == 'warren' %> + <%= image_tag("mascot/Taylor#{pervert ? 'N' : ''}SFW.png", alt: 'A suave little lizard welcoming you to the website.', class: 'mascot', height: 190) if mascot == 'taylor' %> + + + WT + + <%= image_tag("mascot/Ki#{pervert ? 'NSFW' : ''}.png", alt: 'A suave little lizard welcoming you to the website.', class: 'mascot', height: 100) if mascot == 'ki' %> + <%= image_tag("mascot/Warren#{pervert ? 'N' : ''}SFW.png", alt: 'A suave little lizard welcoming you to the website.', class: 'mascot', height: 100) if mascot == 'warren' %> + <%= image_tag("mascot/Taylor#{pervert ? 'N' : ''}SFW.png", alt: 'A suave little lizard welcoming you to the website.', class: 'mascot', height: 100) if mascot == 'taylor' %> + +<% end %> \ No newline at end of file diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index 0e3c3214..57a5d3f8 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -1,16 +1,21 @@ +<% mascot = current_user&.mascot || 'ki' %> - +"> <%= content_for?(:html_title) ? yield(:html_title) : "Walltaker" %> + <%= favicon_link_tag 'mascot/KiHead.png' if mascot == 'ki'%> + <%= favicon_link_tag 'mascot/WarrenHead.png' if mascot == 'warren'%> + <%= favicon_link_tag 'mascot/TaylorHead.png' if mascot == 'taylor'%> - - - - - - + + + + + + + <%= yield :stylesheets %> @@ -19,20 +24,28 @@ <%= action_cable_meta_tag %> <%= stylesheet_link_tag "application", "data-turbo-track": "reload" %> + <%= javascript_importmap_tags %>
    - 🔞 This is an app for adults over the age of 18 only.
    - Requests? Bugs? Gios made us a discord! + 🔞 This is an app for adults over the age of 18 only. + <% if current_user&.current_surrender && cookies.signed[:surrender_id].nil? %> + <% surrender = current_user.current_surrender %> +
    + Your account is surrendered to <%= link_to surrender.controller.username, user_path(surrender.controller.username) %>. +
    + <%= link_to 'Help me!', surrender_path(surrender) %> + <% end %>
    -
    - <% flash.each do |type, msg| %> -
    - <%= msg %> -
    - <% end %> -
    +<%= render 'application/flashes' %> + +<% if current_user %> +
    +
    + +

    Just a note!

    +

    To keep things organized and peaceful, we ask that you verify your walltaker account in the discord when you join. It's really quick, doesn't require anything other than your username and a link ID from your account! We also kick discord accounts newer than a week old or those without profile pictures! Try again after that period if you can't join.

    + +
    + + + Join our Discord
    + <%= render 'layouts/mascot_picker' if current_user %> +
    +<% end %>
    <%= yield %> + <%= yield :content %>
    diff --git a/app/views/layouts/errors.html.erb b/app/views/layouts/errors.html.erb new file mode 100644 index 00000000..f1c744cd --- /dev/null +++ b/app/views/layouts/errors.html.erb @@ -0,0 +1,117 @@ +<% mascot = current_user&.mascot || 'ki' %> + +"> + + <%= content_for?(:html_title) ? yield(:html_title) : "Walltaker" %> + + + <%= favicon_link_tag 'mascot/KiHead.png' if mascot == 'ki'%> + <%= favicon_link_tag 'mascot/WarrenHead.png' if mascot == 'warren'%> + <%= favicon_link_tag 'mascot/TaylorHead.png' if mascot == 'taylor'%> + + + + + + + + <%= yield :stylesheets %> + + + + <%= csrf_meta_tags %> + <%= csp_meta_tag %> + <%= action_cable_meta_tag %> + + <%= stylesheet_link_tag "application", "data-turbo-track": "reload" %> + <%= javascript_importmap_tags %> + + + +
    +
    + 🔞 This is an app for adults over the age of 18 only. +
    +
    +

    + + <%= render 'layouts/title', mascot: mascot, pervert: current_user&.pervert || false %> + +

    +
    + <% if current_user %> + + <%= current_user.username %> + + + + Browse + + <%= link_to 'Links', links_path %> + <%= link_to 'Friends', friendships_path, class: has_requests? ? 'pinged' : '' %> + <%= link_to 'Logout', logout_path %> + +
    + <%= link_to message_thread_index_path do %> + + <% end %> +
    + <% else %> + + + Browse + + <%= link_to 'Sign Up', signup_path %> + <%= link_to 'Login', login_path %> + <% end %> +
    +
    +
    + +
    + <% flash.each do |type, msg| %> +
    + <%= msg %> +
    + <% end %> +
    + +<% if current_user %> +
    +
    + +

    Just a note!

    +

    To keep things organized and peaceful, we ask that you verify your walltaker account in the discord when you join. It's really quick, doesn't require anything other than your username and a link ID from your account! We also kick discord accounts newer than a week old or those without profile pictures! Try again after that period if you can't join.

    + +
    + + + Join our Discord
    +
    +<% end %> + +
    + <%= yield %> +
    + +
    +

    + Want more horny debauchery? Try joi.how.
    + Horny lizards Warren, Taylor, and Ki conceived of and drawn by + CherryzBun
    + <%= link_to 'Need help with Walltaker?', help_path %>   + <%= link_to 'FAQs', faq_path %> +

    + + © joi.how 2024 + +
    + + + + + diff --git a/app/views/layouts/link_wizard.html.erb b/app/views/layouts/link_wizard.html.erb new file mode 100644 index 00000000..5a15e0a2 --- /dev/null +++ b/app/views/layouts/link_wizard.html.erb @@ -0,0 +1,21 @@ +<% content_for :stylesheets do %> + +<% end %> +<% content_for :content do %> +
    +
    + <%= link_to "Come back later", link_path(@link.id) %> +
    +
    + +

    + + + Wizard + + +

    + + <%= yield :step %> +<% end %> +<%= render template: 'layouts/application' %> \ No newline at end of file diff --git a/app/views/link_wizard/control.html.erb b/app/views/link_wizard/control.html.erb new file mode 100644 index 00000000..26460a7e --- /dev/null +++ b/app/views/link_wizard/control.html.erb @@ -0,0 +1,87 @@ +<% content_for :step do %> +
    +
    +
    + No problem hun!
    + We should really get to work though! This fresh link is some nice work, but it's a bit boring at the moment, + maybe even a little dangerous! +
    + <%= image_tag 'mascot/TaylorSFW.png', alt: 'Two link mages crafting your link.', class: 'mascot', width: 160 %> +
    + + +
    + +
    +
    <%= image_tag 'mascot/TaylorHead.png', alt: 'Two link mages crafting your link.', class: 'mascot', width: 70 %>
    +

    + Without a blacklist, someone could send you really shocking posts! +

    +
    + +
    +

    + Probably from me heheheheheheheh +

    +
    <%= image_tag 'mascot/WarrenHead.png', alt: 'Two link mages crafting your link.', class: 'mascot', width: 70 %>
    +
    + +
    +
    <%= image_tag 'mascot/TaylorHead.png', alt: 'Two link mages crafting your link.', class: 'mascot', width: 70 %>
    +

    + Well, yes that sometimes does happen, where someone picks a wallpaper that they think you might like, but ends up + being something you'd rather not see. But there's also assholes out there that just like posting the worst E621 + has to offer! We need to have a strong blacklist to put these dicks in their place! +

    +
    + +
    +

    + You can put my dick... You can put in place... My dick can be put in place by... +

    +
    <%= image_tag 'mascot/WarrenHead.png', alt: 'Two link mages crafting your link.', class: 'mascot', width: 70 %>
    +
    + +
    +
    <%= image_tag 'mascot/TaylorHead.png', alt: 'Two link mages crafting your link.', class: 'mascot', width: 70 %>
    +

    + ... +

    +
    + +
    +

    + I want to fuck your cunt again. +

    +
    <%= image_tag 'mascot/WarrenHead.png', alt: 'Two link mages crafting your link.', class: 'mascot', width: 70 %>
    +
    + +
    +
    <%= image_tag 'mascot/TaylorHead.png', alt: 'Two link mages crafting your link.', class: 'mascot', width: 70 %>
    +

    + ... you tried at least. +

    +
    + +
    +
    <%= image_tag 'mascot/TaylorHead.png', alt: 'Two link mages crafting your link.', class: 'mascot', width: 70 %>
    +

    + <%= current_user.username.capitalize %>, could you choose the level of control you'd like by default? Keeping in mind people like him (<%= image_tag 'mascot/WarrenHead.png', size: '25' %>) exist. I've highlighted the one most people choose for you, but if you're new, I suggest going 1 level up. +

    +
    + +
    +
    + + You choose! +
    + + <%= button_to 'Maximum! I always use protection.', apply_link_wizard_path(protect: 5), class: 'secondary' %> + <%= button_to 'Higher! The internet scares me.', apply_link_wizard_path(protect: 4), class: 'secondary' %> + <%= button_to 'Normal. I know how the internet works.', apply_link_wizard_path(protect: 3) %> + <%= button_to 'Lower! I love how the internet works.', apply_link_wizard_path(protect: 2), class: 'secondary' %> + <%= button_to 'None! Shrek and nazi fan art excites me!', apply_link_wizard_path(protect: 1), class: 'secondary' %> +
    +<% end %> \ No newline at end of file diff --git a/app/views/link_wizard/gender.html.erb b/app/views/link_wizard/gender.html.erb new file mode 100644 index 00000000..3092b160 --- /dev/null +++ b/app/views/link_wizard/gender.html.erb @@ -0,0 +1,124 @@ +<% content_for :step do %> +
    +
    +
    + My turn! We're going to sort out your sexuality... to the extent a lizard and a website can do that! +
    + <%= image_tag 'mascot/Ki.png', alt: 'Two link mages crafting your link.', class: 'mascot', width: 160 %> +
    + + +
    + +
    +
    <%= image_tag 'mascot/KiHead.png', alt: 'Two link mages crafting your link.', class: 'mascot', width: 70 %>
    +

    + Sexual preference and gender identity are diverse subjects, and I'm sure you know many of the details already. +

    +
    + +
    +

    + Many of our users aren't strictly male or female, or straight or gay. +

    +
    <%= image_tag 'mascot/TaylorHead.png', alt: 'Two link mages crafting your link.', class: 'mascot', width: 70 %>
    +
    + +
    +
    <%= image_tag 'mascot/KiHead.png', alt: 'Two link mages crafting your link.', class: 'mascot', width: 70 %>
    +

    + Right! It's really important we understand that. Some people, (possibly + you <%= current_user.username.capitalize %>!) may feel deeply uncomfortable with depictions of genitalia or gender + identity that they would rather not see. +

    +
    + +
    +

    + We can't promise you that your link will always prevent content you'd rather not see from appearing, but we can + get you set up so it works most of the time. +

    +
    <%= image_tag 'mascot/TaylorHead.png', alt: 'Two link mages crafting your link.', class: 'mascot', width: 70 %>
    +
    + +
    +
    <%= image_tag 'mascot/KiHead.png', alt: 'Two link mages crafting your link.', class: 'mascot', width: 70 %>
    +

    + <%= current_user.username.capitalize %>, can you select which of the following gender expressions and genitalia + you'd NOT like to see as your wallpaper? If you don't select something, you will see it. It's OK to select nothing at all as well! +

    +
    + +
    +
    + + You choose! +
    + + + <%= form_with url: apply_link_wizard_path, class: 'full-width' do |f| %> +

    I don't like expressions of:

    +
    + <%= f.check_box :blacklisted_genders, {multiple: true}, :male, '' %> + <%= f.label :blacklisted_genders, 'Maleness' %> +
    + +
    + <%= f.check_box :blacklisted_genders, {multiple: true}, :female, '' %> + <%= f.label :blacklisted_genders, 'Femaleness' %> +
    + +
    + <%= f.check_box :blacklisted_genders, {multiple: true}, :nonbinary, '' %> + <%= f.label :blacklisted_genders, 'Non-Binary/Non-Conforming... ness?' %> +
    + +
    + <%= f.check_box :blacklisted_genders, {multiple: true}, :genderbends, '' %> + <%= f.label :blacklisted_genders, 'Gender Bent Characters' %> +
    + + +

    I don't want to see:

    +
    + <%= f.check_box :blacklisted_parts, {multiple: true}, :cocks, '' %> + <%= f.label :blacklisted_parts, 'Cocks' %> +
    + +
    + <%= f.check_box :blacklisted_parts, {multiple: true}, :pussies, '' %> + <%= f.label :blacklisted_parts, 'Pussies' %> +
    + +
    + <%= f.check_box :blacklisted_parts, {multiple: true}, :breasts, '' %> + <%= f.label :blacklisted_parts, 'Boobs' %> +
    + +
    + <%= f.check_box :blacklisted_parts, {multiple: true}, :assholes, '' %> + <%= f.label :blacklisted_parts, 'Assholes' %> +
    + +
    + <%= f.check_box :blacklisted_parts, {multiple: true}, :cloacas, '' %> + <%= f.label :blacklisted_parts, 'Cloacas (it\'s e621 after all)' %> +
    + + +

    I don't like seeing partners that are:

    +
    + <%= f.check_box :blacklisted_sexualities, {multiple: true}, :gay, '' %> + <%= f.label :blacklisted_sexualities, 'Similar (gay, bi, pan)' %> +
    + +
    + <%= f.check_box :blacklisted_sexualities, {multiple: true}, :straight, '' %> + <%= f.label :blacklisted_sexualities, 'Different (straight)' %> +
    + <%= f.submit "I'm done!" %> + <% end %> +
    +<% end %> \ No newline at end of file diff --git a/app/views/link_wizard/intro.html.erb b/app/views/link_wizard/intro.html.erb new file mode 100644 index 00000000..cd11bae8 --- /dev/null +++ b/app/views/link_wizard/intro.html.erb @@ -0,0 +1,23 @@ +<% content_for :step do %> +

    + OH! Hi, we were expecting you! Let me get the link-mages in here to get this on a roll! They will need a little + input from you, but it won't take long! I promise! There's just one little thing we need to get out of the way. +

    +

    + Could you also, like, just for joke or whatever, just like... + vocalize your undying gratitude for porn and it's unrelenting desire to overtake humanity? +

    + +
    +
    + + You choose! +
    + + <%= button_to 'FUCK YES I FUCKING LOVE LORD PORN', apply_link_wizard_path(gooner: true) %>
    + + <%= button_to 'Oh... ok? Porn is... great?', next_wizard_path(gooner: false), method: :get %>
    + + <%= button_to 'No shut up', next_wizard_path(gooner: false), method: :get %> +
    +<% end %> \ No newline at end of file diff --git a/app/views/link_wizard/kinks.html.erb b/app/views/link_wizard/kinks.html.erb new file mode 100644 index 00000000..8742d753 --- /dev/null +++ b/app/views/link_wizard/kinks.html.erb @@ -0,0 +1,122 @@ +<% warren_thinks_youre_perverted = current_user.pervert || @link.blacklist.split(' ').length < 4 %> + +<% content_for :step do %> +
    +
    +
    + Perfect! I've typed up some blacklist items to best fit your trust level! This space feels really empty + though... +
    + <%= image_tag 'mascot/TaylorSFW.png', alt: 'Two link mages crafting your link.', class: 'mascot', width: 160 %> +
    + + +
    + +
    +
    <%= image_tag 'mascot/TaylorHead.png', alt: 'Two link mages crafting your link.', class: 'mascot', width: 70 %>
    +

    + Your link terms is the best place to let people know what you like! +

    +
    + +
    +

    + Give us all the wet, sloppy, throbbing, sticky details! +

    +
    <%= image_tag 'mascot/WarrenHead.png', alt: 'Two link mages crafting your link.', class: 'mascot', width: 70 %>
    +
    + +
    +
    <%= image_tag 'mascot/TaylorHead.png', alt: 'Two link mages crafting your link.', class: 'mascot', width: 70 %>
    +

    + Exactly! Be detailed, but keep it fun! Big walls of text are rarely read unless they are absolutely dripping in + horny sheen. +

    +
    + +
    +

    + Great place to pick up twinks. That's why I call my terms the certified bussy magnets. +

    +
    <%= image_tag 'mascot/WarrenHead.png', alt: 'Two link mages crafting your link.', class: 'mascot', width: 70 %>
    +
    + +
    +
    <%= image_tag 'mascot/TaylorHead.png', alt: 'Two link mages crafting your link.', class: 'mascot', width: 70 %>
    +

    + I'm sure you're well accredited, Dr. Bussyterms. +

    +
    + +
    +

    + I am, thank you. +

    +
    <%= image_tag 'mascot/WarrenHead.png', alt: 'Two link mages crafting your link.', class: 'mascot', width: 70 %>
    +
    + +
    +

    + Is that why you asked me go to the office store to laminate a picture of my ass with your named signed on it? They + kicked me out and said I can't bring nudes to the Staples ever again. They didn't even have any lick-n-stick gold + certification seals like you said they would! +

    +
    <%= image_tag 'mascot/KiHead.png', alt: 'Two link mages crafting your link.', class: 'mascot', width: 70 %>
    +
    + +
    +

    + Staples isn't ready for my resea- +

    +
    <%= image_tag 'mascot/WarrenHead.png', alt: 'Two link mages crafting your link.', class: 'mascot', width: 70 %>
    +
    + +
    +
    <%= image_tag 'mascot/TaylorHead.png', alt: 'Two link mages crafting your link.', class: 'mascot', width: 70 %>
    +

    + Warren shut up. +

    +
    + +
    +
    <%= image_tag 'mascot/TaylorHead.png', alt: 'Two link mages crafting your link.', class: 'mascot', width: 70 %>
    +

    + <%= current_user.username.capitalize %>, could you write up a basic terms? I've taken the liberty + to give you a few prompts you can fill in. Feel free to expand on them, or start new! Remember, be detailed! +

    +
    + + <% if warren_thinks_youre_perverted %> +
    +

    + pssst, we're both perverts here based on how you're answering this quiz, so I spiced it up a bit! +

    +
    <%= image_tag 'mascot/WarrenHead.png', alt: 'Two link mages crafting your link.', class: 'mascot', width: 70 %>
    +
    + <% end %> + +
    +
    + + You choose! +
    + + <% + default_terms = <<~BIO + #{warren_thinks_youre_perverted ? "I'm a huge fucking gooner" : "Hi! Please read my blacklist carefully just in case I haven't covered everything!"} + #{@link.blacklist.split(' ').length > 15 ? "Please NO EXTREME CONTENT.\n" : ''}My biggest turn ons are: + #{warren_thinks_youre_perverted ? "My new triggers are" : "I'm learning to like:"} + + #{warren_thinks_youre_perverted ? "I'll let you know if you make me CUM >:)" : "Please make sure your choices fit well on my device!"} + BIO + %> + + <%= form_with url: apply_link_wizard_path, class: 'full-width' do |f| %> + <%= f.text_area :terms, value: default_terms %> + <%= f.submit "I'm done!" %> + <% end %> +
    +<% end %> \ No newline at end of file diff --git a/app/views/link_wizard/no_fun.html.erb b/app/views/link_wizard/no_fun.html.erb new file mode 100644 index 00000000..b5deaf36 --- /dev/null +++ b/app/views/link_wizard/no_fun.html.erb @@ -0,0 +1,78 @@ +<% content_for :step do %> +
    +
    +
    + You're pretty much done! There's just one more step! +
    + <% if current_user.pervert %> + <%= image_tag 'mascot/TaylorNSFW.png', alt: 'Two link mages crafting your link.', class: 'mascot', width: 160 %> + <% else %> + <%= image_tag 'mascot/TaylorSFW.png', alt: 'Two link mages crafting your link.', class: 'mascot', width: 160 %> + <% end %> +
    + + +
    + +
    +
    <%= image_tag 'mascot/TaylorHead.png', alt: 'Two link mages crafting your link.', class: 'mascot', width: 70 %>
    +

    + Walltaker is all about controlled transfer of control. (and horny surprises.) But sometimes you might not want to give up all control to just... anyone (<%= image_tag 'mascot/WarrenHead.png', size: '25' %>) on the internet. +

    +
    + +
    +

    + Babe, you're all over the internet. +

    +
    <%= image_tag 'mascot/WarrenHead.png', alt: 'Two link mages crafting your link.', class: 'mascot', width: 70 %>
    +
    + +
    +
    <%= image_tag 'mascot/TaylorTitsSFW.png', alt: 'Two link mages crafting your link.', class: 'mascot', width: 70 %>
    +

    + *shlick shlick shlick shlick shlick shlick shlick shlick* +

    +
    <%= image_tag 'mascot/WarrenJacking.png', alt: 'Two link mages crafting your link.', class: 'mascot', width: 70 %>
    +
    + +
    +
    <%= image_tag 'mascot/TaylorHead.png', alt: 'Two link mages crafting your link.', class: 'mascot', width: 70 %>
    +

    + Warren, I literally just let you cum in my panties. The begging was getting intense. +

    +
    + +
    +

    + ... hey NO! That's not ho- +

    +
    <%= image_tag 'mascot/WarrenHead.png', alt: 'Two link mages crafting your link.', class: 'mascot', width: 70 %>
    +
    + +
    +
    <%= image_tag 'mascot/TaylorHead.png', alt: 'Two link mages crafting your link.', class: 'mascot', width: 70 %>
    +

    + <%= current_user.username.capitalize %> you can opt to not make your link public. This will mean that people MUST make an account (or already have one) and send you a friend request that you then accept in order to be able to set a wallpaper. This slows down wallpaper changes significantly, but it's the only way we can promise you won't get shock content. (as long as you're careful about who you accept!) Would you like to turn it on? +

    +
    + +
    +

    + You can always turn it off later! +

    +
    <%= image_tag 'mascot/WarrenHead.png', alt: 'Two link mages crafting your link.', class: 'mascot', width: 70 %>
    +
    + +
    +
    + + You choose! +
    + + <%= button_to 'Limit to just friends, please!', apply_link_wizard_path(friends_only: true), class: 'secondary' %> + <%= button_to 'Anons on the internet for me, please!', apply_link_wizard_path(friends_only: false) %> +
    +<% end %> \ No newline at end of file diff --git a/app/views/link_wizard/orgasmic_conclusion.html.erb b/app/views/link_wizard/orgasmic_conclusion.html.erb new file mode 100644 index 00000000..d9e10bdb --- /dev/null +++ b/app/views/link_wizard/orgasmic_conclusion.html.erb @@ -0,0 +1,50 @@ +<% content_for :step do %> + + +
    +
    <%= image_tag 'mascot/KiHead.png', alt: 'Two link mages crafting your link.', class: 'mascot', width: 70 %>
    +

    + Great job <%= current_user.username.capitalize %>! +

    +
    + +
    +

    + Yeah that's a great looking link!! +

    +
    <%= image_tag 'mascot/TaylorHead.png', alt: 'Two link mages crafting your link.', class: 'mascot', width: 70 %>
    +
    + +
    +
    <%= image_tag 'mascot/WarrenHead.png', alt: 'Two link mages crafting your link.', class: 'mascot', width: 70 %>
    +

    + We really think you're going to like the site! +

    +
    + +
    + + Remember that you can change all of this later in link settings under the button located on the page! We're just running this little wizard side business to help you get started! Make as many manual edits as you need later. + +
    <%= image_tag 'mascot/TaylorHead.png', alt: 'Two link mages crafting your link.', class: 'mascot', width: 70 %>
    +
    + +
    +
    <%= image_tag 'mascot/KiHead.png', alt: 'Two link mages crafting your link.', class: 'mascot', width: 70 %>
    +

    + There's some stuff in there we didn't tell you about actually, you should check it out! +

    +
    + +
    +
    + + You choose! +
    + + <%= button_to "Settings please!", edit_link_path(@link.id), method: :get, class: 'secondary' %> + <%= button_to 'Let me use my link!', link_path(@link.id), method: :get %> +
    +<% end %> \ No newline at end of file diff --git a/app/views/link_wizard/summon.html.erb b/app/views/link_wizard/summon.html.erb new file mode 100644 index 00000000..991210b6 --- /dev/null +++ b/app/views/link_wizard/summon.html.erb @@ -0,0 +1,21 @@ +<% content_for :step do %> + + +
    +
    Lorem pussydom, dickolar sit amet, bussy pussy nbussy garry bussy, nostra ici construecteura linkus <%= @link.id %>, fuckus penetronis!
    + <%= image_tag 'mascot/Wizards.png', alt: 'Two link mages crafting your link.', class: 'mascot', width: 300 %> +
    + +
    +
    + + You choose! +
    + + <%= button_to 'Thanks cuties!', apply_link_wizard_path(impatient: false) %> + + <%= button_to 'Thanks but... can we get a move on?', apply_link_wizard_path(impatient: true) %> +
    +<% end %> \ No newline at end of file diff --git a/app/views/link_wizard/surprise.html.erb b/app/views/link_wizard/surprise.html.erb new file mode 100644 index 00000000..5e04ac53 --- /dev/null +++ b/app/views/link_wizard/surprise.html.erb @@ -0,0 +1,235 @@ +<% warren_thinks_youre_perverted = current_user.pervert || @link.blacklist.split(' ').length < 4 %> +<% warren_thinks_youre_really_perverted = current_user.pervert && @link.blacklist.split(' ').length < 4 %> +<% warren_thinks_youre_really_really_perverted = current_user.pervert && @link.blacklist.split(' ').length < 2 %> +<% warren_thinks_youre_boring = !current_user.pervert || @link.blacklist.split(' ').length > 8 %> +<% warren_thinks_youre_really_boring = !current_user.pervert && @link.blacklist.split(' ').length > 8 %> +<% warren_thinks_youre_really_really_boring = !current_user.pervert && @link.blacklist.split(' ').length > 15 %> + +<% content_for :step do %> +
    +
    +
    + We're almost done! You'll like this though, how much do you like surprises? +
    + <%= image_tag 'mascot/WarrenSFW.png', alt: 'Two link mages crafting your link.', class: 'mascot', width: 160 %> +
    + + +
    + +
    +
    <%= image_tag 'mascot/WarrenHead.png', alt: 'Two link mages crafting your link.', class: 'mascot', width: 70 %>
    +

    + Sometimes, you want people to surprise you with what they think you'll get off too. Other times, you might want to + restrict the options to a specific theme. +

    +
    + +
    +

    + It's a good question to ask yourself! You might use different links for different moods. Maybe one for "surprises", and + another one for a specific... kink... or... somethi-... Warren. Focus. +

    +
    <%= image_tag 'mascot/TaylorHead.png', alt: 'Two link mages crafting your link.', class: 'mascot', width: 70 %>
    +
    + +
    +
    <%= image_tag 'mascot/WarrenHeadHorny.png', alt: 'Two link mages crafting your link.', class: 'mascot', width: 70 %>
    +

    + Yeah...             I am. +

    +
    <%= image_tag 'mascot/TaylorTits.png', alt: 'Two link mages crafting your link.', class: 'mascot', width: 70 %>
    +
    + +
    +

    + While he finishes up and probably cums in my face without asking for the 7th time today, I can tell you we're + talking about the theme tag! This limits surprises, but it's fun when you're just really + feeling... into something! +

    +
    <%= image_tag 'mascot/TaylorHead.png', alt: 'Two link mages crafting your link.', class: 'mascot', width: 70 %>
    +
    + +
    +
    <%= image_tag 'mascot/WarrenHeadHorny.png', alt: 'Two link mages crafting your link.', class: 'mascot', width: 70 %>
    +

    + Like slutty lizard boobs. +

    +
    + +
    +

    + They're that good, <%= current_user.username.capitalize %>!~ I'd let you grope a bit, but you know... computers? + Are you interested in a theme tag? Warren left some suggestions for you. +

    +
    <%= image_tag 'mascot/TaylorHead.png', alt: 'Two link mages crafting your link.', class: 'mascot', width: 70 %>
    +
    + +
    +
    + + You choose! +
    + +
    + <% unless @link.blacklist.split(' ').include? 'pussy' %> + <%= button_to 'Cunt!', apply_link_wizard_path(theme: 'pussy'), class: 'secondary' %> + <%= button_to 'Dominant Females!', apply_link_wizard_path(theme: 'dominant_female'), class: 'secondary' %> + <%= button_to 'Presenting Pussy!', apply_link_wizard_path(theme: 'presenting_pussy'), class: 'secondary' %> + + <% if warren_thinks_youre_boring %> + <%= button_to 'Panties!', apply_link_wizard_path(theme: 'panties'), class: 'secondary' %> + <%= button_to 'Camel Toe!', apply_link_wizard_path(theme: 'cameltoe'), class: 'secondary' %> + <% end %> + + <% if warren_thinks_youre_perverted %> + <%= button_to 'Horse Pussy!', apply_link_wizard_path(theme: 'horse_pussy'), class: 'secondary' %> + <%= button_to 'Canine Cookie!', apply_link_wizard_path(theme: 'canine_pussy'), class: 'secondary' %> + <% end %> + <% end %> + + <% unless @link.blacklist.split(' ').include? 'female_focus' %> + <%= button_to 'Girls Jacking Off!', apply_link_wizard_path(theme: 'sex_toy_in_pussy'), class: 'secondary' %> + <% end %> + + <% unless @link.blacklist.split(' ').include? 'penis' %> + <%= button_to 'Cocks!', apply_link_wizard_path(theme: 'penis'), class: 'secondary' %> + <%= button_to 'Balls!', apply_link_wizard_path(theme: 'balls'), class: 'secondary' %> + <%= button_to 'Horse Cock!', apply_link_wizard_path(theme: 'horsecock'), class: 'secondary' %> + <%= button_to 'Knotted Cock!', apply_link_wizard_path(theme: 'knot'), class: 'secondary' %> + <%= button_to 'Barbed Cock!', apply_link_wizard_path(theme: 'penile_spines'), class: 'secondary' %> + <%= button_to 'Dominant Males!', apply_link_wizard_path(theme: 'dominant_male'), class: 'secondary' %> + + <% unless @link.blacklist.split(' ').include?('girly') %> + <%= button_to 'Femboys!', apply_link_wizard_path(theme: 'girly'), class: 'secondary' %> + <% end %> + + <% if warren_thinks_youre_boring %> + <%= button_to 'Blowjobs!', apply_link_wizard_path(theme: 'fellatio'), class: 'secondary' %> + <%= button_to 'Frotting!', apply_link_wizard_path(theme: 'frottage'), class: 'secondary' %> + <%= button_to 'Jockstraps!', apply_link_wizard_path(theme: 'jockstrap'), class: 'secondary' %> + <% end %> + + <% if warren_thinks_youre_perverted %> + <% unless @link.blacklist.split(' ').include? 'breasts' %> + <%= button_to 'Udder Balls!', apply_link_wizard_path(theme: 'udder_balls'), class: 'secondary' %> + <% end %> + <%= button_to 'Guys Jacking Off!', apply_link_wizard_path(theme: 'penile_masturbation'), class: 'secondary' %> + <% end %> + + <% if warren_thinks_youre_really_perverted %> + <%= button_to 'Cock and Ball Torture!', apply_link_wizard_path(theme: 'cock_and_ball_torture'), class: 'secondary' %> + <%= button_to 'Sperm!', apply_link_wizard_path(theme: 'sperm_cell'), class: 'secondary' %> + <%= button_to 'Farm Fresh Milked Jizz!', apply_link_wizard_path(theme: 'penis_milking_machine '), class: 'secondary' %> + <% end %> + <% end %> + + <% unless @link.blacklist.split(' ').include? 'breasts' %> + <%= button_to 'Tits!', apply_link_wizard_path(theme: 'breasts'), class: 'secondary' %> + + <% if warren_thinks_youre_perverted %> + <%= button_to 'HUGE Tits!', apply_link_wizard_path(theme: 'hyper_breasts'), class: 'secondary' %> + <%= button_to 'Udders!', apply_link_wizard_path(theme: 'udders'), class: 'secondary' %> + <% end %> + + <% if warren_thinks_youre_really_perverted %> + <%= button_to 'Lactation!', apply_link_wizard_path(theme: 'lactating'), class: 'secondary' %> + <%= button_to 'Milking!', apply_link_wizard_path(theme: 'breast_milking'), class: 'secondary' %> + + <% unless @link.blacklist.split(' ').include?('male_focus') %> + <%= button_to 'Guys Leaking Milk Out Their Pecks!', apply_link_wizard_path(theme: 'male_lactation'), class: 'secondary' %> + <% end %> + <% end %> + <% end %> + + <% unless @link.blacklist.split(' ').include? 'anus' %> + <%= button_to 'Assholes!', apply_link_wizard_path(theme: 'anus'), class: 'secondary' %> + <%= button_to 'Butts!', apply_link_wizard_path(theme: 'butt'), class: 'secondary' %> + + <% if warren_thinks_youre_really_really_perverted %> + <%= button_to 'Shit!', apply_link_wizard_path(theme: 'scat'), class: 'secondary' %> + <% end %> + <% end %> + + <% unless @link.blacklist.split(' ').include? 'cloaca' %> + <%= button_to 'Bird Hole!', apply_link_wizard_path(theme: 'cloaca'), class: 'secondary' %> + <% end %> + + <%= button_to 'Facesitting!', apply_link_wizard_path(theme: 'facesitting'), class: 'secondary' %> + <%= button_to 'Leather!', apply_link_wizard_path(theme: 'leather'), class: 'secondary' %> + <%= button_to 'Flashing!', apply_link_wizard_path(theme: 'flashing'), class: 'secondary' %> + <%= button_to 'Feet!', apply_link_wizard_path(theme: 'feet'), class: 'secondary' %> + + <% if warren_thinks_youre_perverted %> + <%= button_to 'Paws!', apply_link_wizard_path(theme: 'paws'), class: 'secondary' %> + <%= button_to 'Hyper!', apply_link_wizard_path(theme: 'hyper'), class: 'secondary' %> + <%= button_to 'Cum by the Gallon!', apply_link_wizard_path(theme: 'excessive_cum'), class: 'secondary' %> + <%= button_to 'Baby Making!', apply_link_wizard_path(theme: 'impregnation'), class: 'secondary' %> + <%= button_to 'Latex!', apply_link_wizard_path(theme: 'latex'), class: 'secondary' %> + <%= button_to 'Slime!', apply_link_wizard_path(theme: 'slime'), class: 'secondary' %> + <% end %> + + <% if warren_thinks_youre_really_perverted %> + <%= button_to 'Diapers!', apply_link_wizard_path(theme: 'diapers'), class: 'secondary' %> + <%= button_to 'Piss!', apply_link_wizard_path(theme: 'urine'), class: 'secondary' %> + <%= button_to 'Vore!', apply_link_wizard_path(theme: 'vore'), class: 'secondary' %> + <%= button_to 'Feral!', apply_link_wizard_path(theme: 'feral'), class: 'secondary' %> + <% end %> + + <% if warren_thinks_youre_really_really_perverted %> + <%= button_to 'Non Consensual!', apply_link_wizard_path(theme: 'rape'), class: 'secondary' %> + <%= button_to 'Gore!', apply_link_wizard_path(theme: 'gore'), class: 'secondary' %> + <% end %> + + <% if warren_thinks_youre_boring %> + <%= button_to 'Pinups!', apply_link_wizard_path(theme: 'pinup'), class: 'secondary' %> + <%= button_to 'Tickling!', apply_link_wizard_path(theme: 'tickling'), class: 'secondary' %> + <% end %> + + <% if warren_thinks_youre_really_boring %> + <%= button_to 'Romantic!', apply_link_wizard_path(theme: 'romantic'), class: 'secondary' %> + <%= button_to 'Teasing!', apply_link_wizard_path(theme: 'rating:q'), class: 'secondary' %> + <% end %> + + <% if warren_thinks_youre_really_really_boring %> + <%= button_to 'Non Sexual!', apply_link_wizard_path(theme: 'rating:s'), class: 'secondary' %> + <% end %> + + <% if @link.terms.downcase.include? 'goon' %> + <%= button_to 'Gooning!', apply_link_wizard_path(theme: 'gooning'), class: 'secondary' %> + <%= button_to 'Group Masturbation!', apply_link_wizard_path(theme: 'group_masturbation'), class: 'secondary' %> + <%= button_to 'Hypno Eyes!', apply_link_wizard_path(theme: 'hypnotic_eyes'), class: 'secondary' %> + <% end %> + + <% if @link.terms.downcase.include?('fox') || current_user.username.downcase.include?('fox') %> + <%= button_to 'Foxes!', apply_link_wizard_path(theme: 'fox'), class: 'secondary' %> + <% end %> + + <% if @link.terms.downcase.include?('dog') || current_user.username.downcase.include?('dog') || @link.terms.downcase.include?('pup') || current_user.username.downcase.include?('pup') %> + <%= button_to 'Dogs!', apply_link_wizard_path(theme: 'canine'), class: 'secondary' %> + <% end %> + + <% if @link.terms.downcase.include?('cat') || current_user.username.downcase.include?('cat') || @link.terms.downcase.include?('kitty') || current_user.username.downcase.include?('kitty') %> + <%= button_to 'Cats!', apply_link_wizard_path(theme: 'feline'), class: 'secondary' %> + <% end %> + + <% if @link.terms.downcase.include?('horse') || current_user.username.downcase.include?('horse') || @link.terms.downcase.include?('stud') || current_user.username.downcase.include?('stud') %> + <%= button_to 'Horses!', apply_link_wizard_path(theme: 'equine'), class: 'secondary' %> + <% end %> + + <% if @link.terms.downcase.include?('wolf') || current_user.username.downcase.include?('wolf') %> + <%= button_to 'Wolves!', apply_link_wizard_path(theme: 'wolf'), class: 'secondary' %> + <% end %> + + <% if @link.terms.downcase.include?('slut') || current_user.username.downcase.include?('slut') %> + <%= button_to 'Public Use!', apply_link_wizard_path(theme: 'public_use'), class: 'secondary' %> + <% end %> + + <%= button_to 'Loimu!', apply_link_wizard_path(theme: 'loimu_(character)'), class: 'secondary' %> +
    + + <%= button_to 'I prefer to be surprised or will decide later.', next_wizard_path, method: :get %> +
    +<% end %> \ No newline at end of file diff --git a/app/views/links/_details.html.erb b/app/views/links/_details.html.erb index 67464525..a806b702 100644 --- a/app/views/links/_details.html.erb +++ b/app/views/links/_details.html.erb @@ -1,16 +1,20 @@ <% target = "link_details_#{link.id}" %> -<% e621_url = "https://e621.net/posts?tags=#{ CGI.escape("md5:#{ link.post_url[/\w*(?=\.(png|jpg|bmp)$)/] }") }" if link.post_url %> +<% e621_url = "https://e621.net/posts?md5=#{ CGI.escape(link.post_url[/\w*(?=\.(png|jpg|bmp|webm|gif)$)/]) }" if link.post_url %> <% client = link_agent_to_icon link.last_ping_user_agent %> <% devices = { desktop: :desktop, android: :mobile, joihow: :desktop, automate: :mobile, + arson_automate: :mobile, wallpaper_engine: :desktop, ioswidget: :mobile, swift: :desktop, android_changer: :mobile, jberliner: :desktop, + chewtoy: :desktop, + kemkem_userscript: :desktop, + xenofluff: :desktop, unknown: :desktop } %> <% device = devices[client] %> @@ -56,4 +60,4 @@ <% end %> -<% end %> \ No newline at end of file +<% end %> diff --git a/app/views/links/_form.html.erb b/app/views/links/_form.html.erb index de4e5d2c..7cfe499e 100644 --- a/app/views/links/_form.html.erb +++ b/app/views/links/_form.html.erb @@ -152,7 +152,8 @@

    <% if can_show_videos %> - Videos and GIFs can be selected, not just images. Currently, only the Wallpaper Engine client supports videos. + Videos and GIFs can be selected, not just images. Currently, only the Wallpaper Engine client supports + videos. <% else %> Videos and GIFs cannot be selected, only images are selectable. (Default) <% end %> @@ -179,7 +180,8 @@

    <% if can_be_set_by_porn_bot %> Your wallpaper will be changed by our resident horny robot: PornBot. They (a gender-defying icon) select - a highly-rated post that matches your blacklist rules. + a highly-rated post that matches your blacklist rules. PornBot has no preferences unlike the + PornLizards. <% else %> Your wallpaper will not changed by our resident horny robot: PornBot. You will only get wallpapers from fleshy human-beings. @@ -192,6 +194,79 @@ <% end %>

    +
    + <% + can_be_set_by_lizard = link.check_ability 'can_be_set_by_lizard' + lizard = current_user.mascot || 'ki' + %> + +
    + + <% if can_be_set_by_lizard %> + <%= image_tag "mascot/#{lizard.capitalize}Head.png", class: 'mascot', height: 25, width: 25 %> + <% else %> + + <% end %> + <%= current_user&.pervert ? 'Perverted ' : '' %><%= lizard.capitalize %> + is <%= can_be_set_by_lizard ? 'welcomed' : 'turned away' %> + +

    + <% if can_be_set_by_lizard %> + Your wallpaper will be changed by your selected PornLizard <%= lizard.capitalize %> + ! <%= lizard_to_description(lizard, current_user&.pervert || false) %> + <% else %> + Your wallpaper will not changed by any of the PornLizards. It's kind of their thing, you should let at + least one mess with you! I'm sure <%= lizard.capitalize %> is horny. If you'd like to pick another, + choose one from the site header. + <% end %> +

    +
    +
    + <%= button_to toggle_link_ability_link_path(ability: :can_be_set_by_lizard), method: :post, class: can_be_set_by_lizard ? 'enabled' : 'disabled' do %> + <%= can_be_set_by_lizard ? "Disallow #{lizard.capitalize}" : "Allow #{lizard.capitalize}" %> + <% end %> +
    +
    +
    + <% is_kink_aligned = link.check_ability 'is_kink_aligned' %> + +
    + + <% if is_kink_aligned %> + + <% else %> + + <% end %> + Profile Kinks are <%= is_kink_aligned ? 'enforced' : 'ignored' %> + +

    + <% if is_kink_aligned %> + At least 1 of your user profile kinks will be required to appear on posts sent to this link. Be careful! + It's easy to make a configuration that no one can change easily! No one wants to skim massive tag lists + just to find something for you. + <% else %> + Your wallpaper configuration is unaffected by your user profile kinks. All the configuration relative to + this link is shown on this page, nothing else affects it. + <% end %> +

    + <% if is_kink_aligned %> + <%= turbo_frame_tag :kink_tools do %> +
    +
    + <%= render 'kink/kinks', kinks: current_user&.kinks, is_current_user: true %> + + <%= render 'kink/form', kink: Kink.new %> +
    +
    + <% end %> + <% end %> +
    +
    + <%= button_to toggle_link_ability_link_path(ability: :is_kink_aligned), method: :post, class: is_kink_aligned ? 'enabled' : 'disabled' do %> + <%= is_kink_aligned ? 'Be less strict' : 'Be strict' %> + <% end %> +
    +
    <% end %> diff --git a/app/views/links/_link.html.erb b/app/views/links/_link.html.erb index 09573ded..c4532f13 100644 --- a/app/views/links/_link.html.erb +++ b/app/views/links/_link.html.erb @@ -1,4 +1,6 @@ -

    @@ -12,10 +19,29 @@ All public online links ordered by who needs the most attention at the top.

    +<% if @new_user_links.count != 0 %> + + +<% end %> + +<% if @new_user_links.count != 0 %> + +<% end %>
    + + + Link Wizard + <%= button_to "New link", new_link_path, method: :get %>
    @@ -14,18 +18,4 @@ Your Links - \ No newline at end of file +<%= render 'links', links: @links %> \ No newline at end of file diff --git a/app/views/links/show.html.erb b/app/views/links/show.html.erb index 9a1cd69b..ef207a16 100644 --- a/app/views/links/show.html.erb +++ b/app/views/links/show.html.erb @@ -22,12 +22,18 @@ - Copy URL + Copy Edit + <% if @link.wizard_page != nil %> + + + Finish Wizard + + <% end %> <%= button_tag "Delete", data: { action: 'click->link#confirm' } %> @@ -244,7 +250,8 @@

    - Links that are forks of this link will receive wallpaper changes that are set here; but only if their separete blacklists allow it. You may receive responses from anyone of the following: + Links that are forks of this link will receive wallpaper changes that are set here; but only if their separete + blacklists allow it. You may receive responses from anyone of the following:

    <% @link.forks.each do |fork| %> diff --git a/app/views/lizard_tools/index.html.erb b/app/views/lizard_tools/index.html.erb new file mode 100644 index 00000000..47c30281 --- /dev/null +++ b/app/views/lizard_tools/index.html.erb @@ -0,0 +1,9 @@ +

    + + Lizard Browse + Browse which users have a lizard selected. +

    + +<%= button_to "Warren", lizard_tools_warren_path, method: :get %> +<%= button_to "Ki", lizard_tools_ki_path, method: :get %> +<%= button_to "Taylor", lizard_tools_taylor_path, method: :get %> diff --git a/app/views/lizard_tools/lizard_browse.html.erb b/app/views/lizard_tools/lizard_browse.html.erb new file mode 100644 index 00000000..97189806 --- /dev/null +++ b/app/views/lizard_tools/lizard_browse.html.erb @@ -0,0 +1,39 @@ +
    +
    + <%= link_to "Back", lizard_tools_index_path %> +
    +
    + +

    + + <%= @name %> Browse + Browse which users have <%= @name %> selected. +

    + +<% if @links[false] %> +
    Not Perverted
    + <% @links[false].each do |link| %> + <%= render(link) %> + <% end %> +<% else %> +
    +
    + + No <%= @name %> non-perverts online +
    +
    +<% end %> + +<% if @links[true] %> +
    Perverted >:)
    + <% @links[true].each do |link| %> + <%= render(link) %> + <% end %> +<% else %> +
    +
    + + No <%= @name %> perverts online +
    +
    +<% end %> diff --git a/app/views/message_thread/_messages_from_thread.html.erb b/app/views/message_thread/_messages_from_thread.html.erb index 8be538a0..931b2c66 100644 --- a/app/views/message_thread/_messages_from_thread.html.erb +++ b/app/views/message_thread/_messages_from_thread.html.erb @@ -1,4 +1,4 @@ -<% messages = message_thread.messages.order(updated_at: :desc).all %> +<% messages = message_thread.messages.order(updated_at: :desc).limit(40) %>
    <% messages.each_with_index do |message, index| %> diff --git a/app/views/mod_tools/_edit_user_form.erb b/app/views/mod_tools/_edit_user_form.erb new file mode 100644 index 00000000..49a6dec4 --- /dev/null +++ b/app/views/mod_tools/_edit_user_form.erb @@ -0,0 +1,16 @@ +<%= turbo_frame_tag :mod_tools_edit_user_form do %> + <%= link_to "Log in as #{user.username}", mod_tools_users_assume_path(user), target: '_top' %> + <%= form_with model: user, method: :post, url: mod_tools_users_update_path do |f| %> + <% user.class.columns.each do |col| %> +
    + <%= f.label col.name.to_sym %> + <% if user.send(col.name).is_a?(TrueClass) || user.send(col.name).is_a?(FalseClass) %> + <%= f.check_box col.name.to_sym, value: user.send(col.name) %> + <% else %> + <%= f.text_field col.name.to_sym, value: user.send(col.name) %> + <% end %> +
    + <% end %> + <%= f.submit %> + <% end %> +<% end %> diff --git a/app/views/mod_tools/_password_reset_form.erb b/app/views/mod_tools/_password_reset_form.erb new file mode 100644 index 00000000..d4877242 --- /dev/null +++ b/app/views/mod_tools/_password_reset_form.erb @@ -0,0 +1,22 @@ +<%= turbo_frame_tag :mod_tools_password_reset_form do %> + <%= form_with url: mod_tools_passwords_update_path, method: :post do |f| %> + <%= f.label :email, 'Email address user claims is theirs:', style: "display: block" %> + <%= f.text_field :email, value: params['email'] %> + <%= f.submit 'Try to generate a reset link (case insensitive)' %> + <%= f.submit 'Try to generate a reset link (case sensitive)' %> + <% end %> + + <% if params['fail'] || params['link'] %> +
    + <% if params['fail'] %> + <%= params['fail'] %>
    + <% end %> + + <% if params['link'] %> + + Found 1 user, <%= params['username'] %>
    + https://walltaker.joi.how/i-forgor/commit/<%= params['link'] %> + <% end %> + <% end %> +
    +<% end %> diff --git a/app/views/mod_tools/_pick_user_form.erb b/app/views/mod_tools/_pick_user_form.erb new file mode 100644 index 00000000..71290514 --- /dev/null +++ b/app/views/mod_tools/_pick_user_form.erb @@ -0,0 +1,25 @@ +<% + user ||= nil + fail ||= nil +%> + +<%= turbo_frame_tag :mod_tools_pick_user_form do %> + <%= form_with url: mod_tools_users_index_url, method: :get do |f| %> + <%= f.label :email, 'Email address user claims is theirs:', style: "display: block" %> + <%= f.text_field :email, value: params['email'] %> + <%= f.submit 'Lookup' %> + <% end %> + <% if user || fail %> +
    + <% if fail %> + <%= fail %>
    + <% end %> + + <% if user %> + + Found 1 user, <%= user.username %>
    + <%= render 'mod_tools/edit_user_form', user: user %> + <% end %> +
    + <% end %> +<% end %> diff --git a/app/views/mod_tools/destroy_user.html.erb b/app/views/mod_tools/destroy_user.html.erb new file mode 100644 index 00000000..deaa66f3 --- /dev/null +++ b/app/views/mod_tools/destroy_user.html.erb @@ -0,0 +1,2 @@ +

    ModTools#destroy_user

    +

    Find me in app/views/mod_tools/destroy_user.html.erb

    diff --git a/app/views/mod_tools/index.html.erb b/app/views/mod_tools/index.html.erb new file mode 100644 index 00000000..b15d9ea6 --- /dev/null +++ b/app/views/mod_tools/index.html.erb @@ -0,0 +1,55 @@ +
    + +
    + + Read me +
    +

    + You can cause IRREVERSIBLE damage here. Both to the site, and our user's privacy. There are no + confirmations before you do an action. You need to think before you click. +

    +
    +

    + We consider the following to be PII (Personally Identifiable Information): +

    +
      +
    • Email address
    • +
    • IP Addresses
    • +
    • Messages
    • +
    +

    + This information should NEVER be provided to anyone. That includes, + NOT EVEN TELLING THE USER THEIR OWN PII INFO. Always assume someone's account is hacked and + that you are being manipulated. You may ONLY ask for THEM to provide it, and you + can confirm if it's correct or not. Provide no more information. +

    +
    +

    + If you fuck up with tools I provide to you on this panel, you will lose all access. - Gray +

    +
    + +

    + + Mod Tools + Gray trusts you. The highlighted button in each category is the most useful. +

    + +
    +

    Account issues

    + <%= button_to "They forgot their password", mod_tools_passwords_index_path, method: :get %> + <%= button_to "Edit a user", mod_tools_users_index_path, method: :get, class: 'secondary' %> +
    + +
    +

    Raiding

    + <%= button_to "Quarantine account(s)", mod_tools_quarantine_index_path, method: :get %> + <%= button_to "Recent Events", mod_tools_events_index_path, method: :get %> +
    + + \ No newline at end of file diff --git a/app/views/mod_tools/show_password_reset.html.erb b/app/views/mod_tools/show_password_reset.html.erb new file mode 100644 index 00000000..829819cf --- /dev/null +++ b/app/views/mod_tools/show_password_reset.html.erb @@ -0,0 +1,54 @@ +
    +
    + <%= link_to "Cancel", mod_tools_index_path %> +
    +
    + +

    + + Reset Any Account's Password + Gray trusts you. Remember our PII rules. +

    + +

    Step 1: Get proof

    +

    + Ask something like the following: "Can you give me all or part of the email you used to make the account? I only need it to confirm this is your account." A partial email is alright, as long as it contains something on the left side of the @ symbol. Make sure they at least give you most of it though, single words might be guessable. If they say "it was a gmail.com address", this does not count. +

    + +
    + +
    + + Be vigilant. +
    +

    If they refuse, or say they forgot entirely, you cannot reset their password. Simply refuse, explain that it's required, and end the DM. They may be manipulating you to get access to the account. This has happened before.

    +
    +

    If they used an email hiding service, that's not an excuse for not remembering it.

    +
    + +

    Step 2: Check proof and generate link

    +

    + Enter their response below. This does a check thru the user's table, looking for an email similar to the entered value. Capitalization is ignored, but it must be one whole chunk of the email address. ("gray@gmail.com" will match "pupgray@gmail.com", but NOT "pupg@gmail.com") +

    + +
    + +
    + + Be vigilant. +
    +

    Be somewhat flexible if it's obvious they made a typo, but if there is no results, and they are sure that their email is that, end the DM. Give maybe 2 chances to try different ones.

    +
    +

    If they used an email hiding service, that's not an excuse for not remembering it.

    +
    + +<%= render 'mod_tools/password_reset_form' %> + +

    Step 3: If successful, give them the link

    +

    + Verify that you got a green checkbox in step 2, and that the user name that showed up matches what it should be for this user. If yes, you may give them the password reset link from step 2. +

    + +

    + If you got a red cross, this means no account was found. Use your best judgement about if user should be asked again. Remember, anyone could be trying to get into an account. +

    \ No newline at end of file diff --git a/app/views/mod_tools/show_quarantine.html.erb b/app/views/mod_tools/show_quarantine.html.erb new file mode 100644 index 00000000..d3df9839 --- /dev/null +++ b/app/views/mod_tools/show_quarantine.html.erb @@ -0,0 +1,89 @@ +
    +
    + <%= link_to "Cancel", mod_tools_index_path %> +
    +
    + +

    + + Quarantine Accounts + Gray trusts you. +

    + +<%= turbo_frame_tag 'quarantine_table' do %> + + + + + + + + <% @users.each do |user| %> + <% + visits = user.ahoy_visits.where("started_at > ?", 3.days.ago) + sets = user.past_links.where("created_at > ?", 3.days.ago).order(id: :desc) + set_count = sets.count + %> + + + + + + + + + + + <% end %> +
    UserCreated atStatus
    + <%= link_to mod_tools_quarantine_index_url(anchor: dom_id(user)), class: 'no-underline', target: '_top' do %> + + <% end %> + <%= link_to mod_tools_users_index_url(email: user.email), target: '_blank' do %> + <%= user.username %> + <% end %> + + <%= user.created_at %>
    + Created <%= time_ago_in_words user.created_at %> ago +
    + <% if user.quarantined %> + + + Quarantined + <% else %> + + Allowed + <% end %> + + <% if user.quarantined %> + <%= button_to 'Revive', mod_tools_quarantine_update_path(user) %> + <% else %> + <%= button_to 'Quarantine', mod_tools_quarantine_update_path(user) %> + <%= button_to 'IP Ban', mod_tools_quarantine_ipban_path(user) if visits.count > 0 %> + <% end %> +
    +

    Recent activity:

    + <% if set_count > 1 %> + <% avg = ((sets.first.created_at - sets.last.created_at) / set_count).round(2) %> + <% if avg < 10.minutes %> + + <% end %> + <% if avg < 1.minute %> + REALLY LOW!!   + <% end %> + <%= avg %> seconds between sets recently (<%= set_count %> total) + <% else %> + Set <%= pluralize set_count, 'wallpaper' %> recently. + <% end %>
    + + Visited <%= pluralize visits.count, 'time' %> in the last 3 days +
      + <% visits.each do |visit| %> +
    1. + <%= visit.banned_ip.present? ? "❌ IPBANNED BY #{visit.banned_ip.banned_by.username.upcase}" : '' %> + <%= visit.ip %> <%= visit.user_agent %>, + <%= time_ago_in_words visit.started_at %> ago
    2. + <% end %> +
    +
    +<% end %> \ No newline at end of file diff --git a/app/views/mod_tools/show_recent_events.html.erb b/app/views/mod_tools/show_recent_events.html.erb new file mode 100644 index 00000000..6504554c --- /dev/null +++ b/app/views/mod_tools/show_recent_events.html.erb @@ -0,0 +1,44 @@ +
    +
    + <%= link_to "Cancel", mod_tools_index_path %> +
    +
    + +

    + + Recent Events + Gray trusts you. +

    + + + <% @events.each do |event| %> + + + + + + <% end %> +
    +
    <%= event.name %>
    +
    + <%= event.visit.banned_ip.present? ? "❌ IPBANNED BY #{event.visit.banned_ip.banned_by.username.upcase}" : '' %> + <%= event.visit.ip %>
    + <% if event.user %> + <%= link_to "Caused by #{event.user.username}", mod_tools_users_index_url(email: event.user.email) %> + <% else %> + Caused by ⚠️ Anon! + <% end %> +
    + <% if event.properties.key? 'attempted_post_id' %> + <%= link_to "https://e621.net/posts/#{event.properties['attempted_post_id']}", "https://e621.net/posts/#{event.properties['attempted_post_id']}", target: '_blank' %> + <% end %> + <% if event.properties.key? 'past_link_id' %> + <% past_link = PastLink.find_by_id(event.properties['past_link_id']) %> + <% if past_link %> + <%= image_tag past_link.post_thumbnail_url, width: 320 %> + <% end %> + <% end %> + <%= event.properties %> +
    + <%= button_to 'IP Ban', mod_tools_events_ipban_path(event) %> +
    \ No newline at end of file diff --git a/app/views/mod_tools/show_user.html.erb b/app/views/mod_tools/show_user.html.erb new file mode 100644 index 00000000..1ba3feed --- /dev/null +++ b/app/views/mod_tools/show_user.html.erb @@ -0,0 +1,20 @@ +<% user ||= nil %> + +
    +
    + <%= link_to "Cancel", mod_tools_index_path %> +
    +
    + +

    + + Edit Any User + Gray trusts you. Remember our PII rules. +

    + +<%= render 'mod_tools/pick_user_form', user: %> + + + + + diff --git a/app/views/mod_tools/update_password_reset.html.erb b/app/views/mod_tools/update_password_reset.html.erb new file mode 100644 index 00000000..c13f0065 --- /dev/null +++ b/app/views/mod_tools/update_password_reset.html.erb @@ -0,0 +1,2 @@ +

    ModTools#update_password_reset

    +

    Find me in app/views/mod_tools/update_password_reset.html.erb

    diff --git a/app/views/porn_search/_input.html.erb b/app/views/porn_search/_input.html.erb new file mode 100644 index 00000000..8cb5cc59 --- /dev/null +++ b/app/views/porn_search/_input.html.erb @@ -0,0 +1,11 @@ +<%= turbo_frame_tag "pornsearch_input" do %> + <%= form_with url: porn_search_search_url, method: :get, data: { turbo_frame: 'pornsearch_results', 'porn-search-target': 'search' } do |form| %> + <%= form.text_field :tags, placeholder: 'E621 Tags', value: params['tags'] %> + <%= form.hidden_field :link, value: @link.id if @link %> + <%= form.hidden_field :message_thread, value: params['message_thread'] %> + <%= form.button type: :submit do %> + + Search E621 + <% end %> + <% end %> +<% end %> \ No newline at end of file diff --git a/app/views/porn_search/index.html.erb b/app/views/porn_search/index.html.erb index 4950bc9f..91c3e41e 100644 --- a/app/views/porn_search/index.html.erb +++ b/app/views/porn_search/index.html.erb @@ -1,15 +1,11 @@ <%= turbo_frame_tag "pornsearch" do %> + <%= render 'input' %> - <%= form_with url: porn_search_search_url, method: :get, data: { turbo_frame: 'pornsearch_results', 'porn-search-target': 'search' } do |form| %> - <%= form.text_field :tags, placeholder: 'E621 Tags' %> - <%= form.hidden_field :link, value: params['link'] %> - <%= form.hidden_field :message_thread, value: params['message_thread'] %> - <%= form.button type: :submit do %> - - Search E621 - <% end %> + <% if @link && @link.user.kinks.count > 0 %> +
    + Some of <%= @link.user.username %>'s kinks: + <%= turbo_frame_tag :kink_tools, src: search_kinks_path(@link), class: 'kinks' if params['link'] %> <% end %> <%= turbo_frame_tag "pornsearch_results" %> - <% end %> \ No newline at end of file diff --git a/app/views/search/index.html.erb b/app/views/search/index.html.erb new file mode 100644 index 00000000..fcb805c8 --- /dev/null +++ b/app/views/search/index.html.erb @@ -0,0 +1,34 @@ +
    +
    + <%= link_to "Back to browse", browse_path %> +
    +
    + +

    Woogle

    + +<%= form_with url: results_url, method: 'get', class: 'link-search', data: { 'turbo-frame': :link_search_results } do |f| %> + <%= f.text_field :q, value: params['q'] %> + <%= f.submit 'Search Links', name: nil %> + <%= f.label :only_online do %> + Online Only + <%= f.check_box :only_online %> + <% end %> +<% end %> + +<% if params['q'] %> + <%= turbo_frame_tag :link_search_results, src: results_url(q: params['q']) %> +<% else %> + <%= turbo_frame_tag :link_search_results do %> +
    +
    + + Tip +
    +

    + Woogle searches for links, but will look at the user's bio and username as well. + This means you may see links that don't seem to contain your search term. This just means the + word appeared in their user profile. +

    +
    + <% end %> +<% end %> diff --git a/app/views/search/results.html.erb b/app/views/search/results.html.erb new file mode 100644 index 00000000..703ed0b2 --- /dev/null +++ b/app/views/search/results.html.erb @@ -0,0 +1,14 @@ +<%= turbo_frame_tag :link_search_results do %> + Found <%= pluralize @total, 'result' %> + <% @links.each do |link| %> +
    + <%= render link %> +
    + <% end %> + +
    + <%= link_to 'Prev', results_path(q: @query, page: @page - 1, only_online: @only_online ? '1' : '0'), disabled: @page <= 1 %> + <%= @page %> + <%= link_to 'Next', results_path(q: @query, page: @page + 1, only_online: @only_online ? '1' : '0'), disabled: !@has_next_page %> +
    +<% end %> \ No newline at end of file diff --git a/app/views/session/new.html.erb b/app/views/session/new.html.erb index 3b94a3aa..5bcaefca 100644 --- a/app/views/session/new.html.erb +++ b/app/views/session/new.html.erb @@ -19,7 +19,7 @@
    <%= check_box_tag :keep_me_logged_in %> - <%= label_tag :keep_me_logged_in %> + <%= label_tag 'Keep me logged in for 2 weeks' %>
    <%= submit_tag 'Log In' %>
    diff --git a/app/views/settings/index.html.erb b/app/views/settings/index.html.erb new file mode 100644 index 00000000..f33e2b1b --- /dev/null +++ b/app/views/settings/index.html.erb @@ -0,0 +1,38 @@ +<% content_for(:html_title) { "#{@user.username} walltaker settings" } %> + +<% if @user.username.last.downcase == 's' %> +

    + <%= @user.username %>' settings +

    +<% else %> +

    + <%= @user.username %>'s settings +

    +<% end %> + +
    +
    + + Theme Mode +
    + <%= form_with model: @user, url: settings_path, method: :post, data: { 'turbo': 'false' } do |f| %> +
    + <%= f.label :colour_preference, value: :light, class: 'form__row' do %> + + Light mode + <%= f.radio_button :colour_preference, :light %> + <% end %> + <%= f.label :colour_preference, value: :dark, class: 'form__row' do %> + + Dark mode + <%= f.radio_button :colour_preference, :dark %> + <% end %> + <%= f.label :colour_preference, value: :auto, class: 'form__row' do %> + + Automatic + <%= f.radio_button :colour_preference, :auto %> + <% end %> +
    + <%= f.submit 'Save', class: 'accent-block__button' %> + <% end %> +
    \ No newline at end of file diff --git a/app/views/settings/save.html.erb b/app/views/settings/save.html.erb new file mode 100644 index 00000000..127a11e8 --- /dev/null +++ b/app/views/settings/save.html.erb @@ -0,0 +1,2 @@ +

    Settings#save

    +

    Find me in app/views/settings/save.html.erb

    diff --git a/app/views/surrenders/_form.html.erb b/app/views/surrenders/_form.html.erb new file mode 100644 index 00000000..421a4622 --- /dev/null +++ b/app/views/surrenders/_form.html.erb @@ -0,0 +1,10 @@ +<%# locals: (surrender:, friendship_options:) -%> + +<%= form_with model: surrender do |f| %> + <%= f.label :friendship, class: 'form__row form__row--stacking' do %> + Surrender To + <%= f.select :friendship, friendship_options, {}, class: 'no-margin' %> + <% end %> + + <%= f.submit "Give them my account for up to 24hrs", class: 'danger-button accent-block__button' %> +<% end %> \ No newline at end of file diff --git a/app/views/surrenders/_surrender.html.erb b/app/views/surrenders/_surrender.html.erb new file mode 100644 index 00000000..d708b069 --- /dev/null +++ b/app/views/surrenders/_surrender.html.erb @@ -0,0 +1,17 @@ +<%# locals: (surrender:) -%> + +<%= turbo_frame_tag dom_id(surrender), target: '_top' do %> +
    +
    + + Only you see this +
    +

    + Surrendered to <%= surrender.controller.username %> +

    + + <%= button_to surrender_path(surrender), method: :delete, class: 'accent-block__button' do %> + Stop and return account + <% end %> +
    +<% end %> \ No newline at end of file diff --git a/app/views/surrenders/index.html.erb b/app/views/surrenders/index.html.erb new file mode 100644 index 00000000..2641db30 --- /dev/null +++ b/app/views/surrenders/index.html.erb @@ -0,0 +1,74 @@ +

    + + Surrender Account +

    + +<% if @current_surrender.present? && !@current_surrender.expired? %> + <%= turbo_frame_tag dom_id(@current_surrender), src: surrender_path(@current_surrender), target: '_top' %> +
    +<% end %> + +

    + Do you like profile play? Normally, accounts on most websites can only be given up to someone by + sharing a password. Not only is this a horrible idea for your own personal security, for a website, it's also + detrimental! We need to be able to attribute actions to a user to moderate and secure the site. +

    + +

    + So wouldn't it be cool if you could just... + give your account to a friend for a bit, no questions asked? +

    + +
    + +
    + + Your failure to read this is not a bug. +
    +

    + This tool instead allows you to give full access to your account to any friend. You do not need to + share your password. They will not see your email address, or login credentials. (not even + walltaker + can know what your plain-text password is in-fact!) +

    +
    +

    + A walltaker moderator will NEVER ask you to use this tool. +

    +
    +

    + Full access, means full access. This is giving up your account to someone temporarily! You are able + to + abort during the time period when the other user has access to your account, but there is no easy undo option for + things they would've already done. +

    +
    + + They can + +
      +
    • chat,
    • +
    • make links,
    • +
    • send wallpapers to others,
    • +
    • change your profile and link settings,
    • +
    • delete or add friends,
    • +
    • and otherwise, use and abuse your account appearing as you. +
    • +
    + + They cannot + +
      +
    • see your password,
    • +
    • see your email address.
    • +
    • That's literally it. Have I made it clear yet? They can do a lot.
    • +
    +
    + +
    + +

    + If you aren't scared off, and want someone to control your account... +

    + +<%= turbo_frame_tag :surrender_form_wrapper, src: new_surrender_path, target: '_top' %> diff --git a/app/views/surrenders/new.html.erb b/app/views/surrenders/new.html.erb new file mode 100644 index 00000000..2e6cb534 --- /dev/null +++ b/app/views/surrenders/new.html.erb @@ -0,0 +1,18 @@ +<%= turbo_frame_tag :surrender_form_wrapper do %> +
    +
    + + Danger Zone +
    +

    + Remember, you are giving up your full account, for up to 24hrs, to someone that will appear as + you + on the website. You can abort this once started, however damage already done by the person using + your account is only reparable manually. (this includes social damage, they will be able to message your friends + in + Goon Threads, or send gross wallpapers.) +

    + + <%= render 'form', surrender: @surrender, friendship_options: @friendship_options %> +
    +<% end %> diff --git a/app/views/surrenders/show.html.erb b/app/views/surrenders/show.html.erb new file mode 100644 index 00000000..c37155f6 --- /dev/null +++ b/app/views/surrenders/show.html.erb @@ -0,0 +1,3 @@ +<%= render 'surrenders/surrender', surrender: @surrender %> + +<%= render 'links/links', links: @surrender.user.link.all %> \ No newline at end of file diff --git a/app/views/users/_user.html.erb b/app/views/users/_user.html.erb new file mode 100644 index 00000000..ce3d4494 --- /dev/null +++ b/app/views/users/_user.html.erb @@ -0,0 +1,21 @@ +<%# locals: (user:) -%> + +<%= turbo_frame_tag user, class: 'user_segment' do %> +
    +

    <%= user.username %>

    + <%= link_to 'See full profile', user_path(user.username), target: '_blank', class: 'button-like no-underline' %> +
    + + <%= turbo_frame_tag :kink_tools, src: user_kinks_path(user.username) %> + +

    <%= truncate(strip_tags(user.details), length: 80, separator: ' ', escape: false) %>

    + +
    + <% past_links = user.past_links.limit 12 %> + <% past_links.each do |pl| %> + <%= image_tag pl.post_thumbnail_url, style: "transform: scale(#{rand(2) + 1})" if pl.present? && pl.post_thumbnail_url.present? %> + <% end %> +
    + + +<% end %> \ No newline at end of file diff --git a/app/views/users/request_password_reset.html.erb b/app/views/users/request_password_reset.html.erb index 80998a6c..419bc74e 100644 --- a/app/views/users/request_password_reset.html.erb +++ b/app/views/users/request_password_reset.html.erb @@ -4,9 +4,7 @@

    - This is an automated password reset system. I cheaped out HARD here, since email delivery is a surprisingly expensive - service to use when my budget is $3/month. You should receive an email, but some email providers block the service I'm - using to send this. Check your spam folder. If it never arrives, contact + This is an automated password reset system. You'll receive an email with a link to reset your password. If it never arrives, contact Gray.

    diff --git a/app/views/users/sets.html.erb b/app/views/users/sets.html.erb new file mode 100644 index 00000000..e1aef63e --- /dev/null +++ b/app/views/users/sets.html.erb @@ -0,0 +1,12 @@ +<% content_for(:html_title) { "Walltaker #{@user.username} - Wallpapers Set For Others" } %> + +
    +
    + <%= link_to "Back", user_path(@user.username) %> +
    +
    + +

    Below is a list of the last 50 wallpapers by <%= @user.username %> for others. Click any wallpaper to go to see + the full size image.

    + +<%= render partial: 'users/past_links', locals: { past_links: @past_links } %> \ No newline at end of file diff --git a/app/views/users/show.html.erb b/app/views/users/show.html.erb index e7ee4e67..c3efa2c0 100644 --- a/app/views/users/show.html.erb +++ b/app/views/users/show.html.erb @@ -26,6 +26,10 @@ <% if current_user && current_user.id == @user.id %>
    + <%= link_to '/settings' do %> +

    settings

    + <% end %> +
    <%= button_to "New link", new_link_path, method: :get %>
    <% end %> @@ -38,7 +42,16 @@ <% end %> - Set <%= @user.set_count %> wallpapers + <%= link_to user_sets_path(@user.username) do %> + Set <%= @user.set_count %> wallpapers + <% end %> + + + <% if @user.username == 'gray' %> + Caused <%= pluralize @total_orgasms_caused, 'orgasm' %>, technically. + <% else %> + Caused <%= pluralize @total_orgasms_caused, 'orgasm' %> + <% end %> Last online @@ -51,6 +64,8 @@ <% end %> + <%= turbo_frame_tag :kink_tools, src: user_kinks_path(@user.username) %> + <% if @total_orgasms_by_day %> <%= area_chart @total_orgasms_by_day, colors: ["#606575"], legend: false, height: "60px", suffix: '💦', library: { scales: { @@ -75,7 +90,7 @@ <% unless @past_links.empty? %> <%= link_to past_links_path, class: 'small see-all-past-wallpapers' do %> - See all past wallpapers + See past 50 wallpapers <% end %> <% end %> @@ -90,27 +105,29 @@ Only you see this
    -

    - - Your API key -

    +
    +

    + + Your API key +

    -

    - Use this key to grant special powers to a client you're using. It does not grant full access to your account, - but does allow clients to set a response on links on your behalf. - You can only have 1 API key at once. When you randomize a new one, the old one will stop - working. -

    +

    + Use this key to grant special powers to a client you're using. It does not grant full access to your account, + but does allow clients to set a response on links on your behalf. + You can only have 1 API key at once. When you randomize a new one, the old one will stop + working. +

    +
    <% if @user.api_key.present? %>
    <%= @user.api_key %>
    - <%= button_to user_new_api_key_path(current_user.username), class: 'danger-button' do %> + <%= button_to user_new_api_key_path(current_user.username), class: 'danger-button accent-block__button' do %> Give me a new one <% end %> <% else %> - <%= button_to user_new_api_key_path(current_user.username) do %> + <%= button_to user_new_api_key_path(current_user.username), class: 'accent-block__button' do %> Generate my API key <% end %> <% end %> diff --git a/bin/setup b/bin/setup index ec47b79b..3cd5a9d7 100755 --- a/bin/setup +++ b/bin/setup @@ -5,7 +5,7 @@ require "fileutils" APP_ROOT = File.expand_path("..", __dir__) def system!(*args) - system(*args) || abort("\n== Command #{args} failed ==") + system(*args, exception: true) end FileUtils.chdir APP_ROOT do diff --git a/config/application.rb b/config/application.rb index 7de02abe..4e5eb961 100644 --- a/config/application.rb +++ b/config/application.rb @@ -11,6 +11,8 @@ class Application < Rails::Application # Initialize configuration defaults for originally generated Rails version. config.load_defaults 7.0 + config.exceptions_app = self.routes + # Configuration for the application, engines, and railties goes here. # # These settings can be overridden in specific environments using the files @@ -20,6 +22,8 @@ class Application < Rails::Application # config.eager_load_paths << Rails.root.join("extras") Rails.application.config.hosts << "joi.how" Rails.application.config.hosts << "walltaker.joi.how" - + Rails.application.config.hosts << "10.244.14.67" + Rails.application.config.hosts << "walltaker-master-39nrv.ondigitalocean.app" + Rails.application.config.hosts << "walltaker-7e4cf22c7c3d.herokuapp.com" end end diff --git a/config/blazer.yml b/config/blazer.yml index 83e9959a..6c071e7f 100644 --- a/config/blazer.yml +++ b/config/blazer.yml @@ -2,7 +2,7 @@ data_sources: main: - url: postgres://postgres:<%= ENV['POSTGRES_PASSWORD'] %>@database:5432/walltaker + url: <%= ENV['DATABASE_URL'] %> # statement timeout, in seconds # none by default diff --git a/config/cronotab.rb b/config/cronotab.rb new file mode 100644 index 00000000..234472ef --- /dev/null +++ b/config/cronotab.rb @@ -0,0 +1,34 @@ +require 'rake' + +Rails.app_class.load_tasks + +class PornBotJob + def perform + Rake::Task['walltaker:porn_bot_round'].execute + end +end + +class KiJob + def perform + Rake::Task['walltaker:ki_round'].execute + end +end + + +class WarrenJob + def perform + Rake::Task['walltaker:warren_round'].execute + end +end + + +class TaylorJob + def perform + Rake::Task['walltaker:taylor_round'].execute + end +end + +Crono.perform(PornBotJob).every 11.minutes +Crono.perform(KiJob).every 8.minutes +Crono.perform(WarrenJob).every 8.minutes +Crono.perform(TaylorJob).every 8.minutes diff --git a/config/database.yml b/config/database.yml index 643a2a83..76d45bfd 100644 --- a/config/database.yml +++ b/config/database.yml @@ -14,7 +14,7 @@ development: &development adapter: postgresql encoding: unicode database: walltaker - username: toish + username: postgres password: host: localhost port: 5432 @@ -27,11 +27,7 @@ test: database: walltaker_test production: - <<: *default adapter: postgresql encoding: unicode - database: walltaker - username: <%= ENV['POSTGRES_USER'] %> - password: <%= ENV['POSTGRES_PASSWORD'] %> - host: database - port: 5432 + timeout: 5000 + url: <%= ENV['DATABASE_URL'] %> diff --git a/config/environment.rb b/config/environment.rb index 096dee72..759681e5 100644 --- a/config/environment.rb +++ b/config/environment.rb @@ -5,13 +5,12 @@ Rails.application.initialize! ActionMailer::Base.smtp_settings = { - :user_name => 'apikey', # This is the string literal 'apikey', NOT the ID of your API key - :password => ENV['SENDGRID_API_KEY'], # This is the secret sendgrid API key which was issued during API key creation - :domain => 'pawcorp.org', - :address => 'smtp.sendgrid.net', - :port => 587, - :authentication => :plain, - :enable_starttls_auto => true + :port => ENV['MAILGUN_SMTP_PORT'], + :address => ENV['MAILGUN_SMTP_SERVER'], + :user_name => ENV['MAILGUN_SMTP_LOGIN'], + :password => ENV['MAILGUN_SMTP_PASSWORD'], + :domain => 'walltaker.joi.how', + :authentication => :plain } ActionMailer::Base.delivery_method = :smtp ActionMailer::Base.perform_deliveries = true diff --git a/config/environments/production.rb b/config/environments/production.rb index c4a85cb2..01a16d0c 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -23,13 +23,13 @@ # Disable serving static files from the `/public` folder by default since # Apache or NGINX already handles this. - config.public_file_server.enabled = true + config.public_file_server.enabled = ENV["RAILS_SERVE_STATIC_FILES"].present? # Compress CSS using a preprocessor. # config.assets.css_compressor = :sass # Do not fallback to assets pipeline if a precompiled asset is missed. - config.assets.compile = true + config.assets.compile = false # Enable serving of images, stylesheets, and JavaScripts from an asset server. # config.asset_host = "http://assets.example.com" @@ -44,13 +44,13 @@ # Mount Action Cable outside main process or domain. # config.action_cable.mount_path = nil # config.action_cable.url = "wss://walltaker.joi.how/cable" - config.action_cable.allowed_request_origins = [ "https://walltaker.joi.how", /https:\/\/walltaker.joi.how/, /http:\/\/walltaker.joi.how/, "localhost" ] -config.action_cable.disable_request_forgery_protection = true -config.hosts << "localhost" + config.action_cable.allowed_request_origins = [ "https://walltaker.joi.how", /https:\/\/walltaker.joi.how/, /http:\/\/walltaker.joi.how/, "localhost", 'walltaker-7e4cf22c7c3d.herokuapp.com' ] + config.action_cable.disable_request_forgery_protection = true + config.hosts << "localhost" # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. - # config.force_ssl = true + config.force_ssl = true # Include generic and useful information about system operation, but avoid logging too much # information to avoid inadvertent exposure of personally identifiable information (PII). @@ -60,11 +60,11 @@ config.log_tags = [ :request_id ] # Use a different cache store in production. - # config.cache_store = :mem_cache_store + config.cache_store = :redis_cache_store, { url: ENV['REDIS_URL'], ssl_params: { verify_mode: OpenSSL::SSL::VERIFY_NONE } } # Use a real queuing backend for Active Job (and separate queues per environment). # config.active_job.queue_adapter = :resque - # config.active_job.queue_name_prefix = "Walltaker_production" + # config.active_job.queue_name_prefix = "walltaker_production" config.action_mailer.perform_caching = false diff --git a/config/initializers/ahoy.rb b/config/initializers/ahoy.rb index fad5c2c4..120a664c 100644 --- a/config/initializers/ahoy.rb +++ b/config/initializers/ahoy.rb @@ -8,3 +8,7 @@ class Ahoy::Store < Ahoy::DatabaseStore # we recommend configuring local geocoding as well # see https://github.com/ankane/ahoy#geocoding Ahoy.geocode = false + +Ahoy.exclude_method = lambda do |controller, request| + request.path.starts_with?("/api") +end \ No newline at end of file diff --git a/config/initializers/content_security_policy.rb b/config/initializers/content_security_policy.rb index 3621f97f..54f47cf1 100644 --- a/config/initializers/content_security_policy.rb +++ b/config/initializers/content_security_policy.rb @@ -1,8 +1,8 @@ # Be sure to restart your server when you modify this file. -# Define an application-wide content security policy -# For further information see the following documentation -# https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy +# Define an application-wide content security policy. +# See the Securing Rails Applications Guide for more information: +# https://guides.rubyonrails.org/security.html#content-security-policy-header # Rails.application.configure do # config.content_security_policy do |policy| @@ -20,7 +20,6 @@ # config.content_security_policy_nonce_generator = ->(request) { request.session.id.to_s } # config.content_security_policy_nonce_directives = %w(script-src) # -# # Report CSP violations to a specified URI. See: -# # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy-Report-Only +# # Report violations without enforcing the policy. # # config.content_security_policy_report_only = true # end diff --git a/config/initializers/new_framework_defaults_7_0.rb b/config/initializers/new_framework_defaults_7_0.rb new file mode 100644 index 00000000..b13ef5ed --- /dev/null +++ b/config/initializers/new_framework_defaults_7_0.rb @@ -0,0 +1,143 @@ +# Be sure to restart your server when you modify this file. +# +# This file eases your Rails 7.0 framework defaults upgrade. +# +# Uncomment each configuration one by one to switch to the new default. +# Once your application is ready to run with all new defaults, you can remove +# this file and set the `config.load_defaults` to `7.0`. +# +# Read the Guide for Upgrading Ruby on Rails for more info on each option. +# https://guides.rubyonrails.org/upgrading_ruby_on_rails.html + +# `button_to` view helper will render `
    - <%= form_with(model: orgasm, class: 'orgasms') do |form| %> + <%= form_with(model: orgasm, class: 'orgasms orgasms-log') do |form| %>

    Orgasm

    - A logged orgasm is recorded on your public profile, and will benefit your ranking on the browse page! It's also just fun. + An orgasm will be recorded on your public profile for everyone to see! It's fun!

    @@ -22,6 +22,19 @@ <% end %>
    + <% + options = grouped_options_for_select({ + 'Your Pornlizard': [pornlizard.username], + Friends: friends.map { |u| u.username }, + 'Recent Interactions': recent_setters.map { |u| u.username } + }) + %> + +

    Credit a user for causing this orgasm

    + <%= form.label :caused_by, class: 'form__row' do %> + <%= form.select :caused_by, options, include_blank: "No Credit" %> + <% end %> +

    Satisfaction rating for this orgasm

    @@ -61,9 +74,7 @@ <%= form.check_box :is_ruined %> <% end %> -
    - <%= form.submit 'Log Orgasm' %> -
    + <%= form.submit 'Log Orgasm', class: "accent-block__button" %> <% end %>
    <% end %> \ No newline at end of file diff --git a/nuttracker/app/views/nuttracker/orgasms/new.html.erb b/nuttracker/app/views/nuttracker/orgasms/new.html.erb index 163ab973..004d1c9c 100644 --- a/nuttracker/app/views/nuttracker/orgasms/new.html.erb +++ b/nuttracker/app/views/nuttracker/orgasms/new.html.erb @@ -1,6 +1,6 @@

    New orgasm

    -<%= render "form", orgasm: @orgasm %> +<%= render "form", orgasm: @orgasm, pornlizard: @pornlizard, friends: @friends, recent_setters: @recent_setters %>
    diff --git a/public/base.css b/public/base.css index 284fc230..c17e6a80 100644 --- a/public/base.css +++ b/public/base.css @@ -69,12 +69,6 @@ input[type=checkbox]:checked::after { transform: translate(-50%, -50%) rotate(45deg); } -@media (prefers-color-scheme: light) { - :root { - --border: #5b658a; - } -} - h1 > a { text-decoration: none; background: var(--accent); @@ -104,12 +98,85 @@ body { font-size: 0.9rem; } +main { + padding-top: 1rem; +} + body > header { padding-top: 0.25rem; padding-bottom: 0.25rem; + position: relative; +} + + +.mascot { + opacity: 1; +} + +body > header h1 span { + position: relative; +} + +body > header h1 span > .mascot { + position: absolute; + left: 50%; + bottom: -50px; + height: 190px; + transform: translateX(calc(-50% - 149px)); + z-index: 0; + + clip-path: inset(0 0 33px 5px); + transition: all 0.1s ease-out; +} + +@media screen and (max-width: 900px) { + body > header h1 span > .mascot { + left: 50%; + bottom: -50px; + height: 140px; + transform: translateX(calc(-50% - 120px)); + } +} + +@media screen and (max-width: 720px) and (min-width: 475px) { + body > header h1 a { + overflow: hidden; + } + + body > header h1 span { + position: relative; + z-index: 1; + } + + body > header h1 span > .mascot { + height: 165px; + transform: none; + bottom: -80px; + left: -61px; + z-index: -1; + opacity: 0.8; + } +} + +@media screen and (max-width: 475px) { + body > header h1 { + padding-left: 2rem !important; + } + + body > header h1 span > .mascot { + position: absolute; + left: -70px; + bottom: -50px; + transform: none; + height: 100px; + clip-path: inset(0 0 38px 16px); + z-index: -1; + } } header .header__inner { + position: relative; + z-index: 99; display: flex; flex-wrap: wrap; align-items: center; @@ -125,9 +192,18 @@ header .header__age-warning { } header .user-tools { - display: flex; flex: 1 1 320px; +} + +header .user-tools .user-tools__nested { + flex: 0 1 auto; +} + +header .user-tools, header .user-tools .user-tools__nested { + display: flex; justify-content: flex-end; + align-items: center; + flex-wrap: wrap; } header .user-tools > a { @@ -185,6 +261,10 @@ header a ion-icon { font-size: 1em; } +header ion-icon.chat { + color: var(--text); +} + header .notification-bell { color: var(--text); background: none; @@ -208,7 +288,7 @@ h2 { margin-bottom: 1rem; font-size: 2.2rem; font-weight: normal; - margin-top: 3.25rem; + margin-top: 2rem; } h2.one-line { @@ -241,7 +321,7 @@ h2 ion-icon { font-size: 0.8em; } -h2 ion-icon.big { +ion-icon.big { transform: translateY(0.15em); font-size: 1em; } @@ -261,6 +341,7 @@ h4 { h4 + p { margin: 0 auto 2rem auto; max-width: 320px; + text-align: justify; } strong { @@ -275,6 +356,10 @@ strong { margin-top: 5rem; } +blockquote p img { + vertical-align: bottom; +} + blockquote > .byline { text-align: right; padding-right: 3rem; @@ -285,34 +370,38 @@ blockquote > .byline { flex-direction: column; } -.flashes > * { +.flashes .flash { background: var(--accent-bg); padding: 0.5rem 1rem 0.6rem; border: 1px solid var(--border); border-top: 0; } -.flashes > .unbounded { +.flashes .unbounded { border-top: 1px solid var(--border); margin: 0.5rem 0; } -.flashes > .flash--success { +.flashes .flash--success { color: var(--success); } -.flashes > .flash--success::before { +.flashes .flash--success::before { content: "✅" } -.flashes > .flash--danger { +.flashes .flash--danger { color: var(--danger); } -.flashes > .flash--danger::before { +.flashes .flash--danger::before { content: "🚫" } +.flashes > turbo-frame { + display: contents; +} + p { line-height: 1.5rem; } @@ -552,6 +641,16 @@ form.button_to { font-size: 1.2em; } +.friendship__actions { + display: flex; + justify-content: flex-end; + flex-wrap: wrap; + + & > * { + word-break: keep-all; + } +} + label ion-icon { font-size: 1.1rem; transform: translateY(0.1rem); @@ -681,12 +780,59 @@ a.small:hover { position: relative; } +.accent-block > .scare-charm { + position: absolute; + bottom: 10px; + right: 10px; + font-size: 110px; + mix-blend-mode: multiply; + color: #c2c2c2; + opacity: 0.75; +} + +.big-charm { + position: absolute; + bottom: 10px; + right: 10px; + font-size: 110px; + opacity: 0.25; + color: var(--accent); + z-index: -1; +} + +.accent-block--warning { + background-image: linear-gradient(45deg, #ffb300 25%, #d0a536 25%, #d0a536 50%, #ffb300 50%, #ffb300 75%, #d0a536 75%, #d0a536 100%); + background-size: 56.57px 56.57px; + color: #212121; + text-shadow: 0 0 2px #ffb300; + font-weight: bold; +} + +.accent-block .accent-block__button { + width: calc(100% + 3rem) !important; + margin: 0 -1.5rem 0 -1.5rem !important; + transform: translateY(1.5rem); + border-top-right-radius: 0; + border-top-left-radius: 0; +} + +.accent-block .accent-block__button--force-bottom { + position: absolute; + bottom: 0; + left: 0; +} + +.accent-block--warning strong, .accent-block--warning .accent-block__charm { + color: inherit; +} + .accent-block > p { margin: 0; } .accent-block.danger { border: 2px solid var(--danger); + padding-bottom: calc(1.5rem - 2px); } .accent-block.danger > p { @@ -711,6 +857,10 @@ a.small:hover { margin-bottom: 1rem; } +.accent-block.only_charm { + padding: 1.8rem 1.5rem; +} + .accent-block__charm { color: var(--border); display: flex; @@ -836,7 +986,8 @@ footer small a { gap: 0.5rem; } -.modal__buttons > a { +.modal__buttons > a, +.button-like { border: none; border-radius: 5px; background: var(--bg); @@ -849,6 +1000,11 @@ footer small a { color: var(--text-light); } +.button-like { + display: inline-block; + border: 1px solid var(--accent); +} + @media screen and (max-width: 475px) { .modal { background: var(--accent-bg); @@ -891,7 +1047,7 @@ footer small a { background: var(--accent); font-size: 0.9rem; text-align: left; - z-index: 9999; + z-index: 999999999; transform: translate(-0.5rem, 0.75rem); } @@ -953,7 +1109,7 @@ footer small a { display: block !important; } -header turbo-frame { +header .user-tools turbo-frame { display: block; position: relative; font-size: 1.1rem; @@ -1230,6 +1386,10 @@ section .then { transform: translateY(2.5px); } +.one-col { + margin: 1rem 0; +} + .two-col { display: grid; grid-template-columns: repeat(2, 1fr); @@ -1243,3 +1403,124 @@ section .then { gap: 1rem; } } + +.mod_tool__result { + padding: 1rem; + text-align: center; + font-size: 1.2rem; +} + +.mod_tool__result--success::before { + content: ''; + display: block; + height: 2px; + width: 100%; + background: var(--success); + margin-bottom: 1rem; +} + +.mod_tool__result--fail::before { + content: ''; + display: block; + height: 2px; + width: 100%; + background: var(--danger); + margin-bottom: 1rem; +} + +.mod_tool__result--success ion-icon { + color: var(--success); +} + +.mod_tool__result--fail ion-icon { + color: var(--danger); +} + +.float-right { + float: right; +} + +.float-left { + float: left; +} + +.hang { + display: flex; + justify-content: space-between; + flex-direction: row-reverse; + gap: 1rem; + font-size: 0.7rem; +} + +.hang a, .hang > div { + display: flex; + justify-content: flex-end; + align-items: flex-start; + gap: 0.25rem; + text-decoration: none; + cursor: pointer; + color: var(--accent); + border: 1px solid var(--accent); + border-top: none; + border-bottom-left-radius: 4px; + border-bottom-right-radius: 4px; + padding: 0.2rem 0.35rem 0.25rem; +} + +.mascot-picker { + display: flex; + gap: 0.5rem; +} + +.mascot-picker button { + display: flex; + justify-content: center; + align-content: center; + margin: 0; + font-size: 0.8rem; + height: 23px; + align-items: flex-start; + gap: 0.25rem; + text-decoration: none; + border: 1px solid var(--accent); + border-top: none; + border-radius: 0; + border-bottom-left-radius: 4px; + border-bottom-right-radius: 4px; + padding: 0.2rem 0.35rem 0.25rem; + background: none; + color: var(--accent); +} + +.mascot-picker button ion-icon { + font-size: 1rem; +} + +.mascot-picker em { + display: flex; + align-items: center; +} + +pre code.hljs { + background: transparent !important; +} + +.accent-block > pre { + padding: 0; + border: 0; + margin: 0; +} + +.important { + font-size: 1.5em; + text-align: center; + background: var(--accent); + padding: 1rem; + color: var(--bg) !important; + text-shadow: none !important; + + strong { + text-decoration: underline; + } +} + diff --git a/public/dashboard.css b/public/dashboard.css index 8a0a230c..e7341c99 100644 --- a/public/dashboard.css +++ b/public/dashboard.css @@ -156,7 +156,7 @@ } .dashboard__updates__content { - overflow-y: scroll; + overflow-y: auto; flex: 1 1 100%; } @@ -224,3 +224,8 @@ turbo-frame#users_viewing_links { turbo-frame#users_viewing_links strong { color: var(--border); } + +.explainer_graphic { + padding: 2rem 0 0; + color: var(--text-light); +} \ No newline at end of file diff --git a/public/help/header-links-location.png b/public/help/header-links-location.png index 25f44296..fe878c92 100644 Binary files a/public/help/header-links-location.png and b/public/help/header-links-location.png differ diff --git a/public/help/new-link-button-location.png b/public/help/new-link-button-location.png index 146b5718..24a615df 100644 Binary files a/public/help/new-link-button-location.png and b/public/help/new-link-button-location.png differ diff --git a/public/help/new-link-form.png b/public/help/new-link-form.png index efe8df62..e70289d8 100644 Binary files a/public/help/new-link-form.png and b/public/help/new-link-form.png differ diff --git a/public/help/own-link-copy-url.png b/public/help/own-link-copy-url.png index 8d82468a..1fe8f4fa 100644 Binary files a/public/help/own-link-copy-url.png and b/public/help/own-link-copy-url.png differ diff --git a/public/help/own-link-id-location.png b/public/help/own-link-id-location.png index 33131ca3..47204508 100644 Binary files a/public/help/own-link-id-location.png and b/public/help/own-link-id-location.png differ diff --git a/public/help/own-link-location.png b/public/help/own-link-location.png index 6ea83b8f..4170d050 100644 Binary files a/public/help/own-link-location.png and b/public/help/own-link-location.png differ diff --git a/public/help/own-link-online.png b/public/help/own-link-online.png index f7557d16..98683082 100644 Binary files a/public/help/own-link-online.png and b/public/help/own-link-online.png differ diff --git a/public/link.css b/public/link.css index 3940e217..32cf62df 100644 --- a/public/link.css +++ b/public/link.css @@ -1,3 +1,80 @@ +#new-user-links { + margin-bottom: 1rem; +} + +strong.search-heading { + margin-top: 1.5rem; + display: block; + text-align: center; +} + +.browse-links__subheading { + display: flex; + gap: 1rem; + align-items: center; + margin-bottom: 1rem; +} + +.browse-links__subheading h3 { + margin: 0; + color: var(--text-light); + font-size: 0.9rem; + padding: 0.8rem 1rem; + background: var(--disabled); + display: inline-block; + border-radius: 4px; +} + +.browse-links__subheading p { + margin: 0; + flex: 1 1 100px; + color: var(--text-light); + font-size: 0.75rem; +} + +.woogle { + text-align: center; + font-size: 5rem; +} + +.link-search { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 1rem; + position: relative; + min-height: 40px; + justify-content: flex-end; +} + +.link-search label { + display: flex; + gap: 0.5rem; + cursor: pointer; + align-items: center; + margin: 0; +} + +.link-search label input[type=checkbox] { + margin: 0; + transform: translateY(-0.1em); +} + +.link-search input[type=text] { + flex: 1 1 500px; +} + +.link-search input[type=submit] { + flex: 1 1 100px; + padding: 0.5rem; +} + +.link-search input[type=text], +.link-search input[type=submit] { + margin: 0; + height: 100%; +} + .link { display: grid; position: relative; @@ -14,6 +91,11 @@ 'terms blacklist' 'friends-only friends-only'; overflow: hidden; + /* Extreme improvement to scrolling performance in firefox, minor improvement in chrome, + caused by excessive, expensive re-rendering of filtered images combined with blending modes. + this style forces them into their own GPU surfaces, preventing the re-rendering at the cost of + blending accuracy */ + will-change: transform; } .text-light { @@ -23,7 +105,8 @@ /* browse page links list */ @media screen and (min-width: 1220px) { - #links.browse-links { + #links.browse-links, + #new-user-links.browse-links { width: 100vw; position: relative; left: 50%; @@ -34,18 +117,21 @@ column-count: 2; } - #links.browse-links > * { + #links.browse-links > *, + #new-user-links.browse-links > * { page-break-inside: avoid; break-inside: avoid-column; } - #links.browse-links > *:first-child .link { + #links.browse-links > *:first-child .link, + #new-user-links.browse-links > *:first-child .link { margin-top: 0; } } @media screen and (min-width: 1620px) { - #links.browse-links { + #links.browse-links, + #new-user-links.browse-links { column-count: 3; } } @@ -73,6 +159,11 @@ justify-content: flex-end; } + .link--abilities .mascot { + transform: translateY(2px); + padding-right: 3px; + } + .link--theme > strong, .link--abilities > strong { display: inline-flex !important; @@ -114,16 +205,25 @@ } @media screen and (prefers-color-scheme: light) { - .link > .link--backdrop::before { + :root:not(.force-dark) .link > .link--backdrop::before { mix-blend-mode: color; filter: brightness(1); } - .link > .link--backdrop > img { + :root:not(.force-dark) .link > .link--backdrop > img { filter: contrast(0.55) brightness(1.4); } } +:root.force-light .link > .link--backdrop::before { + mix-blend-mode: color; + filter: brightness(1); +} + +:root.force-light .link > .link--backdrop > img { + filter: contrast(0.55) brightness(1.4); +} + .link em { color: var(--border); } @@ -150,15 +250,21 @@ a:hover > .link { z-index: -1; mix-blend-mode: color-dodge; opacity: 0.3; + filter: contrast(150%) saturate(200%); } @media (prefers-color-scheme: light) { - .link::after { + :root:not(.force-dark) .link::after { mix-blend-mode: darken; opacity: 0.2; } } +:root.force-light .link::after { + mix-blend-mode: darken; + opacity: 0.2; +} + .link--presence { grid-area: presence; text-align: right; @@ -271,7 +377,7 @@ h4.forks-header > ion-icon { } .link--terms blockquote { - overflow-y: scroll; + overflow-y: auto; margin: 0 0 0 4px; position: absolute; top: 0; @@ -556,7 +662,8 @@ turbo-frame#pornsearch_results .results { margin: 2rem 0; } -turbo-frame#pornsearch_results .pornsearch_results__buttons { +turbo-frame#pornsearch_results .pornsearch_results__buttons, +.linksearch_results__buttons { display: flex; gap: 0.5rem; justify-content: space-between; @@ -566,13 +673,15 @@ turbo-frame#pornsearch_results .pornsearch_results__buttons { border-radius: 0.3rem; overflow-x: hidden; } -turbo-frame#pornsearch_results .pornsearch_results__buttons a { +turbo-frame#pornsearch_results .pornsearch_results__buttons a, +.linksearch_results__buttons a { padding: 0.5rem 1rem 0.5rem; background: var(--accent); color: var(--bg); } -turbo-frame#pornsearch_results .pornsearch_results__buttons a[disabled] { +turbo-frame#pornsearch_results .pornsearch_results__buttons a[disabled], +.linksearch_results__buttons a[disabled] { pointer-events: none; background: var(--border); color: var(--disabled); @@ -751,7 +860,7 @@ figure > a.background-sample > ion-icon.background-sample__icon--mobile { figcaption.link--description { max-height: 110px; - overflow: scroll; + overflow: auto; padding: 1rem 0.5rem; } @@ -835,7 +944,7 @@ aside.comments-sidebar { position: fixed; top: 0; right: 0; - height: 100vh; + height: 100dvh; background: var(--bg); border-left: 1px solid var(--border); padding: 0 1rem 1rem; @@ -944,7 +1053,7 @@ aside.comments-sidebar > button .toggle-badge[data-hidden=true] { position: absolute; top: 0; left: 0; - z-index: 999; + z-index: 90; } .link--anchor-shade-container { @@ -969,9 +1078,33 @@ aside.comments-sidebar > button .toggle-badge[data-hidden=true] { .abilities__ability { display: flex; - gap: 1rem; + gap: 0.4rem 1rem; +} + +.abilities__ability--disabled { + pointer-events: none; + position: relative; + flex-wrap: wrap; + background: var(--accent-bg); + outline: var(--accent-bg) 12px solid; + margin-top: calc(1rem + 10px) !important; } +.abilities__ability--disabled::before { + content: 'Coming Soon'; + display: block; + padding: 0.2rem 10px; + margin: -10px -10px 0.3rem -10px; + position: relative; + z-index: 99; + flex: 1 1 100%; + font-weight: bold; + background-image: linear-gradient(45deg, #ffb300 25%, #ab8118 25%, #ab8118 50%, #ffb300 50%, #ffb300 75%, #ab8118 75%, #ab8118 100%); + background-size: 56.57px 56.57px; + color: var(--bg); +} + +.abilities__ability--disabled button, .abilities__ability__controls button.enabled { background: var(--bg); color: var(--accent); @@ -1000,6 +1133,11 @@ aside.comments-sidebar > button .toggle-badge[data-hidden=true] { margin-right: 0.3rem; } +.abilities__ability__title .mascot { + transform: translateY(4px); + margin-right: 4px; +} + .video-icon { transform: translateY(2px); margin: 0 0.1rem 0 0.2rem; diff --git a/public/simple.min.css b/public/simple.min.css index 8c3c556c..b3b36734 100644 --- a/public/simple.min.css +++ b/public/simple.min.css @@ -1 +1,452 @@ -:root{--sans-font:-apple-system,BlinkMacSystemFont,"Avenir Next",Avenir,"Nimbus Sans L",Roboto,Noto,"Segoe UI",Arial,Helvetica,"Helvetica Neue",sans-serif;--mono-font:Consolas,Menlo,Monaco,"Andale Mono","Ubuntu Mono",monospace;--bg:#fff;--accent-bg:#f5f7ff;--text:#212121;--text-light:#585858;--border:#d8dae1;--accent:#0d47a1;--code:#d81b60;--preformatted:#444;--marked:#ffdd33;--disabled:#efefef}@media (prefers-color-scheme:dark){:root{--bg:#212121;--accent-bg:#2b2b2b;--text:#dcdcdc;--text-light:#ababab;--border:#666;--accent:#ffb300;--code:#f06292;--preformatted:#ccc;--disabled:#111}img,video{opacity:.8}}html{font-family:var(--sans-font);scroll-behavior:smooth}body{color:var(--text);background:var(--bg);font-size:1.15rem;line-height:1.5;display:grid;grid-template-columns:1fr min(45rem,90%) 1fr;margin:0}body>*{grid-column:2}body>header{background:var(--accent-bg);border-bottom:1px solid var(--border);text-align:center;padding:0 .5rem 2rem .5rem;grid-column:1/-1;box-sizing:border-box}body>header h1{max-width:1200px;margin:1rem auto}body>header p{max-width:40rem;margin:1rem auto}main{padding-top:1.5rem}body>footer{margin-top:4rem;padding:2rem 1rem 1.5rem 1rem;color:var(--text-light);font-size:.9rem;text-align:center;border-top:1px solid var(--border)}h1{font-size:3rem}h2{font-size:2.6rem;margin-top:3rem}h3{font-size:2rem;margin-top:3rem}h4{font-size:1.44rem}h5{font-size:1.15rem}h6{font-size:.96rem}h1,h2,h3{line-height:1.1}@media only screen and (max-width:720px){h1{font-size:2.5rem}h2{font-size:2.1rem}h3{font-size:1.75rem}h4{font-size:1.25rem}}a,a:visited{color:var(--accent)}a:hover{text-decoration:none}[role=button],button,input[type=button],input[type=reset],input[type=submit]{border:none;border-radius:5px;background:var(--accent);font-size:1rem;color:var(--bg);padding:.7rem .9rem;margin:.5rem 0}[role=button][aria-disabled=true],button[disabled],input[type=button][disabled],input[type=checkbox][disabled],input[type=radio][disabled],input[type=reset][disabled],input[type=submit][disabled],select[disabled]{opacity:.5;cursor:not-allowed}input:disabled,select:disabled,textarea:disabled{cursor:not-allowed;background-color:var(--disabled)}input[type=range]{padding:0}abbr{cursor:help}[role=button]:focus,[role=button]:not([aria-disabled=true]):hover,button:enabled:hover,button:focus,input[type=button]:enabled:hover,input[type=button]:focus,input[type=reset]:enabled:hover,input[type=reset]:focus,input[type=submit]:enabled:hover,input[type=submit]:focus{filter:brightness(1.4);cursor:pointer}nav{font-size:1rem;line-height:2;padding:1rem 0 0 0}nav ol,nav ul{align-content:space-around;align-items:center;display:flex;flex-direction:row;justify-content:center;list-style-type:none;margin:0;padding:0}nav ol li,nav ul li{display:inline-block}nav a,nav a:visited{margin:0 1rem 1rem 0;border:1px solid var(--border);border-radius:5px;color:var(--text);display:inline-block;padding:.1rem 1rem;text-decoration:none}nav a:hover{color:var(--accent);border-color:var(--accent)}nav a:last-child{margin-right:0}@media only screen and (max-width:720px){nav a{border:none;padding:0;color:var(--accent);text-decoration:underline;line-height:1}}details{background:var(--accent-bg);border:1px solid var(--border);border-radius:5px;margin-bottom:1rem}summary{cursor:pointer;font-weight:700;padding:.6rem 1rem}details[open]{padding:.6rem 1rem .75rem 1rem}details[open] summary+*{margin-top:0}details[open] summary{margin-bottom:.5rem;padding:0}details[open]>:last-child{margin-bottom:0}table{border-collapse:collapse;width:100%;margin:1.5rem 0}td,th{border:1px solid var(--border);text-align:left;padding:.5rem}th{background:var(--accent-bg);font-weight:700}tr:nth-child(even){background:var(--accent-bg)}table caption{font-weight:700;margin-bottom:.5rem}input,select,textarea{font-size:inherit;font-family:inherit;padding:.5rem;margin-bottom:.5rem;color:var(--text);background:var(--bg);border:1px solid var(--border);border-radius:5px;box-shadow:none;box-sizing:border-box;width:60%;-moz-appearance:none;-webkit-appearance:none;appearance:none}select{background-image:linear-gradient(45deg,transparent 49%,var(--text) 51%),linear-gradient(135deg,var(--text) 51%,transparent 49%);background-position:calc(100% - 20px),calc(100% - 15px);background-size:5px 5px,5px 5px;background-repeat:no-repeat}select[multiple]{background-image:none!important}input[type=checkbox],input[type=radio]{vertical-align:bottom;position:relative}input[type=radio]{border-radius:100%}input[type=checkbox]:checked,input[type=radio]:checked{background:var(--accent)}input[type=checkbox]:checked::after{content:" ";width:.1em;height:.25em;border-radius:0;position:absolute;top:.05em;left:.18em;background:0 0;border-right:solid var(--bg) .08em;border-bottom:solid var(--bg) .08em;font-size:1.8em;transform:rotate(45deg)}input[type=radio]:checked::after{content:" ";width:.25em;height:.25em;border-radius:100%;position:absolute;top:.125em;background:var(--bg);left:.125em;font-size:32px}textarea{width:80%}@media only screen and (max-width:720px){input,select,textarea{width:100%}}input[type=checkbox],input[type=radio]{width:auto}input[type=file]{border:0}hr{color:var(--border);border-top:1px;margin:1rem auto}mark{padding:2px 5px;border-radius:4px;background:var(--marked)}main img,main video{max-width:100%;height:auto;border-radius:5px}figure{margin:0;text-align:center}figcaption{font-size:.9rem;color:var(--text-light);margin-bottom:1rem}blockquote{margin:2rem 0 2rem 2rem;padding:.4rem .8rem;border-left:.35rem solid var(--accent);color:var(--text-light);font-style:italic}cite{font-size:.9rem;color:var(--text-light);font-style:normal}code,kbd,pre,pre span,samp{font-family:var(--mono-font);color:var(--code)}kbd{color:var(--preformatted);border:1px solid var(--preformatted);border-bottom:3px solid var(--preformatted);border-radius:5px;padding:.1rem .4rem}pre{padding:1rem 1.4rem;max-width:100%;overflow:auto;color:var(--preformatted);background:var(--accent-bg);border:1px solid var(--border);border-radius:5px}pre code{color:var(--preformatted);background:0 0;margin:0;padding:0} \ No newline at end of file +:root { + --sans-font: -apple-system, BlinkMacSystemFont, "Avenir Next", Avenir, "Nimbus Sans L", Roboto, Noto, "Segoe UI", Arial, Helvetica, "Helvetica Neue", sans-serif; + --mono-font: Consolas, Menlo, Monaco, "Andale Mono", "Ubuntu Mono", monospace; +} + +:root:not(.force-dark) { + --bg: #fff; + --accent-bg: #f5f7ff; + --text: #212121; + --text-light: #585858; + --border: #5b658a; + --accent: #0d47a1; + --code: #d81b60; + --preformatted: #444; + --marked: #ffdd33; + --disabled: #efefef +} + +:root.force-dark { + --bg: #212121; + --accent-bg: #2b2b2b; + --text: #dcdcdc; + --text-light: #ababab; + --border: #666; + --accent: #ffb300; + --code: #f06292; + --preformatted: #ccc; + --disabled: #111 +} + +@media (prefers-color-scheme: dark) { + :root:not(.force-light) { + --bg: #212121; + --accent-bg: #2b2b2b; + --text: #dcdcdc; + --text-light: #ababab; + --border: #666; + --accent: #ffb300; + --code: #f06292; + --preformatted: #ccc; + --disabled: #111 + } + + img, video { + opacity: .8 + } +} + +html { + font-family: var(--sans-font); + scroll-behavior: smooth +} + +body { + color: var(--text); + background: var(--bg); + font-size: 1.15rem; + line-height: 1.5; + display: grid; + grid-template-columns:1fr min(45rem, 90%) 1fr; + margin: 0 +} + +body > * { + grid-column: 2 +} + +body > header { + background: var(--accent-bg); + border-bottom: 1px solid var(--border); + text-align: center; + padding: 0 .5rem 2rem .5rem; + grid-column: 1/-1; + box-sizing: border-box +} + +body > header h1 { + max-width: 1200px; + margin: 1rem auto +} + +body > header p { + max-width: 40rem; + margin: 1rem auto +} + +main { + padding-top: 1.5rem +} + +body > footer { + margin-top: 4rem; + padding: 2rem 1rem 1.5rem 1rem; + color: var(--text-light); + font-size: .9rem; + text-align: center; + border-top: 1px solid var(--border) +} + +h1 { + font-size: 3rem +} + +h2 { + font-size: 2.6rem; + margin-top: 3rem +} + +h3 { + font-size: 2rem; + margin-top: 3rem +} + +h4 { + font-size: 1.44rem +} + +h5 { + font-size: 1.15rem +} + +h6 { + font-size: .96rem +} + +h1, h2, h3 { + line-height: 1.1 +} + +@media only screen and (max-width: 720px) { + h1 { + font-size: 2.5rem + } + + h2 { + font-size: 2.1rem + } + + h3 { + font-size: 1.75rem + } + + h4 { + font-size: 1.25rem + } +} + +a, a:visited { + color: var(--accent) +} + +a:hover { + text-decoration: none +} + +[role=button], button, input[type=button], input[type=reset], input[type=submit] { + border: none; + border-radius: 5px; + background: var(--accent); + font-size: 1rem; + color: var(--bg); + padding: .7rem .9rem; + margin: .5rem 0 +} + +[role=button][aria-disabled=true], button[disabled], input[type=button][disabled], input[type=checkbox][disabled], input[type=radio][disabled], input[type=reset][disabled], input[type=submit][disabled], select[disabled] { + opacity: .5; + cursor: not-allowed +} + +input:disabled, select:disabled, textarea:disabled { + cursor: not-allowed; + background-color: var(--disabled) +} + +input[type=range] { + padding: 0 +} + +abbr { + cursor: help +} + +[role=button]:focus, [role=button]:not([aria-disabled=true]):hover, button:enabled:hover, button:focus, input[type=button]:enabled:hover, input[type=button]:focus, input[type=reset]:enabled:hover, input[type=reset]:focus, input[type=submit]:enabled:hover, input[type=submit]:focus { + filter: brightness(1.4); + cursor: pointer +} + +nav { + font-size: 1rem; + line-height: 2; + padding: 1rem 0 0 0 +} + +nav ol, nav ul { + align-content: space-around; + align-items: center; + display: flex; + flex-direction: row; + justify-content: center; + list-style-type: none; + margin: 0; + padding: 0 +} + +nav ol li, nav ul li { + display: inline-block +} + +nav a, nav a:visited { + margin: 0 1rem 1rem 0; + border: 1px solid var(--border); + border-radius: 5px; + color: var(--text); + display: inline-block; + padding: .1rem 1rem; + text-decoration: none +} + +nav a:hover { + color: var(--accent); + border-color: var(--accent) +} + +nav a:last-child { + margin-right: 0 +} + +@media only screen and (max-width: 720px) { + nav a { + border: none; + padding: 0; + color: var(--accent); + text-decoration: underline; + line-height: 1 + } +} + +details { + background: var(--accent-bg); + border: 1px solid var(--border); + border-radius: 5px; + margin-bottom: 1rem +} + +summary { + cursor: pointer; + font-weight: 700; + padding: .6rem 1rem +} + +details[open] { + padding: .6rem 1rem .75rem 1rem +} + +details[open] summary + * { + margin-top: 0 +} + +details[open] summary { + margin-bottom: .5rem; + padding: 0 +} + +details[open] > :last-child { + margin-bottom: 0 +} + +table { + border-collapse: collapse; + width: 100%; + margin: 1.5rem 0 +} + +td, th { + border: 1px solid var(--border); + text-align: left; + padding: .5rem +} + +th { + background: var(--accent-bg); + font-weight: 700 +} + +tr:nth-child(even) { + background: var(--accent-bg) +} + +table caption { + font-weight: 700; + margin-bottom: .5rem +} + +input, select, textarea { + font-size: inherit; + font-family: inherit; + padding: .5rem; + margin-bottom: .5rem; + color: var(--text); + background: var(--bg); + border: 1px solid var(--border); + border-radius: 5px; + box-shadow: none; + box-sizing: border-box; + width: 60%; + -moz-appearance: none; + -webkit-appearance: none; + appearance: none +} + +select { + background-image: linear-gradient(45deg, transparent 49%, var(--text) 51%), linear-gradient(135deg, var(--text) 51%, transparent 49%); + background-position: calc(100% - 20px), calc(100% - 15px); + background-size: 5px 5px, 5px 5px; + background-repeat: no-repeat +} + +select[multiple] { + background-image: none !important +} + +input[type=checkbox], input[type=radio] { + vertical-align: bottom; + position: relative +} + +input[type=radio] { + border-radius: 100% +} + +input[type=checkbox]:checked, input[type=radio]:checked { + background: var(--accent) +} + +input[type=checkbox]:checked::after { + content: " "; + width: .1em; + height: .25em; + border-radius: 0; + position: absolute; + top: .05em; + left: .18em; + background: 0 0; + border-right: solid var(--bg) .08em; + border-bottom: solid var(--bg) .08em; + font-size: 1.8em; + transform: rotate(45deg) +} + +input[type=radio]:checked::after { + content: " "; + width: .25em; + height: .25em; + border-radius: 100%; + position: absolute; + top: .125em; + background: var(--bg); + left: .125em; + font-size: 32px +} + +textarea { + width: 80% +} + +@media only screen and (max-width: 720px) { + input, select, textarea { + width: 100% + } +} + +input[type=checkbox], input[type=radio] { + width: auto +} + +input[type=file] { + border: 0 +} + +hr { + color: var(--border); + border-top: 1px; + margin: 1rem auto +} + +mark { + padding: 2px 5px; + border-radius: 4px; + background: var(--marked) +} + +main img, main video { + max-width: 100%; + height: auto; + border-radius: 5px +} + +figure { + margin: 0; + text-align: center +} + +figcaption { + font-size: .9rem; + color: var(--text-light); + margin-bottom: 1rem +} + +blockquote { + margin: 2rem 0 2rem 2rem; + padding: .4rem .8rem; + border-left: .35rem solid var(--accent); + color: var(--text-light); + font-style: italic +} + +cite { + font-size: .9rem; + color: var(--text-light); + font-style: normal +} + +code, kbd, pre, pre span, samp { + font-family: var(--mono-font); + color: var(--code) +} + +kbd { + color: var(--preformatted); + border: 1px solid var(--preformatted); + border-bottom: 3px solid var(--preformatted); + border-radius: 5px; + padding: .1rem .4rem +} + +pre { + padding: 1rem 1.4rem; + max-width: 100%; + overflow: auto; + color: var(--preformatted); + background: var(--accent-bg); + border: 1px solid var(--border); + border-radius: 5px +} + +pre code { + color: var(--preformatted); + background: 0 0; + margin: 0; + padding: 0 +} \ No newline at end of file diff --git a/public/user-profile.css b/public/user-profile.css index 86d2ad5d..6befb1c5 100644 --- a/public/user-profile.css +++ b/public/user-profile.css @@ -59,7 +59,7 @@ h2 > span > .text-charm__medal:hover { .details__container blockquote > h3, .details__container blockquote > h4, .details__container blockquote > h5, -.details__container blockquote > h6{ +.details__container blockquote > h6 { margin-top: 0; } @@ -70,12 +70,26 @@ h2 > span > .text-charm__medal:hover { text-decoration: none; } +#api_key_form, +.orgasms-log { + display: flex; + flex-direction: column; + justify-content: space-between; +} + .api-key { display: flex; flex-direction: column; + justify-content: space-between; align-items: center; } +.api-key form.button_to { + position: relative; + width: 100%; + display: block; +} + .api-key__key { font-size: 2.75rem; font-weight: bold; @@ -90,9 +104,9 @@ h2 > span > .text-charm__medal:hover { margin: 0.5rem 0 1rem; } -.api-key .danger-button { - background: var(--danger); - color: #FFF; +.danger-button { + background: var(--danger) !important; + color: #FFF !important; } @media screen and (max-width: 475px) { @@ -133,6 +147,10 @@ form.orgasms input[type=submit] { display: block; } +.orgasms__credit_to_user { + width: 100%; +} + .orgasms__rating { display: grid; grid-template-columns: repeat(5, 1fr); @@ -147,6 +165,7 @@ form.orgasms input[type=submit] { color: var(--accent); font-weight: bold; } + .orgasms__rating .orgasms__rating__option label { margin-bottom: 0.2rem; } @@ -155,6 +174,18 @@ form.orgasms .form__row { justify-content: center; } +.form__group { + display: flex; + gap: 1rem; + flex-wrap: wrap; + justify-content: center; +} + +.form__group input[type=radio], +.form__group input[type=checkbox] { + margin: 0; +} + .form__row { display: flex; gap: 0.5rem; @@ -163,6 +194,10 @@ form.orgasms .form__row { cursor: pointer; } +.form__row--stacking { + flex-wrap: wrap; +} + .form__row input.full-width { width: 100%; margin-bottom: 0; @@ -182,4 +217,167 @@ h2.with-chart { h2.with-chart #chart-1 { transform: translateY(7px); +} + +.kink_tools { + font-size: 0.9rem; + margin-top: 1rem; +} + +#kink_tools { + margin-top: 1rem; + margin-bottom: 1rem; +} + +.kink_form { + gap: 0 !important; +} + +.kink_form input[type=text] { + margin: 1px 0 1px 1px; + width: 100px; +} + +.kinks { + display: flex; + flex-wrap: wrap; + gap: 0.5rem; +} + +.kinks > turbo-frame { + display: contents; +} + +.kink, +.kink_form { + display: flex; + align-items: center; + border-radius: 6px; + min-height: 30px; +} + +.kink.valid, +.kink_form { + background: var(--accent); + color: var(--bg); +} + +.kink.untested { + background: var(--accent-bg); + color: var(--accent); +} + +.kink > a { + color: inherit; + display: block; + padding-left: 0.5rem; + padding-right: 0.5rem; +} + +.kink > form { + display: contents; +} + +.kink.untested button { + border: none; +} + +.kink button { + background: var(--accent-bg); + color: var(--accent); + border: var(--accent) 2px solid; +} + +.kink button:hover { + filter: none !important; + background: var(--danger); + color: var(--danger-text); +} + +.kink button, +.kink_form input[type=submit] { + margin: 0; + padding: 0 0.5rem; + height: 100%; + font-size: 1.2rem; + width: 39px !important; + height: 38px; +} + +.kink__status { + display: contents; +} + +.kink ion-icon.star { + margin-left: 0.5rem; +} + +.user_segments { + display: flex; + flex-direction: column; + gap: 1rem; +} + +.user_segment { + display: block; + padding: 0.75rem 1rem 1rem; + border: 1px solid var(--border); + border-radius: 6px; + gap: 0.75rem; + position: relative; + overflow: hidden; +} + +.user_segment .wallpapers { + position: absolute; + width: 100%; + top: 0; + left: 0; + z-index: -1; + animation: 120s ease-out infinite scroll-wallpapers; + + column-count: 3; + column-gap: 0.25rem; +} + +@keyframes scroll-wallpapers { + 0% { + opacity: 0; + transform: translateY(4%); + } + + 1% { + opacity: 1; + transform: translateY(0); + } + + 95% { + opacity: 1; + transform: translateY(-90%); + } + + 100% { + opacity: 0; + transform: translateY(-120%); + } +} + +.user_segment .wallpapers img { + opacity: 0.2; + position: relative; + user-select: none; +} + +.user_segment h3 { + margin: 0; +} + +.user_segment .user-segement-tools { + display: flex; + justify-content: space-between; + align-items: center; +} + +.user_segment h3 .text-charm { + transform: translateY(-10px); } \ No newline at end of file diff --git a/public/wizard.css b/public/wizard.css new file mode 100644 index 00000000..12f383df --- /dev/null +++ b/public/wizard.css @@ -0,0 +1,240 @@ +body { + max-width: 100vw; + overflow-x: hidden; +} + +h2.one-line { + display: flex; + justify-content: space-between; +} + +@keyframes sparkle { + 0% { + transform: translate(-5px, 2px) rotate(1deg); + } + 20% { + transform: translate(5px, -4px) rotate(-1deg); + } + 40% { + transform: translate(-7px, 6px) rotate(-2deg); + } + 60% { + transform: translate(5px, -6px) rotate(3deg); + } + 80% { + transform: translate(-8px, -5px) rotate(-2deg); + } + 100% { + transform: translate(5px, -4px) rotate(-3deg); + } +} + +@keyframes enter { + 0% { + transform: scale(2.2) translateY(570px); + opacity: 0; + } + 100% { + transform: scale(0.7) translateY(0); + opacity: 1; + } +} + +@keyframes slideup { + 0% { + transform: scale(0.5) translateY(170px); + opacity: 0; + } + 100% { + transform: scale(1) translateY(0); + opacity: 1; + } +} + +.link-preview { + padding: 0.5rem 0 2rem 0; + opacity: 0; + animation: 2s 3s cubic-bezier(0.34, 1.56, 0.64, 1) forwards enter; +} + +.link-preview--static, .link-preview--static > *, .link-preview--done, .link-preview--done > * { + opacity: 1; + transform: none; + animation: none !important; +} + +.link-preview--static > * { + transform: perspective(800px) scale(0.9) rotate3d(0.2, 0, 0, 45deg) !important; +} + +.link-preview--static .link--blacklist { + max-height: 120px; + overflow-y: auto; +} + +.link-preview--done { + position: relative; +} + +.link-preview--done::before, .link-preview--done::after { + content: '✨'; + display: block; + font-size: 9rem; + position: absolute; + mix-blend-mode: difference; +} + +.link-preview--done::before { + top: -20px; + right: -20px; + animation: 1s cubic-bezier(0.455, 0.030, 0.515, 0.955) infinite alternate-reverse sparkle; +} + +.link-preview--done::after { + bottom: -20px; + left: -20px; + animation: 1s cubic-bezier(0.455, 0.030, 0.515, 0.955) infinite alternate sparkle; +} + +.link-preview > * { + animation: 2s cubic-bezier(0.455, 0.030, 0.515, 0.955) infinite alternate sparkle; + position: relative; +} + +.link-preview > *::after { + content: ''; + position: absolute; + inset: 0; + z-index: -1; + box-shadow: 0 0 45px var(--text); + border-radius: 3px; + opacity: 0.3; +} + +.intro { + display: flex; + justify-content: center; + align-items: center; + flex-direction: column; +} + +.thought { + display: flex; + background-color: var(--text); + padding: 20px; + border-radius: 30px; + min-width: 40px; + max-width: 220px; + min-height: 40px; + margin: 20px; + position: relative; + align-items: center; + justify-content: center; + text-align: center; + color: var(--bg); + opacity: 0; + + animation: 2s 1.5s cubic-bezier(0.34, 1.56, 0.64, 1) forwards slideup; +} + +.thought--small { + max-width: 190px; +} +.thought--large { + max-width: 320px; +} + +.thought:before, +.thought:after { + content: ""; + background-color: var(--text); + border-radius: 50%; + display: block; + position: absolute; + z-index: -1; +} + +.thought:before { + width: 44px; + height: 44px; + top: -12px; + left: 28px; + box-shadow: -50px 30px 0 -12px var(--text); +} + +.thought:after { + bottom: -10px; + right: 26px; + width: 30px; + height: 30px; + box-shadow: 40px -34px 0 0 var(--text), + -28px -6px 0 -2px var(--text), + -24px 17px 0 -6px var(--text), + -5px 25px 0 -10px var(--text); +} + +.choice { + display: flex; + gap: 0.25rem; + flex-direction: column; + align-items: center; +} + +.choice h3 { + color: var(--accent); +} + +.choice .form__row { + max-width: 320px; + align-self: center; + margin: 6px 0; +} + +.choice > .accent-block__charm { + align-self: flex-start; +} + +.choice form.full-width { + width: 100%; + display: contents; +} + +.choice form textarea { + width: 100%; + height: 25ex; +} + +.guide__container { + position: relative; + padding-top: 130px; +} + +.guide { + position: absolute; + z-index: 9999; + display: flex; + flex-direction: column; + align-items: flex-end; +} + +.guide--theme { top: -30px } +.guide--blacklist { bottom: 65px; right: 90px } +.guide--centre { top: calc(50% - 40px); left: 50%; transform: translate(-50%, -50%) } + +main > blockquote { + display: flex; + gap: 1rem; + border-left: none; + margin: 0.25rem 0.5rem; + color: var(--text); + font-style: normal; +} + +main > blockquote.right { + text-align: right; +} + +main > blockquote > p, span { + flex: 1 1 100%; + display: block; +} diff --git a/test/controllers/errors_controller_test.rb b/test/controllers/errors_controller_test.rb new file mode 100644 index 00000000..747dc898 --- /dev/null +++ b/test/controllers/errors_controller_test.rb @@ -0,0 +1,13 @@ +require "test_helper" + +class ErrorsControllerTest < ActionDispatch::IntegrationTest + test "should get not_found" do + get errors_not_found_url + assert_response :success + end + + test "should get server_error" do + get errors_server_error_url + assert_response :success + end +end diff --git a/test/controllers/kink_controller_test.rb b/test/controllers/kink_controller_test.rb new file mode 100644 index 00000000..5658fdd9 --- /dev/null +++ b/test/controllers/kink_controller_test.rb @@ -0,0 +1,18 @@ +require "test_helper" + +class KinkControllerTest < ActionDispatch::IntegrationTest + test "should get show" do + get kink_show_url + assert_response :success + end + + test "should get add" do + get kink_add_url + assert_response :success + end + + test "should get remove" do + get kink_remove_url + assert_response :success + end +end diff --git a/test/controllers/link_wizard_controller_test.rb b/test/controllers/link_wizard_controller_test.rb new file mode 100644 index 00000000..38007575 --- /dev/null +++ b/test/controllers/link_wizard_controller_test.rb @@ -0,0 +1,7 @@ +require "test_helper" + +class LinkWizardControllerTest < ActionDispatch::IntegrationTest + # test "the truth" do + # assert true + # end +end diff --git a/test/controllers/lizard_tools_controller_test.rb b/test/controllers/lizard_tools_controller_test.rb new file mode 100644 index 00000000..64b4b30b --- /dev/null +++ b/test/controllers/lizard_tools_controller_test.rb @@ -0,0 +1,23 @@ +require "test_helper" + +class LizardToolsControllerTest < ActionDispatch::IntegrationTest + test "should get index" do + get lizard_tools_index_url + assert_response :success + end + + test "should get warren" do + get lizard_tools_warren_url + assert_response :success + end + + test "should get ki" do + get lizard_tools_ki_url + assert_response :success + end + + test "should get talyor" do + get lizard_tools_talyor_url + assert_response :success + end +end diff --git a/test/controllers/mod_tools_controller_test.rb b/test/controllers/mod_tools_controller_test.rb new file mode 100644 index 00000000..0cd4d18b --- /dev/null +++ b/test/controllers/mod_tools_controller_test.rb @@ -0,0 +1,28 @@ +require "test_helper" + +class ModToolsControllerTest < ActionDispatch::IntegrationTest + test "should get index" do + get mod_tools_index_url + assert_response :success + end + + test "should get show_password_reset" do + get mod_tools_show_password_reset_url + assert_response :success + end + + test "should get update_password_reset" do + get mod_tools_update_password_reset_url + assert_response :success + end + + test "should get show_user" do + get mod_tools_show_user_url + assert_response :success + end + + test "should get destroy_user" do + get mod_tools_destroy_user_url + assert_response :success + end +end diff --git a/test/controllers/search_controller_test.rb b/test/controllers/search_controller_test.rb new file mode 100644 index 00000000..d614df96 --- /dev/null +++ b/test/controllers/search_controller_test.rb @@ -0,0 +1,13 @@ +require "test_helper" + +class SearchControllerTest < ActionDispatch::IntegrationTest + test "should get index" do + get search_index_url + assert_response :success + end + + test "should get results" do + get search_results_url + assert_response :success + end +end diff --git a/test/controllers/settings_controller_test.rb b/test/controllers/settings_controller_test.rb new file mode 100644 index 00000000..4ba0c18b --- /dev/null +++ b/test/controllers/settings_controller_test.rb @@ -0,0 +1,13 @@ +require "test_helper" + +class SettingsControllerTest < ActionDispatch::IntegrationTest + test "should get index" do + get settings_index_url + assert_response :success + end + + test "should get save" do + get settings_save_url + assert_response :success + end +end diff --git a/test/controllers/surrenders_controller_test.rb b/test/controllers/surrenders_controller_test.rb new file mode 100644 index 00000000..ad1ff386 --- /dev/null +++ b/test/controllers/surrenders_controller_test.rb @@ -0,0 +1,32 @@ +require "test_helper" + +class SurrendersControllerTest < ActionDispatch::IntegrationTest + test "should get index" do + get surrenders_index_url + assert_response :success + end + + test "should get show" do + get surrenders_show_url + assert_response :success + end + test "should get create" do + get surrenders_create_url + assert_response :success + end + + test "should get new" do + get surrenders_new_url + assert_response :success + end + + test "should get destroy" do + get surrenders_destroy_url + assert_response :success + end + + test "should get show" do + get surrenders_show_url + assert_response :success + end +end diff --git a/test/fixtures/banned_ips.yml b/test/fixtures/banned_ips.yml new file mode 100644 index 00000000..d7a33292 --- /dev/null +++ b/test/fixtures/banned_ips.yml @@ -0,0 +1,11 @@ +# Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html + +# This model initially had no columns defined. If you add columns to the +# model remove the "{}" from the fixture names and add the columns immediately +# below each fixture, per the syntax in the comments below +# +one: {} +# column: value +# +two: {} +# column: value diff --git a/test/fixtures/kinks.yml b/test/fixtures/kinks.yml new file mode 100644 index 00000000..d7a33292 --- /dev/null +++ b/test/fixtures/kinks.yml @@ -0,0 +1,11 @@ +# Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html + +# This model initially had no columns defined. If you add columns to the +# model remove the "{}" from the fixture names and add the columns immediately +# below each fixture, per the syntax in the comments below +# +one: {} +# column: value +# +two: {} +# column: value diff --git a/test/fixtures/surrenders.yml b/test/fixtures/surrenders.yml new file mode 100644 index 00000000..c56179db --- /dev/null +++ b/test/fixtures/surrenders.yml @@ -0,0 +1,11 @@ +# Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html + +one: + user: one + friendship: one + expires_at: 2024-03-10 14:42:19 + +two: + user: two + friendship: two + expires_at: 2024-03-10 14:42:19 diff --git a/test/models/banned_ip_test.rb b/test/models/banned_ip_test.rb new file mode 100644 index 00000000..22e0c691 --- /dev/null +++ b/test/models/banned_ip_test.rb @@ -0,0 +1,7 @@ +require "test_helper" + +class BannedIpTest < ActiveSupport::TestCase + # test "the truth" do + # assert true + # end +end diff --git a/test/models/kink_test.rb b/test/models/kink_test.rb new file mode 100644 index 00000000..c536d1cb --- /dev/null +++ b/test/models/kink_test.rb @@ -0,0 +1,7 @@ +require "test_helper" + +class KinkTest < ActiveSupport::TestCase + # test "the truth" do + # assert true + # end +end diff --git a/test/models/surrender_test.rb b/test/models/surrender_test.rb new file mode 100644 index 00000000..beb1c29e --- /dev/null +++ b/test/models/surrender_test.rb @@ -0,0 +1,7 @@ +require "test_helper" + +class SurrenderTest < ActiveSupport::TestCase + # test "the truth" do + # assert true + # end +end