trefoil 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/.codeclimate.yml +3 -0
- data/.gitattributes +2 -0
- data/.gitignore +50 -0
- data/.rspec +1 -0
- data/.rubocop.yml +5 -0
- data/.travis.yml +3 -0
- data/Gemfile +5 -0
- data/Gemfile.lock +78 -0
- data/LICENSE +21 -0
- data/Rakefile +30 -0
- data/lib/trefoil/board.rb +106 -0
- data/lib/trefoil/client.rb +80 -0
- data/lib/trefoil/image.rb +86 -0
- data/lib/trefoil/object.rb +0 -0
- data/lib/trefoil/post.rb +66 -0
- data/lib/trefoil/thread.rb +47 -0
- data/lib/trefoil/version.rb +7 -0
- data/lib/trefoil.rb +7 -0
- data/trefoil.gemspec +29 -0
- metadata +132 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: adc7d1dd5401c32dd327285a1ce3e19abcfadad43ccc6a0b77cd8fb84ef10fce
|
|
4
|
+
data.tar.gz: 4fe016b919392ce8b3c6cab0869f6ad8426a2b58926493411fc8a2e681a5d643
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: eb556fef835e6de55617d0ebe1a5208a8823caa847ea72d7cef592604092db83d4c6dcefc3bdb79921a5556f08e8f5c7fa6fc5d5e69466c169a84b5ef4b94c6f
|
|
7
|
+
data.tar.gz: f4e10541ee9e963e4bd80dfc973d08117fbc033e939945814d074747b188f64f63eb5d1f2ea9f50fc5d2643673c415ae2062d257fa5d6023d66040e81ff30739
|
data/.codeclimate.yml
ADDED
data/.gitattributes
ADDED
data/.gitignore
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
*.gem
|
|
2
|
+
*.rbc
|
|
3
|
+
/.config
|
|
4
|
+
/coverage/
|
|
5
|
+
/InstalledFiles
|
|
6
|
+
/pkg/
|
|
7
|
+
/spec/reports/
|
|
8
|
+
/spec/examples.txt
|
|
9
|
+
/test/tmp/
|
|
10
|
+
/test/version_tmp/
|
|
11
|
+
/tmp/
|
|
12
|
+
|
|
13
|
+
# Used by dotenv library to load environment variables.
|
|
14
|
+
# .env
|
|
15
|
+
|
|
16
|
+
## Specific to RubyMotion:
|
|
17
|
+
.dat*
|
|
18
|
+
.repl_history
|
|
19
|
+
build/
|
|
20
|
+
*.bridgesupport
|
|
21
|
+
build-iPhoneOS/
|
|
22
|
+
build-iPhoneSimulator/
|
|
23
|
+
|
|
24
|
+
## Specific to RubyMotion (use of CocoaPods):
|
|
25
|
+
#
|
|
26
|
+
# We recommend against adding the Pods directory to your .gitignore. However
|
|
27
|
+
# you should judge for yourself, the pros and cons are mentioned at:
|
|
28
|
+
# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
|
|
29
|
+
#
|
|
30
|
+
# vendor/Pods/
|
|
31
|
+
|
|
32
|
+
## Documentation cache and generated files:
|
|
33
|
+
/.yardoc/
|
|
34
|
+
/_yardoc/
|
|
35
|
+
/doc/
|
|
36
|
+
/rdoc/
|
|
37
|
+
|
|
38
|
+
## Environment normalization:
|
|
39
|
+
/.bundle/
|
|
40
|
+
/vendor/bundle
|
|
41
|
+
/lib/bundler/man/
|
|
42
|
+
|
|
43
|
+
# for a library or gem, you might want to ignore these files since the code is
|
|
44
|
+
# intended to run in multiple environments; otherwise, check them in:
|
|
45
|
+
# Gemfile.lock
|
|
46
|
+
# .ruby-version
|
|
47
|
+
# .ruby-gemset
|
|
48
|
+
|
|
49
|
+
# unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
|
|
50
|
+
.rvmrc
|
data/.rspec
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
--require spec_helper -I lib
|
data/.rubocop.yml
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
PATH
|
|
2
|
+
remote: .
|
|
3
|
+
specs:
|
|
4
|
+
trefoil (0.1.0)
|
|
5
|
+
|
|
6
|
+
GEM
|
|
7
|
+
remote: https://rubygems.org/
|
|
8
|
+
specs:
|
|
9
|
+
addressable (2.6.0)
|
|
10
|
+
public_suffix (>= 2.0.2, < 4.0)
|
|
11
|
+
ast (2.4.0)
|
|
12
|
+
crack (0.4.3)
|
|
13
|
+
safe_yaml (~> 1.0.0)
|
|
14
|
+
diff-lcs (1.3)
|
|
15
|
+
docile (1.3.1)
|
|
16
|
+
hashdiff (0.3.8)
|
|
17
|
+
jaro_winkler (1.5.2)
|
|
18
|
+
json (2.2.0)
|
|
19
|
+
parallel (1.14.0)
|
|
20
|
+
parser (2.6.0.0)
|
|
21
|
+
ast (~> 2.4.0)
|
|
22
|
+
powerpack (0.1.2)
|
|
23
|
+
psych (3.1.0)
|
|
24
|
+
public_suffix (3.0.3)
|
|
25
|
+
rainbow (3.0.0)
|
|
26
|
+
rake (12.3.2)
|
|
27
|
+
redcarpet (3.4.0)
|
|
28
|
+
rspec (3.8.0)
|
|
29
|
+
rspec-core (~> 3.8.0)
|
|
30
|
+
rspec-expectations (~> 3.8.0)
|
|
31
|
+
rspec-mocks (~> 3.8.0)
|
|
32
|
+
rspec-core (3.8.0)
|
|
33
|
+
rspec-support (~> 3.8.0)
|
|
34
|
+
rspec-expectations (3.8.2)
|
|
35
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
|
36
|
+
rspec-support (~> 3.8.0)
|
|
37
|
+
rspec-mocks (3.8.0)
|
|
38
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
|
39
|
+
rspec-support (~> 3.8.0)
|
|
40
|
+
rspec-support (3.8.0)
|
|
41
|
+
rubocop (0.65.0)
|
|
42
|
+
jaro_winkler (~> 1.5.1)
|
|
43
|
+
parallel (~> 1.10)
|
|
44
|
+
parser (>= 2.5, != 2.5.1.1)
|
|
45
|
+
powerpack (~> 0.1)
|
|
46
|
+
psych (>= 3.1.0)
|
|
47
|
+
rainbow (>= 2.2.2, < 4.0)
|
|
48
|
+
ruby-progressbar (~> 1.7)
|
|
49
|
+
unicode-display_width (~> 1.4.0)
|
|
50
|
+
ruby-progressbar (1.10.0)
|
|
51
|
+
safe_yaml (1.0.5)
|
|
52
|
+
simplecov (0.16.1)
|
|
53
|
+
docile (~> 1.1)
|
|
54
|
+
json (>= 1.8, < 3)
|
|
55
|
+
simplecov-html (~> 0.10.0)
|
|
56
|
+
simplecov-html (0.10.2)
|
|
57
|
+
unicode-display_width (1.4.1)
|
|
58
|
+
webmock (3.4.2)
|
|
59
|
+
addressable (>= 2.3.6)
|
|
60
|
+
crack (>= 0.3.2)
|
|
61
|
+
hashdiff
|
|
62
|
+
yard (0.9.18)
|
|
63
|
+
|
|
64
|
+
PLATFORMS
|
|
65
|
+
ruby
|
|
66
|
+
|
|
67
|
+
DEPENDENCIES
|
|
68
|
+
rake (~> 12.0)
|
|
69
|
+
redcarpet (~> 3.4.0)
|
|
70
|
+
rspec (~> 3.8.0)
|
|
71
|
+
rubocop (~> 0.65)
|
|
72
|
+
simplecov (~> 0.16.0)
|
|
73
|
+
trefoil!
|
|
74
|
+
webmock (~> 3.4.0)
|
|
75
|
+
yard (~> 0.9.9)
|
|
76
|
+
|
|
77
|
+
BUNDLED WITH
|
|
78
|
+
2.0.1
|
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2019 Matthew Carey
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/Rakefile
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'bundler/gem_helper'
|
|
4
|
+
require 'rspec/core/rake_task'
|
|
5
|
+
require 'rubocop/rake_task'
|
|
6
|
+
require 'yard'
|
|
7
|
+
|
|
8
|
+
namespace :test do
|
|
9
|
+
desc 'Run specs'
|
|
10
|
+
RSpec::Core::RakeTask.new(:spec) do |t|
|
|
11
|
+
t.verbose = false
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
desc 'Run rubocop'
|
|
15
|
+
RuboCop::RakeTask.new(:rubocop)
|
|
16
|
+
|
|
17
|
+
task all: %i[spec rubocop]
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
task test: 'test:all'
|
|
21
|
+
|
|
22
|
+
desc 'Generate yard docs'
|
|
23
|
+
YARD::Rake::YardocTask.new do |t|
|
|
24
|
+
t.files = ['lib/**/*.rb']
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
desc 'alias for `yard`'
|
|
28
|
+
task docs: :yard
|
|
29
|
+
|
|
30
|
+
task default: :test
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Trefoil
|
|
4
|
+
# Class representing a board. Used for requesting threads.
|
|
5
|
+
# A list of all ids can be found with `#threads`.
|
|
6
|
+
# An array of all active Threads can be obtained through `#catalog`.
|
|
7
|
+
# A list of archived thread ids can be found with `#archive.`
|
|
8
|
+
class Board
|
|
9
|
+
# @return [String] The name of the board being referenced.
|
|
10
|
+
attr_reader :name
|
|
11
|
+
|
|
12
|
+
def initialize(client, name)
|
|
13
|
+
@client = client
|
|
14
|
+
@name = name
|
|
15
|
+
@catalog = []
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Get info about this board. Keys based on `/boards.json`
|
|
19
|
+
# @param key [Symbol] The key to the desired data.
|
|
20
|
+
# @return [String, Integer, Hash<Symbol => Integer>, nil]
|
|
21
|
+
def [](key)
|
|
22
|
+
@client.send(:cache_boards) if board_cache.empty?
|
|
23
|
+
return nil unless board_cache[name]
|
|
24
|
+
|
|
25
|
+
board_cache[name][key]
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Whether or not this board is archived
|
|
29
|
+
# @return [true, false]
|
|
30
|
+
def archived?
|
|
31
|
+
self[:archived] == 1
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Whether or not this board is marked as not safe for work
|
|
35
|
+
# @return [true, false]
|
|
36
|
+
def nsfw?
|
|
37
|
+
self[:ws_board].zero?
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Gets a new thread by id.
|
|
41
|
+
# @param id [Integer] Thread id.
|
|
42
|
+
def thread(id)
|
|
43
|
+
Thread.new(client, self, id)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Get a hash of threads and their last modified time
|
|
47
|
+
# @return [Hash<Integer => Integer>] A hash with thread ids as keys and their
|
|
48
|
+
# last modified time as the corresponding value.
|
|
49
|
+
def threads
|
|
50
|
+
thread_list = {}
|
|
51
|
+
pages = @client.get("#{name}/threads.json")
|
|
52
|
+
pages.each do |page|
|
|
53
|
+
page[:threads].each do |thread|
|
|
54
|
+
thread_list[thread[:no]] = thread[:last_modified]
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
thread_list
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Retrive an array of archived thread ids.
|
|
62
|
+
# @return [Array<Integer>]
|
|
63
|
+
def archive
|
|
64
|
+
@client.get("#{name}/archive.json")
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Return a list of all active ops
|
|
68
|
+
# @return [Array<Hash<Symbol => Integer, String>>]
|
|
69
|
+
def catalog
|
|
70
|
+
return @catalog unless @catalog.empty?
|
|
71
|
+
|
|
72
|
+
update_catalog
|
|
73
|
+
@catalog
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Update the catalog with new ops
|
|
77
|
+
def update_catalog
|
|
78
|
+
@catalog = []
|
|
79
|
+
pages = @client.get("#{name}/catalog.json")
|
|
80
|
+
pages.each do |page|
|
|
81
|
+
page[:threads].each do |op|
|
|
82
|
+
@catalog << op
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Link to a board's custom spoiler
|
|
88
|
+
# @param int [Integer, nil] the custom spoiler id to use pics one randomly if left nil.
|
|
89
|
+
# @return [String, nil] The url to a spoiler, or nil if this board does not have custom spoilers.
|
|
90
|
+
def custom_spoiler(int = nil)
|
|
91
|
+
return nil unless self[:custom_spoilers]
|
|
92
|
+
|
|
93
|
+
spoiler_id = int || rand(self[:custom_spoilers]) + 1
|
|
94
|
+
|
|
95
|
+
"http://s.4cdn.org/image/spoiler-#{name}#{spoiler_id}.png"
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
private
|
|
99
|
+
|
|
100
|
+
# Proxy method for accessing the client's board cache
|
|
101
|
+
# @return [Hash<String => Hash<Symbol => String, Integer, Hash<String => Integer>>>]
|
|
102
|
+
def board_cache
|
|
103
|
+
@client.instance_variable_get(:@board_cache)
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'json'
|
|
4
|
+
require 'net/http'
|
|
5
|
+
|
|
6
|
+
module Trefoil
|
|
7
|
+
# Client used for making requests to the API. Respects API rules for rate limiting
|
|
8
|
+
# and thread watching.
|
|
9
|
+
class Client
|
|
10
|
+
BASE_URL = 'a.4cdn.org'
|
|
11
|
+
|
|
12
|
+
# Initialize with a last_request time 1 second prior to present
|
|
13
|
+
# so that we can make requests right away
|
|
14
|
+
def initialize
|
|
15
|
+
@last_request = Time.now - 1
|
|
16
|
+
@mutex = Mutex.new
|
|
17
|
+
@board_cache = {}
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Get a JSON parsed response from a given endpoint. Limited to 1 request
|
|
21
|
+
# per second in compliance with API rules.
|
|
22
|
+
# @param endpoint [String] The endpoint to request data from.
|
|
23
|
+
# @return [Hash<Symbol => String, Fixnum>] The returned data structure
|
|
24
|
+
def get(endpoint)
|
|
25
|
+
@mutex.synchronize do
|
|
26
|
+
sleep time_until_available if must_wait?
|
|
27
|
+
resp = Net::HTTP.get_response(BASE_URL, format_endpoint(endpoint))
|
|
28
|
+
@last_request = Time.now
|
|
29
|
+
case resp.code.to_i
|
|
30
|
+
when 200
|
|
31
|
+
JSON.parse(resp.body, symbolize_names: true)
|
|
32
|
+
when 400..500
|
|
33
|
+
raise 'Error making a request' # TODO: Better error handling
|
|
34
|
+
else
|
|
35
|
+
raise 'Unhandled status code' # TODO: better code handling
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Get a board object based on its name
|
|
41
|
+
# @param board_name [String]
|
|
42
|
+
def board(board_name)
|
|
43
|
+
Board.new(self, board_name)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
private
|
|
47
|
+
|
|
48
|
+
# Adds leading '/' to requests
|
|
49
|
+
# @param endpoint [String] The endpoint being formatted
|
|
50
|
+
# @return [String] `endpoint` with leading `/` added if needed
|
|
51
|
+
def format_endpoint(endpoint)
|
|
52
|
+
return endpoint if endpoint[0] == '/'
|
|
53
|
+
|
|
54
|
+
'/' + endpoint
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Amount of time before the mutex is freed for the next request
|
|
58
|
+
# @return [Fixnum, Float] Time remaining until the next request can be made
|
|
59
|
+
def time_until_available
|
|
60
|
+
(@last_request + 1) - Time.now
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Whether we must wait to make a request
|
|
64
|
+
# @return [true, false] Whether we need to wait or not
|
|
65
|
+
def must_wait?
|
|
66
|
+
time_until_available.positive?
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Cache boards from `/boards.json`. Called when accessing `Board#[]` for
|
|
70
|
+
# the first time. Effects last request time.
|
|
71
|
+
def cache_boards
|
|
72
|
+
boards = get('boards.json')
|
|
73
|
+
boards[:boards].each do |board_obj|
|
|
74
|
+
@board_cache[board_obj[:board]] = board_obj
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
@board_cache[:troll_flags] = boards[:troll_flags]
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'trefoil/board'
|
|
4
|
+
|
|
5
|
+
module Trefoil
|
|
6
|
+
# A class representing an Image using data from a post object.
|
|
7
|
+
class Image
|
|
8
|
+
# @return [Integer] Renamed filename (UNIX timestamp + miliseconds)
|
|
9
|
+
attr_reader :tim
|
|
10
|
+
|
|
11
|
+
# @return [String] Original filename of this image
|
|
12
|
+
attr_reader :filename
|
|
13
|
+
|
|
14
|
+
# @return [String] File extension of the fullsize image.
|
|
15
|
+
attr_reader :ext
|
|
16
|
+
|
|
17
|
+
# @return [Integer] Filesize of the fullsize image.
|
|
18
|
+
attr_reader :fsize
|
|
19
|
+
|
|
20
|
+
# @return [String] MD5 hash of fullsize image.
|
|
21
|
+
attr_reader :md5
|
|
22
|
+
|
|
23
|
+
# @return [Integer] Width of fullsize image.
|
|
24
|
+
attr_reader :w
|
|
25
|
+
|
|
26
|
+
# @return [Integer] Height of fullsize image.
|
|
27
|
+
attr_reader :h
|
|
28
|
+
|
|
29
|
+
# @return [Integer] Width of thumbnail.
|
|
30
|
+
attr_reader :tn_w
|
|
31
|
+
|
|
32
|
+
# @return [Integer] Height of thumbnail.
|
|
33
|
+
attr_reader :tn_h
|
|
34
|
+
|
|
35
|
+
# @return [0, 1, nil] Whether this image is deleted. Use `#image?` for a boolean representation.
|
|
36
|
+
attr_reader :filedeleted
|
|
37
|
+
|
|
38
|
+
# @return [0, 1, nil] Whether this image is a spoiler. Use `#spoiler?` for a boolean representation.
|
|
39
|
+
attr_reader :spoiler
|
|
40
|
+
|
|
41
|
+
# @return [String] The name of the board this image is associated with.
|
|
42
|
+
attr_reader :board_name
|
|
43
|
+
|
|
44
|
+
# Create a new image object. Intended for internal use.
|
|
45
|
+
# @param data [Hash<Symbol => Integer, String] Data from a post object to extrapolate info from.
|
|
46
|
+
# @param board [Board, String] The board, or board name, associated with the post this image is from.
|
|
47
|
+
def initialize(data, board)
|
|
48
|
+
expand_data(data)
|
|
49
|
+
@board_name = board.is_a?(Board) ? board[:name] : board
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# The url to the full size image.
|
|
53
|
+
# @return [String] url to the fullsize version of this image.
|
|
54
|
+
def url
|
|
55
|
+
"http://i.4cdn.org/#{board_name}/#{tim}#{ext}"
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# The url to the thumbnail of this image.
|
|
59
|
+
# @return [String] url to the thumbnail of this image.
|
|
60
|
+
def thumbnail_url
|
|
61
|
+
"http://i.4cdn.org/#{board_name}/#{tim}s.jpg"
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Convenience abstraction for determining if this file was deleted.
|
|
65
|
+
# @return [true, false] Whether this file was deleted.
|
|
66
|
+
def file_deleted?
|
|
67
|
+
filedeleted == 1
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Convenience abstraction for determining if this file is a spoiler.
|
|
71
|
+
# @return [true, false] Whether this file is a spoiler.
|
|
72
|
+
def spoiler?
|
|
73
|
+
spoiler == 1
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
private
|
|
77
|
+
|
|
78
|
+
# Move desired values from post data into instance variables.
|
|
79
|
+
# @param data [Hash<Symbol => Integer, String] Data from a Post object.
|
|
80
|
+
def expand_data(data)
|
|
81
|
+
%i[tim filename ext fsize md5 w h tn_w tn_h filedeleted spoiler].each do |sym|
|
|
82
|
+
instance_variable_set("@#{sym}", data[sym])
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
File without changes
|
data/lib/trefoil/post.rb
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Trefoil
|
|
4
|
+
# Represents a `post` object.
|
|
5
|
+
# Post object data varies greatly based on board, content, and whether it is an OP.
|
|
6
|
+
# Possible fields are available for viewing in the (API documentation)[https://github.com/4chan/4chan-API#posts-object]
|
|
7
|
+
class Post
|
|
8
|
+
# Intended for internal use
|
|
9
|
+
def initialize(client, board, thread, data)
|
|
10
|
+
@client = client
|
|
11
|
+
@board = board
|
|
12
|
+
@thread = thread
|
|
13
|
+
@data = data
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# Access underlying post data with symbolized names
|
|
17
|
+
# Possible fields are available for viewing in the (API documentation)[https://github.com/4chan/4chan-API#posts-object]
|
|
18
|
+
# @param key [Symbol] The API field
|
|
19
|
+
# @return [Hash, Array, String, Fixnum]
|
|
20
|
+
def [](key)
|
|
21
|
+
@data[key]
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Whether this is the first post in a thread. All op's has a resto of 0
|
|
25
|
+
# @return [true, false]
|
|
26
|
+
def op?
|
|
27
|
+
@data[:resto].zero?
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Whether this post has an image associated with it
|
|
31
|
+
# @return [true, false]
|
|
32
|
+
def image?
|
|
33
|
+
!@data[:filename].is_nil?
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Url to the image of this post, if any.
|
|
37
|
+
# @return [Image, nil] An Image or nil if no image is present.
|
|
38
|
+
def image_url
|
|
39
|
+
return nil unless image?
|
|
40
|
+
|
|
41
|
+
Image.new(@data)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Url to this post
|
|
45
|
+
# @return [String] The url to this post.
|
|
46
|
+
def url
|
|
47
|
+
direct_link = if @data[:resto].zero?
|
|
48
|
+
@data[:no]
|
|
49
|
+
else
|
|
50
|
+
"#{@data[:resto]}p##{@data[:no]}"
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
"http://boards.4chan.org/#{@board}/thread/#{direct_link}"
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# The OP to the thread this post is in.
|
|
57
|
+
# @return [Post] A post object for the OP, the instance it was called on if it is the OP.
|
|
58
|
+
def op
|
|
59
|
+
if @data[:resto].zero
|
|
60
|
+
self
|
|
61
|
+
else
|
|
62
|
+
thread.posts[0]
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Trefoil
|
|
4
|
+
# Represents an array of Post objects with additional convenience methods
|
|
5
|
+
class Thread
|
|
6
|
+
def initialize(client, board, id)
|
|
7
|
+
@client = client
|
|
8
|
+
@board = board
|
|
9
|
+
@id = id
|
|
10
|
+
@posts = nil
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# Get a post based on its position
|
|
14
|
+
# @param index [Integer] the index of the desired post
|
|
15
|
+
# @return [Post, nil] A post object or nil if out of bounds.
|
|
16
|
+
def [](index)
|
|
17
|
+
posts[index]
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# An array of all posts in the thread
|
|
21
|
+
# @return [Array<Post>] All posts in this thread since last update
|
|
22
|
+
def posts
|
|
23
|
+
@posts = fetch_posts if @posts.nil?
|
|
24
|
+
@posts
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Convenience method to get the first post in a thread
|
|
28
|
+
# @return [Post]
|
|
29
|
+
def op
|
|
30
|
+
posts[0]
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Refresh post list
|
|
34
|
+
def update
|
|
35
|
+
fetch_posts
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
private
|
|
39
|
+
|
|
40
|
+
# Fetch all posts into
|
|
41
|
+
def fetch_posts
|
|
42
|
+
@client.get("/#{@board}/thread/#{@id}.json")[:posts].each do |post|
|
|
43
|
+
@posts << Post.new(@client, @board, self, post)
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
data/lib/trefoil.rb
ADDED
data/trefoil.gemspec
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
lib = File.expand_path('lib', __dir__)
|
|
4
|
+
$LOAD_PATH.unshift lib
|
|
5
|
+
|
|
6
|
+
require 'trefoil/version'
|
|
7
|
+
|
|
8
|
+
Gem::Specification.new do |s|
|
|
9
|
+
s.name = 'trefoil'
|
|
10
|
+
s.version = Trefoil::VERSION
|
|
11
|
+
s.authors = ['swarley']
|
|
12
|
+
s.email = ['matthew.b.carey@gmail.com']
|
|
13
|
+
|
|
14
|
+
s.summary = '4chan Public API Client'
|
|
15
|
+
s.description = 'A client for accessing the 4chan public api.'
|
|
16
|
+
s.homepage = 'https://github.com/swarley/trefoil-rb'
|
|
17
|
+
s.license = 'MIT'
|
|
18
|
+
|
|
19
|
+
s.files = `git ls-files -z`.split(?\x0).reject do |f|
|
|
20
|
+
f =~ /^(spec|doc|examples)/
|
|
21
|
+
end
|
|
22
|
+
s.require_paths = ['lib']
|
|
23
|
+
|
|
24
|
+
s.add_development_dependency 'rake', '~> 12.0'
|
|
25
|
+
s.add_development_dependency 'rspec', '~> 3.8.0'
|
|
26
|
+
s.add_development_dependency 'rubocop', '~> 0.65'
|
|
27
|
+
s.add_development_dependency 'webmock', '~> 3.4.0'
|
|
28
|
+
s.add_development_dependency 'yard', '~> 0.9.9'
|
|
29
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: trefoil
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- swarley
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2019-03-09 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: rake
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - "~>"
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '12.0'
|
|
20
|
+
type: :development
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - "~>"
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '12.0'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: rspec
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - "~>"
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: 3.8.0
|
|
34
|
+
type: :development
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - "~>"
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: 3.8.0
|
|
41
|
+
- !ruby/object:Gem::Dependency
|
|
42
|
+
name: rubocop
|
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
|
44
|
+
requirements:
|
|
45
|
+
- - "~>"
|
|
46
|
+
- !ruby/object:Gem::Version
|
|
47
|
+
version: '0.65'
|
|
48
|
+
type: :development
|
|
49
|
+
prerelease: false
|
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
+
requirements:
|
|
52
|
+
- - "~>"
|
|
53
|
+
- !ruby/object:Gem::Version
|
|
54
|
+
version: '0.65'
|
|
55
|
+
- !ruby/object:Gem::Dependency
|
|
56
|
+
name: webmock
|
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
|
58
|
+
requirements:
|
|
59
|
+
- - "~>"
|
|
60
|
+
- !ruby/object:Gem::Version
|
|
61
|
+
version: 3.4.0
|
|
62
|
+
type: :development
|
|
63
|
+
prerelease: false
|
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
65
|
+
requirements:
|
|
66
|
+
- - "~>"
|
|
67
|
+
- !ruby/object:Gem::Version
|
|
68
|
+
version: 3.4.0
|
|
69
|
+
- !ruby/object:Gem::Dependency
|
|
70
|
+
name: yard
|
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
|
72
|
+
requirements:
|
|
73
|
+
- - "~>"
|
|
74
|
+
- !ruby/object:Gem::Version
|
|
75
|
+
version: 0.9.9
|
|
76
|
+
type: :development
|
|
77
|
+
prerelease: false
|
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
79
|
+
requirements:
|
|
80
|
+
- - "~>"
|
|
81
|
+
- !ruby/object:Gem::Version
|
|
82
|
+
version: 0.9.9
|
|
83
|
+
description: A client for accessing the 4chan public api.
|
|
84
|
+
email:
|
|
85
|
+
- matthew.b.carey@gmail.com
|
|
86
|
+
executables: []
|
|
87
|
+
extensions: []
|
|
88
|
+
extra_rdoc_files: []
|
|
89
|
+
files:
|
|
90
|
+
- ".codeclimate.yml"
|
|
91
|
+
- ".gitattributes"
|
|
92
|
+
- ".gitignore"
|
|
93
|
+
- ".rspec"
|
|
94
|
+
- ".rubocop.yml"
|
|
95
|
+
- ".travis.yml"
|
|
96
|
+
- Gemfile
|
|
97
|
+
- Gemfile.lock
|
|
98
|
+
- LICENSE
|
|
99
|
+
- Rakefile
|
|
100
|
+
- lib/trefoil.rb
|
|
101
|
+
- lib/trefoil/board.rb
|
|
102
|
+
- lib/trefoil/client.rb
|
|
103
|
+
- lib/trefoil/image.rb
|
|
104
|
+
- lib/trefoil/object.rb
|
|
105
|
+
- lib/trefoil/post.rb
|
|
106
|
+
- lib/trefoil/thread.rb
|
|
107
|
+
- lib/trefoil/version.rb
|
|
108
|
+
- trefoil.gemspec
|
|
109
|
+
homepage: https://github.com/swarley/trefoil-rb
|
|
110
|
+
licenses:
|
|
111
|
+
- MIT
|
|
112
|
+
metadata: {}
|
|
113
|
+
post_install_message:
|
|
114
|
+
rdoc_options: []
|
|
115
|
+
require_paths:
|
|
116
|
+
- lib
|
|
117
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
118
|
+
requirements:
|
|
119
|
+
- - ">="
|
|
120
|
+
- !ruby/object:Gem::Version
|
|
121
|
+
version: '0'
|
|
122
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
123
|
+
requirements:
|
|
124
|
+
- - ">="
|
|
125
|
+
- !ruby/object:Gem::Version
|
|
126
|
+
version: '0'
|
|
127
|
+
requirements: []
|
|
128
|
+
rubygems_version: 3.0.1
|
|
129
|
+
signing_key:
|
|
130
|
+
specification_version: 4
|
|
131
|
+
summary: 4chan Public API Client
|
|
132
|
+
test_files: []
|