about summary refs log tree commit diff
path: root/app/lib
diff options
context:
space:
mode:
Diffstat (limited to 'app/lib')
-rw-r--r--app/lib/feed_manager.rb8
-rw-r--r--app/lib/frontmatter_handler.rb223
-rw-r--r--app/lib/themes.rb71
-rw-r--r--app/lib/user_settings_decorator.rb16
4 files changed, 313 insertions, 5 deletions
diff --git a/app/lib/feed_manager.rb b/app/lib/feed_manager.rb
index dd78e543f..700fd61c4 100644
--- a/app/lib/feed_manager.rb
+++ b/app/lib/feed_manager.rb
@@ -149,7 +149,10 @@ class FeedManager
     return false if receiver_id == status.account_id
     return true  if status.reply? && (status.in_reply_to_id.nil? || status.in_reply_to_account_id.nil?)
 
+    return true if keyword_filter?(status, receiver_id)
+
     check_for_mutes = [status.account_id]
+    check_for_mutes.concat(status.mentions.pluck(:account_id))
     check_for_mutes.concat([status.reblog.account_id]) if status.reblog?
 
     return true if Mute.where(account_id: receiver_id, target_account_id: check_for_mutes).any?
@@ -174,6 +177,10 @@ class FeedManager
     false
   end
 
+  def keyword_filter?(status, receiver_id)
+    Glitch::KeywordMuteHelper.new(receiver_id).matches?(status)
+  end
+
   def filter_from_mentions?(status, receiver_id)
     return true if receiver_id == status.account_id
 
@@ -183,6 +190,7 @@ class FeedManager
 
     should_filter   = Block.where(account_id: receiver_id, target_account_id: check_for_blocks).any?                                     # Filter if it's from someone I blocked, in reply to someone I blocked, or mentioning someone I blocked
     should_filter ||= (status.account.silenced? && !Follow.where(account_id: receiver_id, target_account_id: status.account_id).exists?) # of if the account is silenced and I'm not following them
+    should_filter ||= keyword_filter?(status, receiver_id)                                                                               # or if the mention contains a muted keyword
 
     should_filter
   end
