Merge branch 'master' into missing-br

This commit is contained in:
Kyle Drake 2023-07-19 16:32:15 -05:00 committed by GitHub
commit 22b8af52c3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
20 changed files with 311 additions and 136 deletions

View file

@ -8,7 +8,7 @@ gem 'bcrypt'
gem 'sinatra-flash', require: 'sinatra/flash' gem 'sinatra-flash', require: 'sinatra/flash'
gem 'sinatra-xsendfile', require: 'sinatra/xsendfile' gem 'sinatra-xsendfile', require: 'sinatra/xsendfile'
gem 'puma', '5.6.5', require: nil gem 'puma', '5.6.5', require: nil
gem 'sidekiq', '~> 5.2.0' gem 'sidekiq', '~> 7.0.8'
gem 'mail' gem 'mail'
gem 'net-smtp' gem 'net-smtp'
gem 'tilt' gem 'tilt'

View file

@ -49,7 +49,7 @@ GEM
climate_control (0.2.0) climate_control (0.2.0)
coderay (1.1.3) coderay (1.1.3)
concurrent-ruby (1.2.2) concurrent-ruby (1.2.2)
connection_pool (2.3.0) connection_pool (2.4.0)
coveralls_reborn (0.25.0) coveralls_reborn (0.25.0)
simplecov (>= 0.18.1, < 0.22.0) simplecov (>= 0.18.1, < 0.22.0)
term-ansicolor (~> 1.6) term-ansicolor (~> 1.6)
@ -187,11 +187,11 @@ GEM
net-protocol net-protocol
netrc (0.11.0) netrc (0.11.0)
nio4r (2.5.8) nio4r (2.5.8)
nokogiri (1.13.10-x86_64-linux) nokogiri (1.14.3-x86_64-linux)
racc (~> 1.4) racc (~> 1.4)
ox (2.14.11) ox (2.14.11)
paypal-recurring (1.1.0) paypal-recurring (1.1.0)
pg (1.4.4) pg (1.5.3)
progress (3.6.0) progress (3.6.0)
pry (0.14.1) pry (0.14.1)
coderay (~> 1.1) coderay (~> 1.1)
@ -199,7 +199,7 @@ GEM
public_suffix (5.0.0) public_suffix (5.0.0)
puma (5.6.5) puma (5.6.5)
nio4r (~> 2.0) nio4r (~> 2.0)
racc (1.6.2) racc (1.7.1)
rack (2.2.6.4) rack (2.2.6.4)
rack-cache (1.13.0) rack-cache (1.13.0)
rack (>= 0.4) rack (>= 0.4)
@ -215,6 +215,8 @@ GEM
rb-inotify (0.10.1) rb-inotify (0.10.1)
ffi (~> 1.0) ffi (~> 1.0)
redis (4.5.1) redis (4.5.1)
redis-client (0.14.1)
connection_pool
redis-namespace (1.9.0) redis-namespace (1.9.0)
redis (>= 4) redis (>= 4)
regexp_parser (2.6.0) regexp_parser (2.6.0)
@ -230,7 +232,7 @@ GEM
rszr (1.3.0) rszr (1.3.0)
ruby-progressbar (1.11.0) ruby-progressbar (1.11.0)
ruby2_keywords (0.0.5) ruby2_keywords (0.0.5)
sanitize (6.0.1) sanitize (6.0.2)
crass (~> 1.0.2) crass (~> 1.0.2)
nokogiri (>= 1.12.0) nokogiri (>= 1.12.0)
sass (3.7.4) sass (3.7.4)
@ -239,17 +241,17 @@ GEM
rb-fsevent (~> 0.9, >= 0.9.4) rb-fsevent (~> 0.9, >= 0.9.4)
rb-inotify (~> 0.9, >= 0.9.7) rb-inotify (~> 0.9, >= 0.9.7)
sax-machine (1.3.2) sax-machine (1.3.2)
sequel (5.62.0) sequel (5.68.0)
sequel_pg (1.17.0) sequel_pg (1.17.1)
pg (>= 0.18.0, != 1.2.0) pg (>= 0.18.0, != 1.2.0)
sequel (>= 4.38.0) sequel (>= 4.38.0)
shotgun (0.9.2) shotgun (0.9.2)
rack (>= 1.0) rack (>= 1.0)
sidekiq (5.2.10) sidekiq (7.0.8)
connection_pool (~> 2.2, >= 2.2.2) concurrent-ruby (< 2)
rack (~> 2.0) connection_pool (>= 2.3.0)
rack-protection (>= 1.5.0) rack (>= 2.2.4)
redis (~> 4.5, < 4.6.0) redis-client (>= 0.11.0)
simplecov (0.21.2) simplecov (0.21.2)
docile (~> 1.1) docile (~> 1.1)
simplecov-html (~> 0.11) simplecov-html (~> 0.11)
@ -370,7 +372,7 @@ DEPENDENCIES
sequel sequel
sequel_pg sequel_pg
shotgun shotgun
sidekiq (~> 5.2.0) sidekiq (~> 7.0.8)
simplecov simplecov
simpleidn simpleidn
sinatra sinatra

