telegem 2.0.8 → 2.0.9

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.
Files changed (36) hide show
  1. checksums.yaml +4 -4
  2. data/Test-Projects/Movie-tracker-bot/Gemfile +2 -0
  3. data/Test-Projects/Movie-tracker-bot/bot.rb +62 -0
  4. data/Test-Projects/Movie-tracker-bot/handlers/.gitkeep +0 -0
  5. data/Test-Projects/Movie-tracker-bot/handlers/add_1_.rb +160 -0
  6. data/Test-Projects/Movie-tracker-bot/handlers/add_2_.rb +139 -0
  7. data/Test-Projects/Movie-tracker-bot/handlers/premium.rb +13 -0
  8. data/Test-Projects/Movie-tracker-bot/handlers/report.rb +31 -0
  9. data/Test-Projects/Movie-tracker-bot/handlers/search.rb +150 -0
  10. data/Test-Projects/Movie-tracker-bot/handlers/sponsor.rb +14 -0
  11. data/Test-Projects/Movie-tracker-bot/handlers/start.rb +48 -0
  12. data/Test-Projects/Movie-tracker-bot/handlers/watch.rb +210 -0
  13. data/Test-Projects/Test-submitted-by-marvel/.gitkeep +0 -0
  14. data/Test-Projects/Test-submitted-by-marvel/Marvel-bot.md +3 -0
  15. data/docs-src/.gitkeep +0 -0
  16. data/docs-src/Bot-registration_.PNG +0 -0
  17. data/docs-src/bot.md +295 -0
  18. data/docs-src/context|ctx|.md +531 -0
  19. data/docs-src/getting-started.md +328 -0
  20. data/docs-src/keyboard_inline.md +413 -0
  21. data/docs-src/scene.md +509 -0
  22. data/docs-src/understanding-ctx.md +581 -0
  23. data/lib/api/client.rb +3 -0
  24. data/lib/core/bot.rb +31 -27
  25. data/lib/telegem.rb +1 -1
  26. data/lib/webhook/server.rb +1 -1
  27. metadata +26 -15
  28. data/docs/Api.md +0 -211
  29. data/docs/Cookbook(copy_paste).md +0 -644
  30. data/docs/Getting_started.md +0 -348
  31. data/docs/How_to_use.md +0 -571
  32. data/docs/QuickStart.md +0 -258
  33. data/docs/Understanding_Scene.md +0 -434
  34. data/docs/Usage.md +0 -717
  35. data/docs/webhook_setup.md +0 -199
  36. /data/{docs → Test-Projects/Movie-tracker-bot}/.gitkeep +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b5e8e2d8f412d455281a8ed45a4384161431715964447abf7d323c8e5a651eaf
4
- data.tar.gz: e92cf5b7324c3e745a66dfcf935b9ed18bae231fa0051316070cf726dc259d4b
3
+ metadata.gz: 6f431a8a7aa53b06ed13e1eee3097ad92cff466b3eec7bf447f1502ac1216c3c
4
+ data.tar.gz: 8e3e297669ac814867accea2c898d96c4b73ec080dd5f89b863b3fe42b57d7a4
5
5
  SHA512:
6
- metadata.gz: 95f13660c4b21333d0a4d68a6fb1ba25767a1f3d5aa7f06f07d0f76b82796730f8803c3036c7297d9b8aaea99cd73e22cfe01999d5a52c6456b6b0b9a42921b3
7
- data.tar.gz: f5cc5f64bb886ea1f38b8c51993db49c8850a0017a0722a70f670f783fbb997fcbdebc816fb28fb92e74bf543473addda1da8300430565df0fce260c84ea8cb4
6
+ metadata.gz: fd58b32563a8dd8ae705d2bb3933b4c7f237b81833733c534375bfdd1fb9c6e043de7c6411dfd0b478f24bd0c4d534789885bd64b9dd58116676d6743a92292e
7
+ data.tar.gz: 0b04670c9e2382aedd71d6f1f2c89067cc127c113ac834bb459335fcb366f6b73e32e2088a8abf7cf017a0f044c613d726b69242d382288e58680c4802e773e0
@@ -0,0 +1,2 @@
1
+
2
+ ruby "3.1.0"
@@ -0,0 +1,62 @@
1
+ require 'json'
2
+ require 'telegem'
3
+ require 'sqlite3'
4
+ require 'fileutils'
5
+
6
+
7
+ require_relative './handlers/start'
8
+ require_relative './handlers/add_1_'
9
+ require_relative './handlers/premium'
10
+ require_relative './handlers/add_2_'
11
+ require_relative './handlers/sponsor'
12
+ require_relative './handlers/watch'
13
+ require_relative './handlers/report'
14
+ require_relative './handlers/search'
15
+
16
+ bot_token = ENV['BOT_TOKEN']
17
+
18
+ bot = Telegem.new(ENV['BOT_TOKEN'])
19
+ puts "token is #{bot_token[0..10]}"
20
+ # In your code:
21
+ db = SQLite3::Database.new "movieflix.db"
22
+
23
+ puts "started sqlite"
24
+
25
+ StartHandler.new(bot, db)
26
+ SearchHandler.new(bot, db)
27
+ WatchHandler.new(bot, db)
28
+ HelpHandler.new(bot)
29
+ PremiumHandler.new(bot)
30
+ SponsorHandler.new(bot)
31
+ AddHandler.new(bot, db)
32
+ AddHears.new(bot, db)
33
+
34
+ port = (ENV['PORT'] || 3000).to_i
35
+ puts " 📍 Using port: #{port}"
36
+ puts " 🌐 Bind host: 0.0.0.0"
37
+
38
+ # Start polling
39
+ bot.start_polling
40
+ puts " started polling"
41
+
42
+ # Minimal health check server
43
+ Thread.new do
44
+ require 'socket'
45
+ server = TCPServer.new('0.0.0.0', port || 10000)
46
+ puts "✅ Health check server listening on port #{ENV['PORT'] || 10000}"
47
+
48
+ loop do
49
+ client = server.accept
50
+ request = client.gets
51
+
52
+ # Simple response for any request
53
+ client.puts "HTTP/1.1 200 OK\r\n"
54
+ client.puts "Content-Type: text/plain\r\n"
55
+ client.puts "\r\n"
56
+ client.puts "Telegram Bot is running"
57
+ client.close
58
+ end
59
+ puts "bot is running "
60
+ end
61
+ sleep
62
+ puts "bot is running alive now "
File without changes
@@ -0,0 +1,160 @@
1
+ class AddHandler
2
+ def initialize(bot, db)
3
+ @bot = bot
4
+ @db = db
5
+ setup_add_commands
6
+ setup_my_shows_command # ← FIXED: Added this call
7
+ end
8
+
9
+ def setup_add_commands
10
+ @bot.command("add") do |ctx|
11
+ handle_add_command(ctx)
12
+ end
13
+ end
14
+
15
+ def handle_add_command(ctx)
16
+ args = ctx.message.text.split[1..-1] || []
17
+
18
+ # Input validation
19
+ if args.empty?
20
+ ctx.reply("❌ Usage: /add <show_id>\nExample: /add 1")
21
+ return
22
+ end
23
+
24
+ if args.length > 10
25
+ ctx.reply("❌ You can only add up to 10 shows at once")
26
+ return
27
+ end
28
+
29
+ user_id = ctx.from.id
30
+ first_name = ctx.from.first_name
31
+
32
+ # Send processing message for bulk adds
33
+ if args.length > 3
34
+ ctx.reply("⏳ Adding #{args.length} shows...")
35
+ end
36
+
37
+ added_shows = []
38
+ failed_shows = []
39
+
40
+ begin
41
+ @db.transaction do
42
+ args.each do |show_id_str|
43
+ show_id = show_id_str.to_i
44
+
45
+ if show_id <= 0
46
+ failed_shows << "#{show_id_str} (invalid ID)"
47
+ next
48
+ end
49
+
50
+ result = subscribe_user_to_show(user_id, show_id, first_name)
51
+
52
+ if result[:success]
53
+ added_shows << result[:show_title]
54
+ else
55
+ failed_shows << "#{show_id_str} (#{result[:error]})"
56
+ end
57
+ end
58
+ end
59
+ rescue SQLite3::Exception => e
60
+ ctx.reply("❌ Database error occurred. Please try again.")
61
+ return
62
+ end
63
+
64
+ send_add_response(ctx, added_shows, failed_shows)
65
+ end
66
+
67
+ def subscribe_user_to_show(user_id, show_id, user_name)
68
+ begin
69
+ show = @db.execute(
70
+ "SELECT id, title FROM movies WHERE id = ?",
71
+ [show_id]
72
+ ).first
73
+
74
+ unless show
75
+ return {success: false, error: "Show not found"}
76
+ end
77
+
78
+ show_db_id = show[0]
79
+ show_title = show[1]
80
+
81
+ # Ensure user exists
82
+ @db.execute(
83
+ "INSERT OR IGNORE INTO users (telegram_id, first_name) VALUES (?, ?)",
84
+ [user_id, user_name]
85
+ )
86
+
87
+ # Add to watched table
88
+ @db.execute(<<-SQL, [user_id, show_db_id])
89
+ INSERT OR IGNORE INTO watched (telegram_id, movie_id, season, episode)
90
+ VALUES (?, ?, 1, 0)
91
+ SQL
92
+
93
+ inserted = @db.changes > 0
94
+
95
+ if inserted
96
+ {success: true, show_title: show_title, show_id: show_db_id}
97
+ else
98
+ {success: false, error: "Already subscribed"}
99
+ end
100
+
101
+ rescue SQLite3::Exception => e
102
+ return {success: false, error: "Database error"}
103
+ end
104
+ end
105
+
106
+ def send_add_response(ctx, added_shows, failed_shows)
107
+ response = ""
108
+
109
+ if added_shows.any?
110
+ response += "✅ *Added to your list:*\n"
111
+ added_shows.each_with_index do |show, i|
112
+ response += "#{i + 1}. #{show}\n"
113
+ end
114
+ response += "\n"
115
+ end
116
+
117
+ if failed_shows.any?
118
+ response += "❌ *Failed to add:*\n"
119
+ failed_shows.each do |fail|
120
+ response += "• #{fail}\n"
121
+ end
122
+ end
123
+
124
+ if response.empty?
125
+ response = "⚠️ Nothing was added"
126
+ end
127
+
128
+ ctx.reply(response, parse_mode: 'Markdown')
129
+ end
130
+
131
+ def setup_my_shows_command
132
+ @bot.command("myshows") do |ctx|
133
+ user_id = ctx.from.id
134
+
135
+ begin
136
+ shows = @db.execute(<<-SQL, [user_id])
137
+ SELECT movies.id, movies.title, watched.season, watched.episode
138
+ FROM watched
139
+ JOIN movies ON movies.id = watched.movie_id
140
+ WHERE watched.telegram_id = ?
141
+ ORDER BY movies.title
142
+ SQL
143
+
144
+ if shows.empty?
145
+ ctx.reply("📭 Your list is empty!\nUse /search to find shows, then /add <id>")
146
+ else
147
+ response = "📺 *Your Shows:*\n\n"
148
+ shows.each do |id, title, season, episode|
149
+ response += "🎬 #{title}\n"
150
+ response += " ID: `#{id}` | Last: S#{season}E#{episode}\n"
151
+ response += " /watch #{id} s#{season}e#{episode + 1}\n\n"
152
+ end
153
+ ctx.reply(response, parse_mode: 'Markdown')
154
+ end
155
+ rescue SQLite3::Exception => e
156
+ ctx.reply("❌ Error loading your shows. Please try again.")
157
+ end
158
+ end
159
+ end
160
+ end
@@ -0,0 +1,139 @@
1
+ class AddHears
2
+ def initialize(bot, db)
3
+ @bot = bot
4
+ @db = db
5
+ setup_add_commands
6
+ end
7
+
8
+ def setup_add_commands
9
+ # Command: /add <show_id>
10
+ @bot.hears("〽️Add_Movie") do |ctx|
11
+ handle_add_command(ctx)
12
+ end
13
+ end
14
+
15
+ def handle_add_command(ctx)
16
+
17
+ args = ctx.message.text.split[1..-1] || []
18
+
19
+ if args.empty?
20
+ ctx.reply("❌ Usage: /add <show_id>\nExample: /add 1")
21
+ return
22
+ end
23
+
24
+ user_id = ctx.from.id
25
+ first_name = ctx.from.first_name
26
+
27
+
28
+ added_shows = []
29
+ failed_shows = []
30
+
31
+ args.each do |show_id_str|
32
+ show_id = show_id_str.to_i
33
+
34
+ if show_id <= 0
35
+ failed_shows << "#{show_id_str} (invalid ID)"
36
+ next
37
+ end
38
+
39
+ result = subscribe_user_to_show(user_id, show_id, first_name)
40
+
41
+ if result[:success]
42
+ added_shows << result[:show_title]
43
+ else
44
+ failed_shows << "#{show_id_str} (#{result[:error]})"
45
+ end
46
+ end
47
+
48
+ # Send response
49
+ send_add_response(ctx, added_shows, failed_shows)
50
+ end
51
+
52
+ def subscribe_user_to_show(user_id, show_id, user_name)
53
+
54
+ show = @db.execute(
55
+ "SELECT id, title FROM movies WHERE id = ?",
56
+ [show_id]
57
+ ).first
58
+
59
+ unless show
60
+ return {success: false, error: "Show not found"}
61
+ end
62
+
63
+ show_db_id = show[0]
64
+ show_title = show[1]
65
+
66
+
67
+ @db.execute(
68
+ "INSERT OR IGNORE INTO users (telegram_id, first_name) VALUES (?, ?)",
69
+ [user_id, user_name]
70
+ )
71
+
72
+ # 3. Add to watched table (initial state: season=1, episode=0)
73
+ @db.execute(<<-SQL, [user_id, show_db_id])
74
+ INSERT OR IGNORE INTO watched (telegram_id, movie_id, season, episode)
75
+ VALUES (?, ?, 1, 0)
76
+ SQL
77
+
78
+ # 4. Check if actually inserted
79
+ inserted = @db.changes > 0
80
+
81
+ if inserted
82
+ {success: true, show_title: show_title, show_id: show_db_id}
83
+ else
84
+ {success: false, error: "Already subscribed"}
85
+ end
86
+ end
87
+
88
+ def send_add_response(ctx, added_shows, failed_shows)
89
+ response = ""
90
+
91
+ if added_shows.any?
92
+ response += "✅ *Added to your list:*\n"
93
+ added_shows.each_with_index do |show, i|
94
+ response += "#{i + 1}. #{show}\n"
95
+ end
96
+ response += "\n"
97
+ end
98
+
99
+ if failed_shows.any?
100
+ response += "❌ *Failed to add:*\n"
101
+ failed_shows.each do |fail|
102
+ response += "• #{fail}\n"
103
+ end
104
+ end
105
+
106
+ if response.empty?
107
+ response = "⚠️ Nothing was added"
108
+ end
109
+
110
+ ctx.reply(response, parse_mode: 'Markdown')
111
+ end
112
+
113
+ # Optional: Show user's current shows
114
+ def setup_my_shows_command
115
+ @bot.hears("my shows") do |ctx|
116
+ user_id = ctx.from.id
117
+
118
+ shows = @db.execute(<<-SQL, [user_id])
119
+ SELECT movies.id, movies.title, watched.season, watched.episode
120
+ FROM watched
121
+ JOIN movies ON movies.id = watched.movie_id
122
+ WHERE watched.telegram_id = ?
123
+ ORDER BY movies.title
124
+ SQL
125
+
126
+ if shows.empty?
127
+ ctx.reply("📭 Your list is empty!\nUse /search to find shows, then /add <id>")
128
+ else
129
+ response = "📺 *Your Shows:*\n\n"
130
+ shows.each do |id, title, season, episode|
131
+ response += "🎬 #{title}\n"
132
+ response += " ID: `#{id}` | Last: S#{season}E#{episode}\n"
133
+ response += " /watch #{id} s#{season}e#{episode + 1}\n\n"
134
+ end
135
+ ctx.reply(response, parse_mode: 'Markdown')
136
+ end
137
+ end
138
+ end
139
+ end
@@ -0,0 +1,13 @@
1
+ class PremiumHandler
2
+ def initialize(bot)
3
+ @bot = bot
4
+ payment
5
+ end
6
+ def payment
7
+ @bot.hears("🤴🏼premium") do |ctx|
8
+ ctx.typing
9
+ sleep 3
10
+ ctx.reply("bot is free premium in beta")
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,31 @@
1
+ class HelpHandler
2
+ def initialize(bot)
3
+ @bot = bot
4
+ start_helper
5
+ end
6
+ def start_helper
7
+ @bot.hears("🧑🏿‍✈️report_bug") do |ctx|
8
+ ctx.reply("welcome to MovieFlix report section")
9
+ inline = Telegem.inline do
10
+ url "🙋‍♀️ FAQ", "https://gitlab.com/ynwghosted/flix-faq"
11
+ end
12
+ ctx.reply("👋 pls describe the issues you facing or visit our FAQ", reply_markup: inline)
13
+ issue = ctx.message.text
14
+ user_id = ctx.from.id
15
+ if issue.nil?
16
+ ctx.reply("whats the issue??")
17
+ else
18
+ ctx.reply("issue has been submitted")
19
+ end
20
+ JSON.parse(File.read('issues.json'))
21
+ issues << {
22
+ "text" => issue,
23
+ "user_id" => user_id
24
+ }
25
+ File.write('issues.json', JSON.pretty_generate(issues))
26
+ end
27
+ end
28
+ end
29
+
30
+
31
+
@@ -0,0 +1,150 @@
1
+ require 'uri'
2
+ require 'net/http'
3
+ require 'json'
4
+
5
+ class SearchHandler
6
+ def initialize(bot, db)
7
+ @bot = bot
8
+ @db = db
9
+ setup_search_commands
10
+ end
11
+
12
+ def setup_search_commands
13
+ # Command: /search
14
+ @bot.hears("🔃search") do |ctx|
15
+ ctx.reply("🎬 Search for TV shows\n\nUse: search <show name>\nExample: search Stranger Things")
16
+ end
17
+
18
+ # Hears: "search merlin" or "search stranger things"
19
+ @bot.hears(/^\s*search\s+/i) do |ctx|
20
+ handle_search_request(ctx)
21
+ end
22
+ end
23
+
24
+ def handle_search_request(ctx)
25
+ # Extract show name from message
26
+ text = ctx.message.text
27
+ show_name = text.sub(/^\s*search\s+/i, "").strip
28
+
29
+ if show_name.empty?
30
+ ctx.reply("❌ Please provide a show name")
31
+ return
32
+ end
33
+
34
+ ctx.reply("🔍 Searching for '#{show_name}'...")
35
+
36
+ # Step 1: Check OUR database first
37
+ local_show = find_in_local_database(show_name)
38
+
39
+ if local_show
40
+ # Found in our DB
41
+ send_local_result(ctx, local_show)
42
+ else
43
+ # Not in our DB → Search API
44
+ api_result = search_tvmaze_api(show_name)
45
+
46
+ if api_result[:error]
47
+ ctx.reply("❌ #{api_result[:error]}")
48
+ else
49
+ # Save to OUR database
50
+ saved_show = save_show_to_database(api_result)
51
+ send_api_result(ctx, saved_show)
52
+ end
53
+ end
54
+ end
55
+
56
+
57
+ def find_in_local_database(show_name)
58
+ # Search in movies table
59
+ result = @db.execute(
60
+ "SELECT id, title, created_at FROM movies WHERE title LIKE ? LIMIT 1",
61
+ ["%#{show_name}%"]
62
+ ).first
63
+
64
+ return nil unless result
65
+
66
+ {
67
+ id: result[0],
68
+ title: result[1], # Show title
69
+ created_at: result[2], #
70
+ source: :local_database
71
+ }
72
+ end
73
+
74
+ def save_show_to_database(show_data)
75
+ # Insert into movies table
76
+ @db.execute(
77
+ "INSERT OR IGNORE INTO movies (title) VALUES (?)",
78
+ [show_data[:name]]
79
+ )
80
+
81
+ # Get the ID we just inserted (or existing ID)
82
+ db_id = @db.execute(
83
+ "SELECT id FROM movies WHERE title = ?",
84
+ [show_data[:name]]
85
+ ).first[0]
86
+
87
+
88
+ {
89
+ id: db_id,
90
+ title: show_data[:name],
91
+ tvmaze_id: show_data[:id],
92
+ next_airdate: show_data[:next_airdate],
93
+ source: :tvmaze_api
94
+ }
95
+ end
96
+
97
+
98
+
99
+ def search_tvmaze_api(show_name)
100
+ url = "https://api.tvmaze.com/singlesearch/shows?q=#{URI.encode_www_form_component(show_name)}&embed=nextepisode"
101
+
102
+ begin
103
+ uri = URI(url)
104
+ response = Net::HTTP.get(uri)
105
+ data = JSON.parse(response)
106
+
107
+ if data["id"].nil?
108
+ return { error: "Show '#{show_name}' not found on TVMaze" }
109
+ end
110
+
111
+ {
112
+ id: data["id"],
113
+ name: data["name"],
114
+ air_days: data.dig("schedule", "days") || [],
115
+ next_airdate: data.dig("_embedded", "nextepisode", "airdate"),
116
+ summary: data["summary"] || "No summary available",
117
+ status: data["status"] || "Unknown"
118
+ }
119
+ rescue => e
120
+ return { error: "API error: #{e.message}" }
121
+ end
122
+ end
123
+
124
+
125
+
126
+ def send_local_result(ctx, show)
127
+ message = "✅ Found in our database!\n\n"
128
+ message += "🎬 #{show[:title]}\n"
129
+ message += "🆔 Our ID: #{show[:id]}\n"
130
+ message += "📅 Added: #{show[:created_at]}\n\n"
131
+ message += "Use /add #{show[:id]} to track this show"
132
+
133
+ ctx.reply(message)
134
+ end
135
+
136
+ def send_api_result(ctx, show)
137
+ message = "🎬 #{show[:title]}\n"
138
+ message += "🆔 TVMaze ID: #{show[:tvmaze_id]}\n"
139
+ message += "🆔 Our DB ID: #{show[:id]}\n"
140
+
141
+ if show[:next_airdate]
142
+ message += "📅 Next episode: #{show[:next_airdate]}\n"
143
+ end
144
+
145
+ message += "\n✅ Saved to our database!\n"
146
+ message += "Use /add #{show[:id]} to track this show"
147
+
148
+ ctx.reply(message)
149
+ end
150
+ end
@@ -0,0 +1,14 @@
1
+ class SponsorHandler
2
+ def initialize(bot)
3
+ @bot = bot
4
+ start_sponsoring_div
5
+ end
6
+ def start_sponsoring_div
7
+ @bot.hears("🤠sponsor") do |ctx|
8
+ ctx.typing
9
+ sleep 5
10
+ ctx.reply("utimately your application will be viewed by the board")
11
+ ctx.reply("chat with our board @darksnipe")
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,48 @@
1
+ class StartHandler
2
+ def initialize(bot, db)
3
+ @bot = bot
4
+ @db = db
5
+ start_command
6
+ create_tables
7
+ end
8
+ def start_command
9
+ @bot.command("start") do |ctx|
10
+ ctx.reply("welcome to MOVIE_FLIx")
11
+ keyboard = Telegem.keyboard do
12
+ row "🔃search", "〽️Add_Movie"
13
+ row "👩‍💻help", "🧑🏿‍✈️report_bug"
14
+ row "🤴🏼premium", "🤠sponsor"
15
+ end
16
+ ctx.reply("Best flix tracker bot....", reply_markup: keyboard)
17
+ end
18
+ end
19
+ def create_tables
20
+ @db.execute <<-SQL
21
+ CREATE TABLE IF NOT EXISTS users (
22
+ telegram_id INTEGER PRIMARY KEY,
23
+ first_name TEXT,
24
+ username TEXT,
25
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP
26
+ )
27
+ SQL
28
+
29
+ @db.execute <<-SQL
30
+ CREATE TABLE IF NOT EXISTS movies (
31
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
32
+ title TEXT UNIQUE,
33
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP
34
+ )
35
+ SQL
36
+
37
+ @db.execute <<-SQL
38
+ CREATE TABLE IF NOT EXISTS watched (
39
+ telegram_id INTEGER,
40
+ movie_id INTEGER,
41
+ season INTEGER DEFAULT 1,
42
+ episode INTEGER DEFAULT 0,
43
+ updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
44
+ UNIQUE(telegram_id, movie_id)
45
+ )
46
+ SQL
47
+ end
48
+ end