instagram_connect 0.3.6 → 0.3.7
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 +4 -4
- data/CHANGELOG.md +17 -0
- data/app/jobs/instagram_connect/sync_media_job.rb +89 -0
- data/app/models/instagram_connect/media_item.rb +6 -0
- data/db/migrate/20260727074500_add_removed_flag_to_instagram_connect_media.rb +13 -0
- data/lib/instagram_connect/version.rb +1 -1
- metadata +3 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 9cb1aea4b378e02bab73277ace0304ecdd751c54e535bc6840670870b844fddd
|
|
4
|
+
data.tar.gz: acc6b2589e468d17333a6920c1c5e4c609d8a636629e4ee62e35d8bb5e8cc6fa
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d61e2c92a923fdc3b4fe5b47ecdc9709044635fc658fcc2251928dc0bbf44d3b4234916d4416142a89241caaf667ea2f7635569d0cbd6dfe98cfd478fbd42f42
|
|
7
|
+
data.tar.gz: e81459599d577b340e459571f643dace5a94a3a353921147877c89ac6e99a7647a8808da6ad5f5a00b85c943b4bf175b6903431a18efec073834c0aa6944f92f
|
data/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,23 @@ All notable changes to this project are documented here. The format follows
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.3.7] - 2026-07-27
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- **`SyncMediaJob` — the gem now owns the post mirror.** Walks the account's entire media listing
|
|
14
|
+
(single page by default, full history with cursors via `full: true`), upserting caption,
|
|
15
|
+
permalink, type and timestamps into `MediaItem`. A host built this app-side first; per the
|
|
16
|
+
boundary rule — the gem owns the Graph protocol and every Instagram row — it moves here, and the
|
|
17
|
+
host's copy should be deleted on upgrade.
|
|
18
|
+
- **`removed_from_instagram_at` on media, with `MediaItem#removed?`.** Instagram sends no webhook
|
|
19
|
+
for a deleted post; absence from the account's own **completed** listing is the only signal. A
|
|
20
|
+
vanished post is stamped, never destroyed, so hosts can render it grayed and locked. A failed or
|
|
21
|
+
partial walk marks nothing. The migration uses `if_not_exists` because one host added the column
|
|
22
|
+
app-side before the gem owned it.
|
|
23
|
+
- `SyncMediaJob.enqueue_throttled(account)` for screen-triggered refreshes (10-minute throttle).
|
|
24
|
+
|
|
25
|
+
|
|
9
26
|
## [0.3.6] - 2026-07-26
|
|
10
27
|
|
|
11
28
|
### Fixed
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
module InstagramConnect
|
|
2
|
+
# Mirrors the account's posts — every post, from any app, not just ones
|
|
3
|
+
# published through this gem.
|
|
4
|
+
#
|
|
5
|
+
# The comments webhook names only a media id, so without this a host shows
|
|
6
|
+
# conversations grouped under bare 17-digit numbers. And Instagram sends no
|
|
7
|
+
# webhook when a post is deleted, so absence from the account's own COMPLETED
|
|
8
|
+
# listing is the only deletion signal there is: a vanished post is stamped
|
|
9
|
+
# removed_from_instagram_at, never destroyed. A partial or failed walk marks
|
|
10
|
+
# nothing — absence from an incomplete listing proves nothing.
|
|
11
|
+
#
|
|
12
|
+
# Safe to re-run: MediaItem.record upserts on (account, ig_media_id).
|
|
13
|
+
class SyncMediaJob < ApplicationJob
|
|
14
|
+
queue_as :default
|
|
15
|
+
|
|
16
|
+
# Hosts enqueue this from screens; the cache key stops a busy inbox
|
|
17
|
+
# enqueueing it once per page view.
|
|
18
|
+
THROTTLE = 10.minutes
|
|
19
|
+
|
|
20
|
+
MEDIA_FIELDS = "id,caption,media_type,media_url,permalink,timestamp".freeze
|
|
21
|
+
|
|
22
|
+
def self.enqueue_throttled(account)
|
|
23
|
+
Rails.cache.fetch("instagram_connect:sync_media:#{account.id}", expires_in: THROTTLE) do
|
|
24
|
+
perform_later(account.id)
|
|
25
|
+
true
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# full: walks the entire history with cursors and, having seen it all,
|
|
30
|
+
# marks the known posts the listing no longer contains.
|
|
31
|
+
def perform(account_id, full: false)
|
|
32
|
+
account = Account.find_by(id: account_id)
|
|
33
|
+
return if account.nil? || !account.active?
|
|
34
|
+
|
|
35
|
+
started_at = Time.current
|
|
36
|
+
result = fetch(account, full: full)
|
|
37
|
+
unless result.success?
|
|
38
|
+
# A sync that fails must say so — the silent version of this class of
|
|
39
|
+
# job has already cost a production afternoon.
|
|
40
|
+
return logger.error("[instagram_connect] media sync failed for " \
|
|
41
|
+
"account=#{account.id}: #{result.error_message}")
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
Array(result.data["data"]).each { |item| upsert(account, item) }
|
|
45
|
+
mark_removed(account, started_at) if full
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
private
|
|
49
|
+
|
|
50
|
+
def fetch(account, full:)
|
|
51
|
+
return account.client.list_media unless full
|
|
52
|
+
|
|
53
|
+
account.client.collect("/#{account.ig_user_id}/media",
|
|
54
|
+
{ fields: MEDIA_FIELDS, limit: 25 })
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def upsert(account, item)
|
|
58
|
+
media = MediaItem.record(
|
|
59
|
+
account: account,
|
|
60
|
+
ig_media_id: item["id"],
|
|
61
|
+
media_type: item["media_type"],
|
|
62
|
+
caption: item["caption"],
|
|
63
|
+
permalink: item["permalink"],
|
|
64
|
+
media_url: item["media_url"],
|
|
65
|
+
posted_at: parse_time(item["timestamp"]),
|
|
66
|
+
synced_at: Time.current
|
|
67
|
+
)
|
|
68
|
+
# Explicit, because record compacts nils out of its attributes: a post
|
|
69
|
+
# that reappears in the listing un-grays.
|
|
70
|
+
media.update!(removed_from_instagram_at: nil) if media.removed_from_instagram_at
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def mark_removed(account, started_at)
|
|
74
|
+
marked = MediaItem.where(account_id: account.id, removed_from_instagram_at: nil)
|
|
75
|
+
.where(synced_at: ...started_at)
|
|
76
|
+
.update_all(removed_from_instagram_at: Time.current)
|
|
77
|
+
return unless marked.positive?
|
|
78
|
+
|
|
79
|
+
logger.info("[instagram_connect] #{marked} post(s) newly absent from " \
|
|
80
|
+
"Instagram for account=#{account.id}")
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def parse_time(value)
|
|
84
|
+
Time.zone.parse(value.to_s)
|
|
85
|
+
rescue ArgumentError, TypeError
|
|
86
|
+
nil
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
@@ -22,6 +22,12 @@ module InstagramConnect
|
|
|
22
22
|
# Upserts by Meta's id. Media arrives from several directions — a webhook
|
|
23
23
|
# about a comment, a story expiring, a sync — and whichever gets there first
|
|
24
24
|
# should create the row without the others having to care.
|
|
25
|
+
# Stamped by SyncMediaJob when a completed listing no longer contains this
|
|
26
|
+
# post. Deleted on Instagram never means deleted here.
|
|
27
|
+
def removed?
|
|
28
|
+
removed_from_instagram_at.present?
|
|
29
|
+
end
|
|
30
|
+
|
|
25
31
|
def self.record(account:, ig_media_id:, **attributes)
|
|
26
32
|
media = find_or_initialize_by(account_id: account.id, ig_media_id: ig_media_id.to_s)
|
|
27
33
|
media.assign_attributes(attributes.compact)
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
class AddRemovedFlagToInstagramConnectMedia < ActiveRecord::Migration[7.1]
|
|
2
|
+
# Instagram sends no webhook when a post is deleted; absence from the
|
|
3
|
+
# account's own completed listing is the only signal there is. A vanished
|
|
4
|
+
# post is stamped, never destroyed — hosts render it as a locked, grayed,
|
|
5
|
+
# still-viewable row.
|
|
6
|
+
#
|
|
7
|
+
# if_not_exists because one host added this column app-side before the gem
|
|
8
|
+
# owned it; re-running here must be a no-op there.
|
|
9
|
+
def change
|
|
10
|
+
add_column :instagram_connect_media, :removed_from_instagram_at, :datetime,
|
|
11
|
+
if_not_exists: true
|
|
12
|
+
end
|
|
13
|
+
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: instagram_connect
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.3.
|
|
4
|
+
version: 0.3.7
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Kshitiz Sinha
|
|
@@ -101,6 +101,7 @@ files:
|
|
|
101
101
|
- app/jobs/instagram_connect/replay_events_job.rb
|
|
102
102
|
- app/jobs/instagram_connect/send_message_job.rb
|
|
103
103
|
- app/jobs/instagram_connect/sync_conversations_job.rb
|
|
104
|
+
- app/jobs/instagram_connect/sync_media_job.rb
|
|
104
105
|
- app/models/instagram_connect/account.rb
|
|
105
106
|
- app/models/instagram_connect/api_budget.rb
|
|
106
107
|
- app/models/instagram_connect/application_record.rb
|
|
@@ -143,6 +144,7 @@ files:
|
|
|
143
144
|
- db/migrate/20260725204022_add_per_account_token_fields.rb
|
|
144
145
|
- db/migrate/20260725204815_create_instagram_connect_api_budgets.rb
|
|
145
146
|
- db/migrate/20260725205417_add_conversation_profile_fields.rb
|
|
147
|
+
- db/migrate/20260727074500_add_removed_flag_to_instagram_connect_media.rb
|
|
146
148
|
- docs/CONFIGURATION.md
|
|
147
149
|
- docs/app_review_guide.md
|
|
148
150
|
- docs/auth_paths.md
|