View file

@ -1,7 +1,12 @@
CREATE_MATCH_REGEX = /^username$|^password$|^email$|^new_tags_string$|^is_education$/ CREATE_MATCH_REGEX = /^username$|^password$|^email$|^new_tags_string$|^is_education$/
def education_whitelist_required?
return true if params[:is_education] == 'true' && $config['education_tag_whitelist']
false
end
def education_whitelisted? def education_whitelisted?
return true if params[:is_education] == 'true' && $config['education_tag_whitelist'] && !$config['education_tag_whitelist'].select {|t| params[:new_tags_string].match(t)}.empty? return true if education_whitelist_required? && !$config['education_tag_whitelist'].select {|t| params[:new_tags_string].match(t)}.empty?
false false
end end
@ -63,8 +68,13 @@ post '/create' do
ga_adgroupid: session[:ga_adgroupid] ga_adgroupid: session[:ga_adgroupid]
) )
if education_whitelist_required?
if education_whitelisted? if education_whitelisted?
@site.email_confirmed = true @site.email_confirmed = true
else
flash[:error] = 'The class tag is invalid.'
return {result: 'error'}.to_json
end
else else
if !hcaptcha_valid? if !hcaptcha_valid?
flash[:error] = 'The captcha was not valid, please try again.' flash[:error] = 'The captcha was not valid, please try again.'

View file

@ -20,6 +20,9 @@ get '/settings/:username/?' do |username|
pass if Site.select(:id).where(username: username).first.nil? pass if Site.select(:id).where(username: username).first.nil?
require_login require_login
require_ownership_for_settings require_ownership_for_settings
@bluesky_did = $redis_proxy.hget "dns-_atproto.#{@site.username}.neocities.org", 'TXT'
@title = "Site settings for #{username}" @title = "Site settings for #{username}"
erb :'settings/site' erb :'settings/site'
end end
@ -174,15 +177,33 @@ post '/settings/:username/custom_domain' do
end end
end end
post '/settings/:username/bluesky_set_did' do
require_login
require_ownership_for_settings
redirect '/settings' if !@site.domain.empty?
# todo standards based validation
if params[:did].length > 50
flash[:error] = 'DID provided was too long'
elsif !params[:did].match(/^did=did:plc:([a-z|0-9)]+)$/)
flash[:error] = 'DID was invalid'
else
$redis_proxy.hset "dns-_atproto.#{@site.username}.neocities.org", 'TXT', params[:did]
flash[:success] = 'DID set! You can now verify the domain on the Bluesky app.'
end
redirect "/settings/#{@site.username}#bluesky"
end
post '/settings/:username/generate_api_key' do post '/settings/:username/generate_api_key' do
require_login require_login
require_ownership_for_settings require_ownership_for_settings
is_new = current_site.api_key.nil? is_new = @site.api_key.nil?
current_site.generate_api_key! @site.generate_api_key!
msg = is_new ? "New API key has been generated." : "API key has been regenerated." msg = is_new ? "New API key has been generated." : "API key has been regenerated."
flash[:success] = msg flash[:success] = msg
redirect "/settings/#{current_site.username}#api_key" redirect "/settings/#{@site.username}#api_key"
end end
post '/settings/change_password' do post '/settings/change_password' do

View file

@ -35,6 +35,7 @@ class Event < Sequel::Model
ds = select_all(:events). ds = select_all(:events).
order(:created_at.desc). order(:created_at.desc).
join_table(:inner, :sites, id: :site_id). join_table(:inner, :sites, id: :site_id).
where(follow_id: nil).
exclude(Sequel.qualify(:sites, :is_deleted) => true). exclude(Sequel.qualify(:sites, :is_deleted) => true).
exclude(Sequel.qualify(:events, :is_deleted) => true). exclude(Sequel.qualify(:events, :is_deleted) => true).
exclude(is_banned: true) exclude(is_banned: true)

