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.
- checksums.yaml +7 -0
- data/LICENSE.md +118 -0
- data/README.md +56 -0
- data/data/url_blacklist.json +128175 -0
- data/data/url_shorteners.json +1506 -0
- data/lib/jolie/moderation/result.rb +22 -0
- data/lib/jolie/moderation/url_blocklist.rb +118 -0
- data/lib/jolie/moderation.rb +36 -0
- data/lib/jolie/version.rb +5 -0
- data/lib/jolie.rb +9 -0
- data/lib/joliegg.rb +3 -0
- metadata +51 -0
|
@@ -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
|
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
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: []
|