reddit-to-telegram 0.8.1 → 0.10.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: 9d1bb9660df8f6218a7bcbbbaa14b7e334fca34806c6923db6342ec41d6b44bb
4
- data.tar.gz: 3adf418255edb66a94efd73cb08a603570ad72278fe6ab45177a7ff64232385b
3
+ metadata.gz: 00436ff5f5d09c81d76f4d49f3cc3996e983857f1fbff2ad2db006ea014c0e96
4
+ data.tar.gz: ca6e585387a1165a9dacdee453d4853e531b727f9498918c97675d9484275b6c
5
5
  SHA512:
6
- metadata.gz: 616ec4f213bc2d759aff599a6260f86d131173353e329a911ee4287082ec7b0b29f2dec1a2b3db19cc6da22b3c1c13b9e339db293411ffb43d9b2879a30fba47
7
- data.tar.gz: d401484d93b7ae2e5537074e3c72219b1b267ad29a5e49cd0d944361654a6808d2f7f3df36123d05805c4e213be78f522cbd67623c6ff1d99dfdad0466986df2
6
+ metadata.gz: e2ff22eb616b95a99f3046b0c63677f24f7ca0ee1327bb7a51e97a68190b9c39639562024efaca73afa292b24b05188d9abaedba74de491bf708606526895519
7
+ data.tar.gz: 8658836ee6bb761ff2c75f9cd7c11ea2c2f526604982933d48eced8edf31ac515b8ca3ed97ac34bb515dca7baa21cb3d5de10463e13268519d3b73f8293be8f0
data/.rubocop.yml CHANGED
@@ -1,3 +1,6 @@
1
+ Metrics/ClassLength:
2
+ Max: 150
3
+
1
4
  Metrics/MethodLength:
2
5
  Max: 20
3
6
 
data/Gemfile.lock CHANGED
@@ -1,8 +1,8 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- reddit-to-telegram (0.5.1)
5
- aws-sdk-simpledb
4
+ reddit-to-telegram (0.10.0)
5
+ aws-sdk-dynamodb (~> 1.106)
6
6
  httparty
7
7
 
8
8
  GEM
@@ -11,15 +11,14 @@ GEM
11
11
  ast (2.4.2)
12
12
  aws-eventstream (1.3.0)
13
13
  aws-partitions (1.883.0)
14
- aws-sdk-core (3.190.3)
14
+ aws-sdk-core (3.192.1)
15
15
  aws-eventstream (~> 1, >= 1.3.0)
16
16
  aws-partitions (~> 1, >= 1.651.0)
17
17
  aws-sigv4 (~> 1.8)
18
18
  jmespath (~> 1, >= 1.6.1)
19
- aws-sdk-simpledb (1.42.0)
20
- aws-sdk-core (~> 3, >= 3.188.0)
21
- aws-sigv2 (~> 1.0)
22
- aws-sigv2 (1.2.0)
19
+ aws-sdk-dynamodb (1.106.0)
20
+ aws-sdk-core (~> 3, >= 3.191.0)
21
+ aws-sigv4 (~> 1.1)
23
22
  aws-sigv4 (1.8.0)
24
23
  aws-eventstream (~> 1, >= 1.0.2)
25
24
  httparty (0.21.0)
data/README.md CHANGED
@@ -7,7 +7,7 @@
7
7
  Beware, this is remotely not production-ready, API will change, you'll see lots of bugs and it may break at any time.
8
8
  Be sure to check for gem updates.
9
9
 