View file

@ -35,6 +35,7 @@ class Site < Sequel::Model
application/rss+xml application/rss+xml
application/x-elc application/x-elc
image/webp image/webp
image/avif
image/x-xcf image/x-xcf
application/epub application/epub
application/epub+zip application/epub+zip
@ -43,11 +44,11 @@ class Site < Sequel::Model
} }
VALID_EXTENSIONS = %w{ VALID_EXTENSIONS = %w{
html htm txt text css js jpg jpeg png gif svg md markdown eot ttf woff woff2 json geojson csv tsv mf ico pdf asc key pgp xml mid midi manifest otf webapp less sass rss kml dae obj mtl scss webp xcf epub gltf bin webmanifest knowl atom opml rdf map gpg html htm txt text css js jpg jpeg png gif svg md markdown eot ttf woff woff2 json geojson csv tsv mf ico pdf asc key pgp xml mid midi manifest otf webapp less sass rss kml dae obj mtl scss webp avif xcf epub gltf bin webmanifest knowl atom opml rdf map gpg resolveHandle
} }
VALID_EDITABLE_EXTENSIONS = %w{ VALID_EDITABLE_EXTENSIONS = %w{
html htm txt js css scss md manifest less webmanifest xml json opml rdf svg gpg pgp html htm txt js css scss md manifest less webmanifest xml json opml rdf svg gpg pgp resolveHandle
} }
MINIMUM_PASSWORD_LENGTH = 5 MINIMUM_PASSWORD_LENGTH = 5
@ -166,6 +167,7 @@ class Site < Sequel::Model
MAX_COMMENTS_PER_DAY = 5 MAX_COMMENTS_PER_DAY = 5
SANDBOX_TIME = 14.days SANDBOX_TIME = 14.days
BLACK_BOX_WAIT_TIME = 10.seconds BLACK_BOX_WAIT_TIME = 10.seconds
MAX_DISPLAY_FOLLOWS = 56*3
many_to_many :tags many_to_many :tags
@ -568,8 +570,8 @@ class Site < Sequel::Model
follows_dataset.all follows_dataset.all
end end
def profile_follows_actioning_ids def profile_follows_actioning_ids(limit=nil)
follows_dataset.select(:actioning_site_id).exclude(:sites__site_changed => false).all follows_dataset.select(:actioning_site_id).exclude(:sites__site_changed => false).limit(limit).all
end end
=begin =begin
@ -1447,6 +1449,10 @@ class Site < Sequel::Model
paginate(current_page, limit) paginate(current_page, limit)
end end
def newest_follows
follows_dataset.where(:follows__created_at => (1.month.ago..Time.now)).order(:follows__created_at.desc).all
end
def host def host
!domain.empty? ? domain : "#{username}.neocities.org" !domain.empty? ? domain : "#{username}.neocities.org"
end end

View file

@ -145,6 +145,35 @@ describe 'site/settings' do
end end
end end
describe 'api key' do
include Capybara::DSL
before do
Capybara.reset_sessions!
@site = Fabricate :site
@child_site = Fabricate :site, parent_site_id: @site.id
page.set_rack_session id: @site.id
end
it 'sets api key' do
visit "/settings/#{@child_site[:username]}#api_key"
_(@site.api_key).must_be_nil
_(@child_site.api_key).must_be_nil
click_button 'Generate API Key'
_(@site.reload.api_key).must_be_nil
_(@child_site.reload.api_key).wont_be_nil
_(page.body).must_match @child_site.api_key
end
it 'regenerates api key for child site' do
visit "/settings/#{@child_site[:username]}#api_key"
@child_site.generate_api_key!
api_key = @child_site.api_key
click_button 'Generate API Key'
_(@child_site.reload.api_key).wont_equal api_key
end
end
describe 'delete' do describe 'delete' do
include Capybara::DSL include Capybara::DSL

View file

