Skip to content

feat: add import help center command#16

Closed
scmmishra wants to merge 7 commits into
mainfrom
feat/hc-import-intercom
Closed

feat: add import help center command#16
scmmishra wants to merge 7 commits into
mainfrom
feat/hc-import-intercom

Conversation

@scmmishra

Copy link
Copy Markdown
Member

No description provided.

scmmishra added 7 commits June 2, 2026 14:17
Add CreatePortal, UpdatePortal, ListCategories, CreateCategory,
CreateArticle, UpdateArticle, and UploadImageExternalURL to the help
center service. Response wrappers match the Chatwoot controllers: portal
create/update return a bare object, while category and article writes wrap
the record under a payload key. Image upload posts a multipart external_url
form so the server re-hosts remote images via SafeFetch.
Add a small Prompter interface (single-select, multi-select, text input,
yes/no confirm) with a TermPrompter built on the standard library and
golang.org/x/term. Kept dependency-light and behind an interface so command
flows can be driven by a scripted reader in tests.
Add a source-agnostic engine for importing help center content into
Chatwoot: a provider-neutral IR and Source interface, a pure Plan step and a
write-performing Execute step over a Sink, email-based author matching with a
token-owner fallback, and a resumable state file under ~/.chatwoot/imports.

The transform layer re-hosts images via the upload endpoint and rewrites
recognized provider iframes into the bare URLs Chatwoot expands into embeds.
Translations are linked as Chatwoot expects: per-locale categories and
articles, with variants placed in the matching-locale category and linked to
the default-locale root via associated_category_id/associated_article_id.

Adds golang.org/x/net for HTML parsing in the transform layer.
Add a hand-wrapped Intercom REST client (bearer auth, version header,
cursor pagination, bounded retry/backoff on 429 and 5xx) and a Source that
maps Intercom help centers, collections, articles, and admins into the
importer IR. No Intercom SDK dependency.
Wire 'chatwoot hc import intercom' into the command tree. It requires a
TTY, then prompts for the source help center, target portal (existing or a
new one derived from the source), and locales before showing a plan and
confirming. Articles import as draft; images and embeds are best-effort.
Orchestration is split from the wiring so it is testable with fakes.
Category slugs are unique per slug+locale+portal and the server does not
auto-generate or disambiguate them (unlike article slugs). Two Intercom
collections sharing a default-locale name therefore produced the same slug and
the second create failed, importing its articles uncategorized. The planner now
assigns each collection a slug that is unique among collections and reuses it
across that collection's locales, seeded with the target portal's existing
category slugs so a first import into an existing portal also avoids collisions.
Assignment is deterministic (topo order) so reruns stay stable.
When Intercom returns pages.next as a URL whose starting_after cursor
contains percent-encoded characters (%2F, %3D…), the cursor was extracted as a
raw substring and then re-encoded when placed on the next request, sending the
wrong cursor and skipping later pages. nextCursor now parses the URL/query and
returns the decoded starting_after value.
return EmbedRegistry{matchers: []embedMatcher{
{
name: "youtube",
re: regexp.MustCompile(`(?i)(?:youtube(?:-nocookie)?\.com/(?:embed/|watch\?v=)|youtu\.be/)([A-Za-z0-9_\-]+)`),
},
{
name: "loom",
re: regexp.MustCompile(`(?i)loom\.com/(?:embed|share)/([A-Za-z0-9]+)`),
},
{
name: "vimeo",
re: regexp.MustCompile(`(?i)(?:player\.)?vimeo\.com/(?:video/)?(\d+)`),
},
{
name: "guidejar",
re: regexp.MustCompile(`(?i)guidejar\.com/(?:embed|guides)/([A-Za-z0-9\-]+)`),
},
{
name: "bunny",
re: regexp.MustCompile(`(?i)(?:iframe|player)\.mediadelivery\.net/(?:play|embed)/(\d+)/([A-Za-z0-9\-]+)`),
@scmmishra scmmishra closed this Jun 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants