brutalismbot 0.1.1 → 0.2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7dbe0c2ce29ee2c8169e733c62706340465aaa1307e608f2e84888c4181e1b6b
4
- data.tar.gz: a756eafc6077d900bde350dea0782b14ec988aacd0421f6b871d0c7ddedf83fb
3
+ metadata.gz: b1836d2eecd6d94c8d170223f7c8da3bbfe87909154d82492716d3ac1b3b411e
4
+ data.tar.gz: 825519ee45d36652e631310c38c0ce246c9e08e0f86987c4bfa42cc6345daa8a
5
5
  SHA512:
6
- metadata.gz: 427d8332a5df819072e2932227836aa8a1c62163261f711d070ca89cf745e4ab2dc8510326d2d15e82a6bc8c802fd350630b3614f4e8943a46fcdf75cfbce608
7
- data.tar.gz: e0ffb4aa085e92b6a6fafa2a0930992fd0ab9938ff7df3b36bb77dfce10766be0c78faefc2bee37fce66734f5ee7b6be2788e4f1b894f4c59ab83c083ae63707
6
+ metadata.gz: baed1a146152eb80401cbd75fd2e5265cd99fae63fb588db1ca66e233e52de8c06c6460abd099b6f046b4436923496d94bb7867b645382c81ec8857f9fb1a9f7
7
+ data.tar.gz: dde3ca496744cdf5e45c41be689c36587570c3cb4b79d7998a82d3ad002bc79e5e601aa09f21e246bc40bef00d46b2ec4b8affdc8595135f2ccac4cd5bd11617
data/README.md CHANGED
@@ -1,39 +1,50 @@
1
- # Brutalismbot
1
+ <img alt="brutalismbot" src="https://brutalismbot.com/banner.png"/>
2
2
 
3
- Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/brutalismbot`. To experiment with that code, run `bin/console` for an interactive prompt.
3
+ [![Gem Version](https://badge.fury.io/rb/brutalismbot.svg)](http://badge.fury.io/rb/brutalismbot)
4
+ [![Build Status](https://travis-ci.com/brutalismbot/gem.svg)](https://travis-ci.com/brutalismbot/gem)
5
+ [![codecov](https://codecov.io/gh/brutalismbot/gem/branch/master/graph/badge.svg)](https://codecov.io/gh/brutalismbot/gem)
4
6
 
5
- TODO: Delete this and the text above, and describe your gem
7
+ Brutalismbot RubyGem
6
8
 
7
9
  ## Installation
8
10
 
9
- Add this line to your application's Gemfile:
10
-
11
11
  ```ruby
12
- gem 'brutalismbot'
12
+ gem install brutalismbot
13
13
  ```
14
14
 
15
- And then execute:
16
-
17
- $ bundle
15
+ ## Usage
18
16
 
19
- Or install it yourself as:
17
+ ```ruby
18
+ require "aws-sdk-s3"
19
+ require "brutalismbot"
20
20
 
21
- $ gem install brutalismbot
21
+ bucket = Aws::S3::Bucket.new name: "my-bucket"
22
+ brutbot = Brutalismbot::S3::Client.new bucket: bucket,
23
+ prefix: "my/prefix/"
22
24
 
23
- ## Usage
25
+ # Get new posts after a given time
26
+ brutbot.subreddit.new_posts.after Time.parse("2019-06-01 12:00:00Z")
24
27
 
25
- TODO: Write usage instructions here
28
+ # Get current top post
29
+ brutbot.subreddit.top_post
26
30
 
27
- ## Development
31
+ # Get latest cached post
32
+ brutbot.posts.latest
28
33
 
29
- After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
30
-
31
- To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
34
+ # Get max key in posts
35
+ brutbot.posts.max_key
36
+ ```
32
37
 
33
38
  ## Contributing
34
39
 
35
- Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/brutalismbot.
40
+ Bug reports and pull requests are welcome on [GitHub](https://github.com/brutalismbot/gem).
36
41
 
37
42
  ## License
38
43
 
39
44
  The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
45
+
46
+ ### See Also
47
+
48
+ - [Brutalismbot API](https://github.com/brutalismbot/api)
49
+ - [Brutalismbot App](https://github.com/brutalismbot/brutalismbot)
50
+ - [Brutalismbot Monitoring](https://github.com/brutalismbot/monitoring)
@@ -1,112 +1,53 @@
1
- module R
2
- class Subreddit
3
- def initialize(endpoint:nil, user_agent:nil)
4
- @endpoint = endpoint
5
- @user_agent = user_agent
6
- end
7
-
8
- def new_posts(**params)
9
- url = File.join @endpoint, "new.json"
10
- qry = URI.encode_www_form params
11
- uri = URI.parse "#{url}?#{qry}"
12
- PostCollection.new uri: uri, user_agent: @user_agent
13
- end
1
+ module Brutalismbot
2
+ module R
3
+ class Subreddit
4
+ attr_reader :endpoint, :user_agent
14
5
 
15
- def top_post(**params)
16
- url = File.join @endpoint, "top.json"
17
- qry = URI.encode_www_form params
18
- uri = URI.parse "#{url}?#{qry}"
19
- PostCollection.new(uri: uri, user_agent: @user_agent).each do |post|
20
- break post unless post.url.nil?
6
+ def initialize(endpoint:nil, user_agent:nil)
7
+ @endpoint = endpoint
8
+ @user_agent = user_agent
21
9
  end
22
- end
23
- end
24
-
25
- class PostCollection
26
- include Enumerable
27
-
28
- def initialize(uri:, user_agent:, min_time:nil)
29
- @uri = uri
30
- @ssl = uri.scheme == "https"
31
- @user_agent = user_agent
32
- @min_time = min_time.to_i
33
- end
34
-
35
- def after(time)
36
- PostCollection.new uri: @uri, user_agent: @user_agent, min_time: time
37
- end
38
10
 
39
- def each
40
- puts "GET #{@uri}"
41
- Net::HTTP.start(@uri.host, @uri.port, use_ssl: @ssl) do |http|
42
- request = Net::HTTP::Get.new @uri, "user-agent" => @user_agent
43
- response = JSON.parse http.request(request).body
44
- children = response.dig("data", "children") || []
45
- children.reverse.each do |child|
46
- post = R::Brutalism::Post[child]
47
- yield post if post.created_after @min_time
48
- end
11
+ def posts(resource, **params)
12
+ url = File.join @endpoint, "#{resource}.json"
13
+ qry = URI.encode_www_form params
14
+ uri = URI.parse "#{url}?#{qry}"
15
+ PostCollection.new uri: uri, user_agent: @user_agent
49
16
  end
50
17
  end
51
- end
52
-
53
- class Brutalism < Subreddit
54
- def initialize(endpoint:nil, user_agent:nil)
55
- super endpoint: endpoint || "https://www.reddit.com/r/brutalism",
56
- user_agent: user_agent || "Brutalismbot #{Brutalismbot::VERSION}"
57
- end
58
-
59
- class Post < Hash
60
- def created_after(time)
61
- created_utc.to_i > time.to_i
62
- end
63
18
 
64
- def created_utc
65
- Time.at(dig("data", "created_utc").to_i).utc
66
- end
19
+ class PostCollection
20
+ include Enumerable
67
21
 
68
- def permalink
69
- dig "data", "permalink"
22
+ def initialize(uri:, user_agent:, min_time:nil)
23
+ @uri = uri
24
+ @ssl = uri.scheme == "https"
25
+ @user_agent = user_agent
26
+ @min_time = min_time.to_i
70
27
  end
71
28
 
72
- def title
73
- dig "data", "title"
29
+ def each
30
+ Brutalismbot.logger.info "GET #{@uri}"
31
+ Net::HTTP.start(@uri.host, @uri.port, use_ssl: @ssl) do |http|
32
+ request = Net::HTTP::Get.new @uri, "user-agent" => @user_agent
33
+ response = JSON.parse http.request(request).body
34
+ children = response.dig("data", "children") || []
35
+ children.reverse.each do |child|
36
+ post = Brutalismbot::Post[child]
37
+ yield post if post.created_after time: @min_time
38
+ end
39
+ end
74
40
  end
75
41
 
76
- def to_slack
77
- {
78
- blocks: [
79
- {
80
- type: "image",
81
- title: {
82
- type: "plain_text",
83
- text: "/r/brutalism",
84
- emoji: true,
85
- },
86
- image_url: url,
87
- alt_text: title,
88
- },
89
- {
90
- type: "context",
91
- elements: [
92
- {
93
- type: "mrkdwn",
94
- text: "<https://reddit.com#{permalink}|#{title}>",
95
- },
96
- ],
97
- },
98
- ],
99
- }
42
+ def since(time:)
43
+ PostCollection.new uri: @uri, user_agent: @user_agent, min_time: time
100
44
  end
45
+ end
101
46
 
102
- def url
103
- images = dig "data", "preview", "images"
104
- source = images.map{|x| x["source"] }.compact.max do |a,b|
105
- a.slice("width", "height").values <=> b.slice("width", "height").values
106
- end
107
- CGI.unescapeHTML source.dig("url")
108
- rescue NoMethodError
109
- dig("data", "media_metadata")&.values&.first&.dig("s", "u")
47
+ class Brutalism < Subreddit
48
+ def initialize(endpoint:nil, user_agent:nil)
49
+ super endpoint: endpoint || "https://www.reddit.com/r/brutalism",
50
+ user_agent: user_agent || "Brutalismbot #{Brutalismbot::VERSION}"
110
51
  end
111
52
  end
112
53
  end
@@ -1,39 +1,17 @@
1
1
  module Brutalismbot
2
2
  module S3
3
- class Client
4
- VERSION = "v1"
5
-
6
- def initialize(bucket:, prefix:nil)
7
- @bucket = bucket
8
- @prefix = prefix
9
- end
10
-
11
- def subreddit(endpoint:nil, user_agent:nil)
12
- R::Brutalism.new endpoint: endpoint,
13
- user_agent: user_agent
14
- end
15
-
16
- def auths
17
- AuthCollection.new bucket: @bucket,
18
- prefix: "#{@prefix}oauth/#{VERSION}/"
19
- end
20
-
21
- def posts
22
- PostCollection.new bucket: @bucket,
23
- prefix: "#{@prefix}posts/#{VERSION}/"
24
- end
25
- end
26
-
27
3
  class Collection
28
4
  include Enumerable
29
5
 
30
- def initialize(bucket:, prefix:)
6
+ attr_reader :bucket, :prefix
7
+
8
+ def initialize(bucket:, prefix:nil)
31
9
  @bucket = bucket
32
10
  @prefix = prefix
33
11
  end
34
12
 
35
13
  def each
36
- puts "GET s3://#{@bucket.name}/#{@prefix}*"
14
+ Brutalismbot.logger.info "GET s3://#{@bucket.name}/#{@prefix}*"
37
15
  @bucket.objects(prefix: @prefix).each do |object|
38
16
  yield object
39
17
  end
@@ -41,37 +19,52 @@ module Brutalismbot
41
19
 
42
20
  def put(body:, key:, dryrun:nil)
43
21
  if dryrun
44
- puts "PUT DRYRUN s3://#{@bucket.name}/#{key}"
22
+ Brutalismbot.logger.info "PUT DRYRUN s3://#{@bucket.name}/#{key}"
45
23
  else
46
- puts "PUT s3://#{@bucket.name}/#{key}"
24
+ Brutalismbot.logger.info "PUT s3://#{@bucket.name}/#{key}"
47
25
  @bucket.put_object key: key, body: body
48
26
  end
27
+ end
28
+ end
49
29
 
50
- {bucket: @bucket.name, key: key}
30
+ class Client < Collection
31
+ def subreddit(endpoint:nil, user_agent:nil)
32
+ Brutalismbot::R::Brutalism.new endpoint:endpoint, user_agent: user_agent
33
+ end
34
+
35
+ def auths
36
+ AuthCollection.new bucket: @bucket, prefix: "#{@prefix}auths/"
37
+ end
38
+
39
+ def posts
40
+ PostCollection.new bucket: @bucket, prefix: "#{@prefix}posts/"
51
41
  end
52
42
  end
53
43
 
54
44
  class AuthCollection < Collection
55
45
  def each
56
46
  super do |object|
57
- yield Brutalismbot::OAuth[JSON.parse object.get.body.read]
47
+ yield Brutalismbot::Auth[JSON.parse object.get.body.read]
58
48
  end
59
49
  end
60
50
 
61
- def delete(team_id:, dryrun:nil)
62
- prefix = "#{@prefix}team=#{team_id}/"
63
- puts "GET s3://#{@bucket.name}/#{prefix}*"
51
+ def remove(team:, dryrun:nil)
52
+ prefix = "#{@prefix}team=#{team}/"
53
+ Brutalismbot.logger.info "GET s3://#{@bucket.name}/#{prefix}*"
64
54
  @bucket.objects(prefix: prefix).map do |object|
65
55
  if dryrun
66
- puts "DELETE DRYRUN s3://#{@bucket.name}/#{object.key}"
67
- {bucket: @bucket.name, key: object.key}
56
+ Brutalismbot.logger.info "DELETE DRYRUN s3://#{@bucket.name}/#{object.key}"
68
57
  else
69
- puts "DELETE s3://#{@bucket.name}/#{object.key}"
58
+ Brutalismbot.logger.info "DELETE s3://#{@bucket.name}/#{object.key}"
70
59
  object.delete
71
60
  end
72
61
  end
73
62
  end
74
63
 
64
+ def mirror(body:, dryrun:nil)
65
+ map{|auth| auth.post body: body, dryrun: dryrun }
66
+ end
67
+
75
68
  def put(auth:, dryrun:nil)
76
69
  key = "#{@prefix}team=#{auth.team_id}/channel=#{auth.channel_id}/oauth.json"
77
70
  super key: key, body: auth.to_json, dryrun: dryrun
@@ -81,23 +74,23 @@ module Brutalismbot
81
74
  class PostCollection < Collection
82
75
  def each
83
76
  super do |object|
84
- yield R::Brutalism::Post[JSON.parse object.get.body.read]
77
+ yield Brutalismbot::Post[JSON.parse object.get.body.read]
85
78
  end
86
79
  end
87
80
 
88
81
  def latest
89
- R::Brutalism::Post[JSON.parse max_key.get.body.read]
82
+ Brutalismbot::Post[JSON.parse max_key.get.body.read]
90
83
  end
91
84
 
92
85
  def max_key
93
86
  # Dig for max key
94
- prefix = prefix_for Time.now.utc
95
- puts "GET s3://#{@bucket.name}/#{prefix}*"
87
+ prefix = prefix_for time: Time.now.utc
88
+ Brutalismbot.logger.info "GET s3://#{@bucket.name}/#{prefix}*"
96
89
 
97
90
  # Go up a level in prefix if no keys found
98
91
  until (keys = @bucket.objects(prefix: prefix)).any?
99
92
  prefix = prefix.split(/[^\/]+\/\z/).first
100
- puts "GET s3://#{@bucket.name}/#{prefix}*"
93
+ Brutalismbot.logger.info "GET s3://#{@bucket.name}/#{prefix}*"
101
94
  end
102
95
 
103
96
  # Return max by key
@@ -108,7 +101,7 @@ module Brutalismbot
108
101
  max_key.key.match(/(\d+).json\z/).to_a.last.to_i
109
102
  end
110
103
 
111
- def prefix_for(time)
104
+ def prefix_for(time:)
112
105
  time = Time.at(time.to_i).utc
113
106
  year = time.strftime '%Y'
114
107
  month = time.strftime '%Y-%m'
@@ -117,9 +110,13 @@ module Brutalismbot
117
110
  end
118
111
 
119
112
  def put(post:, dryrun:nil)
120
- key = "#{prefix_for post.created_utc}#{post.created_utc.to_i}.json"
113
+ key = "#{prefix_for time: post.created_utc}#{post.created_utc.to_i}.json"
121
114
  super key: key, body: post.to_json, dryrun: dryrun
122
115
  end
116
+
117
+ def update(posts:, dryrun:nil)
118
+ posts.map{|post| put post: post, dryrun: dryrun }
119
+ end
123
120
  end
124
121
  end
125
122
  end
@@ -1,3 +1,3 @@
1
1
  module Brutalismbot
2
- VERSION = "0.1.1"
2
+ VERSION = "0.2.0"
3
3
  end
data/lib/brutalismbot.rb CHANGED
@@ -1,10 +1,114 @@
1
- require "brutalismbot/event"
2
- require "brutalismbot/oauth"
3
1
  require "brutalismbot/r"
4
2
  require "brutalismbot/s3"
5
3
  require "brutalismbot/version"
4
+ require "logger"
5
+ require "net/https"
6
6
 
7
7
  module Brutalismbot
8
+ class << self
9
+ @@config = {}
10
+ @@logger = Logger.new File::NULL
11
+
12
+ def config
13
+ @@config
14
+ end
15
+
16
+ def config=(config)
17
+ @@config = config || {}
18
+ end
19
+
20
+ def logger
21
+ config[:logger] || @@logger
22
+ end
23
+
24
+ def logger=(logger)
25
+ config[:logger] = logger
26
+ end
27
+ end
28
+
8
29
  class Error < StandardError
9
30
  end
31
+
32
+ class Auth < Hash
33
+ def channel_id
34
+ dig "incoming_webhook", "channel_id"
35
+ end
36
+
37
+ def post(body:, dryrun:nil)
38
+ uri = URI.parse webhook_url
39
+ ssl = uri.scheme == "https"
40
+ Net::HTTP.start(uri.host, uri.port, use_ssl: ssl) do |http|
41
+ if dryrun
42
+ Brutalismbot.logger&.info "POST DRYRUN #{uri}"
43
+ else
44
+ Brutalismbot.logger&.info "POST #{uri}"
45
+ req = Net::HTTP::Post.new uri, "content-type" => "application/json"
46
+ req.body = body
47
+ http.request req
48
+ end
49
+ end
50
+ end
51
+
52
+ def team_id
53
+ dig "team_id"
54
+ end
55
+
56
+ def webhook_url
57
+ dig "incoming_webhook", "url"
58
+ end
59
+ end
60
+
61
+ class Post < Hash
62
+ def created_after(time:)
63
+ created_utc.to_i > time.to_i
64
+ end
65
+
66
+ def created_utc
67
+ Time.at(dig("data", "created_utc").to_i).utc
68
+ end
69
+
70
+ def permalink
71
+ dig "data", "permalink"
72
+ end
73
+
74
+ def title
75
+ dig "data", "title"
76
+ end
77
+
78
+ def to_slack
79
+ {
80
+ blocks: [
81
+ {
82
+ type: "image",
83
+ title: {
84
+ type: "plain_text",
85
+ text: "/r/brutalism",
86
+ emoji: true,
87
+ },
88
+ image_url: url,
89
+ alt_text: title,
90
+ },
91
+ {
92
+ type: "context",
93
+ elements: [
94
+ {
95
+ type: "mrkdwn",
96
+ text: "<https://reddit.com#{permalink}|#{title}>",
97
+ },
98
+ ],
99
+ },
100
+ ],
101
+ }
102
+ end
103
+
104
+ def url
105
+ images = dig "data", "preview", "images"
106
+ source = images.map{|x| x["source"] }.compact.max do |a,b|
107
+ a.slice("width", "height").values <=> b.slice("width", "height").values
108
+ end
109
+ CGI.unescapeHTML source.dig("url")
110
+ rescue NoMethodError
111
+ dig("data", "media_metadata")&.values&.first&.dig("s", "u")
112
+ end
113
+ end
10
114
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: brutalismbot
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alexander Mancevice
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-06-06 00:00:00.000000000 Z
11
+ date: 2019-06-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: aws-sdk-s3
@@ -24,6 +24,20 @@ dependencies:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
26
  version: '1.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: codecov
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '0.1'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '0.1'
27
41
  - !ruby/object:Gem::Dependency
28
42
  name: bundler
29
43
  requirement: !ruby/object:Gem::Requirement
@@ -80,6 +94,20 @@ dependencies:
80
94
  - - "~>"
81
95
  - !ruby/object:Gem::Version
82
96
  version: '3.0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: simplecov
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '0.16'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '0.16'
83
111
  description: 'A Slack app that mirrors posts from /r/brutalism to a #channel of your
84
112
  choosing using incoming webhooks.'
85
113
  email:
@@ -92,8 +120,6 @@ files:
92
120
  - README.md
93
121
  - Rakefile
94
122
  - lib/brutalismbot.rb
95
- - lib/brutalismbot/event.rb
96
- - lib/brutalismbot/oauth.rb
97
123
  - lib/brutalismbot/r.rb
98
124
  - lib/brutalismbot/s3.rb
99
125
  - lib/brutalismbot/version.rb
@@ -1,30 +0,0 @@
1
- module Brutalismbot
2
- module Event
3
- class RecordCollection < Hash
4
- include Enumerable
5
-
6
- def each
7
- puts "EVENT #{to_json}"
8
- dig("Records").each{|x| yield x }
9
- end
10
- end
11
-
12
- class SNS < RecordCollection
13
- def each
14
- super do |record|
15
- yield JSON.parse record.dig("Sns", "Message")
16
- end
17
- end
18
- end
19
-
20
- class S3 < RecordCollection
21
- def each
22
- super do |record|
23
- bucket = URI.unescape record.dig("s3", "bucket", "name")
24
- key = URI.unescape record.dig("s3", "object", "key")
25
- yield bucket: bucket, key: key
26
- end
27
- end
28
- end
29
- end
30
- end
@@ -1,32 +0,0 @@
1
- module Brutalismbot
2
- class OAuth < Hash
3
- def channel_id
4
- dig "incoming_webhook", "channel_id"
5
- end
6
-
7
- def post(body:, dryrun:nil)
8
- uri = URI.parse webhook_url
9
- ssl = uri.scheme == "https"
10
- res = Net::HTTP.start(uri.host, uri.port, use_ssl: ssl) do |http|
11
- if dryrun
12
- puts "POST DRYRUN #{uri}"
13
- OpenStruct.new code: 200, body: JSON.parse(body)
14
- else
15
- puts "POST #{uri}"
16
- req = Net::HTTP::Post.new uri, "content-type" => "application/json"
17
- req.body = body
18
- http.request req
19
- end
20
- end
21
- {statusCode: res.code, body: res.body}
22
- end
23
-
24
- def team_id
25
- dig "team_id"
26
- end
27
-
28
- def webhook_url
29
- dig "incoming_webhook", "url"
30
- end
31
- end
32
- end