@ -64,36 +64,45 @@ describe 'site page' do
end end
describe 'blocking' do
before do
@tag = SecureRandom.hex 10
@blocked_site = Fabricate :site, new_tags_string: @tag, created_at: 2.weeks.ago, site_changed: true, views: Site::BROWSE_MINIMUM_FOLLOWER_VIEWS+1
end
after do
@blocked_site.destroy
end
it 'allows site blocking and unblocking' do it 'allows site blocking and unblocking' do
tag = SecureRandom.hex 10
blocked_site = Fabricate :site, new_tags_string: tag, created_at: 2.weeks.ago, site_changed: true, views: Site::BROWSE_MINIMUM_FOLLOWER_VIEWS+1
site = Fabricate :site site = Fabricate :site
page.set_rack_session id: site.id page.set_rack_session id: site.id
visit "/browse?tag=#{tag}" visit "/browse?tag=#{@tag}"
_(page.find('.website-Gallery .username a')['href']).must_match /\/site\/#{blocked_site.username}/ _(page.find('.website-Gallery .username a')['href']).must_match /\/site\/#{@blocked_site.username}/
visit "/site/#{blocked_site.username}" visit "/site/#{@blocked_site.username}"
click_link 'Block' click_link 'Block'
click_button 'Block Site' click_button 'Block Site'
visit "/browse?tag=#{tag}" visit "/browse?tag=#{@tag}"
_(page).must_have_content /no active sites found/i _(page).must_have_content /no active sites found/i
site.reload site.reload
_(site.blockings.length).must_equal 1 _(site.blockings.length).must_equal 1
_(site.blockings.first.site_id).must_equal blocked_site.id _(site.blockings.first.site_id).must_equal @blocked_site.id
visit "/site/#{blocked_site.username}" visit "/site/#{@blocked_site.username}"
click_link 'Unblock' click_link 'Unblock'
visit "/browse?tag=#{tag}" visit "/browse?tag=#{@tag}"
_(page.find('.website-Gallery .username a')['href']).must_match /\/site\/#{blocked_site.username}/ _(page.find('.website-Gallery .username a')['href']).must_match /\/site\/#{@blocked_site.username}/
end
end end
it '404s if site is banned' do it '404s if site is banned' do

View file

@ -4,7 +4,28 @@ DEBIAN_FRONTEND=noninteractive
. /vagrant/vagrant/redis.sh . /vagrant/vagrant/redis.sh
apt-get install -y git curl zlib1g-dev build-essential libssl-dev libreadline-dev libyaml-dev libsqlite3-dev sqlite3 libxml2-dev libxslt1-dev libcurl4-openssl-dev libffi-dev libpq-dev libmagickwand-dev imagemagick libmagickwand-dev libmagic-dev file clamav-daemon apt-get install -y \
build-essential \
clamav-daemon \
curl \
file \
git \
imagemagick \
libcurl4-openssl-dev \
libffi-dev \
libimlib2-dev \
libmagic-dev \
libmagickwand-dev \
libpq-dev \
libreadline-dev \
libsqlite3-dev \
libssl-dev \
libwebp-dev \
libxml2-dev \
libxslt1-dev \
libyaml-dev \
sqlite3 \
zlib1g-dev
sed -i 's|[#]*DetectPUA false|DetectPUA true|g' /etc/clamav/clamd.conf sed -i 's|[#]*DetectPUA false|DetectPUA true|g' /etc/clamav/clamd.conf

View file