10
- You can set this bot up absolutely for free [via AWS Lambda](https://gist.github.com/dersnek/851c32a6b45eab19f1c8748095b2a481#file-free-rtt-bot-in-aws-lambda).
10
+ You can set this bot up absolutely for free [via AWS Lambda](https://gist.github.com/dersnek/851c32a6b45eab19f1c8748095b2a481#file-free-rtt-bot-in-aws-lambda), no ruby knowledge required.
11
11
 
12
12
  ## Installation
13
13
  In your `Gemfile` add:
@@ -19,33 +19,33 @@ Then run `bundle install`.
19
19
  Or `gem install reddit-to-telegram`. Don't forget to `require` it.
20
20
 
21
21
  ## Prerequisites
22
- - (Optionally) You'll need an [AWS account](https://aws.amazon.com/) to host a free SimpleDB (best available storage type, also default one). I also recommend hosting the bot on AWS lambda, since it would be free.
23
- - (Optionally) [Create a Reddit app](https://www.reddit.com/prefs/apps), which would allow more requests to reddit
24
22
  - [Obtain a telegram bot token](https://core.telegram.org/bots/tutorial#obtain-your-bot-token)
23
+ - (Optionally) You'll need an [AWS account](https://aws.amazon.com/) to host a free DynamoDB (best available storage type, also default one). I also recommend hosting the bot on AWS lambda, since it would be free.
24
+ - (Optionally) [Create a Reddit app](https://www.reddit.com/prefs/apps), which would allow more requests to reddit
25
25
 
26
- To run it, you'll need some env variables set.
27
- | Variable Name | Description | Required |
28
- | ------------- | ----------- | -------- |
29
- | RTT_AWS_ACCESS_KEY_ID | Your AWS access key ID. Needed for AWS SimpleDB storage | No |
30
- | RTT_AWS_DOMAIN_NAME | Domain name to use for SimpleDB | No |
31
- | RTT_AWS_REGION | AWS region your SimpleDB will be hosted on. Beware, it's not available in all regions. | No |
32
- | RTT_AWS_SECRET_ACCESS_KEY | Your AWS access key ID. Needed for AWS SimpleDB storage. | No |
33
- | RTT_GOOGLE_API_KEY | Your Google API key to translate posts via Google Translate. | No |
34
- | RTT_MAX_STORED_POSTS | Number of posts to store in the database to avoid duplicates, default is 25. | No |
35
- | RTT_REDDIT_CLIENT_ID | Reddit app credentials to access API. Reddit allows more authenticated requests. | No |
36
- | RTT_REDDIT_CLIENT_SECRET | Reddit app credentials to access API. Reddit allows more authenticated requests. | No |
37
- | RTT_STORE_TYPE | Choose between `aws_simple_db`, `memory` or `temp_file`. Default is `aws_simple_db`, so if you're not specifying your AWS credentials, you have to choose another store type. | No |
38
- | RTT_TELEGRAM_BOT_TOKEN | The token you've received when you've created a telegram bot. | Yes |
39
- | RTT_TELEGRAM_ERROR_CHANNEL_ID | Telegram channel to send errors to (without `@`, only errors from Telegram API responses would be sent for now) | No |
40
- | RTT_TEMP_DIR | Directory to write temp files to without trailing `/` | No |
26
+ It is pretty congifurable, either dynamically or via ENV variables.
27
+ To assign variables dynamically, set them via `RedditToTelegram.config.variable_name= `, e.g. `RedditToTelegram.config.aws.access_key_id = ...`.
28
+ You can also create an ENV variable with a corresponding name. Here is the full configuration explained. Required options have a * next to them.
41
29
 
42
-
43
- You can also set them dynamically:
44
- ```
45
- RedditToTelegram.config.aws.access_key_id =
46
- RedditToTelegram.config.telegram.bot_token =
47
- ```
48
- Check out `lib/configuration` for full configuration.
30
+ Config variable | Corresponding ENV Variable | Description |
31
+ | ----------------------- | ----------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
32
+ add_channel_handle | - | Add channel handle to Telegram posts. Accepted values: true or false. Default is false |
33
+ add_reddit_link | - | Add reddit link to Telegram posts. Accepted values: true or false. Default is false. |
34
+ logger | - | Which logger to use. You can pass your own ruby logger |
35
+ on_error | - | What to do when an error happens. Default is :log, but you can also :raise or :ignore |
36
+ send_errors_to_telegram | - | Also log errors to telegram (besides regular logging). Accepted values: true or false, default is false |
37
+ translate | - | Translate posts via Google Translate. Leave empty for no translation. More details below |
38
+ aws.access_key_id | RTT_AWS_ACCESS_KEY_ID | Your AWS access key ID. Needed for AWS DynamoDB storage |
39
+ aws.region | RTT_AWS_REGION | AWS region your DynamoDB is hosted on |
40
+ aws.secret_access_key | RTT_AWS_SECRET_ACCESS_KEY | Your AWS access key ID. Needed for AWS DynamoDB storage. |
41
+ google.api_key | RTT_GOOGLE_API_KEY | Your Google API key to translate posts via Google Translate |
42
+ reddit.client_id | RTT_REDDIT_CLIENT_ID | Reddit app credentials to access API. Reddit allows more authenticated requests |
43
+ reddit.client_secret | RTT_REDDIT_CLIENT_SECRET | Reddit app credentials to access API. Reddit allows more authenticated requests |
44
+ store.max_stored_posts | RTT_MAX_STORED_POSTS | Number of posts to store in the database to avoid duplicates, default is 25 |
45
+ store.tmp_dir | RTT_TEMP_DIR | Directory to write temp files to without trailing `/` |
46
+ store.type | RTT_STORE_TYPE | Choose between `aws_dynamo_db`, `memory` or `temp_file`. Default is `aws_dynamo_db`, so if you're not specifying your AWS credentials, you have to choose another store type |
47
+ telegram.bot_token * | RTT_TELEGRAM_BOT_TOKEN | The token you've received when you've created a telegram bot |
48
+ telegram.error_channel_id | RTT_TELEGRAM_ERROR_CHANNEL_ID | Telegram channel to send errors to (without `@`, only errors from Telegram API responses would be sent for now) |
49
49
 
50
50
  ## Usage
51
51
 
@@ -53,30 +53,19 @@ Check out `lib/configuration` for full configuration.
53
53
  2. To fetch latest hot post which hasn't been pushed yet:
54
54
  ```
55
55
  RedditToTelegram.hot(
56
- subreddit_name_1: :telegram_channel_id_1,
57
- subreddit_name_2: :telegram_channel_id_2
56
+ telegram_channel_id_1: :subreddit_name_1,
57
+ telegram_channel_id_2: :subreddit_name_2
58
58
  )
59
59
  ```
60
- Or to push one specific post (the only thing you need to set up for this is your telegram bot token):
60
+ You can push posts from one subreddit to one telegram channel, several-to-one, one-to-several, several-to-several, whatever you like.
61
+ You can also push one specific post:
61
62
  ```
62
- RedditToTelegram.from_link("regular_link_to_post", :telegram_channel_id)
63
+ RedditToTelegram.from_link(telegram_channel_id: "regular_link_to_post")
63
64
  ```
64
65
  Use `:telegram_channel_id` without the `@`.
65
66
 
66
- ### Options
67
+ ### Translation
67
68
 
68
- Translate option is supported. You will have to set up Google Translate API key and add it to env. You can find available languages in [Google Translate docs](https://cloud.google.com/translate/docs/languages).
69
- ```
70
- RedditToTelegram.hot(
71
- { subreddit_name_1: :telegram_channel_id_1 },
72
- translate: :ja
73
- )
74
- ```
75
- You can also specify if you want to add reddit link or telegram channel handle to the post text. By default they won't be added.
76
- ```
77
- RedditToTelegram.hot(
78
- { subreddit_name_1: :telegram_channel_id_1 },
79
- add_reddit_link: true,
80
- add_channel_handle: true
81
- )
82
- ```
69
+ Translation option is supported.
70
+ You will have to set `RedditToTelegram.config.translate` to the language key you want to translate to. You can find available languages in [Google Translate docs](https://cloud.google.com/translate/docs/languages).
71
+ You will also have to set up Google Translate API key assign it to `RedditToTelegram.config.google.api_key`.
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "reddit_to_telegram/configuration"
3
4
  Dir["#{File.dirname(__FILE__)}/reddit_to_telegram/**/*.rb"].each { |file| require file }
4
5
 
5
6
  module RedditToTelegram
@@ -5,7 +5,16 @@ require "logger"
5
5
  module RedditToTelegram
6
6
  module Configuration
7
7
  class << self
8
- attr_writer :logger, :on_error
8
+ attr_writer :add_channel_handle, :add_reddit_link, :logger,
9
+ :on_error, :send_errors_to_telegram, :translate
10
+
11
+ def add_channel_handle
12
+ @add_channel_handle ||= false
13
+ end
14
+
15
+ def add_reddit_link
16
+ @add_reddit_link ||= false
17
+ end
9
18
 
10
19
  def logger
11
20
  @logger ||= Logger.new($stdout).tap do |log|
@@ -16,11 +25,19 @@ module RedditToTelegram
16
25
  def on_error
17
26
  @on_error ||= :log
18
27
  end
28
+
29
+ def send_errors_to_telegram
30
+ @send_errors_to_telegram ||= false
31
+ end
32
+
33
+ def translate
34
+ @translate ||= nil
35
+ end
19
36
  end
20
37
 
21
38
  class Store
22
39
  DEFAULT_TMP_DIR = "#{Dir.pwd}/tmp".freeze
23
- DEFAULT_TYPE = :aws_simple_db
40
+ DEFAULT_TYPE = :aws_dynamo_db
24
41
 
25
42
  class << self
26
43
  attr_writer :max_stored_posts, :tmp_dir, :type
@@ -40,8 +57,7 @@ module RedditToTelegram
40
57
  end
41
58
 
42
59
  class AWS
43
- ATTRS = %i[access_key_id secret_access_key region domain_name].freeze
44
- DEFAULT_DOMAIN_NAME = "reddit_to_telegram"
60
+ ATTRS = %i[access_key_id secret_access_key region].freeze
45
61
 
46
62
  class << self
47
63
  attr_writer(*ATTRS)
@@ -58,10 +74,6 @@ module RedditToTelegram
58
74
  @region ||= ENV["RTT_AWS_REGION"]
59
75
  end
60
76
 
61
- def domain_name
62
- @domain_name ||= ENV["RTT_AWS_DOMAIN_NAME"] || DEFAULT_DOMAIN_NAME
63
- end
64
-
65
77
  def set_up?
66
78
  ATTRS.all? { |a| !a.to_s.empty? }
67
79
  end
@@ -3,19 +3,50 @@
3
3
  module RedditToTelegram
4
4
  class RedditToTelegramError < StandardError; end
5
5
 
6
+ class BadResponseFromTelegram < RedditToTelegramError; end
7
+ class FailedToCreateDatabaseTable < RedditToTelegramError; end
8
+ class FailedToFetchFromReddit < RedditToTelegramError; end
9
+ class FailedToPersistData < RedditToTelegramError; end
6
10
  class InvalidStoreType < RedditToTelegramError; end
7
11
  class MissingConfiguration < RedditToTelegramError; end
8
12
 
9
13
  class Errors
10
14
  class << self
11
15
  def new(error, message = nil)
16
+ log_message = error.to_s
17
+ log_message += ": #{message}" unless message.nil?
18
+
12
19
  if Configuration.on_error == :raise
13
20
  raise(error.new(message))
14
21
  elsif Configuration.on_error == :log
15
- log_message = error.to_s
16
- log_message += ": #{message}" unless message.nil?
17
22
  Configuration.logger.error(log_message)
18
23
  end
24
+
25
+ return unless Configuration.send_errors_to_telegram
26
+
27
+ push_error_to_telegram(log_message)
28
+
29
+ nil
30
+ end
31
+
32
+ private
33
+
34
+ def push_error_to_telegram(message)
35
+ if Configuration.telegram.error_channel_id.to_s.empty?
36
+ Configuration.logger.warn("Telegram Error Channel ID is not set up, can't send errors there")
37
+ return
38
+ end
39
+
40
+ Telegram::Post.push(
41
+ {
42
+ type: :text,
43
+ text: message,
44
+ misc: { no_retry: true, disable_link_preview: true }
45
+ },
46
+ Configuration.telegram.error_channel_id
47
+ )
48
+
49
+ nil
19
50
  end
20
51
  end
21
52
  end
@@ -3,29 +3,29 @@
3
3
  module RedditToTelegram
4
4
  class Post
5
5
  class << self
6
- def hot(sources, opts = {})
6
+ def hot(sources)
7
7
  check_config
8
- return if sources.empty?
9
-
10
8
  Store.setup
11
9
 
12
- sources.each do |subreddit, telegram_chat_id|
13
- res = Reddit::Fetch.hot(subreddit)
14
- handle_res(res, subreddit, telegram_chat_id, opts)
10
+ sources.each do |telegram_chat_id, subreddits|
11
+ Array(subreddits).each do |subreddit|
12
+ res = Reddit::Fetch.hot(subreddit)
13
+ handle_res(res, subreddit, telegram_chat_id)
14
+ end
15
15
  end
16
16
  end
17
17
 
18
- def from_link(link, telegram_chat_id, opts = {})
18
+ def from_link(sources)
19
19
  check_config
20
- return if link.empty?
20
+ return unless check_from_link_sources(sources)
21
21
 
22
22
  Configuration.store.type = :memory
23
23
  Store.setup
24
24
 
25
- res = Reddit::Fetch.post(link)
25
+ res = Reddit::Fetch.post(sources.values.first)
26
26
  return unless res_ok?(res)
27
27
 
28
- Telegram::Post.push(res, telegram_chat_id, opts)
28
+ Telegram::Post.push(res, sources.keys.first)
29
29
  res
30
30
  end
31
31
 
@@ -35,19 +35,17 @@ module RedditToTelegram
35
35
  Errors.new(MissingConfiguration, "Missing Telegram bot token") if Configuration.telegram.bot_token.to_s.empty?
36
36
  end
37
37
 
38
- def handle_res(res, subreddit, telegram_chat_id, opts = {})
38
+ def handle_res(res, subreddit, telegram_chat_id)
39
39
  return unless res_ok?(res)
40
40
 
41
- post = find_new_post(subreddit, res)
41
+ post = Store.posts.next(telegram_chat_id, subreddit, res)
42
42
 
43
43
  if post.nil?
44
44
  Configuration.logger.info("Could not find a new post to push")
45
45
  return
46
46
  end
47
47
 
48
- res = Telegram::Post.push(post, telegram_chat_id, opts)
49
- Store::Posts.add(subreddit, post[:id])
50
- res
48
+ Telegram::Post.push(post, telegram_chat_id)
51
49
  end
52
50
 
53
51
  def res_ok?(res)
@@ -62,8 +60,13 @@ module RedditToTelegram
62
60
  end
63
61
  end
64
62
 
65
- def find_new_post(subreddit, posts)
66
- posts.find { |post| !Store::Posts.dup?(subreddit, post[:id]) }
63
+ def check_from_link_sources(sources)
64
+ if !sources.is_a?(Hash) || sources.keys.count != 1 || sources.values.count != 1
65
+ Errors.new(ArgumentError, "Check documentation on usage")
66
+ return false
67
+ end
68
+
69
+ true
67
70
  end
68
71
  end
69
72
  end
@@ -55,12 +55,22 @@ module RedditToTelegram
55
55
  handle_429(func_name, func_args)
56
56
  when 200
57
57
  Output.format_response(res)
58
+ else
59
+ Errors.new(FailedToFetchFromReddit, res.to_s)
58
60
  end
59
61
  end
60
62
 
61
63
  def handle_401(func_name, func_args)
64
+ retries_left = func_args.last
65
+ func_args[func_args.length - 1] = retries_left - 1
66
+
62
67
  Store::Reddit.token = Auth.token
63
- send(func_name, *func_args) if func_args.last > 0
68
+
69
+ if retries_left > 0
70
+ send(func_name, *func_args)
71
+ else
72
+ Errors.new(FailedToFetchFromReddit, "Failed to authenticate")
73
+ end
64
74
  end
65
75
 
66
76
  def handle_429(func_name, func_args)
@@ -68,7 +78,12 @@ module RedditToTelegram
68
78
 
69
79
  sleep(10 / retries_left) if retries_left > 0
70
80
  func_args[func_args.length - 1] = retries_left - 1
71
- send(func_name, *func_args) if retries_left > 0
81
+
82
+ if retries_left > 0
83
+ send(func_name, *func_args)
84
+ else
85
+ Errors.new(FailedToFetchFromReddit, "Too many requests")
86
+ end
72
87
  end
73
88
  end
74
89
  end
@@ -96,8 +96,9 @@ module RedditToTelegram
96
96
  end
97
97
 
98
98
  def base_post_format_attrs(data)
99
- { id: data["name"],
100
- text: CGI.unescapeHTML(data["title"]) }
99
+ { id: data["name"].split("_")&.dig(1),
100
+ text: CGI.unescapeHTML(data["title"]),
101
+ misc: {} }
101
102
  end
102
103
 
103
104
  def prepare_gallery_links(data)
@@ -0,0 +1,132 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "aws-sdk-dynamodb"
4
+ require "json"
5
+
6
+ module RedditToTelegram
7
+ module Store
8
+ class AWSDynamoDB
9
+ ITEM_NAME = "cached_data"
10
+
11
+ class << self
12
+ def client
13
+ @client ||= Aws::DynamoDB::Client.new(
14
+ access_key_id: Configuration.aws.access_key_id,
15
+ secret_access_key: Configuration.aws.secret_access_key,
16
+ region: Configuration.aws.region
17
+ )
18
+ end
19
+
20
+ private
21
+
22
+ attr_reader :reddit_token
23
+
24
+ def setup
25
+ check_credentials
26
+ prepare_db
27
+ assign_default_values
28
+ end
29
+
30
+ def check_credentials
31
+ return unless Configuration.store.type == :aws_dynamo_db
32
+
33
+ return if Configuration.aws.set_up?
34
+
35
+ Errors.new(
36
+ MissingConfiguration,
37
+ "Missing AWS credentials. Set them up or change store type to anything other than aws_dynamo_db"
38
+ )
39
+ end
40
+
41
+ def add_post(telegram_chat_id, subreddit, id)
42
+ assign_empty_values_to_posts(telegram_chat_id, subreddit)
43
+
44
+ @posts[telegram_chat_id][subreddit] << id
45
+
46
+ if @posts[telegram_chat_id][subreddit].count > Store.max_stored_posts
47
+ @posts[telegram_chat_id][subreddit].shift
48
+ end
49
+
50
+ persist_posts(telegram_chat_id)
51
+ end
52
+
53
+ def assign_empty_values_to_posts(telegram_chat_id, subreddit)
54
+ @posts[telegram_chat_id] = {} if @posts[telegram_chat_id].nil?
55
+ @posts[telegram_chat_id][subreddit] = [] if @posts[telegram_chat_id][subreddit].nil?
56
+ end
57
+
58
+ def persist_posts(telegram_chat_id)
59
+ res = client.put_item(
60
+ {
61
+ item: {
62
+ "TelegramChannel" => telegram_chat_id.to_s,
63
+ "Posts" => @posts[telegram_chat_id].to_json
64
+ },
65
+ return_consumed_capacity: "TOTAL",
66
+ table_name: POSTS_TABLE_NAME
67
+ }
68
+ )
69
+
70
+ Errors.new(FailedToPersistData, "Failed to persist data to DynamoDB") unless res.successful?
71
+ end
72
+
73
+ def dup_post?(telegram_chat_id, subreddit, id)
74
+ return false if @posts.dig(telegram_chat_id, subreddit).nil?
75
+
76
+ @posts[telegram_chat_id][subreddit].include?(id)
77
+ end
78
+
79
+ def load_posts(telegram_chat_id)
80
+ res = client.get_item(
81
+ { key: { "TelegramChannel" => telegram_chat_id.to_s },
82
+ table_name: POSTS_TABLE_NAME }
83
+ )
84
+ @posts[telegram_chat_id] = JSON.parse(res.item["Posts"]).transform_keys(&:to_sym)
85
+ rescue StandardError
86
+ @posts[telegram_chat_id] = nil
87
+ end
88
+
89
+ def assign_default_values
90
+ @reddit_token = ""
91
+ @posts = {}
92
+ end
93
+
94
+ POSTS_TABLE_NAME = "Posts"
95
+ POSTS_TABLE_ATTRIBUTES = {
96
+ attribute_definitions: [
97
+ { attribute_name: "TelegramChannel",
98
+ attribute_type: "S" }
99
+ ],
100
+ key_schema: [
101
+ { attribute_name: "TelegramChannel",
102
+ key_type: "HASH" }
103
+ ],
104
+ provisioned_throughput: {
105
+ read_capacity_units: 1,
106
+ write_capacity_units: 1
107
+ },
108
+ table_name: POSTS_TABLE_NAME
109
+ }.freeze
110
+
111
+ def prepare_db
112
+ res = client.list_tables
113
+ return unless res.successful?
114
+
115
+ return if res.table_names.include?(POSTS_TABLE_NAME)
116
+
117
+ client.create_table(POSTS_TABLE_ATTRIBUTES)
118
+
119
+ waited = 0
120
+ while client.describe_table(table_name: POSTS_TABLE_NAME).table.table_status != "ACTIVE"
121
+ if waited == 10
122
+ Errors.new(FailedToCreateDatabaseTable, "Failed to create #{POSTS_TABLE_NAME} table in DynamoDB")
123
+ break
124
+ end
125
+ sleep(1)
126
+ waited += 1
127
+ end
128
+ end
129
+ end
130
+ end
131
+ end
132
+ end
@@ -11,17 +11,26 @@ module RedditToTelegram
11
11
  @posts = {}
12
12
 
13
13
  def setup; end
14
+ def load_posts(_); end
14
15
 
15
- def add_post(subreddit, id)
16
- @posts[subreddit] = [] if @posts[subreddit].nil?
17
- @posts[subreddit] << id
18
- @posts[subreddit].shift if @posts[subreddit].count > Store::MAX_STORED_POSTS
16
+ def add_post(telegram_chat_id, subreddit, id)
17
+ assign_empty_values_to_posts(telegram_chat_id, subreddit)
18
+
19
+ posts[telegram_chat_id][subreddit] << id
20
+ return unless posts[telegram_chat_id][subreddit].count > Store.max_stored_posts
21
+
22
+ posts[telegram_chat_id][subreddit].shift
23
+ end
24
+
25
+ def assign_empty_values_to_posts(telegram_chat_id, subreddit)
26
+ posts[telegram_chat_id] = {} if posts[telegram_chat_id].nil?
27
+ posts[telegram_chat_id][subreddit] = [] if posts[telegram_chat_id][subreddit].nil?
19
28
  end
20
29
 
21
- def dup_post?(subreddit, id)
22
- return false if posts[subreddit].nil?
30
+ def dup_post?(telegram_channel, subreddit, id)
31
+ return false if posts.dig(telegram_channel, subreddit).nil?
23
32
 
24
- posts[subreddit].include?(id)
33
+ posts[telegram_channel][subreddit].include?(id)
25
34
  end
26
35
 
27
36
  def posts
@@ -17,22 +17,34 @@ module RedditToTelegram
17
17
  read_file
18
18
  end
19
19
 
20
+ def load_posts(_); end
21
+
20
22
  def reddit_token=(val)
21
23
  @reddit_token = val
22
24
  write_file
23
25
  end
24
26
 
25
- def add_post(subreddit, id)
26
- @posts[subreddit] = [] if @posts[subreddit].nil?
27
- @posts[subreddit] << id
28
- @posts[subreddit].shift if @posts[subreddit].count > Store::MAX_STORED_POSTS
27
+ def add_post(telegram_chat_id, subreddit, id)
28
+ assign_empty_values_to_posts(telegram_chat_id, subreddit)
29
+
30
+ @posts[telegram_chat_id][subreddit] << id
31
+
32
+ if @posts[telegram_chat_id][subreddit].count > Store.max_stored_posts
33
+ @posts[telegram_chat_id][subreddit].shift
34
+ end
35
+
29
36
  write_file
30
37
  end
31
38
 
32
- def dup_post?(subreddit, id)
33
- return false if posts[subreddit].nil?
39
+ def assign_empty_values_to_posts(telegram_chat_id, subreddit)
40
+ @posts[telegram_chat_id] = {} if @posts[telegram_chat_id].nil?
41
+ @posts[telegram_chat_id][subreddit] = [] if @posts[telegram_chat_id][subreddit].nil?
42
+ end
43
+
44
+ def dup_post?(telegram_channel, subreddit, id)
45
+ return false if posts.dig(telegram_channel, subreddit).nil?
34
46
 
35
- posts[subreddit].include?(id)
47
+ posts[telegram_channel][subreddit].include?(id)
36
48
  end
37
49
 
38
50
  def read_file
@@ -43,14 +55,14 @@ module RedditToTelegram
43
55
  @reddit_token = data["reddit_token"]
44
56
  @posts = {}
45
57
  data.each do |key, value|
46
- @posts[key.split("_").last.to_sym] = value if key.match?(/posts_.+/)
58
+ @posts[key.split("_").last.to_sym] = value.transform_keys(&:to_sym) if key.match?(/posts_.+/)
47
59
  end
48
60
  end
49
61
 
50
62
  def write_file
51
63
  data = { reddit_token: @reddit_token }
52
- @posts.each do |subreddit, values|
53
- data["posts_#{subreddit}".to_sym] = values
64
+ @posts.each do |telegram_chat_id, values|
65
+ data["posts_#{telegram_chat_id}"] = values
54
66
  end
55
67
  File.open(temp_file_path, "w") { |f| f.write(data.to_json) }
56
68
  end
@@ -2,15 +2,12 @@
2
2
 
3
3
  module RedditToTelegram
4
4
  module Store
5
- MAX_STORED_POSTS = Configuration.store.max_stored_posts - 1
6
5
  CLASS_MAP = {
7
- aws_simple_db: "RedditToTelegram::Store::AWSSimpleDB",
6
+ aws_dynamo_db: "RedditToTelegram::Store::AWSDynamoDB",
8
7
  memory: "RedditToTelegram::Store::Memory",
9
8
  temp_file: "RedditToTelegram::Store::TempFile"
10
9
  }.freeze
11
10
 
12
- STORE = Object.const_get("RedditToTelegram::Store::AWSSimpleDB")
13
-
14
11
  class << self
15
12
  attr_accessor :active
16
13
 
@@ -20,6 +17,18 @@ module RedditToTelegram
20
17
  self.active = Object.const_get(CLASS_MAP[Configuration.store.type])
21
18
  active.send(:setup)
22
19
  end
20
+
21
+ def max_stored_posts
22
+ Configuration.store.max_stored_posts - 1
23
+ end
24
+
25
+ def reddit
26
+ Reddit
27
+ end
28
+
29
+ def posts
30
+ Posts
31
+ end
23
32
  end
24
33
 
25
34
  class Reddit
@@ -36,12 +45,19 @@ module RedditToTelegram
36
45
 
37
46
  class Posts
38
47
  class << self
39
- def add(subreddit, id)
40
- Store.active.send(:add_post, subreddit, id)
48
+ def add(telegram_chat_id, subreddit, id)
49
+ Store.active.send(:add_post, telegram_chat_id, subreddit, id)
50
+ end
51
+
52
+ def dup?(telegram_chat_id, subreddit, id)
53
+ Store.active.send(:dup_post?, telegram_chat_id, subreddit, id)
41
54
  end
42
55
 
43
- def dup?(subreddit, id)
44
- Store.active.send(:dup_post?, subreddit, id)
56
+ def next(telegram_chat_id, subreddit, posts)
57
+ Store.active.send(:load_posts, telegram_chat_id)
58
+ new_post = posts.find { |post| !dup?(telegram_chat_id, subreddit, post[:id]) }
59
+ add(telegram_chat_id, subreddit, new_post[:id]) unless new_post.nil?
60
+ new_post
45
61
  end
46
62
  end
47
63
  end
@@ -5,30 +5,30 @@ module RedditToTelegram
5
5
  class Post
6
6
  class Gallery
7
7
  class << self
8
- def push_remaining_gallery_data(post, channel, res, opts = {})
8
+ def push_remaining_gallery_data(post, channel, res)
9
9
  if post[:additional_media]
10
- push_remaining_gallery_images(post, channel, opts)
10
+ push_remaining_gallery_images(post, channel)
11
11
  else
12
- push_gallery_caption(post, channel, res, opts)
12
+ push_gallery_caption(post, channel, res)
13
13
  end
14
14
  end
15
15
 
16
16
  private
17
17
 
18
- def push_remaining_gallery_images(post, channel,opts = {})
18
+ def push_remaining_gallery_images(post, channel)
19
19
  post[:media] = post[:additional_media].first(10)
20
20
  remaining = post.delete(:additional_media).drop(10)
21
21
  post[:additional_media] = remaining unless remaining.empty?
22
- Post.push(post, channel, opts)
22
+ Post.push(post, channel)
23
23
  end
24
24
 
25
- def push_gallery_caption(post, channel, res, opts = {})
25
+ def push_gallery_caption(post, channel, res)
26
26
  Telegram::Post.push(
27
27
  { type: :text,
28
28
  id: post[:id],
29
- text: post[:text] },
30
- channel,
31
- opts.merge(gallery_caption_opts(res))
29
+ text: post[:text],
30
+ misc: gallery_caption_opts(res) },
31
+ channel
32
32
  )
33
33
  end
34
34
 
@@ -19,20 +19,20 @@ module RedditToTelegram
19
19
  }.freeze
20
20
 
21
21
  class << self
22
- def push(post, channel, opts = {})
22
+ def push(post, channel)
23
23
  res = HTTParty.post(
24
24
  "#{BASE_URI}#{Configuration.telegram.bot_token}/send#{METHOD_MAP[post[:type]]}",
25
- **params(post, channel, opts)
25
+ **params(post, channel)
26
26
  )
27
27
 
28
- handle_response(post, channel, res, opts)
28
+ handle_response(post, channel, res)
29
29
  end
30
30
 
31
31
  private
32
32
 
33
- def params(post, channel, opts = {})
33
+ def params(post, channel)
34
34
  binary = post.dig(:misc)&.dig(:binary)
35
- body = PrepareRequest.body(post, channel, opts)
35
+ body = PrepareRequest.body(post, channel)
36
36
 
37
37
  pars = {
38
38
  body: binary ? body : body.to_json,
@@ -42,29 +42,16 @@ module RedditToTelegram
42
42
  pars
43
43
  end
44
44
 
45
- def handle_response(post, channel, res, opts = {})
46
- push_error(post, channel, res, opts) unless res["ok"] || opts[:no_retry]
47
- Gallery.push_remaining_gallery_data(post, channel, res, opts) if post[:type] == :gallery
48
- Video.delete_file if post[:type] == :video && post.dig(:misc)&.dig(:binary)
45
+ def handle_response(post, channel, res)
46
+ log_error(post, channel, res) unless res["ok"]
47
+ Gallery.push_remaining_gallery_data(post, channel, res) if post[:type] == :gallery
48
+ Video.delete_file if post[:type] == :video && post.dig(:misc, :binary)
49
49
  res
50
50
  end
51
51
 
52
- def push_error(post, channel, res, opts = {})
53
- return if Configuration.telegram.error_channel_id.to_s.empty?
54
-
55
- push(
56
- {
57
- type: :text,
58
- id: post[:id],
59
- text: "Channel: @#{channel}\n\nResponse: #{res}"
60
- },
61
- Configuration.telegram.error_channel_id,
62
- opts.merge(
63
- add_reddit_link: true,
64
- disable_link_preview: true,
65
- no_retry: true
66
- )
67
- )
52
+ def log_error(post, channel, res)
53
+ message = "\n\nChannel: #{channel}\n\nPost data: #{post}\n\nResponse: #{res}"
54
+ Errors.new(BadResponseFromTelegram, message)
68
55
  end
69
56
  end
70
57
  end
@@ -4,54 +4,64 @@ module RedditToTelegram
4
4
  module Telegram
5
5
  class PrepareRequest
6
6
  class << self
7
- def body(post, chat_id, opts = {})
8
- body = prepare_body(post, chat_id, opts)
9
- body[:link_preview_options] = { is_disabled: true } if opts[:disable_link_preview]
10
- body[:reply_parameters] = { message_id: opts[:reply_to] } if opts[:reply_to]
7
+ def body(post, chat_id)
8
+ body = prepare_body(post, chat_id)
9
+ body[:link_preview_options] = { is_disabled: true } if post.dig(:misc, :disable_link_preview)
10
+ body[:reply_parameters] = { message_id: post[:misc][:reply_to] } if post.dig(:misc, :reply_to)
11
11
  body
12
12
  end
13
13
 
14
14
  private
15
15
 
16
- def prepare_body(post, chat_id, opts = {})
16
+ def prepare_body(post, chat_id)
17
17
  case post[:type]
18
18
  when :image
19
- { chat_id: "@#{chat_id}", photo: post[:media], caption: prepare_text(post, chat_id, opts) }
19
+ { chat_id: "@#{chat_id}", photo: post[:media], caption: prepare_text(post, chat_id) }
20
20
  when :gallery
21
- { chat_id: "@#{chat_id}", media: prepare_gallery_media(post), caption: prepare_text(post, chat_id, opts) }
21
+ { chat_id: "@#{chat_id}", media: prepare_gallery_media(post), caption: prepare_text(post, chat_id) }
22
22
  when :gif
23
- { chat_id: "@#{chat_id}", animation: post[:media], caption: prepare_text(post, chat_id, opts) }
23
+ { chat_id: "@#{chat_id}", animation: post[:media], caption: prepare_text(post, chat_id) }
24
24
  when :text
25
- { chat_id: "@#{chat_id}", text: prepare_text(post, chat_id, opts) }
25
+ { chat_id: "@#{chat_id}", text: prepare_text(post, chat_id) }
26
26
  when :video
27
27
  {
28
28
  chat_id: "@#{chat_id}",
29
29
  video: prepare_video(post),
30
30
  height: post[:misc][:video_height],
31
31
  width: post[:misc][:video_width],
32
- caption: prepare_text(post, chat_id, opts)
32
+ caption: prepare_text(post, chat_id)
33
33
  }
34
34
  end
35
35
  end
36
36
 
37
- def prepare_text(post, chat_id, opts = {})
37
+ def prepare_text(post, chat_id)
38
38
  text = post[:text]
39
39
 
40
- text = Services::Translate.text(text, opts[:translate]) if opts[:translate]
40
+ text = translate(text)
41
+ text = add_reddit_link(text, post)
42
+ add_channel_handle(text, chat_id)
43
+ end
41
44
 
42
- if opts[:add_reddit_link]
43
- id = post[:id].split("_")[1]
44
- text += "\n\nhttps://redd.it/#{id}"
45
- end
45
+ def translate(text)
46
+ return text unless Configuration.translate
46
47
 
47
- if opts[:add_channel_handle]
48
- text += opts[:add_reddit_link] ? "\n" : "\n\n"
49
- text += "@#{chat_id}"
50
- end
48
+ Services::Translate.text(text, Configuration.translate)
49
+ end
51
50
 
51
+ def add_reddit_link(text, post)
52
+ return text unless Configuration.add_reddit_link
53
+
54
+ text += "\n\nhttps://redd.it/#{post[:id]}"
52
55
  text
53
56
  end
54
57
 
58
+ def add_channel_handle(text, chat_id)
59
+ return text unless Configuration.add_channel_handle
60
+
61
+ text += Configuration.add_reddit_link ? "\n" : "\n\n"
62
+ text + "@#{chat_id}"
63
+ end
64
+
55
65
  def prepare_gallery_media(post)
56
66
  Array(post[:media]).map { |link| { type: "photo", media: link } }
57
67
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RedditToTelegram
4
- VERSION = "0.8.1"
4
+ VERSION = "0.10.0"
5
5
  end
@@ -14,7 +14,7 @@ Gem::Specification.new do |s|
14
14
  s.homepage = "https://github.com/dersnek/reddit-to-telegram"
15
15
  s.license = "MIT"
16
16
 
17
- s.add_dependency "aws-sdk-simpledb"
17
+ s.add_dependency "aws-sdk-dynamodb", "~> 1.106"
18
18
  s.add_dependency "httparty"
19
19
 
20
20
  s.add_development_dependency "rubocop"
metadata CHANGED
@@ -1,29 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: reddit-to-telegram
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.1
4
+ version: 0.10.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mark Tityuk
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-04-08 00:00:00.000000000 Z
11
+ date: 2024-04-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: aws-sdk-simpledb
14
+ name: aws-sdk-dynamodb
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - ">="
17
+ - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '0'
19
+ version: '1.106'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - ">="
24
+ - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: '0'
26
+ version: '1.106'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: httparty
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -75,7 +75,7 @@ files:
75
75
  - lib/reddit_to_telegram/reddit/output/imgur.rb
76
76
  - lib/reddit_to_telegram/services/translate.rb
77
77
  - lib/reddit_to_telegram/store.rb
78
- - lib/reddit_to_telegram/store/aws_simple_db.rb
78
+ - lib/reddit_to_telegram/store/aws_dynamo_db.rb
79
79
  - lib/reddit_to_telegram/store/memory.rb
80
80
  - lib/reddit_to_telegram/store/temp_file.rb
81
81
  - lib/reddit_to_telegram/telegram/post.rb
@@ -1,123 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "aws-sdk-simpledb"
4
- require "json"
5
-
6
- module RedditToTelegram
7
- module Store
8
- class AWSSimpleDB
9
- ITEM_NAME = "cached_data"
10
-
11
- class << self
12
- def client
13
- @client ||= Aws::SimpleDB::Client.new(
14
- access_key_id: Configuration.aws.access_key_id,
15
- secret_access_key: Configuration.aws.secret_access_key,
16
- region: Configuration.aws.region
17
- )
18
- end
19
-
20
- private
21
-
22
- attr_reader :reddit_token
23
-
24
- def setup
25
- check_credentials
26
- create_domain unless client.list_domains.domain_names.include?(Configuration.aws.domain_name)
27
- read_db
28
- end
29
-
30
- def check_credentials
31
- return unless Configuration.store.type == :aws_simple_db
32
-
33
- return if Configuration.aws.set_up?
34
-
35
- Errors.new(
36
- MissingConfiguration,
37
- "Missing AWS credentials. Set them up or change store type to anything other than aws_simple_db"
38
- )
39
- end
40
-
41
- def reddit_token=(val)
42
- @reddit_token = val
43
- write_db
44
- end
45
-
46
- def add_post(subreddit, id)
47
- @posts[subreddit] = [] if @posts[subreddit].nil?
48
- @posts[subreddit] << id
49
- @posts[subreddit].shift if @posts[subreddit].count > Store::MAX_STORED_POSTS
50
- write_db
51
- end
52
-
53
- def dup_post?(subreddit, id)
54
- return false if @posts[subreddit].nil?
55
-
56
- @posts[subreddit].include?(id)
57
- end
58
-
59
- def read_db
60
- res = client.get_attributes(
61
- {
62
- domain_name: Configuration.aws.domain_name,
63
- item_name: "cached_data",
64
- consistent_read: true
65
- }
66
- )
67
-
68
- return assign_default_values if res.attributes.empty?
69
-
70
- assign_values_from_db(res)
71
- end
72
-
73
- def assign_values_from_db(data)
74
- @reddit_token = data.attributes.find { |a| a.name == "reddit_token" }.value || ""
75
- @posts = {}
76
- data.attributes.each do |attr|
77
- @posts[attr.name.split("_").last.to_sym] = JSON.parse(attr.value) if attr.name.match?(/posts_.+/)
78
- end
79
- end
80
-
81
- def write_db
82
- client.put_attributes(
83
- {
84
- domain_name: Configuration.aws.domain_name,
85
- item_name: ITEM_NAME,
86
- attributes: prepare_db_attrs
87
- }
88
- )
89
- end
90
-
91
- def prepare_db_attrs
92
- attrs = [
93
- {
94
- name: "reddit_token",
95
- value: @reddit_token,
96
- replace: true
97
- }
98
- ]
99
-
100
- @posts.each do |subreddit, values|
101
- attrs << { name: "posts_#{subreddit}", value: values.to_json, replace: true }
102
- end
103
-
104
- attrs
105
- end
106
-
107
- def assign_default_values
108
- @reddit_token = ""
109
- @posts = {}
110
- end
111
-
112
- def create_domain
113
- res = client.list_domains
114
- return unless res.successful?
115
-
116
- return if res.domain_names.include?(Configuration.aws.domain_name)
117
-
118
- client.create_domain({ domain_name: Configuration.aws.domain_name })
119
- end
120
- end
121
- end
122
- end
123
- end