diff --git a/app/lib/frontmatter_handler.rb b/app/lib/frontmatter_handler.rb
new file mode 100644
index 000000000..fa787630d
--- /dev/null
+++ b/app/lib/frontmatter_handler.rb
@@ -0,0 +1,223 @@
+# frozen_string_literal: true
+
+require 'singleton'
+
+#  See also `app/javascript/features/account/util/bio_metadata.js`.
+
+class FrontmatterHandler
+  include Singleton
+
+  #  CONVENIENCE FUNCTIONS  #
+
+  def self.unirex(str)
+    Regexp.new str, Regexp::MULTILINE
+  end
+  def self.rexstr(exp)
+    '(?:' + exp.source + ')'
+  end
+
+  #  CHARACTER CLASSES  #
+
+  DOCUMENT_START    = /^/
+  DOCUMENT_END      = /$/
+  ALLOWED_CHAR      =  #  c-printable` in the YAML 1.2 spec.
+    /[\t\n\r\u{20}-\u{7e}\u{85}\u{a0}-\u{d7ff}\u{e000}-\u{fffd}\u{10000}-\u{10ffff}]/u
+  WHITE_SPACE       = /[ \t]/
+  INDENTATION       = / */
+  LINE_BREAK        = /\r?\n|\r|<br\s*\/?>/
+  ESCAPE_CHAR       = /[0abt\tnvfre "\/\\N_LP]/
+  HEXADECIMAL_CHARS = /[0-9a-fA-F]/
+  INDICATOR         = /[-?:,\[\]{}&#*!|>'"%@`]/
+  FLOW_CHAR         = /[,\[\]{}]/
+
+  #  NEGATED CHARACTER CLASSES  #
+
+  NOT_WHITE_SPACE   = unirex '(?!' + rexstr(WHITE_SPACE) + ').'
+  NOT_LINE_BREAK    = unirex '(?!' + rexstr(LINE_BREAK) + ').'
+  NOT_INDICATOR     = unirex '(?!' + rexstr(INDICATOR) + ').'
+  NOT_FLOW_CHAR     = unirex '(?!' + rexstr(FLOW_CHAR) + ').'
+  NOT_ALLOWED_CHAR  = unirex '(?!' + rexstr(ALLOWED_CHAR) + ').'
+
+  #  BASIC CONSTRUCTS  #
+
+  ANY_WHITE_SPACE   = unirex rexstr(WHITE_SPACE) + '*'
+  ANY_ALLOWED_CHARS = unirex rexstr(ALLOWED_CHAR) + '*'
+  NEW_LINE          = unirex(
+    rexstr(ANY_WHITE_SPACE) + rexstr(LINE_BREAK)
+  )
+  SOME_NEW_LINES    = unirex(
+    '(?:' + rexstr(ANY_WHITE_SPACE) + rexstr(LINE_BREAK) + ')+'
+  )
+  POSSIBLE_STARTS   = unirex(
+    rexstr(DOCUMENT_START) + rexstr(/<p[^<>]*>/) + '?'
+  )
+  POSSIBLE_ENDS     = unirex(
+    rexstr(SOME_NEW_LINES) + '|' +
+    rexstr(DOCUMENT_END) + '|' +
+    rexstr(/<\/p>/)
+  )
+  CHARACTER_ESCAPE  = unirex(
+    rexstr(/\\/) +
+    '(?:' +
+      rexstr(ESCAPE_CHAR) + '|' +
+      rexstr(/x/) + rexstr(HEXADECIMAL_CHARS) + '{2}' + '|' +
+      rexstr(/u/) + rexstr(HEXADECIMAL_CHARS) + '{4}' + '|' +
+      rexstr(/U/) + rexstr(HEXADECIMAL_CHARS) + '{8}' +
+    ')'
+  )
+  ESCAPED_CHAR      = unirex(
+    rexstr(/(?!["\\])/) + rexstr(NOT_LINE_BREAK) + '|' +
+    rexstr(CHARACTER_ESCAPE)
+  )
+  ANY_ESCAPED_CHARS = unirex(
+    rexstr(ESCAPED_CHAR) + '*'
+  )
+  ESCAPED_APOS      = unirex(
+    '(?=' + rexstr(NOT_LINE_BREAK) + ')' + rexstr(/[^']|''/)
+  )
+  ANY_ESCAPED_APOS  = unirex(
+    rexstr(ESCAPED_APOS) + '*'
+  )
+  FIRST_KEY_CHAR    = unirex(
+    '(?=' + rexstr(NOT_LINE_BREAK) + ')' +
+    '(?=' + rexstr(NOT_WHITE_SPACE) + ')' +
+    rexstr(NOT_INDICATOR) + '|' +
+    rexstr(/[?:-]/) +
+    '(?=' + rexstr(NOT_LINE_BREAK) + ')' +
+    '(?=' + rexstr(NOT_WHITE_SPACE) + ')' +
+    '(?=' + rexstr(NOT_FLOW_CHAR) + ')'
+  )
+  FIRST_VALUE_CHAR  = unirex(
+    '(?=' + rexstr(NOT_LINE_BREAK) + ')' +
+    '(?=' + rexstr(NOT_WHITE_SPACE) + ')' +
+    rexstr(NOT_INDICATOR) + '|' +
+    rexstr(/[?:-]/) +
+    '(?=' + rexstr(NOT_LINE_BREAK) + ')' +
+    '(?=' + rexstr(NOT_WHITE_SPACE) + ')'
+    #  Flow indicators are allowed in values.
+  )
+  LATER_KEY_CHAR    = unirex(
+    rexstr(WHITE_SPACE) + '|' +
+    '(?=' + rexstr(NOT_LINE_BREAK) + ')' +
+    '(?=' + rexstr(NOT_WHITE_SPACE) + ')' +
+    '(?=' + rexstr(NOT_FLOW_CHAR) + ')' +
+    rexstr(/[^:#]#?/) + '|' +
+    rexstr(/:/) + '(?=' + rexstr(NOT_WHITE_SPACE) + ')'
+  )
+  LATER_VALUE_CHAR  = unirex(
+    rexstr(WHITE_SPACE) + '|' +
+    '(?=' + rexstr(NOT_LINE_BREAK) + ')' +
+    '(?=' + rexstr(NOT_WHITE_SPACE) + ')' +
+    #  Flow indicators are allowed in values.
+    rexstr(/[^:#]#?/) + '|' +
+    rexstr(/:/) + '(?=' + rexstr(NOT_WHITE_SPACE) + ')'
+  )
+
+  #  YAML CONSTRUCTS  #
+
+  YAML_START        = unirex(
+    rexstr(ANY_WHITE_SPACE) + rexstr(/---/)
+  )
+  YAML_END          = unirex(
+    rexstr(ANY_WHITE_SPACE) + rexstr(/(?:---|\.\.\.)/)
+  )
+  YAML_LOOKAHEAD    = unirex(
+    '(?=' +
+      rexstr(YAML_START) +
+      rexstr(ANY_ALLOWED_CHARS) + rexstr(NEW_LINE) +
+      rexstr(YAML_END) + rexstr(POSSIBLE_ENDS) +
+    ')'
+  )
+  YAML_DOUBLE_QUOTE = unirex(
+    rexstr(/"/) + rexstr(ANY_ESCAPED_CHARS) + rexstr(/"/)
+  )
+  YAML_SINGLE_QUOTE = unirex(
+    rexstr(/'/) + rexstr(ANY_ESCAPED_APOS) + rexstr(/'/)
+  )
+  YAML_SIMPLE_KEY   = unirex(
+    rexstr(FIRST_KEY_CHAR) + rexstr(LATER_KEY_CHAR) + '*'
+  )
+  YAML_SIMPLE_VALUE = unirex(
+    rexstr(FIRST_VALUE_CHAR) + rexstr(LATER_VALUE_CHAR) + '*'
+  )
+  YAML_KEY          = unirex(
+    rexstr(YAML_DOUBLE_QUOTE) + '|' +
+    rexstr(YAML_SINGLE_QUOTE) + '|' +
+    rexstr(YAML_SIMPLE_KEY)
+  )
+  YAML_VALUE        = unirex(
+    rexstr(YAML_DOUBLE_QUOTE) + '|' +
+    rexstr(YAML_SINGLE_QUOTE) + '|' +
+    rexstr(YAML_SIMPLE_VALUE)
+  )
+  YAML_SEPARATOR    = unirex(
+    rexstr(ANY_WHITE_SPACE) +
+    ':' + rexstr(WHITE_SPACE) +
+    rexstr(ANY_WHITE_SPACE)
+  )
+  YAML_LINE         = unirex(
+    '(' + rexstr(YAML_KEY) + ')' +
+    rexstr(YAML_SEPARATOR) +
+    '(' + rexstr(YAML_VALUE) + ')'
+  )
+
+  #  FRONTMATTER REGEX  #
+
+  YAML_FRONTMATTER  = unirex(
+    rexstr(POSSIBLE_STARTS) +
+    rexstr(YAML_LOOKAHEAD) +
+    rexstr(YAML_START) + rexstr(SOME_NEW_LINES) +
+    '(?:' +
+      '(' + rexstr(INDENTATION) + ')' +
+      rexstr(YAML_LINE) + rexstr(SOME_NEW_LINES) +
+      '(?:' +
+        '\\1' + rexstr(YAML_LINE) + rexstr(SOME_NEW_LINES) +
+      '){0,4}' +
+    ')?' +
+    rexstr(YAML_END) + rexstr(POSSIBLE_ENDS)
+  )
+
+  #  SEARCHES  #
+
+  FIND_YAML_LINES   = unirex(
+    rexstr(NEW_LINE) + rexstr(INDENTATION) + rexstr(YAML_LINE)
+  )
+
+  #  STRING PROCESSING  #
+
+  def process_string(str)
+    case str[0]
+    when '"'
+      str[1..-2]
+    when "'"
+      str[1..-2].gsub(/''/, "'")
+    else
+      str
+    end
+  end
+
+  #  BIO PROCESSING  #
+
+  def process_bio content
+    result = {
+      text: content.gsub(/&quot;/, '"').gsub(/&apos;/, "'"),
+      metadata: []
+    }
+    yaml = YAML_FRONTMATTER.match(result[:text])
+    return result unless yaml
+    yaml = yaml[0]
+    start = YAML_START =~ result[:text]
+    ending = start + yaml.length - (YAML_START =~ yaml)
+    result[:text][start..ending - 1] = ''
+    metadata = nil
+    index = 0
+    while metadata = FIND_YAML_LINES.match(yaml, index) do
+      index = metadata.end(0)
+      result[:metadata].push [
+        process_string(metadata[1]), process_string(metadata[2])
+      ]
+    end
+    return result
+  end
+
+end
diff --git a/app/lib/themes.rb b/app/lib/themes.rb
index 243ffb9ab..55824a5c4 100644
--- a/app/lib/themes.rb
+++ b/app/lib/themes.rb
@@ -7,10 +7,77 @@ class Themes
   include Singleton
 
   def initialize
-    @conf = YAML.load_file(Rails.root.join('config', 'themes.yml'))
+
+    core = YAML.load_file(Rails.root.join('app', 'javascript', 'core', 'theme.yml'))
+    core['pack'] = Hash.new unless core['pack']
+
+    result = Hash.new
+    Dir.glob(Rails.root.join('app', 'javascript', 'flavours', '*', 'theme.yml')) do |path|
+      data = YAML.load_file(path)
+      dir = File.dirname(path)
+      name = File.basename(dir)
+      locales = []
+      screenshots = []
+      if data['locales']
+        Dir.glob(File.join(dir, data['locales'], '*.{js,json}')) do |locale|
+          localeName = File.basename(locale, File.extname(locale))
+          locales.push(localeName) unless localeName.match(/defaultMessages|whitelist|index/)
+        end
+      end
+      if data['screenshot']
+        if data['screenshot'].is_a? Array
+          screenshots = data['screenshot']
+        else
+          screenshots.push(data['screenshot'])
+        end
+      end
+      if data['pack']
+        data['name'] = name
+        data['locales'] = locales
+        data['screenshot'] = screenshots
+        data['skin'] = { 'default' => [] }
+        result[name] = data
+      end
+    end
+
+    Dir.glob(Rails.root.join('app', 'javascript', 'skins', '*', '*')) do |path|
+      ext = File.extname(path)
+      skin = File.basename(path)
+      name = File.basename(File.dirname(path))
+      if result[name]
+        if File.directory?(path)
+          pack = []
+          Dir.glob(File.join(path, '*.{css,scss}')) do |sheet|
+            pack.push(File.basename(sheet, File.extname(sheet)))
+          end
+        elsif ext.match(/^\.s?css$/i)
+          skin = File.basename(path, ext)
+          pack = ['common']
+        end
+        if skin != 'default'
+          result[name]['skin'][skin] = pack
+        end
+      end
+    end
+
+    @core = core
+    @conf = result
+
   end
 
-  def names
+  def core
+    @core
+  end
+
+  def flavour(name)
+    @conf[name]
+  end
+
+  def flavours
     @conf.keys
   end
+
+  def skins_for(name)
+    @conf[name]['skin'].keys
+  end
 end
diff --git a/app/lib/user_settings_decorator.rb b/app/lib/user_settings_decorator.rb
index 9260a81bc..78b3aa77c 100644
--- a/app/lib/user_settings_decorator.rb
+++ b/app/lib/user_settings_decorator.rb
@@ -21,13 +21,15 @@ class UserSettingsDecorator
     user.settings['default_sensitive']       = default_sensitive_preference if change?('setting_default_sensitive')
     user.settings['unfollow_modal']          = unfollow_modal_preference if change?('setting_unfollow_modal')
     user.settings['boost_modal']             = boost_modal_preference if change?('setting_boost_modal')
+    user.settings['favourite_modal']         = favourite_modal_preference if change?('setting_favourite_modal')
     user.settings['delete_modal']            = delete_modal_preference if change?('setting_delete_modal')
     user.settings['auto_play_gif']           = auto_play_gif_preference if change?('setting_auto_play_gif')
     user.settings['display_sensitive_media'] = display_sensitive_media_preference if change?('setting_display_sensitive_media')
     user.settings['reduce_motion']           = reduce_motion_preference if change?('setting_reduce_motion')
     user.settings['system_font_ui']          = system_font_ui_preference if change?('setting_system_font_ui')
     user.settings['noindex']                 = noindex_preference if change?('setting_noindex')
-    user.settings['theme']                   = theme_preference if change?('setting_theme')
+    user.settings['flavour']                 = flavour_preference if change?('setting_flavour')
+    user.settings['skin']                    = skin_preference if change?('setting_skin')
   end
 
   def merged_notification_emails
@@ -53,6 +55,10 @@ class UserSettingsDecorator
   def boost_modal_preference
     boolean_cast_setting 'setting_boost_modal'
   end
+  
+  def favourite_modal_preference
+    boolean_cast_setting 'setting_favourite_modal'
+  end
 
   def delete_modal_preference
     boolean_cast_setting 'setting_delete_modal'
@@ -78,8 +84,12 @@ class UserSettingsDecorator
     boolean_cast_setting 'setting_noindex'
   end
 
-  def theme_preference
-    settings['setting_theme']
+  def flavour_preference
+    settings['setting_flavour']
+  end
+
+  def skin_preference
+    settings['setting_skin']
   end
 
   def boolean_cast_setting(key)