@ -1,8 +1,8 @@
<% site_followings = site.followings %> <% site_followings = site.followings_dataset.count %>
<% if (!is_current_site && !site_followings.empty?) || is_current_site %> <% if (!is_current_site && site_followings > 0) || is_current_site %>
<div class="following-list"> <div class="following-list">
<h3><a href="/site/<%= site.username %>/follows"><%= is_current_site ? 'Sites you follow' : 'This site follows' %></a></h3> <h3><a href="/site/<%= site.username %>/follows"><%= is_current_site ? 'Sites you follow' : 'This site follows' %></a></h3>
<% if site_followings.empty? %> <% if site_followings == 0 %>
<p>You are not following any sites yet. Add some by <a href="/browse">browsing sites</a> or looking at your tags. <p>You are not following any sites yet. Add some by <a href="/browse">browsing sites</a> or looking at your tags.
<% else %> <% else %>
<% site.followings_dataset.select(:site_id).all.each do |following| %> <% site.followings_dataset.select(:site_id).all.each do |following| %>
@ -12,17 +12,22 @@
</div> </div>
<% end %> <% end %>
<% site_follows = site.follows %> <% site_follows = site.follows_dataset.count %>
<% if (!is_current_site && !site_follows.empty?) || is_current_site %> <% if (!is_current_site && site_follows > 0) || is_current_site %>
<div class="follower-list"> <div class="follower-list">
<h3><a href="/site/<%= site.username %>/followers">Followers</a></h3> <h3><a href="/site/<%= site.username %>/followers">Followers</a></h3>
<% if site_follows.empty? %> <% if site_follows == 0 %>
No followers yet. No followers yet.
<% else %> <% else %>
<% site.profile_follows_actioning_ids.each do |follow| %> <% site_profile_follows_actioning_ids = site.profile_follows_actioning_ids %>
<% site_profile_follows_actioning_ids[0...Site::MAX_DISPLAY_FOLLOWS].each do |follow| %>
<% follow_actioning_site = follow.actioning_site_dataset.select(:username).first %> <% follow_actioning_site = follow.actioning_site_dataset.select(:username).first %>
<a href="/site/<%= follow_actioning_site.username %>" title="<%= follow_actioning_site.title %>"><img src="<%= follow_actioning_site.screenshot_url 'index.html', '50x50' %>" class="avatar" onerror="this.src='/img/50x50.png'"></a> <a href="/site/<%= follow_actioning_site.username %>" title="<%= follow_actioning_site.title %>"><img src="<%= follow_actioning_site.screenshot_url 'index.html', '50x50' %>" class="avatar" onerror="this.src='/img/50x50.png'"></a>
<% end %> <% end %>
<% if Site::MAX_DISPLAY_FOLLOWS < site_profile_follows_actioning_ids.count %>
<a href="/site/<%= site.username %>/followers"><strong>see more <i class="fa fa-arrow-right"></i></strong></a>
<% end %>
<% end %> <% end %>
</div> </div>
<% end %> <% end %>

View file

@ -10,51 +10,32 @@
<script src="/js/news/event.js"></script> <script src="/js/news/event.js"></script>
<script src="/js/news/site.js"></script> <script src="/js/news/site.js"></script>
<% if defined?(site) && !params[:event_id] %>
<% follow_events = site.newest_follows %>
<% unless follow_events.empty? %>
<div class="news-item follow">
<div class="icon"><a href="/site/<%= site.username %>" title="<%= site.username %>" class="avatar" style="background-image: url(<%= site.screenshot_url 'index.html', '50x50' %>);"></a></div>
<div class="text">
<strong>New Followers</strong>
</div>
<div class="content">
<% follow_events.first.site.newest_follows.each_with_index do |event,i| %>
<a href="/site/<%= event.actioning_site.username %>" class="user" title="<%= event.actioning_site.title %>"><i class="fa fa-user"><% if event.actioning_site.supporter? %><i class="fa fa-heart"></i><% end %></i><%= event.actioning_site.username %></a><% unless follow_events.length == i+1 %>, <% end %>
<% end %>
</div>
</div>
<% end %>
<% end %>
<% events.each do |event| %> <% events.each do |event| %>
<% if event.profile_comment_id %> <% if event.profile_comment_id %>
<div class="news-item comment" id="event_<%= event.id %>"> <div class="news-item comment" id="event_<%= event.id %>">
<%== erb :'_news_profile_comment', layout: false, locals: {profile_comment: event.profile_comment, event: event} %> <%== erb :'_news_profile_comment', layout: false, locals: {profile_comment: event.profile_comment, event: event} %>
<% elsif event.tip_id %> <% elsif event.tip_id %>
<% actioning_site = event.actioning_site_dataset.select(:id, :username, :title, :domain, :stripe_customer_id, :plan_type).first %> <div class="news-item comment" id="event_<%= event.id %>">
<% event_site = event.site_dataset.select(:id, :username, :title, :domain, :stripe_customer_id, :plan_type).first %> <%== erb :'_news_tip', layout: false, locals: {tip: event.tip, event: event} %>
<% tip = event.tip %>
<div class="news-item tip">
<div class="title">
<div class="icon">
<% if actioning_site %>
<a href="/site/<%= actioning_site.username %>" title="<%= actioning_site.username %>" class="avatar" style="background-image: url(<%= actioning_site.screenshot_url 'index.html', '50x50' %>);"></a>
<% end %>
</div>
<div class="text">
<div class="headline">
<% if actioning_site %>
<% if current_site && current_site.id == actioning_site.id %>
<a href="/site/<%= current_site.username %>" class="you">You</a>
<% else %>
<a href="/site/<%= actioning_site.username %>" class="user" title="<%= actioning_site.title %>"><i class="fa fa-user"><% if actioning_site.supporter? %><i class="fa fa-heart"></i><% end %></i><%= actioning_site.username %></a>
<% end %>
<% else %>
An anonymous donor
<% end %>
sent a <strong><%= tip.amount_string %></strong> tip to
<% if current_site && event_site.id == current_site.id %>
<a href="/site/<%= current_site.username %>" class="you">you</a>!
<% else %>
<a href="/site/<%= event_site.username %>" class="user" title="<%= event_site.title %>"><i class="fa fa-user"><% if event_site.supporter? %><i class="fa fa-heart"></i><% end %></i><%= event_site.username %></a>!
<% end %>
</div>
<span class="comment"><%= tip.message %></span>
</div>
<span class="date">
<a href="/site/<%= event_site.username %>?event_id=<%= event.id %>"><%= event.created_at.ago %></a>
</span>
</div>
<% elsif event.follow_id %> <% elsif event.follow_id %>
<% actioning_site = event.actioning_site_dataset.select(:id, :username, :title, :domain, :stripe_customer_id, :plan_type).first %> <% actioning_site = event.actioning_site_dataset.select(:id, :username, :title, :domain, :stripe_customer_id, :plan_type).first %>
<% next if actioning_site.nil? %> <% next if actioning_site.nil? %>

36
views/_news_tip.erb Normal file
View file

@ -0,0 +1,36 @@
<% actioning_site = event.tip.actioning_site_dataset.select(:id, :username, :stripe_customer_id, :plan_type, :parent_site_id).first %>
<% event_site = event.site_dataset.select(:id, :username, :stripe_customer_id, :plan_type, :parent_site_id).first %>
<div class="title">
<div class="icon">
<% if actioning_site %>
<a href="/site/<%= actioning_site.username %>" title="<%= actioning_site.username %>" class="avatar" style="background-image: url(<%= actioning_site.screenshot_url 'index.html', '50x50' %>);"></a>
<% end %>
</div>
<div class="text">
<% if actioning_site %>
<% if current_site && current_site.id == actioning_site.id %>
<a href="/site/<%= current_site.username %>" class="you">You</a>
<% else %>
<a href="/site/<%= actioning_site.username %>" class="user" title="<%= actioning_site.title %>"><i class="fa fa-user"><% if actioning_site.supporter? %><i class="fa fa-heart"></i><% end %></i><%= actioning_site.username %></a>
<% end %>
<% else %>
An anonymous donor
<% end %>
sent a <strong style="color: #229954 !important;"><%= tip.amount_string %></strong> tip to
<% if current_site && event_site.id == current_site.id %>
<a href="/site/<%= current_site.username %>" class="you">you</a>
<% else %>
<a href="/site/<%= event_site.username %>" class="user" title="<%= event_site.title %>"><i class="fa fa-user"><% if event_site.supporter? %><i class="fa fa-heart"></i><% end %></i><%= event_site.username %></a>
<% end %>
</div>
<span class="date">
<a href="/site/<%= event_site.username %>?event_id=<%= event.id %>"><%= event.created_at.ago %></a>
</span>
</div>
<div class="content"><%== sanitize_comment tip.message %></div>

View file

@ -13,4 +13,4 @@
<br> <br>
<a href="https://www.tumblr.com/share?<%= Rack::Utils.build_query(v: 3, u: "#{page_uri}", t: "#{site.title}") %>" target="_blank">Tumblr</a> <a href="https://www.tumblr.com/share?<%= Rack::Utils.build_query(v: 3, u: "#{page_uri}", t: "#{site.title}") %>" target="_blank">Tumblr</a>
<br> <br>
<a href="https://toot.kytta.dev/?<%= Rack::Utils.build_query(text: "#{page_uri}") %>" target="_blank">Mastodon</a> <a href="https://toot.kytta.dev/?<%= Rack::Utils.build_query(text: "#{site.title}: #{page_uri}") %>" target="_blank">Mastodon</a>

View file

