joliegg 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Jolie
4
+ module Moderation
5
+ # One detected moderation category, mirroring the TS package's
6
+ # { category, confidence } entries, plus the list entry that matched when
7
+ # the source is one of the bundled lists (nil for API-backed categories).
8
+ Category = Struct.new(:category, :confidence, :matched)
9
+
10
+ # The outcome of moderating one input, mirroring the TS package's
11
+ # ModerationResult: the original source plus every category detected on it.
12
+ Result = Struct.new(:source, :moderation) do
13
+ def flagged?
14
+ moderation.any?
15
+ end
16
+
17
+ def categories
18
+ moderation.map(&:category)
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,118 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+
5
+ module Jolie
6
+ module Moderation
7
+ # Domain blocklist and URL-shortener list extracted from the
8
+ # @joliegg/moderation TypeScript package: data/url_blacklist.json and
9
+ # data/url_shorteners.json are verbatim copies of that repo's
10
+ # src/url-blacklist.json / src/url-shorteners.json (run
11
+ # `rake "data:sync[/path/to/moderation]"` to refresh them).
12
+ #
13
+ # Matching mirrors ModerationClient#moderateLink: an input (full URL, bare
14
+ # domain, or email address) is reduced to its host, then every domain
15
+ # suffix is checked against the set — a listed "example.com" also blocks
16
+ # "sub.example.com", and an email at either address is blocked. Unlike the
17
+ # TS package's exact-match shortener check, shorteners are suffix-matched
18
+ # too, so "links.bit.ly" is recognized when "bit.ly" is listed.
19
+ module UrlBlocklist
20
+ DATA_DIR = File.expand_path("../../../data", __dir__).freeze
21
+ BLOCKLIST_PATH = File.join(DATA_DIR, "url_blacklist.json").freeze
22
+ SHORTENERS_PATH = File.join(DATA_DIR, "url_shorteners.json").freeze
23
+
24
+ module_function
25
+
26
+ # True when the input's host (or any parent domain of it) is blocklisted.
27
+ def blocked?(input)
28
+ !match(input).nil?
29
+ end
30
+
31
+ # The blocklist entry that matched the input's host, or nil.
32
+ def match(input)
33
+ find_in(blocklist, input)
34
+ end
35
+
36
+ # True when the input's host is (or belongs to) a listed URL shortener.
37
+ def shortener?(input)
38
+ !matched_shortener(input).nil?
39
+ end
40
+
41
+ # The shortener-list entry that matched the input's host, or nil.
42
+ def matched_shortener(input)
43
+ find_in(shorteners, input)
44
+ end
45
+
46
+ # Reduces a URL, bare domain, or email address to a bare lowercase host:
47
+ # "User@Sub.Example.COM" -> "sub.example.com"
48
+ # "HTTPS://example.com:8080/a?b" -> "example.com"
49
+ # "a/b@mailinator.com" -> "mailinator.com"
50
+ # "example.com." -> "example.com"
51
+ # Returns "" when nothing host-like can be extracted. IP literals pass
52
+ # through unchanged (the lists hold domains, so they simply never match).
53
+ #
54
+ # A schemeless input containing @ is treated as an email address FIRST:
55
+ # the local part may legally contain / ? # (RFC 5322 atext), so applying
56
+ # URL delimiters to it would sever the domain ("a/b@mailinator.com" would
57
+ # otherwise read as host "a"). The host is everything after the LAST @.
58
+ #
59
+ # With a scheme:// present it's a URL: the authority is isolated BEFORE
60
+ # any @ handling, so an @ in the path/query/fragment
61
+ # ("/redirect?to=user@mailinator.com") is never mistaken for userinfo,
62
+ # and only the LAST @ within the authority is the userinfo delimiter (per
63
+ # RFC 3986), so "example.com@evil.example" still resolves to
64
+ # "evil.example".
65
+ def extract_host(input)
66
+ candidate = input.to_s.strip.downcase
67
+
68
+ if !candidate.match?(%r{\A[a-z][a-z0-9+.-]*://}) && candidate.include?("@")
69
+ return candidate.split("@").last.to_s.sub(/:\d*\z/, "").sub(/\.\z/, "")
70
+ end
71
+
72
+ candidate = candidate.sub(%r{\A[a-z][a-z0-9+.-]*://}, "") # scheme
73
+ candidate = candidate.split(%r{[/\\?#]}).first.to_s # authority: cut path/query/fragment
74
+ candidate = candidate.split("@").last.to_s # URL userinfo
75
+ candidate = candidate.sub(/:\d*\z/, "") # port
76
+ candidate.sub(/\.\z/, "") # trailing root dot
77
+ end
78
+
79
+ def blocklist
80
+ @blocklist ||= load_set(BLOCKLIST_PATH)
81
+ end
82
+
83
+ def shorteners
84
+ @shorteners ||= load_set(SHORTENERS_PATH)
85
+ end
86
+
87
+ def blocklist_size
88
+ blocklist.size
89
+ end
90
+
91
+ def shortener_size
92
+ shorteners.size
93
+ end
94
+
95
+ # Drops the memoized sets so the next check re-reads the data files.
96
+ def reload!
97
+ @blocklist = nil
98
+ @shorteners = nil
99
+ end
100
+
101
+ def find_in(set, input)
102
+ host = extract_host(input)
103
+ return nil if host.empty?
104
+
105
+ parts = host.split(".")
106
+ (parts.length - 1).downto(0) do |index|
107
+ suffix = parts[index..].join(".")
108
+ return suffix if set.include?(suffix)
109
+ end
110
+ nil
111
+ end
112
+
113
+ def load_set(path)
114
+ Set.new(JSON.parse(File.read(path)))
115
+ end
116
+ end
117
+ end
118
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "moderation/result"
4
+ require_relative "moderation/url_blocklist"
5
+
6
+ module Jolie
7
+ # Ruby counterpart of the @joliegg/moderation TypeScript package. The URL
8
+ # blocklist is the first capability ported; text/image/audio moderation can
9
+ # follow under this same namespace.
10
+ module Moderation
11
+ module_function
12
+
13
+ # Checks a link against the bundled lists, mirroring the TS
14
+ # ModerationClient#moderateLink list checks (without the Google WebRisk
15
+ # lookup). Returns a Result whose moderation categories are "BLACK_LIST"
16
+ # for a blocklisted host and "URL_SHORTENER" for a shortener host; a
17
+ # shortener is only reported when allow_shorteners is false, and a
18
+ # blocklisted host is reported even when it is also a shortener.
19
+ def moderate_link(url, allow_shorteners: false)
20
+ moderation = []
21
+
22
+ if (matched = UrlBlocklist.match(url))
23
+ moderation << Category.new(category: "BLACK_LIST", confidence: 100, matched: matched)
24
+ elsif !allow_shorteners && (matched = UrlBlocklist.matched_shortener(url))
25
+ moderation << Category.new(category: "URL_SHORTENER", confidence: 100, matched: matched)
26
+ end
27
+
28
+ Result.new(source: url, moderation: moderation)
29
+ end
30
+
31
+ # True when moderate_link flags the link for any category.
32
+ def link_blocked?(url, allow_shorteners: false)
33
+ moderate_link(url, allow_shorteners: allow_shorteners).flagged?
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Jolie
4
+ VERSION = "0.1.0"
5
+ end
data/lib/jolie.rb ADDED
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "jolie/version"
4
+ require_relative "jolie/moderation"
5
+
6
+ # Top-level namespace for the Jolie libraries. Moderation is the first
7
+ # capability; others can live under this module alongside it.
8
+ module Jolie
9
+ end
data/lib/joliegg.rb ADDED
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "jolie"
metadata ADDED
@@ -0,0 +1,51 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: joliegg
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Diana Islas Ocampo
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies: []
12
+ description: Jolie's Ruby libraries
13
+ email: dev@hyuchia.com
14
+ executables: []
15
+ extensions: []
16
+ extra_rdoc_files: []
17
+ files:
18
+ - LICENSE.md
19
+ - README.md
20
+ - data/url_blacklist.json
21
+ - data/url_shorteners.json
22
+ - lib/jolie.rb
23
+ - lib/jolie/moderation.rb
24
+ - lib/jolie/moderation/result.rb
25
+ - lib/jolie/moderation/url_blocklist.rb
26
+ - lib/jolie/version.rb
27
+ - lib/joliegg.rb
28
+ homepage: https://github.com/joliegg/gem
29
+ licenses:
30
+ - Nonstandard
31
+ metadata:
32
+ source_code_uri: https://github.com/joliegg/gem
33
+ rubygems_mfa_required: 'true'
34
+ rdoc_options: []
35
+ require_paths:
36
+ - lib
37
+ required_ruby_version: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - ">="
40
+ - !ruby/object:Gem::Version
41
+ version: '3.4'
42
+ required_rubygems_version: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: '0'
47
+ requirements: []
48
+ rubygems_version: 3.6.9
49
+ specification_version: 4
50
+ summary: Jolie's Ruby toolkit
51
+ test_files: []