diff options
Diffstat (limited to 'app/lib')
-rw-r--r-- | app/lib/bangtags.rb | 549 |
1 files changed, 361 insertions, 188 deletions
diff --git a/app/lib/bangtags.rb b/app/lib/bangtags.rb index 44c2e5c75..0a238d349 100644 --- a/app/lib/bangtags.rb +++ b/app/lib/bangtags.rb @@ -30,50 +30,50 @@ class Bangtags } @aliases = { - ['media', 'end'] => ['var', 'end'], - ['media', 'stop'] => ['var', 'end'], - ['media', 'endall'] => ['var', 'endall'], - ['media', 'stopall'] => ['var', 'endall'], - - ['admin', 'end'] => ['var', 'end'], - ['admin', 'stop'] => ['var', 'end'], - ['admin', 'endall'] => ['var', 'endall'], - ['admin', 'stopall'] => ['var', 'endall'], - - ['parent', 'visibility'] => ['visibility', 'parent'], - ['parent', 'v'] => ['visibility', 'parent'], - ['parent', 'kick'] => ['thread', 'kick'], - ['parent', 'unkick'] => ['thread', 'unkick'], - - ['parent', 'l'] => ['live', 'parent'], - ['parent', 'live'] => ['live', 'parent'], - ['parent', 'lifespan'] => ['lifespan', 'parent'], - ['parent', 'delete_in'] => ['delete_in', 'parent'], - - ['thread', 'l'] => ['l', 'thread'], - ['thread', 'live'] => ['live', 'thread'], - ['thread', 'lifespan'] => ['lifespan', 'thread'], - ['thread', 'delete_in'] => ['delete_in', 'thread'], - - ['all', 'l'] => ['l', 'all'], - ['all', 'live'] => ['live', 'all'], - ['all', 'lifespan'] => ['lifespan', 'all'], - ['all', 'delete_in'] => ['delete_in', 'all'], - - ['parent', 'd'] => ['defederate_in', 'parent'], - ['parent', 'defed'] => ['defederate_in', 'parent'], - ['parent', 'defed_in'] => ['defederate_in', 'parent'], - ['parent', 'defederate'] => ['defederate_in', 'parent'], - - ['thread', 'd'] => ['defederate_in', 'thread'], - ['thread', 'defed'] => ['defederate_in', 'thread'], - ['thread', 'defed_in'] => ['defederate_in', 'thread'], - ['thread', 'defederate'] => ['defederate_in', 'thread'], - - ['all', 'd'] => ['defederate_in', 'all'], - ['all', 'defed'] => ['defederate_in', 'all'], - ['all', 'defed_in'] => ['defederate_in', 'all'], - ['all', 'defederate'] => ['defederate_in', 'all'], + %w(media end) => %w(var end), + %w(media stop) => %w(var end), + %w(media endall) => %w(var endall), + %w(media stopall) => %w(var endall), + + %w(admin end) => %w(var end), + %w(admin stop) => %w(var end), + %w(admin endall) => %w(var endall), + %w(admin stopall) => %w(var endall), + + %w(parent visibility) => %w(visibility parent), + %w(parent v) => %w(visibility parent), + %w(parent kick) => %w(thread kick), + %w(parent unkick) => %w(thread unkick), + + %w(parent l) => %w(live parent), + %w(parent live) => %w(live parent), + %w(parent lifespan) => %w(lifespan parent), + %w(parent delete_in) => %w(delete_in parent), + + %w(thread l) => %w(l thread), + %w(thread live) => %w(live thread), + %w(thread lifespan) => %w(lifespan thread), + %w(thread delete_in) => %w(delete_in thread), + + %w(all l) => %w(l all), + %w(all live) => %w(live all), + %w(all lifespan) => %w(lifespan all), + %w(all delete_in) => %w(delete_in all), + + %w(parent d) => %w(defederate_in parent), + %w(parent defed) => %w(defederate_in parent), + %w(parent defed_in) => %w(defederate_in parent), + %w(parent defederate) => %w(defederate_in parent), + + %w(thread d) => %w(defederate_in thread), + %w(thread defed) => %w(defederate_in thread), + %w(thread defed_in) => %w(defederate_in thread), + %w(thread defederate) => %w(defederate_in thread), + + %w(all d) => %w(defederate_in all), + %w(all defed) => %w(defederate_in all), + %w(all defed_in) => %w(defederate_in all), + %w(all defederate) => %w(defederate_in all), } # sections of the final status text @@ -91,7 +91,7 @@ class Bangtags end def process - return unless !@vars['_bangtags:disable'] && status.text&.present? && status.text.include?('#!') + return unless !@vars['_bangtags:disable'] && status.text&.present? && status.text&.include?('#!') status.text.gsub!('#!!', "#\ufdd6!") @@ -99,7 +99,7 @@ class Bangtags if @vore_stack.last == '_draft' || (@chunks.present? && status.draft?) chunk.gsub("#\ufdd6!", '#!') @chunks << chunk - elsif chunk.starts_with?("#!") + elsif chunk.starts_with?('#!') orig_chunk = chunk chunk.sub!(/(\\:)?+:+?!#\Z/, '\1') chunk.sub!(/{(.*)}\Z/, '\1') @@ -107,11 +107,12 @@ class Bangtags if @vore_stack.last != '_comment' cmd = chunk[2..-1].strip next if cmd.blank? + cmd = cmd.split(':::') cmd = cmd[0].split('::') + cmd[1..-1] cmd = cmd[0].split(':') + cmd[1..-1] - cmd.map! {|c| c.gsub(/\\:/, ':').gsub(/\\\\:/, '\:')} + cmd.map! { |c| c.gsub(/\\:/, ':').gsub(/\\\\:/, '\:') } prefix = @prefix_ns[cmd[0]] cmd = prefix + cmd unless prefix.nil? @@ -128,6 +129,7 @@ class Bangtags end next if cmd[0].nil? + if cmd[0].downcase == 'once' @once = true cmd.shift @@ -135,19 +137,24 @@ class Bangtags end case cmd[0].downcase + when 'var' chunk = nil next if cmd[1].nil? + case cmd[1].downcase + when 'end', 'stop' @vore_stack.pop @component_stack.pop + when 'endall', 'stopall' @vore_stack = [] - @component_stack.reject! {|c| c == :var} + @component_stack.reject! { |c| c == :var } else var = cmd[1] next if var.nil? || var.starts_with?('_') + new_value = cmd[2..-1] if new_value.blank? chunk = @vars[var] @@ -158,50 +165,66 @@ class Bangtags @vars[var] = new_value.join(':') end end + when 'strip' chunk = nil @strip_lines = cmd[1]&.downcase.in?(['y', 'yes', '', nil]) + when 'tf' chunk = nil next if cmd[1].nil? + case cmd[1].downcase + when 'end', 'stop' @tf_cmds.pop @component_stack.pop + when 'endall', 'stopall' @tf_cmds = [] - @component_stack.reject! {|c| c == :tf} + @component_stack.reject! { |c| c == :tf } else @vars['_tf:head:count'] = 0 if cmd[1].downcase.in?(%w(head take)) @tf_cmds.push(cmd[1..-1]) @component_stack.push(:tf) end + when 'end', 'stop' chunk = nil case @component_stack.pop + when :tf @tf_cmds.pop + when :var, :hide @vore_stack.pop end + when 'endall', 'stopall' chunk = nil @tf_cmds = [] @vore_stack = [] @component_stack = [] + when 'emojify' chunk = nil next if cmd[1].nil? + src_img = nil shortcode = cmd[2] case cmd[1].downcase + when 'avatar' src_img = status.account.avatar + when 'parent' next unless cmd[3].present? && reply? + shortcode = cmd[3] next if cmd[2].nil? || @parent_status.nil? + case cmd[2].downcase + when 'avatar' src_img = @parent_status.account.avatar end @@ -216,25 +239,28 @@ class Bangtags emoji.save user_friendly_action_log(@account, :create, emoji) end + when 'emoji' chunk = nil next if cmd[1].nil? + shortcode = cmd[1] domain = (cmd[2].blank? ? nil : cmd[2].downcase) chunk = ":#{shortcode}:" ours = CustomEmoji.find_or_initialize_by(shortcode: shortcode, domain: nil) if ours.id.nil? - if domain.nil? - theirs = CustomEmoji.find_by(shortcode: shortcode) - else - theirs = CustomEmoji.find_by(shortcode: shortcode, domain: domain) - end + theirs = if domain.nil? + CustomEmoji.find_by(shortcode: shortcode) + else + CustomEmoji.find_by(shortcode: shortcode, domain: domain) + end unless theirs.nil? ours.image = theirs.image ours.save user_friendly_action_log(@account, :create, ours) end end + when 'char' chunk = nil charmap = { @@ -244,41 +270,52 @@ class Bangtags '\n' => "\n", '\r' => "\r", '\t' => "\t", - '\T' => ' ' + '\T' => ' ', } cmd[1..-1].each do |c| next if c.nil? + if c.in?(charmap) @chunks << charmap[cmd[1]] elsif (/^\h{1,5}$/ =~ c) && c.to_i(16) > 0 begin @chunks << [c.to_i(16)].pack('U*') - rescue + rescue StandardError @chunks << '?' end end end + when 'link' chunk = nil next if cmd[1].nil? + case cmd[1].downcase + when 'permalink', 'self' chunk = TagManager.instance.url_for(status) + when 'cloudroot' chunk = "https://monsterpit.cloud/~/#{account.username}" + when 'blogroot' chunk = "https://monsterpit.blog/~/#{account.username}" end + when 'ping' mentions = [] next if cmd[1].nil? + case cmd[1].downcase + when 'admins' mentions = User.admins.map { |u| "@#{u.account.username}" } mentions.sort! + when 'mods' mentions = User.moderators.map { |u| "@#{u.account.username}" } mentions.sort! + when 'staff' mentions = User.admins.map { |u| "@#{u.account.username}" } mentions += User.moderators.map { |u| "@#{u.account.username}" } @@ -286,29 +323,39 @@ class Bangtags mentions.sort! end chunk = mentions.join(' ') + when 'tag' chunk = nil - tags = cmd[1..-1].map {|t| t.gsub(':', '.')} + tags = cmd[1..-1].map { |t| t.gsub(':', '.') } add_tags(status, *tags) + when '10629' chunk = "\u200b:gargamel:\u200b I really don't think we should do this." + when 'thread' chunk = nil next if cmd[1].nil? + case cmd[1].downcase + when 'leave', 'part', 'quit' next if status.conversation_id.nil? + @account.mute_conversation!(status.conversation) if %w(replyguy reply-guy reply-guy-mode).include?(cmd[2]) rum = Account.find_remote('RumPartov', 'weirder.earth') - next unless rum.present? + next if rum.blank? + rum.mentions.where(status: status).first_or_create(status: status) end + when 'kick' next if cmd[2].blank? && @parent_status.nil? + convo_statuses = status.conversation.statuses.reorder(:id) first = convo_statuses.first next if status.conversation_id.nil? || first.account_id != @account.id + if cmd[2].present? cmd[2] = cmd[2][1..-1] if cmd[2].start_with?('@') cmd[2], cmd[3] = cmd[2].split('@', 2) if cmd[2].include?('@') @@ -316,9 +363,11 @@ class Bangtags parent_account = Account.find_by(username: cmd[2].strip, domain: cmd[3]) else next if @parent_status.nil? + parent_account = @parent_status.account end next if parent_account.nil? || parent_account.id == @account.id + ConversationKick.find_or_create_by(account_id: parent_account.id, conversation_id: status.conversation_id) convo_statuses.where(account_id: parent_account.id).find_each do |s| RemoveStatusForAccountService.new.call(@account, s) @@ -328,39 +377,48 @@ class Bangtags "Kicked @#{parent_account.acct} from [thread #{status.conversation_id}](#{TagManager.instance.url_for(first)}).", footer: '#!thread:kick' ) + when 'unkick' next if cmd[2].blank? && @parent_status.nil? + convo_statuses = status.conversation.statuses.reorder(:id) first = convo_statuses.first next if status.conversation_id.nil? || first.account_id != @account.id + if cmd[2].present? cmd[2] = cmd[2][1..-1] if cmd[2].start_with?('@') cmd[3] = cmd[3].strip unless cmd[3].nil? parent_account = Account.find_by(username: cmd[2].strip, domain: cmd[3]) else next if @parent_status.nil? + parent_account = @parent_status.account end next if parent_account.nil? || parent_account.id == @account.id + ConversationKick.where(account_id: parent_account.id, conversation_id: status.conversation_id).destroy_all service_dm( 'janitor', @account, "Allowed @#{parent_account.acct} back into [thread ##{status.conversation_id}](#{TagManager.instance.url_for(first)}).", footer: '#!thread:unkick' ) + when 'reall' if status.conversation_id.present? participants = Status.where(conversation_id: status.conversation_id) - .pluck(:account_id).uniq.without(@account.id) + .pluck(:account_id).uniq.without(@account.id) participants = Account.where(id: participants) - .pluck(:username, :domain) - .map { |a| "@#{a.compact.join('@')}" } + .pluck(:username, :domain) + .map { |a| "@#{a.compact.join('@')}" } participants = (cmd[2..-1].map(&:strip) | participants) unless cmd[2].nil? chunk = participants.join(' ') end + when 'sharekey' next if cmd[2].nil? + case cmd[2].downcase + when 'revoke' if status.conversation_id.present? roars = Status.where(conversation_id: status.conversation_id, account_id: @account.id) @@ -371,6 +429,7 @@ class Bangtags end end end + when 'sync', 'new' if status.conversation_id.present? roars = Status.where(conversation_id: status.conversation_id, account_id: @account.id) @@ -393,87 +452,107 @@ class Bangtags Rails.cache.delete("statuses/#{status.id}") end end + when 'emoji' next if status.conversation_id.nil? + roars = Status.where(conversation_id: status.conversation_id) roars.each do |roar| roar.emojis.each do |theirs| ours = CustomEmoji.find_or_initialize_by(shortcode: theirs.shortcode, domain: nil) - if ours.id.nil? - ours.image = theirs.image - ours.save - user_friendly_action_log(@account, :create, ours) - end + next unless ours.id.nil? + + ours.image = theirs.image + ours.save + user_friendly_action_log(@account, :create, ours) end end + when 'noreplies', 'noats', 'close' next if status.conversation_id.nil? + roars = Status.where(conversation_id: status.conversation_id, account_id: @account.id) roars.each do |roar| roar.reject_replies = true roar.save Rails.cache.delete("statuses/#{roar.id}") end + when 'fetch', 'refetch' next if status.conversation_id.nil? + Status.where(conversation_id: status.conversation_id).pluck(:id).uniq.each do |acct_id| FetchAvatarWorker.perform_async(acct_id) end media_ids = Status.where(conversation_id: status.conversation_id).joins(:media_attachments).distinct(:id).pluck(:id) BatchFetchMediaWorker.perform_async(media_ids) unless media_ids.empty? end + when 'parent' chunk = nil next if cmd[1].nil? || @parent_status.nil? + case cmd[1].downcase + when 'permalink', 'link' chunk = TagManager.instance.url_for(@parent_status) + when 'tag', 'untag' chunk = nil next unless @parent_status.account.id == @account.id || @user.admin? - tags = cmd[2..-1].map {|t| t.gsub(':', '.')} + + tags = cmd[2..-1].map { |t| t.gsub(':', '.') } if cmd[1].downcase == 'tag' add_tags(@parent_status, *tags) else del_tags(@parent_status, *tags) end Rails.cache.delete("statuses/#{@parent_status.id}") + when 'emoji' @parent_status.emojis.each do |theirs| ours = CustomEmoji.find_or_initialize_by(shortcode: theirs.shortcode, domain: nil) - if ours.id.nil? - ours.image = theirs.image - ours.save - user_friendly_action_log(@account, :create, ours) - end + next unless ours.id.nil? + + ours.image = theirs.image + ours.save + user_friendly_action_log(@account, :create, ours) end + when 'urls' plain = @parent_status.text.gsub(/(<br \/>|<br>|<\/p>)+/) { |match| "#{match}\n" } plain = ActionController::Base.helpers.strip_tags(plain) plain.gsub!(/ dot /i, '.') chunk = plain.scan(/https?:\/\/[\w\-]+\.[\w\-]+(?:\.[\w\-]+)*/).uniq.join(' ') + when 'domains' plain = @parent_status.text.gsub(/(<br \/>|<br>|<\/p>)+/) { |match| "#{match}\n" } plain = ActionController::Base.helpers.strip_tags(plain) plain.gsub!(/ dot /i, '.') chunk = plain.scan(/[\w\-]+\.[\w\-]+(?:\.[\w\-]+)*/).uniq.join(' ') + when 'noreplies', 'noats', 'close' next unless @parent_status.account.id == @account.id || @user.admin? + @parent_status.reject_replies = true @parent_status.save Rails.cache.delete("statuses/#{@parent_status.id}") + when 'bookmark', 'bm' Bookmark.find_or_create_by!(account: @account, status: @parent_status) next if @parent_status.curated || !@parent_status.distributable? next if @parent_status.reply? && @status.in_reply_to_account_id != @account.id + @parent_status.update(curated: true) FanOutOnWriteService.new.call(@parent_status) + when 'fetch', 'refetch' chunk = nil media_ids = @parent_status.media_attachments.pluck(:id) BatchFetchMediaWorker.perform_async(media_ids) unless media_ids.empty? FetchAvatarWorker.perform_async(@parent_status.account.id) end + when 'media' chunk = nil @@ -482,10 +561,12 @@ class Bangtags media_args = cmd[3..-1] next unless media_cmd.present? && media_idx.present? && media_idx.scan(/\D/).empty? + media_idx = media_idx.to_i - next if status.media_attachments[media_idx-1].nil? + next if status.media_attachments[media_idx - 1].nil? case media_cmd.downcase + when 'desc' if media_args.present? @vars["_media:#{media_idx}:desc"] = media_args.join(':') @@ -497,11 +578,14 @@ class Bangtags end @post_cmds.push(['media', media_idx, media_cmd]) + when 'bangtag' chunk = orig_chunk.sub('bangtag:', '').gsub(':', ":\u200b") + when 'join' chunk = nil next if cmd[1].nil? + charmap = { 'zws' => "\u200b", 'zwnj' => "\u200c", @@ -509,35 +593,42 @@ class Bangtags '\n' => "\n", '\r' => "\r", '\t' => "\t", - '\T' => ' ' + '\T' => ' ', } sep = charmap[cmd[1]] chunk = cmd[2..-1].join(sep.nil? ? cmd[1] : sep) + when 'hide' chunk = nil next if cmd[1].nil? + case cmd[1].downcase + when 'end', 'stop', 'endall', 'stopall' - @vore_stack.reject! {|v| v == '_'} - @compontent_stack.reject! {|c| c == :hide} + @vore_stack.reject! { |v| v == '_' } + @compontent_stack.reject! { |c| c == :hide } else if cmd[1].nil? && !'_'.in?(@vore_stack) @vore_stack.push('_') @component_stack.push(:hide) end end + when 'comment' chunk = nil if cmd[1].nil? @vore_stack.push('_comment') @component_stack.push(:var) end + when 'i', 'we' chunk = nil cmd.shift c = cmd.shift next if c.nil? + case c.downcase + when 'am', 'are' if cmd[0].blank? @vars.delete('_they:are') @@ -548,6 +639,7 @@ class Bangtags name = name.downcase.gsub(/\s+/, '') @vars.delete("_they:are:#{name}") next unless @vars['_they:are'] == name + @vars.delete('_they:are') status.footer = nil end @@ -584,9 +676,7 @@ class Bangtags name = who.downcase.gsub(/\s+/, '').strip description = cmd[1..-1].join(':').strip if description.blank? - if @vars["_they:are:#{name}"].nil? - @vars["_they:are:#{name}"] = who.strip - end + @vars["_they:are:#{name}"] = who.strip if @vars["_they:are:#{name}"].nil? else @vars["_they:are:#{name}"] = description end @@ -595,13 +685,17 @@ class Bangtags @vars['_they:are'] = name unless @once status.footer = @vars["_they:are:#{name}"] end + when 'sharekey' next if cmd[1].nil? + case cmd[1].downcase + when 'new' chunk = nil status.sharekey = SecureRandom.urlsafe_base64(32) end + when 'draft' chunk = nil @status.visibility = :direct @@ -609,79 +703,85 @@ class Bangtags @vore_stack.push('_draft') @component_stack.push(:var) add_tags(status, 'self.draft') + when 'format', 'type' chunk = nil next if cmd[1].nil? + content_types = { - 't' => 'text/plain', - 'txt' => 'text/plain', - 'text' => 'text/plain', - 'plain' => 'text/plain', - 'plaintext' => 'text/plain', - - 'c' => 'text/console', - 'console' => 'text/console', - 'terminal' => 'text/console', - 'monospace' => 'text/console', - - 'm' => 'text/markdown', - 'md' => 'text/markdown', - 'markdown' => 'text/markdown', - - 'b' => 'text/x-bbcode', - 'bbc' => 'text/x-bbcode', - 'bbcode' => 'text/x-bbcode', - - 'd' => 'text/x-bbcode+markdown', - 'bm' => 'text/x-bbcode+markdown', - 'bbm' => 'text/x-bbcode+markdown', - 'bbdown' => 'text/x-bbcode+markdown', - - 'h' => 'text/html', - 'htm' => 'text/html', - 'html' => 'text/html', + 't' => 'text/plain', + 'txt' => 'text/plain', + 'text' => 'text/plain', + 'plain' => 'text/plain', + 'plaintext' => 'text/plain', + + 'c' => 'text/console', + 'console' => 'text/console', + 'terminal' => 'text/console', + 'monospace' => 'text/console', + + 'm' => 'text/markdown', + 'md' => 'text/markdown', + 'markdown' => 'text/markdown', + + 'b' => 'text/x-bbcode', + 'bbc' => 'text/x-bbcode', + 'bbcode' => 'text/x-bbcode', + + 'd' => 'text/x-bbcode+markdown', + 'bm' => 'text/x-bbcode+markdown', + 'bbm' => 'text/x-bbcode+markdown', + 'bbdown' => 'text/x-bbcode+markdown', + + 'h' => 'text/html', + 'htm' => 'text/html', + 'html' => 'text/html', } v = cmd[1].downcase status.content_type = content_types[c] unless content_types[c].nil? + when 'visibility', 'v' chunk = nil next if cmd[1].nil? + visibilities = { - 'direct' => :direct, - 'dm' => :direct, - 'whisper' => :direct, - 'd' => :direct, - - 'private' => :private, - 'packmate' => :private, - 'group' => :private, - 'f' => :private, - 'g' => :private, - - 'unlisted' => :unlisted, - 'u' => :unlisted, - - 'local' => :local, - 'monsterpit' => :local, - 'community' => :local, - 'c' => :local, - 'l' => :local, - 'm' => :local, - - 'public' => :public, - 'world' => :public, - 'p' => :public, + 'direct' => :direct, + 'dm' => :direct, + 'whisper' => :direct, + 'd' => :direct, + + 'private' => :private, + 'packmate' => :private, + 'group' => :private, + 'f' => :private, + 'g' => :private, + + 'unlisted' => :unlisted, + 'u' => :unlisted, + + 'local' => :local, + 'monsterpit' => :local, + 'community' => :local, + 'c' => :local, + 'l' => :local, + 'm' => :local, + + 'public' => :public, + 'world' => :public, + 'p' => :public, } allowed_visibility_changes = { - 'unlisted' => [:local], - 'local' => [:unlisted], + 'unlisted' => [:local], + 'local' => [:unlisted], } if cmd[1].downcase == 'parent' next unless cmd[2].present? && @parent_status.present? && @parent_status.account_id == @account.id + v = visibilities[cmd[2].downcase] o = @parent_status.visibility next if v.nil? || allowed_visibility_changes[o].nil? next unless allowed_visibility_changes[o].include?(v) + @parent_status.visibility = v @parent_status.local_only = false if cmd[3].downcase.in? %w(federate f public p world) @parent_status.save @@ -692,24 +792,31 @@ class Bangtags v = cmd[1].downcase status.visibility = visibilities[v] unless visibilities[v].nil? case cmd[2]&.downcase + when 'federate', 'f', 'public', 'p', 'world' status.local_only = false + when 'nofederate', 'nf', 'localonly', 'lo', 'local', 'l', 'monsterpit', 'm', 'community', 'c' status.local_only = true end end + when 'noreplies', 'noats' chunk = nil @status.reject_replies = true + when 'live', 'lifespan', 'l', 'delete_in' chunk = nil next if cmd[1].nil? + case cmd[1].downcase + when 'parent', 'thread', 'all' s = cmd[1].downcase.to_sym s = @parent_status if s == :parent - next unless s == :all || @parent_status.present? + next unless s == :all || @parent_status.present? next unless s == :thread || s == :all || @parent_status.account_id == @account.id + i = cmd[2].to_i unit = cmd[3].present? ? cmd[3].downcase : 'minutes' else @@ -718,16 +825,22 @@ class Bangtags unit = cmd[2].present? ? cmd[2].downcase : 'minutes' end delete_after = case unit + when 'm', 'min', 'mins', 'minute', 'minutes' i.minutes + when 'h', 'hr', 'hrs', 'hour', 'hours' i.hours + when 'd', 'dy', 'dys', 'day', 'days' i.days + when 'w', 'wk', 'wks', 'week', 'weeks' i.weeks + when 'mn', 'mns', 'month', 'months' i.months + when 'y', 'yr', 'yrs', 'year', 'years' i.years end @@ -745,15 +858,19 @@ class Bangtags s.delete_after = delete_after Rails.cache.delete("statuses/#{s.id}") end + when 'd', 'defed', 'defed_in', 'defederate', 'defederate_in' chunk = nil next if cmd[1].nil? + case cmd[1].downcase + when 'parent', 'thread', 'all' s = cmd[1].downcase.to_sym s = @parent_status if s == :parent - next unless s == :all || @parent_status.present? + next unless s == :all || @parent_status.present? next unless s == :thread || s == :all || @parent_status.account_id == @account.id + i = cmd[2].to_i unit = cmd[3].present? ? cmd[3].downcase : 'minutes' else @@ -762,16 +879,22 @@ class Bangtags unit = cmd[2].present? ? cmd[2].downcase : 'minutes' end defederate_after = case unit + when 'm', 'min', 'mins', 'minute', 'minutes' i.minutes + when 'h', 'hr', 'hrs', 'hour', 'hours' i.hours + when 'd', 'dy', 'dys', 'day', 'days' i.days + when 'w', 'wk', 'wks', 'week', 'weeks' i.weeks + when 'mn', 'mns', 'month', 'months' i.months + when 'y', 'yr', 'yrs', 'year', 'years' i.years end @@ -789,6 +912,7 @@ class Bangtags s.defederate_after = defederate_after Rails.cache.delete("statuses/#{s.id}") end + when 'keysmash' keyboard = [ 'asdf', 'jkl;', @@ -801,32 +925,38 @@ class Bangtags 'y', 'b', 'p', '[', '.', '/', - ']', "\\", + ']', '\\' ] - chunk = rand(6..33).times.collect do - keyboard[(keyboard.size * (rand ** 3)).floor].split('').sample + chunk = rand(6..33).times.map do + keyboard[(keyboard.size * (rand**3)).floor].split('').sample end chunk = chunk.join + when 'nosr', 'sroff', 'srskip' next if @sroff_open + @sroff_open = true chunk = "\ufdd3" + when 'sr', 'sron', 'srcont' next unless @sroff_open + @sroff_open = false chunk = "\ufdd4" + when 'histogram' @status.content_type = 'text/html' barchars = " #{(0x2588..0x258F).to_a.reverse.pack('U*')}" q = cmd[1..-1].join.strip next if q.blank? + begin data = @account.statuses.search(q.unaccent) - .reorder(:created_at) - .pluck(:created_at) - .map { |d| d.strftime('%Y-%m') } - .reduce(Hash.new(0)) { |h, v| h.store(v, h[v] + 1); h } + .reorder(:created_at) + .pluck(:created_at) + .map { |d| d.strftime('%Y-%m') } + .each_with_object(Hash.new(0)) { |v, h| h.store(v, h[v] + 1); } rescue ActiveRecord::StatementInvalid raise Mastodon::ValidationError, 'Invalid query.' end @@ -838,48 +968,56 @@ class Bangtags bar = "#{"\u2588" * (fill / 8).to_i}#{barchars[fill % 8]}" "<code>#{date}: #{bar} #{count}</code>" end - chunk = "<p>\"<code>#{q.split('').join("\u200c")}</code>\" mentions by post count:<br/>#{data.join("<br/>")}<br/>#{avg}<br/>#{total}</p>" + chunk = "<p>\"<code>#{q.split('').join("\u200c")}</code>\" mentions by post count:<br/>#{data.join('<br/>')}<br/>#{avg}<br/>#{total}</p>" + when 'admin' chunk = nil next unless @user.admin? next if cmd[1].nil? + @status.visibility = :direct @status.local_only = true add_tags(@status, 'monsterpit.admin.log') @status.content_type = 'text/markdown' @chunks << "\n# <code>#!</code><code>admin:#{cmd[1].downcase}</code>:\n<br/>\n" case cmd[1].downcase + when 'silence', 'unsilence', 'suspend', 'unsuspend', 'force_unlisted', 'allow_public', 'force_sensitive', 'allow_nonsensitive', 'reset', 'forgive' @status.spoiler_text = "admin #{cmd[1].downcase}" if @status.spoiler_text.blank? @tf_cmds.push(cmd) @component_stack.push(:tf) + when 'exec', 'eval' unless @account.username.in?((ENV['ALLOW_ADMIN_EVAL_FROM'] || '').split) - @chunks << "<em>Unauthorized.</em>" + @chunks << '<em>Unauthorized.</em>' next end unless cmd[2].present? && cmd[2].downcase == 'last' - @vars.delete("_admin:eval") - @vore_stack.push("_admin:eval") + @vars.delete('_admin:eval') + @vore_stack.push('_admin:eval') @component_stack.push(:var) end - @post_cmds.push(['admin', 'eval']) + @post_cmds.push(%w(admin eval)) + when 'announce' - @vars.delete("_admin:announce") - @vore_stack.push("_admin:announce") + @vars.delete('_admin:announce') + @vore_stack.push('_admin:announce') @component_stack.push(:var) - c = ['admin', 'announce'] + c = %w(admin announce) c << 'local' if cmd[2].present? && cmd[2].downcase == 'local' @post_cmds.push(c) + when 'unannounce' @tf_cmds.push(cmd) @component_stack.push(:tf) end + when 'account' chunk = nil cmd.shift c = cmd.shift next if c.nil? + @status.visibility = :direct @status.local_only = true @status.content_type = 'text/markdown' @@ -887,10 +1025,13 @@ class Bangtags @chunks << "\n# <code>#!</code><code>account:#{c.downcase}</code>:\n<br />\n" output = [] case c.downcase + when 'link' c = cmd.shift next if c.nil? + case c.downcase + when 'add' target = cmd.shift token = cmd.shift @@ -913,29 +1054,34 @@ class Bangtags LinkedUser.find_or_create_by!(user_id: @user.id, target_user_id: target_acct.user.id) LinkedUser.find_or_create_by!(user_id: target_acct.user.id, target_user_id: @user.id) output << "\u2705 Linked with <strong>@\u200c#{target}</strong>." + when 'del', 'delete' cmd.each do |target| target_acct = Account.find_local(target) next if target_acct&.user.nil? || target_acct.id == @account.id + LinkedUser.where(user_id: @user.id, target_user_id: target_acct.user.id).destroy_all LinkedUser.where(user_id: target_acct.user.id, target_user_id: @user.id).destroy_all output << "\u2705 <strong>@\u200c#{target}</strong> unlinked." end + when 'clear', 'delall', 'deleteall' LinkedUser.where(target_user_id: @user.id).destroy_all LinkedUser.where(user_id: @user.id).destroy_all output << "\u2705 Cleared all links." + when 'token' @vars['_account:link:token'] = SecureRandom.urlsafe_base64(32) - output << "Account link token is:" + output << 'Account link token is:' output << "<code>#{@vars['_account:link:token']}</code>" output << '' - output << "On the local account you want to link, paste:" + output << 'On the local account you want to link, paste:' output << "<code>#!account:link:add:#{@account.username}:#{@vars['_account:link:token']}</code>" output << '' output << 'The token can only be used once.' output << '' output << "\xe2\x9a\xa0\xef\xb8\x8f <strong>This grants full access to your account! Be careful!</strong>" + when 'list' @user.linked_users.find_each do |linked_user| if linked_user&.account.nil? @@ -948,26 +1094,31 @@ class Bangtags end output = ['<em>No action.</em>'] if output.blank? chunk = output.join("\n") + "\n" + when 'queued', 'scheduled' chunk = nil next if cmd[1].nil? + case cmd[1].downcase + when 'boosts', 'repeats' output = ["# Queued boosts\n"] @account.queued_boosts.find_each do |q| s = Status.find_by(id: q.status_id) next if s.nil? + output << "\\- [#{q.status_id}](#{TagManager.instance.url_for(s)})" end - if Redis.current.exists("queued_boost:#{@account.id}") - output << "\nNext boost in #{Redis.current.ttl("queued_boost:#{@account.id}") / 60} minutes." - else - output << "\nNothing scheduled yet." - end + output << if Redis.current.exists("queued_boost:#{@account.id}") + "\nNext boost in #{Redis.current.ttl("queued_boost:#{@account.id}") / 60} minutes." + else + "\nNothing scheduled yet." + end service_dm('announcements', @account, output.join("\n"), footer: '#!queued:boosts') + when 'posts', 'statuses', 'roars' - output = ["<h1>Queued roars</h1><br>"] + output = ['<h1>Queued roars</h1><br>'] @account.scheduled_statuses.find_each do |s| s = Status.find_by(id: s.status_id) next if s.nil? @@ -979,7 +1130,7 @@ class Bangtags output << "- <a href=\"#{TagManager.instance.url_for(s)}\">#{preview}</a>" end - service_dm('announcements', @account, output.join("<br>"), content_type: 'text/html', footer: '#!queued:posts') + service_dm('announcements', @account, output.join('<br>'), content_type: 'text/html', footer: '#!queued:posts') end end end @@ -989,34 +1140,44 @@ class Bangtags if chunk.present? && @tf_cmds.present? @tf_cmds.each do |tf_cmd| next if chunk.nil? || tf_cmd[0].nil? + case tf_cmd[0].downcase + when 'replace', 'sub', 's' tf_cmd[1..-1].in_groups_of(2) do |args| chunk.sub!(*args) if args.all? end + when 'replaceall', 'gsub', 'gs' tf_cmd[1..-1].in_groups_of(2) do |args| chunk.gsub!(*args) if args.all? end + when 'stripanchors' chunk.gsub!(/<a .*?<\/a>/mi, '') + when 'striplinks' chunk.gsub!(/\S+:\/\/[\w\-]+\.\S+/, '') chunk = ActionController::Base.helpers.strip_links(chunk) + when 'head', 'take' n = tf_cmd[1].to_i n = 1 unless n > 0 next if @vars['_tf:head:count'] == n + c = @vars['_tf:head:count'] || 0 parts = chunk.split.take(n - c) @vars['_tf:head:full'] = c + parts.count chunk = parts.join(' ') + when 'admin' next unless @user.admin? next if tf_cmd[1].nil? || chunk.start_with?('`admin:') + output = [] action = tf_cmd[1].downcase case action + when 'unannounce' announcer = ENV['ANNOUNCEMENTS_USER'].to_i if announcer == 0 @@ -1042,6 +1203,7 @@ class Bangtags RemoveStatusService.new.call(s) end end + when 'silence', 'unsilence', 'suspend', 'unsuspend', 'force_unlisted', 'allow_public', 'force_sensitive', 'allow_nonsensitive', 'reset', 'forgive' action = 'reset' if action == 'forgive' reason = tf_cmd[2..-1].join(':') @@ -1052,15 +1214,15 @@ class Bangtags else successful = domain_policy(c, action, reason) end - if successful - output << "\u2705 <code>#{c}</code>" - else - output << "\u274c <code>#{c}</code>" - end + output << if successful + "\u2705 <code>#{c}</code>" + else + "\u274c <code>#{c}</code>" + end end if output.blank? output = ['<em>No action.</em>'] - elsif !reason.blank? + elsif reason.present? output << '' output << "<strong>Comment:</strong> <em>#{reason}</em>" end @@ -1073,6 +1235,7 @@ class Bangtags unless chunk.blank? || @vore_stack.empty? var = @vore_stack.last next if var == '_' + if @vars[var].nil? @vars[var] = chunk.lstrip else @@ -1084,7 +1247,7 @@ class Bangtags @chunks << chunk unless chunk.nil? end - @vars.transform_values! {|v| v.rstrip if v.is_a?(String)} + @vars.transform_values! { |v| v.rstrip if v.is_a?(String) } postprocess_before_save @@ -1093,7 +1256,7 @@ class Bangtags text = @chunks.join text.gsub!(/\n\n+/, "\n") if @crunch_newlines text.strip! - text = text.split("\n").map { |chunk| chunk.strip }.join("\n") if @strip_lines + text = text.split("\n").map(&:strip).join("\n") if @strip_lines if text.blank? || has_only_mentions?(text) RemoveStatusService.new.call(@status) @@ -1109,37 +1272,43 @@ class Bangtags def postprocess_before_save @post_cmds.each do |post_cmd| case post_cmd[0] + when 'media' media_idx = post_cmd[1] media_cmd = post_cmd[2] media_args = post_cmd[3..-1] case media_cmd + when 'desc' - status.media_attachments[media_idx-1].description = @vars["_media:#{media_idx}:desc"] - status.media_attachments[media_idx-1].save + status.media_attachments[media_idx - 1].description = @vars["_media:#{media_idx}:desc"] + status.media_attachments[media_idx - 1].save @vars.delete("_media:#{media_idx}:desc") end + when 'admin' next unless @user.admin? next if post_cmd[1].nil? + case post_cmd[1] + when 'eval' @crunch_newlines = true - @vars["_admin:eval"].strip! + @vars['_admin:eval'].strip! @chunks << "\n<strong>Input:</strong>" - @chunks << "<pre><code>" - @chunks << html_entities.encode(@vars["_admin:eval"]).gsub("\n", '<br/>') - @chunks << "</code></pre>" + @chunks << '<pre><code>' + @chunks << html_entities.encode(@vars['_admin:eval']).gsub("\n", '<br/>') + @chunks << '</code></pre>' begin - result = eval(@vars["_admin:eval"]) + result = eval(@vars['_admin:eval']) rescue Exception => e result = "\u274c #{e.message}" end - @chunks << "<strong>Output:</strong>" - @chunks << "<pre><code>" + @chunks << '<strong>Output:</strong>' + @chunks << '<pre><code>' @chunks << html_entities.encode(result).gsub("\n", '<br/>') - @chunks << "</code></pre>" + @chunks << '</code></pre>' + when 'announce' announcer = ENV['ANNOUNCEMENTS_USER'].to_i if announcer == 0 @@ -1153,11 +1322,11 @@ class Bangtags end name = @user.vars['_they:are'] - if name.present? - footer = "#{@user.vars["_they:are:#{name}"]} from @#{@account.username}" - else - footer = "@#{@account.username}" - end + footer = if name.present? + "#{@user.vars["_they:are:#{name}"]} from @#{@account.username}" + else + "@#{@account.username}" + end s = PostStatusService.new.call( announcer, @@ -1178,6 +1347,7 @@ class Bangtags def postprocess_after_save @post_cmds.each do |post_cmd| case post_cmd[0] + when 'mention' mention = @account.mentions.where(status: status).first_or_create(status: status) end @@ -1190,22 +1360,23 @@ class Bangtags def add_tags(to_status, *tags) valid_name = /^[[:word:]:._\-]*[[:alpha:]:._·\-][[:word:]:._\-]*$/ - tags = tags.select {|t| t.present? && valid_name.match?(t)}.uniq + tags = tags.select { |t| t.present? && valid_name.match?(t) }.uniq ProcessHashtagsService.new.call(to_status, tags) to_status.save end def del_tags(from_status, *tags) valid_name = /^[[:word:]:._\-]*[[:alpha:]:._·\-][[:word:]:._\-]*$/ - tags = tags.select {|t| t.present? && valid_name.match?(t)}.uniq + tags = tags.select { |t| t.present? && valid_name.match?(t) }.uniq tags.map { |str| str.mb_chars.downcase }.uniq(&:to_s).each do |name| name.gsub!(/[:.]+/, '.') next if name.blank? || name == '.' - if name.ends_with?('.') - filtered_tags = from_status.tags.select { |t| t.name == name || t.name.starts_with?(name) } - else - filtered_tags = from_status.tags.select { |t| t.name == name } - end + + filtered_tags = if name.ends_with?('.') + from_status.tags.select { |t| t.name == name || t.name.starts_with?(name) } + else + from_status.tags.select { |t| t.name == name } + end from_status.tags.destroy(filtered_tags) end from_status.save @@ -1214,6 +1385,7 @@ class Bangtags def switch_account(target_acct) target_acct = Account.find_local(target_acct) return false unless target_acct&.user.present? && target_acct.user.in?(@user.linked_users) + Redis.current.publish("timeline:#{@account.id}", Oj.dump(event: :switch_accounts, payload: target_acct.user.id)) true end @@ -1221,6 +1393,7 @@ class Bangtags def post_as(target_acct) target_acct = Account.find_local(target_acct) return false unless target_acct&.user.present? && target_acct.user.in?(@user.linked_users) + status.account_id = target_acct.id end |