@ -26,15 +26,19 @@
<li class="active"><a href="#profile" data-toggle="tab">Profile</a></li> <li class="active"><a href="#profile" data-toggle="tab">Profile</a></li>
<!-- <li><a href="#domain" data-toggle="tab">Domain Name</a></li> --> <!-- <li><a href="#domain" data-toggle="tab">Domain Name</a></li> -->
<li><a href="#custom_domain" data-toggle="tab">Custom Domain</a></li> <li><a href="#custom_domain" data-toggle="tab">Custom Domain</a></li>
<li><a href="#username" data-toggle="tab">Change Site Name</a></li> <li><a href="#username" data-toggle="tab">Rename</a></li>
<li><a href="#tipping" data-toggle="tab">Tipping</a></li> <li><a href="#tipping" data-toggle="tab">Tipping</a></li>
<li><a href="#api_key" data-toggle="tab">API Key</a></li> <li><a href="#api_key" data-toggle="tab">API</a></li>
<% if @site.admin_nsfw != true %> <% if @site.admin_nsfw != true %>
<li><a href="#nsfw" data-toggle="tab">18+</a></li> <li><a href="#nsfw" data-toggle="tab">18+</a></li>
<% end %> <% end %>
<% if @site.domain.empty? %>
<li><a href="#bluesky" data-toggle="tab">Bluesky</a></li>
<% end %>
<li><a href="#delete" data-toggle="tab">Delete</a></li> <li><a href="#delete" data-toggle="tab">Delete</a></li>
</ul> </ul>
<div class="tab-content"> <div class="tab-content">
@ -64,6 +68,10 @@
</div> </div>
<% end %> <% end %>
<div class="tab-pane" id="bluesky">
<%== erb :'settings/site/bluesky' %>
</div>
<div class="tab-pane" id="delete"> <div class="tab-pane" id="delete">
<%== erb :'settings/site/delete' %> <%== erb :'settings/site/delete' %>
</div> </div>

View file

@ -0,0 +1,24 @@
<h2>Bluesky Integration (beta)</h2>
<p>
You can now verify control of your site on Neocities to create a handle on <a href="https://bsky.app/">Bluesky</a>.
</p>
<p>
<strong>Bluesky App <i class="fa fa-arrow-right"></i> "Settings" <i class="fa fa-arrow-right"></i> "Change my handle" <i class="fa fa-arrow-right"></i> "I have my own domain"</strong>
</p>
<p>
Domain: <span style="color: gray">@</span><strong><%= @site.username %>.neocities.org</strong>
</p>
<p>
TXT value:
</p>
<form method="POST" action="/settings/<%= @site.username %>/bluesky_set_did">
<%== csrf_token_input_html %>
<input name="did" type="text" style="width: 50%" placeholder="did=did:plc:somethingexamplesomething" value="<%= @bluesky_did %>">
<br>
<input class="btn-Action" type="submit" value="Update">
</form>

View file

@ -74,9 +74,9 @@
</p> </p>
<% end %> <% end %>
</div> </div>
<%== erb :'_news', layout: false, locals: {site: @site, events: @latest_events} %> <%== erb :'_news', layout: false, locals: {site: site, events: @latest_events} %>
<% else %> <% else %>
<div class="site-profile-padding"><%== erb :'_news', layout: false, locals: {site: @site, events: @latest_events} %></div> <div class="site-profile-padding"><%== erb :'_news', layout: false, locals: {site: site, events: @latest_events} %></div>
<% end %> <% end %>
</div> </div>

View file

@ -163,8 +163,12 @@
var editor = {} var editor = {}
$(document).ready(function() { $(document).ready(function() {
$.get("/site_files/download/<%= Addressable::URI.parse(@filename).normalized_path.to_s %>", function(resp) { $.ajax({
url: "/site_files/download/<%= Addressable::URI.parse(@filename).normalized_path.to_s %>",
cache: false,
success: function(resp) {
editor = ace.edit("editor") editor = ace.edit("editor")
setTheme() setTheme()
<% if @ace_mode %> <% if @ace_mode %>
@ -197,8 +201,9 @@
saveTextFile(false) saveTextFile(false)
} }
}) })
}) }
}) });
});
window.onbeforeunload = function() { window.onbeforeunload = function() {
if(unsavedChanges == true) if(unsavedChanges == true)

View file

@ -64,7 +64,11 @@ class LetsEncryptWorker
puts "testing #{challenge_url}" puts "testing #{challenge_url}"
begin begin
res = HTTP.timeout(connect: 10, write: 10, read: 10).follow.get(challenge_url) # Some dumb letsencrypt related cert expiration issue hotfix
ctx = OpenSSL::SSL::SSLContext.new
ctx.verify_mode = OpenSSL::SSL::VERIFY_NONE
res = HTTP.timeout(connect: 10, write: 10, read: 10).follow.get(challenge_url, ssl_context: ctx)
rescue => e rescue => e
puts e.inspect puts e.inspect
puts "error with #{challenge_url}" puts "error with #{challenge_url}"

View file

@ -6,7 +6,7 @@ class ScreenshotWorker
HARD_TIMEOUT = 30.freeze HARD_TIMEOUT = 30.freeze
PAGE_WAIT_TIME = 5.freeze # 3D/VR sites take a bit to render after loading usually. PAGE_WAIT_TIME = 5.freeze # 3D/VR sites take a bit to render after loading usually.
include Sidekiq::Worker include Sidekiq::Worker
sidekiq_options queue: :screenshots, retry: 2, backtrace: true sidekiq_options queue: :screenshots, retry: 10, backtrace: true
def perform(username, path) def perform(username, path)
site = Site[username: username] site = Site[username: username]
@ -47,19 +47,21 @@ class ScreenshotWorker
base_image_tmpfile_path = "/tmp/#{SecureRandom.uuid}.png" base_image_tmpfile_path = "/tmp/#{SecureRandom.uuid}.png"
http_resp = HTTP.basic_auth(user: api_user, pass: api_password).get(uri) http_resp = HTTP.basic_auth(user: api_user, pass: api_password).get(uri)
BlackBox.new(site, path).check_uri(http_resp.headers['X-URL']) if defined?(BlackBox) BlackBox.new(site, path).check_uri(http_resp.headers['X-URL']) if defined?(BlackBox) && http_resp.headers['X-URL']
File.write base_image_tmpfile_path, http_resp.to_s File.write base_image_tmpfile_path, http_resp.to_s
user_screenshots_path = File.join SCREENSHOTS_PATH, Site.sharding_dir(username), username user_screenshots_path = File.join SCREENSHOTS_PATH, Site.sharding_dir(username), username
screenshot_path = File.join user_screenshots_path, File.dirname(path_for_screenshot) screenshot_path = File.join user_screenshots_path, File.dirname(path_for_screenshot)
FileUtils.mkdir_p screenshot_path unless Dir.exist?(screenshot_path) FileUtils.mkdir_p screenshot_path unless Dir.exist?(screenshot_path)
FileUtils.cp base_image_tmpfile_path, File.join(user_screenshots_path, "#{path_for_screenshot}.png")
Site::SCREENSHOT_RESOLUTIONS.each do |res| Site::SCREENSHOT_RESOLUTIONS.each do |res|
width, height = res.split('x').collect {|r| r.to_i} width, height = res.split('x').collect {|r| r.to_i}
full_screenshot_path = File.join(user_screenshots_path, "#{path_for_screenshot}.#{res}.webp") full_screenshot_path = File.join(user_screenshots_path, "#{path_for_screenshot}.#{res}.webp")
opts = {quality: 90, resize_w: width, resize_h: height} opts = {resize_w: width, resize_h: height, near_lossless: 0}
if width == height if width == height
opts.merge! crop_x: 160, crop_y: 0, crop_w: 960, crop_h: 960 opts.merge! crop_x: 160, crop_y: 0, crop_w: 960, crop_h: 960
@ -69,6 +71,8 @@ class ScreenshotWorker
end end
true true
rescue WebP::EncoderError => e
puts "Failed: #{username} #{path} #{e.inspect}"
rescue => e rescue => e
raise e raise e
ensure ensure

View file

@ -24,12 +24,21 @@ class ThumbnailWorker
format = File.extname(path).gsub('.', '') format = File.extname(path).gsub('.', '')
full_thumbnail_path = File.join(user_thumbnails_path, "#{path}.#{res}.webp") full_thumbnail_path = File.join(user_thumbnails_path, "#{path}.#{res}.webp")
begin
image = Rszr::Image.load site_file_path image = Rszr::Image.load site_file_path
rescue Rszr::LoadError
next
end
begin
if image.width > image.height if image.width > image.height
image.resize! width, :auto image.resize! width, :auto
else else
image.resize! :auto, height image.resize! :auto, height
end end
rescue Rszr::TransformationError
next
end
begin begin
tmpfile = "/tmp/#{SecureRandom.uuid}.png" tmpfile = "/tmp/#{SecureRandom.uuid}.png"