basecamp-sdk 0.25.0 → 0.26.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 +4 -4
- data/README.md +1 -0
- data/lib/basecamp/campfire_index.rb +19 -13
- data/lib/basecamp/client.rb +5 -0
- data/lib/basecamp/generated/metadata.json +60 -1
- data/lib/basecamp/generated/services/boosts_service.rb +10 -0
- data/lib/basecamp/generated/services/cards_service.rb +3 -2
- data/lib/basecamp/generated/services/pings_service.rb +41 -0
- data/lib/basecamp/generated/services/wormholes_service.rb +3 -3
- data/lib/basecamp/generated/types.rb +79 -1
- data/lib/basecamp/services/recordings_extensions.rb +55 -18
- data/lib/basecamp/version.rb +2 -2
- data/scripts/generate-services.rb +2 -1
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 1fd42c71ec94ed3fca5d41b7f015309154da9cba442bd5d7e1a4daa41fcc47f7
|
|
4
|
+
data.tar.gz: 38b36b58f82eb91498ec501a1d497ea18210ad07a7f4548820350a3f69c08724
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 89dd7bc9af9f11a0365f768ffec3a6335b801f4278834dfadbbfe5963046f4da5f33bece26b8b112798896128e2def06c405d31f04b7f07b29e7432a384b9d83
|
|
7
|
+
data.tar.gz: 716839ec940b554a08f9049ec03caa88090e7965575dd29567545a38e04f8cd46176daf2de2882df079c32684f339e1b4c7ad09be523e0840854b0e0240919c3
|
data/README.md
CHANGED
|
@@ -348,6 +348,7 @@ The SDK provides the full account-scoped service set documented in SPEC §5. The
|
|
|
348
348
|
| `card_columns` | Card table columns |
|
|
349
349
|
| `card_steps` | Card workflow steps |
|
|
350
350
|
| `subtasks` | Subtasks (checklist items) on to-dos and cards |
|
|
351
|
+
| `pings` | Pings (direct and group chats) |
|
|
351
352
|
| `wormholes` | Card table wormholes (cross-project moves) |
|
|
352
353
|
| `lineup` | Card lineup view |
|
|
353
354
|
| `tools` | Project dock tools |
|
|
@@ -8,12 +8,15 @@ module Basecamp
|
|
|
8
8
|
# Candidates come from two sources, tried in order and each cached {TTL}
|
|
9
9
|
# seconds:
|
|
10
10
|
#
|
|
11
|
-
# 1. The bucket's project dock
|
|
12
|
-
#
|
|
13
|
-
# project.
|
|
11
|
+
# 1. The bucket's own Campfire. For a project, its dock's "chat" tool: one
|
|
12
|
+
# project read per bucket, and the answer for every line posted in a
|
|
13
|
+
# project. For a bucket that is not a project, its Ping's Campfire, since
|
|
14
|
+
# a Ping's bucket type is "Circle". Cached per bucket.
|
|
14
15
|
# 2. The account-wide Campfire listing (BC3 has no per-bucket one), filtered
|
|
15
|
-
# to the bucket, for buckets that are
|
|
16
|
-
# hold the line.
|
|
16
|
+
# to the bucket, for buckets that are neither or whose own Campfire did not
|
|
17
|
+
# hold the line. BC3 lists only dock-positioned Campfires there, so a
|
|
18
|
+
# ping's is never in it. Cached per account, so a burst of lines costs one
|
|
19
|
+
# listing.
|
|
17
20
|
#
|
|
18
21
|
# This class holds the caching only; the reads themselves are passed in as
|
|
19
22
|
# blocks by +RecordingsExtensions+, so every request still goes through a
|
|
@@ -28,7 +31,7 @@ module Basecamp
|
|
|
28
31
|
# buckets and accounts consulted within the last TTL, and never more than the
|
|
29
32
|
# bound, whatever the Client has seen.
|
|
30
33
|
class CampfireIndex
|
|
31
|
-
# How long a cached discovery source — a bucket's
|
|
34
|
+
# How long a cached discovery source — a bucket's own Campfire, the
|
|
32
35
|
# account's Campfire listing — is reused before it is read again.
|
|
33
36
|
TTL = 600.0
|
|
34
37
|
|
|
@@ -40,12 +43,13 @@ module Basecamp
|
|
|
40
43
|
|
|
41
44
|
# Caps the account-wide Campfire listing the fallback source reads. A
|
|
42
45
|
# listing that overflows it is not cached and the call reports
|
|
43
|
-
# {CampfireDiscoveryIncompleteError}: the
|
|
44
|
-
# listing only ever serves the leftover, and
|
|
45
|
-
# than this should not pay a full walk per
|
|
46
|
+
# {CampfireDiscoveryIncompleteError}: the bucket's own Campfire covers every
|
|
47
|
+
# project and every ping, so the listing only ever serves the leftover, and
|
|
48
|
+
# an account with more Campfires than this should not pay a full walk per
|
|
49
|
+
# TTL for it.
|
|
46
50
|
MAX_LISTING = 1000
|
|
47
51
|
|
|
48
|
-
# Bounds each cache's entry count. The
|
|
52
|
+
# Bounds each cache's entry count. The bucket cache holds one snapshot per
|
|
49
53
|
# bucket consulted within the TTL: a connector listening across every
|
|
50
54
|
# project an agent can see touches hundreds of buckets, not thousands, and a
|
|
51
55
|
# snapshot is a handful of ids, so this is generous headroom at a few
|
|
@@ -75,12 +79,13 @@ module Basecamp
|
|
|
75
79
|
@listings = TTLCache.new(ttl: TTL, floor: MIN_REFRESH, max_items: MAX_ITEMS, clock: clock)
|
|
76
80
|
end
|
|
77
81
|
|
|
78
|
-
# The Campfire ids a bucket's project dock
|
|
82
|
+
# The Campfire ids a bucket's own source names: a project's dock, or a
|
|
83
|
+
# Ping's room.
|
|
79
84
|
#
|
|
80
85
|
# @param account_id [String]
|
|
81
86
|
# @param bucket_id [Integer]
|
|
82
87
|
# @param refresh [Boolean] re-read a cached snapshot older than {MIN_REFRESH}
|
|
83
|
-
# @yieldreturn [Array<Integer>] the
|
|
88
|
+
# @yieldreturn [Array<Integer>] the bucket's own Campfire ids
|
|
84
89
|
# @return [SourceRead]
|
|
85
90
|
def dock_campfires(account_id:, bucket_id:, refresh: false, &load)
|
|
86
91
|
hit = @docks.get([ account_id, bucket_id ], refresh: refresh, &load)
|
|
@@ -92,7 +97,8 @@ module Basecamp
|
|
|
92
97
|
#
|
|
93
98
|
# It exists so a caller can consult what the source already holds before
|
|
94
99
|
# deciding whether to pay for the listing fetch — the expensive, slow
|
|
95
|
-
# request, which the
|
|
100
|
+
# request, which the bucket's own source (including its refresh) should get
|
|
101
|
+
# to pre-empt.
|
|
96
102
|
#
|
|
97
103
|
# @param account_id [String]
|
|
98
104
|
# @param bucket_id [Integer]
|
data/lib/basecamp/client.rb
CHANGED
|
@@ -612,6 +612,11 @@ module Basecamp
|
|
|
612
612
|
service(:subtasks) { Services::SubtasksService.new(self) }
|
|
613
613
|
end
|
|
614
614
|
|
|
615
|
+
# @return [Services::PingsService]
|
|
616
|
+
def pings
|
|
617
|
+
service(:pings) { Services::PingsService.new(self) }
|
|
618
|
+
end
|
|
619
|
+
|
|
615
620
|
# @return [Services::AccountService]
|
|
616
621
|
def account
|
|
617
622
|
service(:account) { Services::AccountService.new(self) }
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://basecamp.com/schemas/sdk-metadata.json",
|
|
3
3
|
"version": "1.0.0",
|
|
4
|
-
"generated": "2026-10-
|
|
4
|
+
"generated": "2026-10-19T21:23:14Z",
|
|
5
5
|
"operations": {
|
|
6
6
|
"GetAccount": {
|
|
7
7
|
"retry": {
|
|
@@ -78,6 +78,20 @@
|
|
|
78
78
|
]
|
|
79
79
|
}
|
|
80
80
|
},
|
|
81
|
+
"UpdateBoost": {
|
|
82
|
+
"retry": {
|
|
83
|
+
"maxAttempts": 3,
|
|
84
|
+
"baseDelayMs": 1000,
|
|
85
|
+
"backoff": "exponential",
|
|
86
|
+
"retryOn": [
|
|
87
|
+
429,
|
|
88
|
+
503
|
|
89
|
+
]
|
|
90
|
+
},
|
|
91
|
+
"idempotent": {
|
|
92
|
+
"natural": true
|
|
93
|
+
}
|
|
94
|
+
},
|
|
81
95
|
"DeleteBoost": {
|
|
82
96
|
"retry": {
|
|
83
97
|
"maxAttempts": 3,
|
|
@@ -919,6 +933,28 @@
|
|
|
919
933
|
"maxPageSize": 50
|
|
920
934
|
}
|
|
921
935
|
},
|
|
936
|
+
"ListPings": {
|
|
937
|
+
"retry": {
|
|
938
|
+
"maxAttempts": 3,
|
|
939
|
+
"baseDelayMs": 1000,
|
|
940
|
+
"backoff": "exponential",
|
|
941
|
+
"retryOn": [
|
|
942
|
+
429,
|
|
943
|
+
503
|
|
944
|
+
]
|
|
945
|
+
}
|
|
946
|
+
},
|
|
947
|
+
"CreatePing": {
|
|
948
|
+
"retry": {
|
|
949
|
+
"maxAttempts": 2,
|
|
950
|
+
"baseDelayMs": 1000,
|
|
951
|
+
"backoff": "exponential",
|
|
952
|
+
"retryOn": [
|
|
953
|
+
429,
|
|
954
|
+
503
|
|
955
|
+
]
|
|
956
|
+
}
|
|
957
|
+
},
|
|
922
958
|
"ListPingablePeople": {
|
|
923
959
|
"retry": {
|
|
924
960
|
"maxAttempts": 3,
|
|
@@ -935,6 +971,17 @@
|
|
|
935
971
|
"maxPageSize": 50
|
|
936
972
|
}
|
|
937
973
|
},
|
|
974
|
+
"GetPing": {
|
|
975
|
+
"retry": {
|
|
976
|
+
"maxAttempts": 3,
|
|
977
|
+
"baseDelayMs": 1000,
|
|
978
|
+
"backoff": "exponential",
|
|
979
|
+
"retryOn": [
|
|
980
|
+
429,
|
|
981
|
+
503
|
|
982
|
+
]
|
|
983
|
+
}
|
|
984
|
+
},
|
|
938
985
|
"GetClientApproval": {
|
|
939
986
|
"retry": {
|
|
940
987
|
"maxAttempts": 3,
|
|
@@ -3730,6 +3777,9 @@
|
|
|
3730
3777
|
"CreateMessage": [
|
|
3731
3778
|
"creator"
|
|
3732
3779
|
],
|
|
3780
|
+
"CreatePing": [
|
|
3781
|
+
"people.[]"
|
|
3782
|
+
],
|
|
3733
3783
|
"CreateQuestion": [
|
|
3734
3784
|
"creator"
|
|
3735
3785
|
],
|
|
@@ -4077,6 +4127,9 @@
|
|
|
4077
4127
|
"events.[].creator",
|
|
4078
4128
|
"person"
|
|
4079
4129
|
],
|
|
4130
|
+
"GetPing": [
|
|
4131
|
+
"people.[]"
|
|
4132
|
+
],
|
|
4080
4133
|
"GetProgressReport": [
|
|
4081
4134
|
"[].attachments.[].creator",
|
|
4082
4135
|
"[].creator"
|
|
@@ -4251,6 +4304,9 @@
|
|
|
4251
4304
|
"ListPingablePeople": [
|
|
4252
4305
|
"[]"
|
|
4253
4306
|
],
|
|
4307
|
+
"ListPings": [
|
|
4308
|
+
"[].people.[]"
|
|
4309
|
+
],
|
|
4254
4310
|
"ListProjectPeople": [
|
|
4255
4311
|
"[]"
|
|
4256
4312
|
],
|
|
@@ -4340,6 +4396,9 @@
|
|
|
4340
4396
|
"Subscribe": [
|
|
4341
4397
|
"subscribers.[]"
|
|
4342
4398
|
],
|
|
4399
|
+
"UpdateBoost": [
|
|
4400
|
+
"booster"
|
|
4401
|
+
],
|
|
4343
4402
|
"UpdateCard": [
|
|
4344
4403
|
"assignees.[]",
|
|
4345
4404
|
"completer",
|
|
@@ -16,6 +16,16 @@ module Basecamp
|
|
|
16
16
|
end
|
|
17
17
|
end
|
|
18
18
|
|
|
19
|
+
# Replace the content of a boost. Only the boost's creator may update it: an
|
|
20
|
+
# @param boost_id [Integer] boost id ID
|
|
21
|
+
# @param content [String] content
|
|
22
|
+
# @return [Hash] response data
|
|
23
|
+
def update_boost(boost_id:, content:)
|
|
24
|
+
with_operation(service: "boosts", operation: "update_boost", is_mutation: true, resource_id: boost_id) do
|
|
25
|
+
http_put("/boosts/#{boost_id}", body: compact_params(content: content)).json(operation: "UpdateBoost")
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
19
29
|
# Delete a boost
|
|
20
30
|
# @param boost_id [Integer] boost id ID
|
|
21
31
|
# @return [void]
|
|
@@ -29,9 +29,10 @@ module Basecamp
|
|
|
29
29
|
end
|
|
30
30
|
end
|
|
31
31
|
|
|
32
|
-
# Move a card to a
|
|
32
|
+
# Move a card to any column in its project, including on another card table there, or through a linked wormhole to another project (queued: the card gets a new id, and position, if sent, must still be positive but doesn't set placement).
|
|
33
33
|
# @param card_id [Integer] card id ID
|
|
34
|
-
# @param column_id [Integer] column
|
|
34
|
+
# @param column_id [Integer] Any column in the card's project, on this or another card table, or a
|
|
35
|
+
# wormhole id to send the card to another project.
|
|
35
36
|
# @param position [Integer, nil] 1-indexed position within the destination column. Defaults to 1 (top).
|
|
36
37
|
# @return [void]
|
|
37
38
|
def move(card_id:, column_id:, position: nil)
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Basecamp
|
|
4
|
+
module Services
|
|
5
|
+
# Service for Pings operations
|
|
6
|
+
#
|
|
7
|
+
# @generated from OpenAPI spec
|
|
8
|
+
class PingsService < BaseService
|
|
9
|
+
|
|
10
|
+
# Get Pings: the caller's active Pings that have a line in them, most
|
|
11
|
+
# @return [Array<Hash>] response data
|
|
12
|
+
def list()
|
|
13
|
+
with_operation(service: "pings", operation: "list", is_mutation: false) do
|
|
14
|
+
http_get("/circles.json", operation: "ListPings").json(operation: "ListPings")
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Create a Ping with people, or return the one that already holds exactly
|
|
19
|
+
# @param user_ids [Array] Person ids of the people to ping. People the caller cannot reach are
|
|
20
|
+
# dropped.
|
|
21
|
+
# @param new_group [Boolean, nil] Create a new group even when a Ping with exactly these people exists.
|
|
22
|
+
# @return [Hash] response data
|
|
23
|
+
def create(user_ids:, new_group: nil)
|
|
24
|
+
with_operation(service: "pings", operation: "create", is_mutation: true) do
|
|
25
|
+
http_post("/circles.json", body: compact_params(user_ids: user_ids, new_group: new_group)).json(operation: "CreatePing")
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Get a Ping by its id, which is its bucket's id.
|
|
30
|
+
# @param ping_id [Integer] A Ping's id is the id of the bucket that holds it (the bucket type is
|
|
31
|
+
# "Circle", which is also the wire path), so the `bucket.id` a Ping's
|
|
32
|
+
# recordings carry is the id GetPing takes.
|
|
33
|
+
# @return [Hash] response data
|
|
34
|
+
def get(ping_id:)
|
|
35
|
+
with_operation(service: "pings", operation: "get", is_mutation: false, resource_id: ping_id) do
|
|
36
|
+
http_get("/circles/#{ping_id}", operation: "GetPing").json(operation: "GetPing")
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -7,7 +7,7 @@ module Basecamp
|
|
|
7
7
|
# @generated from OpenAPI spec
|
|
8
8
|
class WormholesService < BaseService
|
|
9
9
|
|
|
10
|
-
#
|
|
10
|
+
# Point a card table's wormhole, its shortcut for sending cards to another card table, at a different destination column
|
|
11
11
|
# @param bucket_id [Integer] bucket id ID
|
|
12
12
|
# @param wormhole_id [Integer] wormhole id ID
|
|
13
13
|
# @param destination_recording_id [Integer] Id of the new destination column (on another accessible card table).
|
|
@@ -18,7 +18,7 @@ module Basecamp
|
|
|
18
18
|
end
|
|
19
19
|
end
|
|
20
20
|
|
|
21
|
-
#
|
|
21
|
+
# Permanently delete a card table's wormhole, the shortcut that sends cards to another card table.
|
|
22
22
|
# @param bucket_id [Integer] bucket id ID
|
|
23
23
|
# @param wormhole_id [Integer] wormhole id ID
|
|
24
24
|
# @return [void]
|
|
@@ -29,7 +29,7 @@ module Basecamp
|
|
|
29
29
|
end
|
|
30
30
|
end
|
|
31
31
|
|
|
32
|
-
#
|
|
32
|
+
# Add a wormhole to a card table: a standing shortcut that sends cards to a column on another card table, usually in another project.
|
|
33
33
|
# @param bucket_id [Integer] bucket id ID
|
|
34
34
|
# @param card_table_id [Integer] card table id ID
|
|
35
35
|
# @param destination_recording_id [Integer] Id of the destination column (on another accessible card table) to link to.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
# Auto-generated from OpenAPI spec. Do not edit manually.
|
|
4
|
-
# Generated: 2026-10-
|
|
4
|
+
# Generated: 2026-10-19T21:23:14Z
|
|
5
5
|
|
|
6
6
|
require "json"
|
|
7
7
|
require "time"
|
|
@@ -3208,6 +3208,84 @@ module Basecamp
|
|
|
3208
3208
|
end
|
|
3209
3209
|
end
|
|
3210
3210
|
|
|
3211
|
+
# Ping
|
|
3212
|
+
class Ping
|
|
3213
|
+
include TypeHelpers
|
|
3214
|
+
attr_accessor :app_url, :avatar_url, :bookmark_url, :created_at, :id, :links, :named, :people, :room_url, :title, :updated_at, :url, :edit_title_url, :image_url
|
|
3215
|
+
|
|
3216
|
+
# @return [Array<Symbol>]
|
|
3217
|
+
def self.required_fields
|
|
3218
|
+
%i[app_url avatar_url bookmark_url created_at id links named people room_url title updated_at url].freeze
|
|
3219
|
+
end
|
|
3220
|
+
|
|
3221
|
+
def initialize(data = {})
|
|
3222
|
+
@app_url = data["app_url"]
|
|
3223
|
+
@avatar_url = data["avatar_url"]
|
|
3224
|
+
@bookmark_url = data["bookmark_url"]
|
|
3225
|
+
@created_at = parse_datetime(data["created_at"])
|
|
3226
|
+
@id = parse_integer(data["id"])
|
|
3227
|
+
@links = parse_type(data["links"], "PingLinks")
|
|
3228
|
+
@named = parse_boolean(data["named"])
|
|
3229
|
+
@people = parse_array(data["people"], "Person")
|
|
3230
|
+
@room_url = data["room_url"]
|
|
3231
|
+
@title = data["title"]
|
|
3232
|
+
@updated_at = parse_datetime(data["updated_at"])
|
|
3233
|
+
@url = data["url"]
|
|
3234
|
+
@edit_title_url = data["edit_title_url"]
|
|
3235
|
+
@image_url = data["image_url"]
|
|
3236
|
+
end
|
|
3237
|
+
|
|
3238
|
+
def to_h
|
|
3239
|
+
{
|
|
3240
|
+
"app_url" => @app_url,
|
|
3241
|
+
"avatar_url" => @avatar_url,
|
|
3242
|
+
"bookmark_url" => @bookmark_url,
|
|
3243
|
+
"created_at" => @created_at,
|
|
3244
|
+
"id" => @id,
|
|
3245
|
+
"links" => @links,
|
|
3246
|
+
"named" => @named,
|
|
3247
|
+
"people" => @people,
|
|
3248
|
+
"room_url" => @room_url,
|
|
3249
|
+
"title" => @title,
|
|
3250
|
+
"updated_at" => @updated_at,
|
|
3251
|
+
"url" => @url,
|
|
3252
|
+
"edit_title_url" => @edit_title_url,
|
|
3253
|
+
"image_url" => @image_url,
|
|
3254
|
+
}.compact
|
|
3255
|
+
end
|
|
3256
|
+
|
|
3257
|
+
def to_json(*args)
|
|
3258
|
+
to_h.to_json(*args)
|
|
3259
|
+
end
|
|
3260
|
+
end
|
|
3261
|
+
|
|
3262
|
+
# PingLinks
|
|
3263
|
+
class PingLinks
|
|
3264
|
+
include TypeHelpers
|
|
3265
|
+
attr_accessor :files, :lines
|
|
3266
|
+
|
|
3267
|
+
# @return [Array<Symbol>]
|
|
3268
|
+
def self.required_fields
|
|
3269
|
+
%i[files lines].freeze
|
|
3270
|
+
end
|
|
3271
|
+
|
|
3272
|
+
def initialize(data = {})
|
|
3273
|
+
@files = data["files"]
|
|
3274
|
+
@lines = data["lines"]
|
|
3275
|
+
end
|
|
3276
|
+
|
|
3277
|
+
def to_h
|
|
3278
|
+
{
|
|
3279
|
+
"files" => @files,
|
|
3280
|
+
"lines" => @lines,
|
|
3281
|
+
}.compact
|
|
3282
|
+
end
|
|
3283
|
+
|
|
3284
|
+
def to_json(*args)
|
|
3285
|
+
to_h.to_json(*args)
|
|
3286
|
+
end
|
|
3287
|
+
end
|
|
3288
|
+
|
|
3211
3289
|
# Preferences
|
|
3212
3290
|
class Preferences
|
|
3213
3291
|
include TypeHelpers
|
|
@@ -49,6 +49,8 @@ module Basecamp
|
|
|
49
49
|
"The recording summary reads this field to decide what the recording is and which " \
|
|
50
50
|
"project it belongs to, so a value of the wrong type cannot be used."
|
|
51
51
|
|
|
52
|
+
ROOM_URL_CAMPFIRE = %r{/chats/([0-9]+)(?:\.json)?\z}
|
|
53
|
+
|
|
52
54
|
DISCOVERY_HINT =
|
|
53
55
|
"The recording summary reads this field to decide which Campfires a chat line could " \
|
|
54
56
|
"be in, so a value of the wrong type cannot be used."
|
|
@@ -133,10 +135,10 @@ module Basecamp
|
|
|
133
135
|
RICH_TEXT_CHAT_LINE_TYPES = [ "Chat::Lines::RichText", "Chat::Lines::Integration" ].freeze
|
|
134
136
|
|
|
135
137
|
# Bounds how many Campfires one {#summarize} call tries, across both
|
|
136
|
-
# discovery sources and the refresh. A project
|
|
137
|
-
#
|
|
138
|
-
#
|
|
139
|
-
#
|
|
138
|
+
# discovery sources and the refresh. A project or a Ping has one
|
|
139
|
+
# Campfire; a bucket past this bound is not a shape BC3 produces, and the
|
|
140
|
+
# call reports {Basecamp::CampfireDiscoveryIncompleteError} rather than
|
|
141
|
+
# calling the rest absent.
|
|
140
142
|
MAX_CAMPFIRE_CANDIDATES = 50
|
|
141
143
|
|
|
142
144
|
# Where #project leaves the validated bucket id for the cross-bucket
|
|
@@ -1029,14 +1031,14 @@ module Basecamp
|
|
|
1029
1031
|
account_id = @client.account_id
|
|
1030
1032
|
search = ChatLineSearch.new(campfires: @client.campfires, line_id: line_id)
|
|
1031
1033
|
|
|
1032
|
-
# Pass 1: what the sources already hold — the
|
|
1033
|
-
# then the listing only if it is cached. A listing
|
|
1034
|
-
# expensive, slow request, and it is not made until the
|
|
1035
|
-
# its refresh — has had its say, so a
|
|
1036
|
-
# or stalled never stands between a
|
|
1037
|
-
# read that finds it.
|
|
1034
|
+
# Pass 1: what the sources already hold — the bucket's own Campfire
|
|
1035
|
+
# (read if it must be), then the listing only if it is cached. A listing
|
|
1036
|
+
# fetch is the expensive, slow request, and it is not made until the
|
|
1037
|
+
# bucket's own source — including its refresh — has had its say, so a
|
|
1038
|
+
# listing that is down, over its cap, or stalled never stands between a
|
|
1039
|
+
# project's line and the one project read that finds it.
|
|
1038
1040
|
dock = index.dock_campfires(account_id: account_id, bucket_id: bucket_id) do
|
|
1039
|
-
|
|
1041
|
+
bucket_campfire_ids(bucket_id)
|
|
1040
1042
|
end
|
|
1041
1043
|
found = search.try(dock.ids)
|
|
1042
1044
|
return found if found
|
|
@@ -1048,7 +1050,7 @@ module Basecamp
|
|
|
1048
1050
|
return found if found
|
|
1049
1051
|
end
|
|
1050
1052
|
|
|
1051
|
-
# Pass 2: re-read the
|
|
1053
|
+
# Pass 2: re-read the bucket's own source if it was served from cache (the floor may
|
|
1052
1054
|
# decline), then fetch or refresh the listing. Whatever comes back is the
|
|
1053
1055
|
# current snapshot of that source, whoever loaded it — another caller may
|
|
1054
1056
|
# have populated or refreshed it in the meantime — so it always replaces
|
|
@@ -1071,7 +1073,7 @@ module Basecamp
|
|
|
1071
1073
|
|
|
1072
1074
|
if search.budget_left? && dock.cached?
|
|
1073
1075
|
again = index.dock_campfires(account_id: account_id, bucket_id: bucket_id, refresh: true) do
|
|
1074
|
-
|
|
1076
|
+
bucket_campfire_ids(bucket_id)
|
|
1075
1077
|
end
|
|
1076
1078
|
refreshed = true if again.fetched > dock.fetched || !again.cached?
|
|
1077
1079
|
dock = again
|
|
@@ -1119,17 +1121,52 @@ module Basecamp
|
|
|
1119
1121
|
)
|
|
1120
1122
|
end
|
|
1121
1123
|
|
|
1122
|
-
# The Campfire ids a
|
|
1123
|
-
#
|
|
1124
|
-
# read is raised
|
|
1125
|
-
|
|
1124
|
+
# The bucket's own Campfire ids: a project's dock "chat" tools or, when
|
|
1125
|
+
# the project read 404s, its Ping's Campfire. A bucket that is neither
|
|
1126
|
+
# has none; any other failure of either read is raised, so a 403 "Pings
|
|
1127
|
+
# have been disabled" is a failed read and never an unresolved line.
|
|
1128
|
+
def bucket_campfire_ids(bucket_id)
|
|
1126
1129
|
project = begin
|
|
1127
1130
|
@client.projects.get(project_id: bucket_id)
|
|
1131
|
+
rescue NotFoundError
|
|
1132
|
+
return ping_campfire_ids(bucket_id)
|
|
1133
|
+
end
|
|
1134
|
+
|
|
1135
|
+
dock_campfire_ids(read_record(project))
|
|
1136
|
+
end
|
|
1137
|
+
|
|
1138
|
+
# The one Campfire a Ping holds, named by its +room_url+. A Ping whose
|
|
1139
|
+
# room_url names no Campfire has none.
|
|
1140
|
+
def ping_campfire_ids(bucket_id)
|
|
1141
|
+
ping = begin
|
|
1142
|
+
@client.pings.get(ping_id: bucket_id)
|
|
1128
1143
|
rescue NotFoundError
|
|
1129
1144
|
return []
|
|
1130
1145
|
end
|
|
1131
1146
|
|
|
1132
|
-
|
|
1147
|
+
room_url = read_record(ping)["room_url"]
|
|
1148
|
+
return [] if room_url.nil?
|
|
1149
|
+
|
|
1150
|
+
unless room_url.is_a?(String)
|
|
1151
|
+
raise malformed_response("the Ping's \"room_url\" is #{MergeSafe.describe(room_url)}, not a string",
|
|
1152
|
+
hint: DISCOVERY_HINT)
|
|
1153
|
+
end
|
|
1154
|
+
|
|
1155
|
+
id = campfire_id_from_room_url(room_url)
|
|
1156
|
+
id ? [ id ] : []
|
|
1157
|
+
end
|
|
1158
|
+
|
|
1159
|
+
# Strict: the path must end in +/chats/{digits}+, optionally +.json+, so a
|
|
1160
|
+
# Campfire's lines, a to-do or a garbled URL name no Campfire.
|
|
1161
|
+
def campfire_id_from_room_url(room_url)
|
|
1162
|
+
match = ROOM_URL_CAMPFIRE.match(room_url.split(/[?#]/, 2).first)
|
|
1163
|
+
return nil if match.nil?
|
|
1164
|
+
|
|
1165
|
+
id = Ids.bounded_decimal(match[1], signed: false)
|
|
1166
|
+
id.is_a?(Integer) && id.positive? ? id : nil
|
|
1167
|
+
end
|
|
1168
|
+
|
|
1169
|
+
def dock_campfire_ids(project)
|
|
1133
1170
|
dock = project["dock"]
|
|
1134
1171
|
# An absent dock is genuinely none — a project need not have one, and the
|
|
1135
1172
|
# reference reads its zero value. Anything else that is not an array is a
|
data/lib/basecamp/version.rb
CHANGED
|
@@ -83,6 +83,7 @@ class ServiceGenerator
|
|
|
83
83
|
'ClientFeatures' => 'ClientFeatures',
|
|
84
84
|
'Boosts' => 'Boosts',
|
|
85
85
|
'Subtasks' => 'Subtasks',
|
|
86
|
+
'Pings' => 'Pings',
|
|
86
87
|
'Untagged' => 'Miscellaneous'
|
|
87
88
|
}.freeze
|
|
88
89
|
|
|
@@ -363,7 +364,7 @@ class ServiceGenerator
|
|
|
363
364
|
lineupmarker clientapproval clientapprovals clientcorrespondence
|
|
364
365
|
clientcorrespondences clientreply clientreplies forwardreply
|
|
365
366
|
forwardreplies campfireline campfirelines todolistgroup todolistgroups
|
|
366
|
-
todolistorgroup uploadversions hillchart hillcharts subtask subtasks
|
|
367
|
+
todolistorgroup uploadversions hillchart hillcharts subtask subtasks ping pings
|
|
367
368
|
wormhole wormholes
|
|
368
369
|
].freeze
|
|
369
370
|
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: basecamp-sdk
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.26.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Basecamp
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-10-
|
|
11
|
+
date: 2026-10-20 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: faraday
|
|
@@ -218,6 +218,7 @@ files:
|
|
|
218
218
|
- lib/basecamp/generated/services/my_notes_service.rb
|
|
219
219
|
- lib/basecamp/generated/services/my_notifications_service.rb
|
|
220
220
|
- lib/basecamp/generated/services/people_service.rb
|
|
221
|
+
- lib/basecamp/generated/services/pings_service.rb
|
|
221
222
|
- lib/basecamp/generated/services/projects_service.rb
|
|
222
223
|
- lib/basecamp/generated/services/recordings_service.rb
|
|
223
224
|
- lib/basecamp/generated/services/reports_service.rb
|