@hasna/connectors 1.3.37 → 1.3.39

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.
@@ -1467,12 +1467,1070 @@ class BlueskyAdapter {
1467
1467
  }
1468
1468
  }
1469
1469
 
1470
+ // src/social/http.ts
1471
+ function resolveFetch(fetchImpl) {
1472
+ return fetchImpl ?? globalThis.fetch;
1473
+ }
1474
+ function appendQuery(url, query) {
1475
+ if (!query)
1476
+ return url;
1477
+ const qs = new URLSearchParams;
1478
+ for (const [k, v] of Object.entries(query)) {
1479
+ if (v !== undefined && v !== null && v !== "")
1480
+ qs.append(k, String(v));
1481
+ }
1482
+ const s = qs.toString();
1483
+ return s ? `${url}${url.includes("?") ? "&" : "?"}${s}` : url;
1484
+ }
1485
+ function extractError(data, fallback) {
1486
+ if (data && typeof data === "object") {
1487
+ const d = data;
1488
+ const candidates = [
1489
+ d.error_description,
1490
+ typeof d.error === "string" ? d.error : undefined,
1491
+ d.message,
1492
+ d.error && typeof d.error === "object" ? d.error.message : undefined
1493
+ ];
1494
+ for (const c of candidates) {
1495
+ if (typeof c === "string" && c)
1496
+ return c;
1497
+ }
1498
+ }
1499
+ return fallback;
1500
+ }
1501
+ async function jsonRequest(fetchImpl, url, options = {}) {
1502
+ const { method = "GET", query, body } = options;
1503
+ const finalUrl = appendQuery(url, query);
1504
+ const headers = { Accept: "application/json", ...options.headers ?? {} };
1505
+ let serialized;
1506
+ if (body !== undefined && method !== "GET" && method !== "HEAD") {
1507
+ if (typeof body === "string" || body instanceof FormData) {
1508
+ serialized = body;
1509
+ } else {
1510
+ if (!headers["Content-Type"])
1511
+ headers["Content-Type"] = "application/json";
1512
+ serialized = JSON.stringify(body);
1513
+ }
1514
+ }
1515
+ const res = await fetchImpl(finalUrl, { method, headers, body: serialized });
1516
+ const text = await res.text();
1517
+ const data = text ? safeJsonParse(text) : {};
1518
+ if (!res.ok) {
1519
+ const label = options.errorLabel ?? "request";
1520
+ throw new Error(`${label} ${res.status}: ${extractError(data, res.statusText || "failed")}`);
1521
+ }
1522
+ return data;
1523
+ }
1524
+ function safeJsonParse(text) {
1525
+ try {
1526
+ return JSON.parse(text);
1527
+ } catch {
1528
+ return { raw: text };
1529
+ }
1530
+ }
1531
+
1532
+ // src/social/linkedin.ts
1533
+ var DEFAULT_BASE = "https://api.linkedin.com";
1534
+
1535
+ class LinkedInAdapter {
1536
+ accessToken;
1537
+ baseUrl;
1538
+ authorUrn;
1539
+ fetchImpl;
1540
+ constructor(creds, fetchImpl) {
1541
+ if (!creds || !creds.accessToken) {
1542
+ throw new Error("linkedin credentials require `accessToken`");
1543
+ }
1544
+ this.accessToken = creds.accessToken;
1545
+ this.authorUrn = creds.authorUrn;
1546
+ this.baseUrl = (creds.baseUrl ?? DEFAULT_BASE).replace(/\/+$/, "");
1547
+ this.fetchImpl = resolveFetch(fetchImpl);
1548
+ }
1549
+ static fromCredentials(creds, fetchImpl) {
1550
+ return new LinkedInAdapter(creds, fetchImpl);
1551
+ }
1552
+ headers(extra) {
1553
+ return {
1554
+ Authorization: `Bearer ${this.accessToken}`,
1555
+ "X-Restli-Protocol-Version": "2.0.0",
1556
+ ...extra ?? {}
1557
+ };
1558
+ }
1559
+ async resolveAuthorUrn() {
1560
+ if (this.authorUrn)
1561
+ return this.authorUrn;
1562
+ const me = await jsonRequest(this.fetchImpl, `${this.baseUrl}/v2/me`, {
1563
+ headers: this.headers(),
1564
+ errorLabel: "LinkedIn"
1565
+ });
1566
+ this.authorUrn = `urn:li:person:${me.id}`;
1567
+ return this.authorUrn;
1568
+ }
1569
+ async accountMe() {
1570
+ const me = await jsonRequest(this.fetchImpl, `${this.baseUrl}/v2/me`, { headers: this.headers(), errorLabel: "LinkedIn" });
1571
+ this.authorUrn = `urn:li:person:${me.id}`;
1572
+ const displayName = [me.localizedFirstName, me.localizedLastName].filter(Boolean).join(" ") || undefined;
1573
+ return {
1574
+ id: me.id,
1575
+ username: me.vanityName ?? me.id,
1576
+ displayName,
1577
+ url: me.vanityName ? `https://www.linkedin.com/in/${me.vanityName}` : undefined
1578
+ };
1579
+ }
1580
+ async postCreate(input) {
1581
+ const author = await this.resolveAuthorUrn();
1582
+ const visibility = input.visibility ?? "PUBLIC";
1583
+ const hasMedia = Boolean(input.mediaIds && input.mediaIds.length > 0);
1584
+ const shareContent = {
1585
+ shareCommentary: { text: input.text },
1586
+ shareMediaCategory: hasMedia ? "IMAGE" : "NONE"
1587
+ };
1588
+ if (hasMedia) {
1589
+ shareContent.media = input.mediaIds.map((m) => ({ status: "READY", media: m }));
1590
+ }
1591
+ const body = {
1592
+ author,
1593
+ lifecycleState: "PUBLISHED",
1594
+ specificContent: { "com.linkedin.ugc.ShareContent": shareContent },
1595
+ visibility: { "com.linkedin.ugc.MemberNetworkVisibility": visibility }
1596
+ };
1597
+ const res = await jsonRequest(this.fetchImpl, `${this.baseUrl}/v2/ugcPosts`, {
1598
+ method: "POST",
1599
+ headers: this.headers(),
1600
+ body,
1601
+ errorLabel: "LinkedIn"
1602
+ });
1603
+ return {
1604
+ id: res.id,
1605
+ url: `https://www.linkedin.com/feed/update/${res.id}`
1606
+ };
1607
+ }
1608
+ async postDelete(input) {
1609
+ await jsonRequest(this.fetchImpl, `${this.baseUrl}/v2/ugcPosts/${encodeURIComponent(input.id)}`, {
1610
+ method: "DELETE",
1611
+ headers: this.headers(),
1612
+ errorLabel: "LinkedIn"
1613
+ });
1614
+ return { id: input.id, deleted: true };
1615
+ }
1616
+ async mediaUpload(input) {
1617
+ const author = await this.resolveAuthorUrn();
1618
+ const register = await jsonRequest(this.fetchImpl, `${this.baseUrl}/v2/assets?action=registerUpload`, {
1619
+ method: "POST",
1620
+ headers: this.headers(),
1621
+ body: {
1622
+ registerUploadRequest: {
1623
+ owner: author,
1624
+ recipes: ["urn:li:digitalmediaRecipe:feedshare-image"],
1625
+ serviceRelationships: [
1626
+ { relationshipType: "OWNER", identifier: "urn:li:userGeneratedContent" }
1627
+ ]
1628
+ }
1629
+ },
1630
+ errorLabel: "LinkedIn"
1631
+ });
1632
+ const asset = register.value.asset;
1633
+ const uploadUrl = register.value.uploadMechanism["com.linkedin.digitalmedia.uploading.MediaUploadHttpRequest"].uploadUrl;
1634
+ const buffer = decodeBase64(input.dataBase64);
1635
+ const bytes = Uint8Array.from(buffer);
1636
+ const put = await this.fetchImpl(uploadUrl, {
1637
+ method: "PUT",
1638
+ headers: { Authorization: `Bearer ${this.accessToken}`, "Content-Type": input.mimeType },
1639
+ body: bytes
1640
+ });
1641
+ if (!put.ok) {
1642
+ throw new Error(`LinkedIn ${put.status}: media upload failed`);
1643
+ }
1644
+ return { mediaId: asset };
1645
+ }
1646
+ async mentionsList(_input = {}) {
1647
+ throw new ConnectorOperationNotSupported("linkedin", "mentions.list (no public mentions/notifications endpoint)");
1648
+ }
1649
+ async analyticsPost(_input) {
1650
+ throw new ConnectorOperationNotSupported("linkedin", "analytics.post (requires organization socialActions scope; not available for member shares)");
1651
+ }
1652
+ }
1653
+
1654
+ // src/social/reddit.ts
1655
+ var BASE = "https://oauth.reddit.com";
1656
+ function fullname(id) {
1657
+ return id.startsWith("t3_") ? id : `t3_${id}`;
1658
+ }
1659
+
1660
+ class RedditAdapter {
1661
+ accessToken;
1662
+ userAgent;
1663
+ fetchImpl;
1664
+ constructor(creds, fetchImpl) {
1665
+ if (!creds || !creds.accessToken) {
1666
+ throw new Error("reddit credentials require `accessToken`");
1667
+ }
1668
+ this.accessToken = creds.accessToken;
1669
+ this.userAgent = creds.userAgent ?? "hasna-connectors/social";
1670
+ this.fetchImpl = resolveFetch(fetchImpl);
1671
+ }
1672
+ static fromCredentials(creds, fetchImpl) {
1673
+ return new RedditAdapter(creds, fetchImpl);
1674
+ }
1675
+ headers(extra) {
1676
+ return {
1677
+ Authorization: `Bearer ${this.accessToken}`,
1678
+ "User-Agent": this.userAgent,
1679
+ ...extra ?? {}
1680
+ };
1681
+ }
1682
+ form(params) {
1683
+ const qs = new URLSearchParams;
1684
+ for (const [k, v] of Object.entries(params)) {
1685
+ if (v !== undefined)
1686
+ qs.append(k, v);
1687
+ }
1688
+ return qs.toString();
1689
+ }
1690
+ async accountMe() {
1691
+ const me = await jsonRequest(this.fetchImpl, `${BASE}/api/v1/me`, { headers: this.headers(), errorLabel: "Reddit" });
1692
+ return {
1693
+ id: me.id,
1694
+ username: me.name,
1695
+ displayName: me.name,
1696
+ url: `https://www.reddit.com/user/${me.name}`
1697
+ };
1698
+ }
1699
+ async postCreate(input) {
1700
+ if (!input.title) {
1701
+ throw new Error("reddit post.create requires `title`");
1702
+ }
1703
+ if (!input.subreddit) {
1704
+ throw new Error("reddit post.create requires `subreddit`");
1705
+ }
1706
+ const kind = input.kind ?? "self";
1707
+ if (kind === "link" && !input.url) {
1708
+ throw new Error('reddit post.create with kind "link" requires `url`');
1709
+ }
1710
+ const body = this.form({
1711
+ sr: input.subreddit,
1712
+ title: input.title,
1713
+ kind,
1714
+ text: kind === "self" ? input.text : undefined,
1715
+ url: kind === "link" ? input.url : undefined,
1716
+ api_type: "json"
1717
+ });
1718
+ const res = await jsonRequest(this.fetchImpl, `${BASE}/api/submit`, {
1719
+ method: "POST",
1720
+ headers: this.headers({ "Content-Type": "application/x-www-form-urlencoded" }),
1721
+ body,
1722
+ errorLabel: "Reddit"
1723
+ });
1724
+ const errors = res.json?.errors ?? [];
1725
+ if (errors.length > 0) {
1726
+ throw new Error(`Reddit submit failed: ${JSON.stringify(errors)}`);
1727
+ }
1728
+ const data = res.json?.data ?? {};
1729
+ return { id: data.id ?? data.name ?? "", url: data.url };
1730
+ }
1731
+ async postDelete(input) {
1732
+ await jsonRequest(this.fetchImpl, `${BASE}/api/del`, {
1733
+ method: "POST",
1734
+ headers: this.headers({ "Content-Type": "application/x-www-form-urlencoded" }),
1735
+ body: this.form({ id: fullname(input.id) }),
1736
+ errorLabel: "Reddit"
1737
+ });
1738
+ return { id: input.id, deleted: true };
1739
+ }
1740
+ async mediaUpload(_input) {
1741
+ throw new ConnectorOperationNotSupported("reddit", "media.upload (image uploads require the media asset-lease flow; not supported by this SDK)");
1742
+ }
1743
+ async mentionsList(input = {}) {
1744
+ const res = await jsonRequest(this.fetchImpl, `${BASE}/message/inbox`, {
1745
+ headers: this.headers(),
1746
+ query: { limit: input.limit, after: input.sinceId },
1747
+ errorLabel: "Reddit"
1748
+ });
1749
+ const items = (res.data?.children ?? []).map((c) => ({
1750
+ id: c.data.name ?? c.data.id,
1751
+ text: c.data.body ?? "",
1752
+ authorId: c.data.author_fullname,
1753
+ authorHandle: c.data.author,
1754
+ createdAt: c.data.created_utc ? new Date(c.data.created_utc * 1000).toISOString() : undefined
1755
+ }));
1756
+ return { items };
1757
+ }
1758
+ async analyticsPost(input) {
1759
+ const res = await jsonRequest(this.fetchImpl, `${BASE}/api/info`, {
1760
+ headers: this.headers(),
1761
+ query: { id: fullname(input.id) },
1762
+ errorLabel: "Reddit"
1763
+ });
1764
+ const post = res.data?.children?.[0]?.data;
1765
+ if (!post) {
1766
+ throw new ConnectorOperationNotSupported("reddit", `analytics.post (post not found: ${input.id})`);
1767
+ }
1768
+ return {
1769
+ metrics: {
1770
+ ups: post.ups ?? 0,
1771
+ downs: post.downs ?? 0,
1772
+ score: post.score ?? 0,
1773
+ numComments: post.num_comments ?? 0,
1774
+ upvoteRatio: post.upvote_ratio ?? 0
1775
+ }
1776
+ };
1777
+ }
1778
+ }
1779
+
1780
+ // src/social/tiktok.ts
1781
+ var BASE2 = "https://open.tiktokapis.com";
1782
+
1783
+ class TikTokAdapter {
1784
+ accessToken;
1785
+ fetchImpl;
1786
+ constructor(creds, fetchImpl) {
1787
+ if (!creds || !creds.accessToken) {
1788
+ throw new Error("tiktok credentials require `accessToken`");
1789
+ }
1790
+ this.accessToken = creds.accessToken;
1791
+ this.fetchImpl = resolveFetch(fetchImpl);
1792
+ }
1793
+ static fromCredentials(creds, fetchImpl) {
1794
+ return new TikTokAdapter(creds, fetchImpl);
1795
+ }
1796
+ headers(extra) {
1797
+ return { Authorization: `Bearer ${this.accessToken}`, ...extra ?? {} };
1798
+ }
1799
+ async accountMe() {
1800
+ const res = await jsonRequest(this.fetchImpl, `${BASE2}/v2/user/info/`, {
1801
+ headers: this.headers(),
1802
+ query: { fields: "open_id,union_id,display_name,profile_deep_link" },
1803
+ errorLabel: "TikTok"
1804
+ });
1805
+ const user = res.data?.user ?? {};
1806
+ return {
1807
+ id: user.open_id ?? user.union_id ?? "",
1808
+ username: user.display_name,
1809
+ displayName: user.display_name,
1810
+ url: user.profile_deep_link
1811
+ };
1812
+ }
1813
+ async postCreate(input) {
1814
+ const videoId = input.mediaIds?.[0];
1815
+ if (!videoId) {
1816
+ throw new ConnectorOperationNotSupported("tiktok", "post.create (TikTok is video-only: provide a video media id in `mediaIds` via media.upload)");
1817
+ }
1818
+ const body = {
1819
+ post_info: {
1820
+ title: input.text ?? "",
1821
+ privacy_level: input.privacyLevel ?? "PUBLIC_TO_EVERYONE"
1822
+ },
1823
+ source_info: {
1824
+ source: "FILE_UPLOAD",
1825
+ video_id: videoId
1826
+ }
1827
+ };
1828
+ const res = await jsonRequest(this.fetchImpl, `${BASE2}/v2/post/publish/video/init/`, { method: "POST", headers: this.headers({ "Content-Type": "application/json" }), body, errorLabel: "TikTok" });
1829
+ return { id: res.data?.publish_id ?? videoId };
1830
+ }
1831
+ async postDelete(_input) {
1832
+ throw new ConnectorOperationNotSupported("tiktok", "post.delete (the Content Posting API does not expose post deletion)");
1833
+ }
1834
+ async mediaUpload(input) {
1835
+ const buffer = decodeBase64(input.dataBase64);
1836
+ const size = buffer.byteLength;
1837
+ const init = await jsonRequest(this.fetchImpl, `${BASE2}/v2/post/publish/inbox/video/init/`, {
1838
+ method: "POST",
1839
+ headers: this.headers({ "Content-Type": "application/json" }),
1840
+ body: {
1841
+ source_info: {
1842
+ source: "FILE_UPLOAD",
1843
+ video_size: size,
1844
+ chunk_size: size,
1845
+ total_chunk_count: 1
1846
+ }
1847
+ },
1848
+ errorLabel: "TikTok"
1849
+ });
1850
+ const uploadUrl = init.data?.upload_url;
1851
+ const publishId = init.data?.publish_id;
1852
+ if (!uploadUrl || !publishId) {
1853
+ throw new Error("TikTok media.upload: init did not return an upload_url/publish_id");
1854
+ }
1855
+ const bytes = Uint8Array.from(buffer);
1856
+ const put = await this.fetchImpl(uploadUrl, {
1857
+ method: "PUT",
1858
+ headers: {
1859
+ "Content-Type": input.mimeType,
1860
+ "Content-Range": `bytes 0-${size - 1}/${size}`
1861
+ },
1862
+ body: bytes
1863
+ });
1864
+ if (!put.ok) {
1865
+ throw new Error(`TikTok ${put.status}: video upload failed`);
1866
+ }
1867
+ return { mediaId: publishId };
1868
+ }
1869
+ async mentionsList(_input = {}) {
1870
+ throw new ConnectorOperationNotSupported("tiktok", "mentions.list (no mentions endpoint in the Content Posting / Display API)");
1871
+ }
1872
+ async analyticsPost(_input) {
1873
+ throw new ConnectorOperationNotSupported("tiktok", "analytics.post (per-post metrics require the Research/Business API, not available here)");
1874
+ }
1875
+ }
1876
+
1877
+ // src/social/youtube.ts
1878
+ var API = "https://www.googleapis.com/youtube/v3";
1879
+ var UPLOAD = "https://www.googleapis.com/upload/youtube/v3";
1880
+
1881
+ class YouTubeAdapter {
1882
+ accessToken;
1883
+ fetchImpl;
1884
+ constructor(creds, fetchImpl) {
1885
+ if (!creds || !creds.accessToken) {
1886
+ throw new Error("youtube credentials require `accessToken`");
1887
+ }
1888
+ this.accessToken = creds.accessToken;
1889
+ this.fetchImpl = resolveFetch(fetchImpl);
1890
+ }
1891
+ static fromCredentials(creds, fetchImpl) {
1892
+ return new YouTubeAdapter(creds, fetchImpl);
1893
+ }
1894
+ headers(extra) {
1895
+ return { Authorization: `Bearer ${this.accessToken}`, ...extra ?? {} };
1896
+ }
1897
+ async accountMe() {
1898
+ const res = await jsonRequest(this.fetchImpl, `${API}/channels`, {
1899
+ headers: this.headers(),
1900
+ query: { part: "snippet", mine: "true" },
1901
+ errorLabel: "YouTube"
1902
+ });
1903
+ const channel = res.items?.[0];
1904
+ if (!channel) {
1905
+ throw new Error("YouTube account.me: no channel found for the authenticated user");
1906
+ }
1907
+ return {
1908
+ id: channel.id,
1909
+ username: channel.snippet?.customUrl ?? channel.snippet?.title,
1910
+ displayName: channel.snippet?.title,
1911
+ url: `https://www.youtube.com/channel/${channel.id}`
1912
+ };
1913
+ }
1914
+ async postCreate(input) {
1915
+ const videoId = input.mediaIds?.[0];
1916
+ if (!videoId) {
1917
+ throw new ConnectorOperationNotSupported("youtube", "post.create (YouTube is video-only: upload a video via media.upload and pass its id in `mediaIds`)");
1918
+ }
1919
+ const title = input.title ?? input.text ?? "";
1920
+ const description = input.description ?? input.text ?? "";
1921
+ await jsonRequest(this.fetchImpl, `${API}/videos`, {
1922
+ method: "PUT",
1923
+ headers: this.headers({ "Content-Type": "application/json" }),
1924
+ query: { part: "snippet" },
1925
+ body: {
1926
+ id: videoId,
1927
+ snippet: { title, description, categoryId: input.categoryId ?? "22" }
1928
+ },
1929
+ errorLabel: "YouTube"
1930
+ });
1931
+ return { id: videoId, url: `https://www.youtube.com/watch?v=${videoId}` };
1932
+ }
1933
+ async postDelete(input) {
1934
+ await jsonRequest(this.fetchImpl, `${API}/videos`, {
1935
+ method: "DELETE",
1936
+ headers: this.headers(),
1937
+ query: { id: input.id },
1938
+ errorLabel: "YouTube"
1939
+ });
1940
+ return { id: input.id, deleted: true };
1941
+ }
1942
+ async mediaUpload(input) {
1943
+ const buffer = decodeBase64(input.dataBase64);
1944
+ const size = buffer.byteLength;
1945
+ const start = await this.fetchImpl(`${UPLOAD}/videos?uploadType=resumable&part=snippet,status`, {
1946
+ method: "POST",
1947
+ headers: this.headers({
1948
+ "Content-Type": "application/json",
1949
+ "X-Upload-Content-Type": input.mimeType,
1950
+ "X-Upload-Content-Length": String(size)
1951
+ }),
1952
+ body: JSON.stringify({
1953
+ snippet: { title: input.title ?? input.altText ?? "Untitled", description: input.description ?? "" },
1954
+ status: { privacyStatus: input.privacyStatus ?? "private" }
1955
+ })
1956
+ });
1957
+ if (!start.ok) {
1958
+ throw new Error(`YouTube ${start.status}: resumable upload init failed`);
1959
+ }
1960
+ const location = start.headers?.get("location");
1961
+ if (!location) {
1962
+ throw new Error("YouTube media.upload: resumable session did not return a Location header");
1963
+ }
1964
+ const bytes = Uint8Array.from(buffer);
1965
+ const put = await this.fetchImpl(location, {
1966
+ method: "PUT",
1967
+ headers: { "Content-Type": input.mimeType, "Content-Length": String(size) },
1968
+ body: bytes
1969
+ });
1970
+ const text = await put.text();
1971
+ const data = text ? JSON.parse(text) : {};
1972
+ if (!put.ok) {
1973
+ throw new Error(`YouTube ${put.status}: video upload failed`);
1974
+ }
1975
+ return { mediaId: String(data.id) };
1976
+ }
1977
+ async mentionsList(_input = {}) {
1978
+ throw new ConnectorOperationNotSupported("youtube", "mentions.list (no mentions endpoint in the YouTube Data API)");
1979
+ }
1980
+ async analyticsPost(input) {
1981
+ const res = await jsonRequest(this.fetchImpl, `${API}/videos`, {
1982
+ headers: this.headers(),
1983
+ query: { part: "statistics", id: input.id },
1984
+ errorLabel: "YouTube"
1985
+ });
1986
+ const stats = res.items?.[0]?.statistics;
1987
+ if (!stats) {
1988
+ throw new ConnectorOperationNotSupported("youtube", `analytics.post (video not found: ${input.id})`);
1989
+ }
1990
+ return {
1991
+ metrics: {
1992
+ viewCount: Number(stats.viewCount ?? 0),
1993
+ likeCount: Number(stats.likeCount ?? 0),
1994
+ commentCount: Number(stats.commentCount ?? 0),
1995
+ favoriteCount: Number(stats.favoriteCount ?? 0)
1996
+ }
1997
+ };
1998
+ }
1999
+ }
2000
+
2001
+ // src/social/pinterest.ts
2002
+ var BASE3 = "https://api.pinterest.com";
2003
+
2004
+ class PinterestAdapter {
2005
+ accessToken;
2006
+ defaultBoardId;
2007
+ fetchImpl;
2008
+ constructor(creds, fetchImpl) {
2009
+ if (!creds || !creds.accessToken) {
2010
+ throw new Error("pinterest credentials require `accessToken`");
2011
+ }
2012
+ this.accessToken = creds.accessToken;
2013
+ this.defaultBoardId = creds.boardId;
2014
+ this.fetchImpl = resolveFetch(fetchImpl);
2015
+ }
2016
+ static fromCredentials(creds, fetchImpl) {
2017
+ return new PinterestAdapter(creds, fetchImpl);
2018
+ }
2019
+ headers(extra) {
2020
+ return { Authorization: `Bearer ${this.accessToken}`, ...extra ?? {} };
2021
+ }
2022
+ async accountMe() {
2023
+ const acct = await jsonRequest(this.fetchImpl, `${BASE3}/v5/user_account`, { headers: this.headers(), errorLabel: "Pinterest" });
2024
+ return {
2025
+ id: acct.username ?? "",
2026
+ username: acct.username,
2027
+ displayName: acct.username,
2028
+ url: acct.username ? `https://www.pinterest.com/${acct.username}/` : undefined
2029
+ };
2030
+ }
2031
+ async postCreate(input) {
2032
+ const boardId = input.boardId ?? this.defaultBoardId;
2033
+ if (!boardId) {
2034
+ throw new Error("pinterest post.create requires `boardId` (in input or credentials)");
2035
+ }
2036
+ const mediaId = input.mediaIds?.[0];
2037
+ let mediaSource;
2038
+ if (mediaId) {
2039
+ mediaSource = { source_type: "image_upload", media_id: mediaId };
2040
+ } else if (input.imageUrl) {
2041
+ mediaSource = { source_type: "image_url", url: input.imageUrl };
2042
+ } else {
2043
+ throw new Error("pinterest post.create requires an image: provide a `mediaIds` entry or `imageUrl`");
2044
+ }
2045
+ const body = {
2046
+ board_id: boardId,
2047
+ description: input.text,
2048
+ media_source: mediaSource
2049
+ };
2050
+ if (input.title)
2051
+ body.title = input.title;
2052
+ if (input.link)
2053
+ body.link = input.link;
2054
+ const pin = await jsonRequest(this.fetchImpl, `${BASE3}/v5/pins`, {
2055
+ method: "POST",
2056
+ headers: this.headers({ "Content-Type": "application/json" }),
2057
+ body,
2058
+ errorLabel: "Pinterest"
2059
+ });
2060
+ return { id: pin.id, url: `https://www.pinterest.com/pin/${pin.id}/` };
2061
+ }
2062
+ async postDelete(input) {
2063
+ await jsonRequest(this.fetchImpl, `${BASE3}/v5/pins/${encodeURIComponent(input.id)}`, {
2064
+ method: "DELETE",
2065
+ headers: this.headers(),
2066
+ errorLabel: "Pinterest"
2067
+ });
2068
+ return { id: input.id, deleted: true };
2069
+ }
2070
+ async mediaUpload(input) {
2071
+ const register = await jsonRequest(this.fetchImpl, `${BASE3}/v5/media`, {
2072
+ method: "POST",
2073
+ headers: this.headers({ "Content-Type": "application/json" }),
2074
+ body: { media_type: "image" },
2075
+ errorLabel: "Pinterest"
2076
+ });
2077
+ const buffer = decodeBase64(input.dataBase64);
2078
+ const bytes = Uint8Array.from(buffer);
2079
+ const form = new FormData;
2080
+ for (const [k, v] of Object.entries(register.upload_parameters ?? {})) {
2081
+ form.append(k, v);
2082
+ }
2083
+ form.append("file", new Blob([bytes], { type: input.mimeType }));
2084
+ const put = await this.fetchImpl(register.upload_url, { method: "POST", body: form });
2085
+ if (!put.ok) {
2086
+ throw new Error(`Pinterest ${put.status}: media upload failed`);
2087
+ }
2088
+ return { mediaId: register.media_id };
2089
+ }
2090
+ async mentionsList(_input = {}) {
2091
+ throw new ConnectorOperationNotSupported("pinterest", "mentions.list (no mentions endpoint in the Pinterest API)");
2092
+ }
2093
+ async analyticsPost(input) {
2094
+ const res = await jsonRequest(this.fetchImpl, `${BASE3}/v5/pins/${encodeURIComponent(input.id)}/analytics`, {
2095
+ headers: this.headers(),
2096
+ query: { metric_types: "IMPRESSION,PIN_CLICK,SAVE,OUTBOUND_CLICK" },
2097
+ errorLabel: "Pinterest"
2098
+ });
2099
+ const summary = res.all?.["DAILY"]?.summary_metrics ?? res.all?.["TOTAL"]?.summary_metrics ?? {};
2100
+ const metrics = {};
2101
+ for (const [k, v] of Object.entries(summary)) {
2102
+ metrics[k] = typeof v === "number" ? v : Number(v) || 0;
2103
+ }
2104
+ return { metrics };
2105
+ }
2106
+ }
2107
+
2108
+ // src/social/googlebusinessprofile.ts
2109
+ var ACCT_MGMT = "https://mybusinessaccountmanagement.googleapis.com/v1";
2110
+ var V4 = "https://mybusiness.googleapis.com/v4";
2111
+
2112
+ class GoogleBusinessProfileAdapter {
2113
+ accessToken;
2114
+ defaultAccountId;
2115
+ defaultLocationId;
2116
+ fetchImpl;
2117
+ constructor(creds, fetchImpl) {
2118
+ if (!creds || !creds.accessToken) {
2119
+ throw new Error("googlebusinessprofile credentials require `accessToken`");
2120
+ }
2121
+ this.accessToken = creds.accessToken;
2122
+ this.defaultAccountId = creds.accountId;
2123
+ this.defaultLocationId = creds.locationId;
2124
+ this.fetchImpl = resolveFetch(fetchImpl);
2125
+ }
2126
+ static fromCredentials(creds, fetchImpl) {
2127
+ return new GoogleBusinessProfileAdapter(creds, fetchImpl);
2128
+ }
2129
+ headers(extra) {
2130
+ return { Authorization: `Bearer ${this.accessToken}`, ...extra ?? {} };
2131
+ }
2132
+ resolveIds(input) {
2133
+ const accountId = input.accountId ?? this.defaultAccountId;
2134
+ const locationId = input.locationId ?? this.defaultLocationId;
2135
+ if (!accountId)
2136
+ throw new Error("googlebusinessprofile requires `accountId` (in input or credentials)");
2137
+ if (!locationId)
2138
+ throw new Error("googlebusinessprofile requires `locationId` (in input or credentials)");
2139
+ return { accountId, locationId };
2140
+ }
2141
+ async accountMe() {
2142
+ const res = await jsonRequest(this.fetchImpl, `${ACCT_MGMT}/accounts`, { headers: this.headers(), errorLabel: "GoogleBusinessProfile" });
2143
+ const account = res.accounts?.[0];
2144
+ if (!account) {
2145
+ throw new Error("googlebusinessprofile account.me: no accounts found for the authenticated user");
2146
+ }
2147
+ const id = (account.name ?? "").replace(/^accounts\//, "");
2148
+ return {
2149
+ id,
2150
+ username: account.accountName,
2151
+ displayName: account.accountName,
2152
+ url: undefined
2153
+ };
2154
+ }
2155
+ async postCreate(input) {
2156
+ const { accountId, locationId } = this.resolveIds(input);
2157
+ const parent = `accounts/${accountId}/locations/${locationId}`;
2158
+ const body = {
2159
+ languageCode: "en-US",
2160
+ summary: input.text,
2161
+ topicType: "STANDARD"
2162
+ };
2163
+ if (input.cta) {
2164
+ body.callToAction = { actionType: input.cta.actionType, url: input.cta.url };
2165
+ }
2166
+ if (input.mediaIds && input.mediaIds.length > 0) {
2167
+ body.media = input.mediaIds.map((m) => ({ mediaFormat: "PHOTO", sourceUrl: m }));
2168
+ }
2169
+ const post = await jsonRequest(this.fetchImpl, `${V4}/${parent}/localPosts`, { method: "POST", headers: this.headers({ "Content-Type": "application/json" }), body, errorLabel: "GoogleBusinessProfile" });
2170
+ return { id: post.name, url: post.searchUrl };
2171
+ }
2172
+ async postDelete(input) {
2173
+ await jsonRequest(this.fetchImpl, `${V4}/${input.id}`, {
2174
+ method: "DELETE",
2175
+ headers: this.headers(),
2176
+ errorLabel: "GoogleBusinessProfile"
2177
+ });
2178
+ return { id: input.id, deleted: true };
2179
+ }
2180
+ async mediaUpload(input) {
2181
+ if (!input.sourceUrl) {
2182
+ throw new ConnectorOperationNotSupported("googlebusinessprofile", "media.upload (provide a public `sourceUrl`; raw binary upload uses a separate resumable service)");
2183
+ }
2184
+ const { accountId, locationId } = this.resolveIds(input);
2185
+ const parent = `accounts/${accountId}/locations/${locationId}`;
2186
+ const media = await jsonRequest(this.fetchImpl, `${V4}/${parent}/media`, {
2187
+ method: "POST",
2188
+ headers: this.headers({ "Content-Type": "application/json" }),
2189
+ body: {
2190
+ mediaFormat: "PHOTO",
2191
+ locationAssociation: { category: "ADDITIONAL" },
2192
+ sourceUrl: input.sourceUrl
2193
+ },
2194
+ errorLabel: "GoogleBusinessProfile"
2195
+ });
2196
+ return { mediaId: media.name };
2197
+ }
2198
+ async mentionsList(_input = {}) {
2199
+ throw new ConnectorOperationNotSupported("googlebusinessprofile", "mentions.list (no mentions concept; reviews are a separate surface)");
2200
+ }
2201
+ async analyticsPost(_input) {
2202
+ throw new ConnectorOperationNotSupported("googlebusinessprofile", "analytics.post (per-localPost insights are not exposed; use location-level Performance API)");
2203
+ }
2204
+ }
2205
+
2206
+ // src/social/facebook.ts
2207
+ var DEFAULT_BASE2 = "https://graph.facebook.com/v19.0";
2208
+
2209
+ class FacebookAdapter {
2210
+ accessToken;
2211
+ pageId;
2212
+ baseUrl;
2213
+ fetchImpl;
2214
+ constructor(creds, fetchImpl) {
2215
+ if (!creds || !creds.accessToken) {
2216
+ throw new Error("facebook credentials require `accessToken`");
2217
+ }
2218
+ if (!creds.pageId) {
2219
+ throw new Error("facebook credentials require `pageId`");
2220
+ }
2221
+ this.accessToken = creds.accessToken;
2222
+ this.pageId = creds.pageId;
2223
+ this.baseUrl = (creds.baseUrl ?? DEFAULT_BASE2).replace(/\/+$/, "");
2224
+ this.fetchImpl = resolveFetch(fetchImpl);
2225
+ }
2226
+ static fromCredentials(creds, fetchImpl) {
2227
+ return new FacebookAdapter(creds, fetchImpl);
2228
+ }
2229
+ auth() {
2230
+ return { access_token: this.accessToken };
2231
+ }
2232
+ async accountMe() {
2233
+ const me = await jsonRequest(this.fetchImpl, `${this.baseUrl}/${encodeURIComponent(this.pageId)}`, { query: { fields: "id,name,link", ...this.auth() }, errorLabel: "Facebook" });
2234
+ return {
2235
+ id: me.id,
2236
+ displayName: me.name,
2237
+ url: me.link
2238
+ };
2239
+ }
2240
+ async postCreate(input) {
2241
+ const body = { access_token: this.accessToken };
2242
+ if (input.text)
2243
+ body.message = input.text;
2244
+ if (input.link)
2245
+ body.link = input.link;
2246
+ if (input.mediaIds && input.mediaIds.length > 0) {
2247
+ body.attached_media = JSON.stringify(input.mediaIds.map((id2) => ({ media_fbid: id2 })));
2248
+ }
2249
+ const res = await jsonRequest(this.fetchImpl, `${this.baseUrl}/${encodeURIComponent(this.pageId)}/feed`, { method: "POST", body: new URLSearchParams(body).toString(), headers: { "Content-Type": "application/x-www-form-urlencoded" }, errorLabel: "Facebook" });
2250
+ const id = res.post_id ?? res.id;
2251
+ return { id, url: `https://facebook.com/${id}` };
2252
+ }
2253
+ async postDelete(input) {
2254
+ await jsonRequest(this.fetchImpl, `${this.baseUrl}/${encodeURIComponent(input.id)}`, { method: "DELETE", query: this.auth(), errorLabel: "Facebook" });
2255
+ return { id: input.id, deleted: true };
2256
+ }
2257
+ async mediaUpload(input) {
2258
+ const buffer = decodeBase64(input.dataBase64);
2259
+ const bytes = Uint8Array.from(buffer);
2260
+ const form = new FormData;
2261
+ form.append("access_token", this.accessToken);
2262
+ form.append("published", "false");
2263
+ form.append("source", new Blob([bytes], { type: input.mimeType }), "upload");
2264
+ const res = await jsonRequest(this.fetchImpl, `${this.baseUrl}/${encodeURIComponent(this.pageId)}/photos`, { method: "POST", body: form, errorLabel: "Facebook" });
2265
+ return { mediaId: res.id };
2266
+ }
2267
+ async mentionsList(_input = {}) {
2268
+ throw new ConnectorOperationNotSupported("facebook", "mentions.list (Page mentions require the deprecated/limited tagged edge; not modeled)");
2269
+ }
2270
+ async analyticsPost(input) {
2271
+ const res = await jsonRequest(this.fetchImpl, `${this.baseUrl}/${encodeURIComponent(input.id)}`, {
2272
+ query: { fields: "likes.summary(true),comments.summary(true),shares", ...this.auth() },
2273
+ errorLabel: "Facebook"
2274
+ });
2275
+ const metrics = {
2276
+ likes: res.likes?.summary?.total_count ?? 0,
2277
+ comments: res.comments?.summary?.total_count ?? 0,
2278
+ shares: res.shares?.count ?? 0
2279
+ };
2280
+ return { metrics };
2281
+ }
2282
+ }
2283
+
2284
+ // src/social/instagram.ts
2285
+ var DEFAULT_BASE3 = "https://graph.facebook.com/v19.0";
2286
+
2287
+ class InstagramAdapter {
2288
+ accessToken;
2289
+ igUserId;
2290
+ baseUrl;
2291
+ fetchImpl;
2292
+ constructor(creds, fetchImpl) {
2293
+ if (!creds || !creds.accessToken) {
2294
+ throw new Error("instagram credentials require `accessToken`");
2295
+ }
2296
+ if (!creds.igUserId) {
2297
+ throw new Error("instagram credentials require `igUserId`");
2298
+ }
2299
+ this.accessToken = creds.accessToken;
2300
+ this.igUserId = creds.igUserId;
2301
+ this.baseUrl = (creds.baseUrl ?? DEFAULT_BASE3).replace(/\/+$/, "");
2302
+ this.fetchImpl = resolveFetch(fetchImpl);
2303
+ }
2304
+ static fromCredentials(creds, fetchImpl) {
2305
+ return new InstagramAdapter(creds, fetchImpl);
2306
+ }
2307
+ form(fields) {
2308
+ const params = new URLSearchParams;
2309
+ params.append("access_token", this.accessToken);
2310
+ for (const [k, v] of Object.entries(fields)) {
2311
+ if (v !== undefined && v !== "")
2312
+ params.append(k, v);
2313
+ }
2314
+ return params.toString();
2315
+ }
2316
+ postForm(path, fields) {
2317
+ return jsonRequest(this.fetchImpl, `${this.baseUrl}/${path}`, {
2318
+ method: "POST",
2319
+ body: this.form(fields),
2320
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
2321
+ errorLabel: "Instagram"
2322
+ });
2323
+ }
2324
+ async createContainer(imageUrl, caption) {
2325
+ const res = await this.postForm(`${encodeURIComponent(this.igUserId)}/media`, {
2326
+ image_url: imageUrl,
2327
+ caption
2328
+ });
2329
+ return res.id;
2330
+ }
2331
+ async accountMe() {
2332
+ const me = await jsonRequest(this.fetchImpl, `${this.baseUrl}/${encodeURIComponent(this.igUserId)}`, { query: { fields: "id,username", access_token: this.accessToken }, errorLabel: "Instagram" });
2333
+ return {
2334
+ id: me.id,
2335
+ username: me.username,
2336
+ url: me.username ? `https://www.instagram.com/${me.username}/` : undefined
2337
+ };
2338
+ }
2339
+ async postCreate(input) {
2340
+ let creationId = input.mediaIds?.[0];
2341
+ if (!creationId) {
2342
+ if (!input.imageUrl) {
2343
+ throw new ConnectorOperationNotSupported("instagram", "post.create requires an image or video (provide `imageUrl` or a `mediaIds` container)");
2344
+ }
2345
+ creationId = await this.createContainer(input.imageUrl, input.text);
2346
+ }
2347
+ const published = await this.postForm(`${encodeURIComponent(this.igUserId)}/media_publish`, { creation_id: creationId });
2348
+ return {
2349
+ id: published.id,
2350
+ url: `https://www.instagram.com/p/${published.id}/`
2351
+ };
2352
+ }
2353
+ async postDelete(_input) {
2354
+ throw new ConnectorOperationNotSupported("instagram", "post.delete (the Instagram Graph API cannot delete published media)");
2355
+ }
2356
+ async mediaUpload(input) {
2357
+ if (!input.sourceUrl) {
2358
+ throw new ConnectorOperationNotSupported("instagram", "media.upload requires a public `sourceUrl` (IG builds containers from a URL, not raw bytes)");
2359
+ }
2360
+ const creationId = await this.createContainer(input.sourceUrl, input.caption);
2361
+ return { mediaId: creationId };
2362
+ }
2363
+ async mentionsList(_input = {}) {
2364
+ throw new ConnectorOperationNotSupported("instagram", "mentions.list (tagged/mentioned media require the dedicated mentions edge; not modeled)");
2365
+ }
2366
+ async analyticsPost(input) {
2367
+ const res = await jsonRequest(this.fetchImpl, `${this.baseUrl}/${encodeURIComponent(input.id)}/insights`, {
2368
+ query: { metric: "impressions,reach,likes,comments,saved", access_token: this.accessToken },
2369
+ errorLabel: "Instagram"
2370
+ });
2371
+ const metrics = {};
2372
+ for (const m of res.data ?? []) {
2373
+ const v = m.values?.[0]?.value;
2374
+ if (typeof v === "number")
2375
+ metrics[m.name] = v;
2376
+ }
2377
+ return { metrics };
2378
+ }
2379
+ }
2380
+
2381
+ // src/social/threads.ts
2382
+ var DEFAULT_BASE4 = "https://graph.threads.net/v1.0";
2383
+
2384
+ class ThreadsAdapter {
2385
+ accessToken;
2386
+ userId;
2387
+ baseUrl;
2388
+ fetchImpl;
2389
+ cachedUsername;
2390
+ constructor(creds, fetchImpl) {
2391
+ if (!creds || !creds.accessToken) {
2392
+ throw new Error("threads credentials require `accessToken`");
2393
+ }
2394
+ if (!creds.userId) {
2395
+ throw new Error("threads credentials require `userId`");
2396
+ }
2397
+ this.accessToken = creds.accessToken;
2398
+ this.userId = creds.userId;
2399
+ this.baseUrl = (creds.baseUrl ?? DEFAULT_BASE4).replace(/\/+$/, "");
2400
+ this.fetchImpl = resolveFetch(fetchImpl);
2401
+ }
2402
+ static fromCredentials(creds, fetchImpl) {
2403
+ return new ThreadsAdapter(creds, fetchImpl);
2404
+ }
2405
+ form(fields) {
2406
+ const params = new URLSearchParams;
2407
+ params.append("access_token", this.accessToken);
2408
+ for (const [k, v] of Object.entries(fields)) {
2409
+ if (v !== undefined && v !== "")
2410
+ params.append(k, v);
2411
+ }
2412
+ return params.toString();
2413
+ }
2414
+ postForm(path, fields) {
2415
+ return jsonRequest(this.fetchImpl, `${this.baseUrl}/${path}`, {
2416
+ method: "POST",
2417
+ body: this.form(fields),
2418
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
2419
+ errorLabel: "Threads"
2420
+ });
2421
+ }
2422
+ async accountMe() {
2423
+ const me = await jsonRequest(this.fetchImpl, `${this.baseUrl}/${encodeURIComponent(this.userId)}`, { query: { fields: "id,username", access_token: this.accessToken }, errorLabel: "Threads" });
2424
+ this.cachedUsername = me.username;
2425
+ return {
2426
+ id: me.id,
2427
+ username: me.username,
2428
+ url: me.username ? `https://www.threads.net/@${me.username}` : undefined
2429
+ };
2430
+ }
2431
+ async postCreate(input) {
2432
+ let creationId = input.mediaIds?.[0];
2433
+ if (!creationId) {
2434
+ const container = await this.postForm(`${encodeURIComponent(this.userId)}/threads`, { media_type: "TEXT", text: input.text, reply_to_id: input.replyToId });
2435
+ creationId = container.id;
2436
+ }
2437
+ const published = await this.postForm(`${encodeURIComponent(this.userId)}/threads_publish`, { creation_id: creationId });
2438
+ const url = this.cachedUsername ? `https://www.threads.net/@${this.cachedUsername}/post/${published.id}` : undefined;
2439
+ return { id: published.id, url };
2440
+ }
2441
+ async postDelete(input) {
2442
+ await jsonRequest(this.fetchImpl, `${this.baseUrl}/${encodeURIComponent(input.id)}`, {
2443
+ method: "DELETE",
2444
+ query: { access_token: this.accessToken },
2445
+ errorLabel: "Threads"
2446
+ });
2447
+ return { id: input.id, deleted: true };
2448
+ }
2449
+ async mediaUpload(input) {
2450
+ let fields;
2451
+ if (input.sourceUrl) {
2452
+ fields = { media_type: "IMAGE", image_url: input.sourceUrl, text: input.text };
2453
+ } else if (input.videoUrl) {
2454
+ fields = { media_type: "VIDEO", video_url: input.videoUrl, text: input.text };
2455
+ } else {
2456
+ throw new ConnectorOperationNotSupported("threads", "media.upload requires a public `sourceUrl` (image) or `videoUrl` (video)");
2457
+ }
2458
+ const container = await this.postForm(`${encodeURIComponent(this.userId)}/threads`, fields);
2459
+ return { mediaId: container.id };
2460
+ }
2461
+ async mentionsList(input = {}) {
2462
+ let res;
2463
+ try {
2464
+ res = await jsonRequest(this.fetchImpl, `${this.baseUrl}/${encodeURIComponent(this.userId)}/mentions`, {
2465
+ query: {
2466
+ fields: "id,text,username,timestamp",
2467
+ limit: input.limit !== undefined ? String(input.limit) : undefined,
2468
+ access_token: this.accessToken
2469
+ },
2470
+ errorLabel: "Threads"
2471
+ });
2472
+ } catch {
2473
+ throw new ConnectorOperationNotSupported("threads", "mentions.list (the Threads mentions edge is not available for this account)");
2474
+ }
2475
+ const items = (res.data ?? []).map((m) => ({
2476
+ id: m.id,
2477
+ text: m.text ?? "",
2478
+ authorHandle: m.username,
2479
+ createdAt: m.timestamp
2480
+ }));
2481
+ return { items };
2482
+ }
2483
+ async analyticsPost(input) {
2484
+ const res = await jsonRequest(this.fetchImpl, `${this.baseUrl}/${encodeURIComponent(input.id)}/insights`, {
2485
+ query: { metric: "views,likes,replies,reposts,quotes", access_token: this.accessToken },
2486
+ errorLabel: "Threads"
2487
+ });
2488
+ const metrics = {};
2489
+ for (const m of res.data ?? []) {
2490
+ const v = m.values?.[0]?.value;
2491
+ if (typeof v === "number")
2492
+ metrics[m.name] = v;
2493
+ }
2494
+ return { metrics };
2495
+ }
2496
+ }
2497
+
1470
2498
  // src/social/index.ts
1471
- var SUPPORTED_CONNECTORS = ["x", "mastodon", "bluesky"];
2499
+ var SUPPORTED_CONNECTORS = [
2500
+ "x",
2501
+ "mastodon",
2502
+ "bluesky",
2503
+ "linkedin",
2504
+ "reddit",
2505
+ "tiktok",
2506
+ "youtube",
2507
+ "pinterest",
2508
+ "googlebusinessprofile",
2509
+ "facebook",
2510
+ "instagram",
2511
+ "threads"
2512
+ ];
2513
+ var ALL_OPS = [
2514
+ "account.me",
2515
+ "post.create",
2516
+ "post.delete",
2517
+ "media.upload",
2518
+ "mentions.list",
2519
+ "analytics.post"
2520
+ ];
1472
2521
  var SUPPORTED_OPERATIONS = {
1473
- x: ["account.me", "post.create", "post.delete", "media.upload", "mentions.list", "analytics.post"],
1474
- mastodon: ["account.me", "post.create", "post.delete", "media.upload", "mentions.list", "analytics.post"],
1475
- bluesky: ["account.me", "post.create", "post.delete", "media.upload", "mentions.list", "analytics.post"]
2522
+ x: [...ALL_OPS],
2523
+ mastodon: [...ALL_OPS],
2524
+ bluesky: [...ALL_OPS],
2525
+ linkedin: ["account.me", "post.create", "post.delete", "media.upload"],
2526
+ reddit: ["account.me", "post.create", "post.delete", "mentions.list", "analytics.post"],
2527
+ tiktok: ["account.me", "post.create", "media.upload"],
2528
+ youtube: ["account.me", "post.create", "post.delete", "media.upload", "analytics.post"],
2529
+ pinterest: ["account.me", "post.create", "post.delete", "media.upload", "analytics.post"],
2530
+ googlebusinessprofile: ["account.me", "post.create", "post.delete", "media.upload"],
2531
+ facebook: ["account.me", "post.create", "post.delete", "media.upload", "analytics.post"],
2532
+ instagram: ["account.me", "post.create", "media.upload", "analytics.post"],
2533
+ threads: ["account.me", "post.create", "post.delete", "media.upload", "mentions.list", "analytics.post"]
1476
2534
  };
1477
2535
  function listSocialConnectors() {
1478
2536
  return [...SUPPORTED_CONNECTORS];
@@ -1492,6 +2550,24 @@ function buildAdapter(connector, credentials) {
1492
2550
  return MastodonAdapter.fromCredentials(credentials);
1493
2551
  case "bluesky":
1494
2552
  return BlueskyAdapter.fromCredentials(credentials);
2553
+ case "linkedin":
2554
+ return LinkedInAdapter.fromCredentials(credentials);
2555
+ case "reddit":
2556
+ return RedditAdapter.fromCredentials(credentials);
2557
+ case "tiktok":
2558
+ return TikTokAdapter.fromCredentials(credentials);
2559
+ case "youtube":
2560
+ return YouTubeAdapter.fromCredentials(credentials);
2561
+ case "pinterest":
2562
+ return PinterestAdapter.fromCredentials(credentials);
2563
+ case "googlebusinessprofile":
2564
+ return GoogleBusinessProfileAdapter.fromCredentials(credentials);
2565
+ case "facebook":
2566
+ return FacebookAdapter.fromCredentials(credentials);
2567
+ case "instagram":
2568
+ return InstagramAdapter.fromCredentials(credentials);
2569
+ case "threads":
2570
+ return ThreadsAdapter.fromCredentials(credentials);
1495
2571
  default:
1496
2572
  throw new ConnectorOperationNotSupported(connector, "*");
1497
2573
  }