@churchapps/content-providers 0.7.3 → 0.9.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.
- package/dist/index.cjs +150 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +150 -4
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -82,7 +82,9 @@ interface ContentFile {
|
|
|
82
82
|
type ContentItem = ContentFolder | ContentFile;
|
|
83
83
|
declare function isContentFolder(item: ContentItem): item is ContentFolder;
|
|
84
84
|
declare function isContentFile(item: ContentItem): item is ContentFile;
|
|
85
|
-
type DeviceFlowPollResult = ContentProviderAuthData
|
|
85
|
+
type DeviceFlowPollResult = (ContentProviderAuthData & {
|
|
86
|
+
planTypeId?: string;
|
|
87
|
+
}) | {
|
|
86
88
|
error: string;
|
|
87
89
|
shouldSlowDown?: boolean;
|
|
88
90
|
} | null;
|
package/dist/index.d.ts
CHANGED
|
@@ -82,7 +82,9 @@ interface ContentFile {
|
|
|
82
82
|
type ContentItem = ContentFolder | ContentFile;
|
|
83
83
|
declare function isContentFolder(item: ContentItem): item is ContentFolder;
|
|
84
84
|
declare function isContentFile(item: ContentItem): item is ContentFile;
|
|
85
|
-
type DeviceFlowPollResult = ContentProviderAuthData
|
|
85
|
+
type DeviceFlowPollResult = (ContentProviderAuthData & {
|
|
86
|
+
planTypeId?: string;
|
|
87
|
+
}) | {
|
|
86
88
|
error: string;
|
|
87
89
|
shouldSlowDown?: boolean;
|
|
88
90
|
} | null;
|
package/dist/index.js
CHANGED
|
@@ -151,7 +151,8 @@ var DeviceFlowHelper = class {
|
|
|
151
151
|
const response = await fetch(`${config.oauthBase}/token`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ grant_type: "urn:ietf:params:oauth:grant-type:device_code", device_code: deviceCode, client_id: config.clientId }) });
|
|
152
152
|
if (response.ok) {
|
|
153
153
|
const data = await response.json();
|
|
154
|
-
|
|
154
|
+
const auth = toAuthData(data, { scope: config.scopes.join(" ") });
|
|
155
|
+
return data.plan_type_id ? { ...auth, planTypeId: data.plan_type_id } : auth;
|
|
155
156
|
}
|
|
156
157
|
const errorData = await response.json();
|
|
157
158
|
switch (errorData.error) {
|
|
@@ -659,7 +660,7 @@ async function convertAddOnToFile(addOn) {
|
|
|
659
660
|
} else {
|
|
660
661
|
return null;
|
|
661
662
|
}
|
|
662
|
-
return { type: "file", id: addOn.id, title: addOn.name, mediaType, thumbnail: addOn.image, url, downloadUrl: url, seconds, loopVideo: video?.loopVideo || false };
|
|
663
|
+
return { type: "file", id: addOn.id, title: addOn.name ?? detail.name, mediaType, thumbnail: addOn.image ?? detail.image, url, downloadUrl: url, seconds, loopVideo: video?.loopVideo || false };
|
|
663
664
|
}
|
|
664
665
|
function buildSectionActionsMap(actionsResponse, lessonImage, feedResponse) {
|
|
665
666
|
const sectionActionsMap = /* @__PURE__ */ new Map();
|
|
@@ -1184,7 +1185,7 @@ var SECTION_TYPES = /* @__PURE__ */ new Set(["item", "section", "lessonSection",
|
|
|
1184
1185
|
var ACTION_TYPES = /* @__PURE__ */ new Set(["action", "lessonAction", "providerPresentation", "lessonAddOn", "addon", "providerFile", "file"]);
|
|
1185
1186
|
function collectFilesFromNode(node) {
|
|
1186
1187
|
const files = [];
|
|
1187
|
-
if (node.downloadUrl) {
|
|
1188
|
+
if (node.downloadUrl && (node.itemType === "file" || !node.children?.length)) {
|
|
1188
1189
|
files.push({
|
|
1189
1190
|
id: node.id,
|
|
1190
1191
|
name: node.label || "",
|
|
@@ -17702,6 +17703,150 @@ var GoCurriculumProvider = class extends BaseProvider {
|
|
|
17702
17703
|
}
|
|
17703
17704
|
};
|
|
17704
17705
|
|
|
17706
|
+
// src/providers/googledrive/GoogleDriveProvider.ts
|
|
17707
|
+
var FOLDER_MIME = "application/vnd.google-apps.folder";
|
|
17708
|
+
var MAX_PAGINATION_CALLS2 = 10;
|
|
17709
|
+
var LEAF_CHECK_MAX_FOLDERS = 50;
|
|
17710
|
+
var GoogleDriveProvider = class extends BaseProvider {
|
|
17711
|
+
constructor() {
|
|
17712
|
+
super(...arguments);
|
|
17713
|
+
this.oauthHelper = new OAuthHelper();
|
|
17714
|
+
this.id = "googledrive";
|
|
17715
|
+
this.name = "Google Drive";
|
|
17716
|
+
this.logos = {
|
|
17717
|
+
light: "https://ssl.gstatic.com/images/branding/product/2x/drive_2020q4_48dp.png",
|
|
17718
|
+
dark: "https://ssl.gstatic.com/images/branding/product/2x/drive_2020q4_48dp.png"
|
|
17719
|
+
};
|
|
17720
|
+
this.config = {
|
|
17721
|
+
id: "googledrive",
|
|
17722
|
+
name: "Google Drive",
|
|
17723
|
+
apiBase: "https://www.googleapis.com/drive/v3",
|
|
17724
|
+
// Token endpoint host; the user-facing consent screen is accounts.google.com (see buildAuthUrlFromChallenge)
|
|
17725
|
+
oauthBase: "https://oauth2.googleapis.com",
|
|
17726
|
+
// TODO owner action: Google Cloud OAuth web client id (drive.readonly scope) — fill before publishing
|
|
17727
|
+
clientId: "",
|
|
17728
|
+
// Google requires client_secret even for PKCE public clients; injected server-side via setProviderSecret("googledrive", ...)
|
|
17729
|
+
get clientSecret() {
|
|
17730
|
+
return getProviderSecret("googledrive");
|
|
17731
|
+
},
|
|
17732
|
+
scopes: ["https://www.googleapis.com/auth/drive.readonly"]
|
|
17733
|
+
};
|
|
17734
|
+
this.requiresAuth = true;
|
|
17735
|
+
this.authTypes = ["oauth_pkce"];
|
|
17736
|
+
this.capabilities = {
|
|
17737
|
+
browse: true,
|
|
17738
|
+
playlist: true,
|
|
17739
|
+
instructions: true,
|
|
17740
|
+
mediaLicensing: false
|
|
17741
|
+
};
|
|
17742
|
+
}
|
|
17743
|
+
// Paths are Drive folder ids ("/<id>"), not names; root is "" or "/"
|
|
17744
|
+
folderIdFromPath(path) {
|
|
17745
|
+
const trimmed = (path || "").replace(/^\//, "");
|
|
17746
|
+
return trimmed || "root";
|
|
17747
|
+
}
|
|
17748
|
+
isValidId(id) {
|
|
17749
|
+
return /^[\w-]+$/.test(id);
|
|
17750
|
+
}
|
|
17751
|
+
async listFiles(query, auth) {
|
|
17752
|
+
if (!auth) {
|
|
17753
|
+
console.error("[GoogleDrive] No auth token provided");
|
|
17754
|
+
return [];
|
|
17755
|
+
}
|
|
17756
|
+
const all = [];
|
|
17757
|
+
let pageToken;
|
|
17758
|
+
for (let i = 0; i < MAX_PAGINATION_CALLS2; i++) {
|
|
17759
|
+
const params = new URLSearchParams({
|
|
17760
|
+
q: query,
|
|
17761
|
+
fields: "nextPageToken,files(id,name,mimeType,thumbnailLink,webContentLink,parents)",
|
|
17762
|
+
orderBy: "folder,name_natural",
|
|
17763
|
+
pageSize: "1000",
|
|
17764
|
+
supportsAllDrives: "true",
|
|
17765
|
+
includeItemsFromAllDrives: "true"
|
|
17766
|
+
});
|
|
17767
|
+
if (pageToken) params.set("pageToken", pageToken);
|
|
17768
|
+
const response = await this.apiRequest(`/files?${params.toString()}`, auth);
|
|
17769
|
+
if (!response) break;
|
|
17770
|
+
all.push(...response.files || []);
|
|
17771
|
+
pageToken = response.nextPageToken;
|
|
17772
|
+
if (!pageToken) break;
|
|
17773
|
+
}
|
|
17774
|
+
return all;
|
|
17775
|
+
}
|
|
17776
|
+
async childItems(folderId, auth) {
|
|
17777
|
+
const entries = await this.listFiles(`'${folderId}' in parents and trashed=false`, auth);
|
|
17778
|
+
const folders = entries.filter((e) => e.mimeType === FOLDER_MIME);
|
|
17779
|
+
const mediaFiles = entries.filter((e) => e.mimeType.startsWith("video/") || e.mimeType.startsWith("image/"));
|
|
17780
|
+
return { folders, mediaFiles };
|
|
17781
|
+
}
|
|
17782
|
+
/** One batched query marks which sibling folders have subfolders; null = unknown (isLeaf omitted). */
|
|
17783
|
+
async leafIds(folders, auth) {
|
|
17784
|
+
if (folders.length === 0 || folders.length > LEAF_CHECK_MAX_FOLDERS) return null;
|
|
17785
|
+
const parentClause = folders.map((f) => `'${f.id}' in parents`).join(" or ");
|
|
17786
|
+
const subfolders = await this.listFiles(`mimeType='${FOLDER_MIME}' and trashed=false and (${parentClause})`, auth);
|
|
17787
|
+
const parentsWithSubfolders = new Set(subfolders.flatMap((s) => s.parents || []));
|
|
17788
|
+
return new Set(folders.filter((f) => !parentsWithSubfolders.has(f.id)).map((f) => f.id));
|
|
17789
|
+
}
|
|
17790
|
+
toContentFile(file) {
|
|
17791
|
+
const url = file.webContentLink || `https://drive.google.com/uc?id=${file.id}&export=download`;
|
|
17792
|
+
return {
|
|
17793
|
+
type: "file",
|
|
17794
|
+
id: file.id,
|
|
17795
|
+
title: file.name,
|
|
17796
|
+
mediaType: detectMediaType(file.name, file.mimeType),
|
|
17797
|
+
thumbnail: file.thumbnailLink,
|
|
17798
|
+
url,
|
|
17799
|
+
downloadUrl: url
|
|
17800
|
+
};
|
|
17801
|
+
}
|
|
17802
|
+
async browse(path, auth) {
|
|
17803
|
+
const folderId = this.folderIdFromPath(path);
|
|
17804
|
+
if (!this.isValidId(folderId)) return [];
|
|
17805
|
+
const { folders, mediaFiles } = await this.childItems(folderId, auth);
|
|
17806
|
+
const leaves = await this.leafIds(folders, auth);
|
|
17807
|
+
const folderItems = folders.map((f) => ({ type: "folder", id: f.id, title: f.name, path: `/${f.id}`, isLeaf: leaves ? leaves.has(f.id) : void 0 }));
|
|
17808
|
+
return [...folderItems, ...mediaFiles.map((f) => this.toContentFile(f))];
|
|
17809
|
+
}
|
|
17810
|
+
async getPlaylist(path, auth, _resolution) {
|
|
17811
|
+
const folderId = this.folderIdFromPath(path);
|
|
17812
|
+
if (folderId === "root" || !this.isValidId(folderId)) return null;
|
|
17813
|
+
const { mediaFiles } = await this.childItems(folderId, auth);
|
|
17814
|
+
return mediaFiles.length > 0 ? mediaFiles.map((f) => this.toContentFile(f)) : null;
|
|
17815
|
+
}
|
|
17816
|
+
async getInstructions(path, auth) {
|
|
17817
|
+
const files = await this.getPlaylist(path, auth);
|
|
17818
|
+
if (!files) return null;
|
|
17819
|
+
const meta = await this.apiRequest(`/files/${this.folderIdFromPath(path)}?fields=name&supportsAllDrives=true`, auth);
|
|
17820
|
+
const folderName = meta?.name || "Google Drive";
|
|
17821
|
+
return filesToInstructions(folderName, files, { id: "googledrive-section", label: folderName });
|
|
17822
|
+
}
|
|
17823
|
+
generateCodeVerifier() {
|
|
17824
|
+
return this.oauthHelper.generateCodeVerifier();
|
|
17825
|
+
}
|
|
17826
|
+
async buildAuthUrl(codeVerifier, redirectUri, state) {
|
|
17827
|
+
const codeChallenge = await this.oauthHelper.generateCodeChallenge(codeVerifier);
|
|
17828
|
+
return { url: this.buildAuthUrlFromChallenge(codeChallenge, redirectUri, state || this.id), challengeMethod: "S256" };
|
|
17829
|
+
}
|
|
17830
|
+
buildAuthUrlFromChallenge(codeChallenge, redirectUri, state) {
|
|
17831
|
+
const params = new URLSearchParams({
|
|
17832
|
+
response_type: "code",
|
|
17833
|
+
client_id: this.config.clientId,
|
|
17834
|
+
redirect_uri: redirectUri,
|
|
17835
|
+
scope: this.config.scopes.join(" "),
|
|
17836
|
+
code_challenge: codeChallenge,
|
|
17837
|
+
code_challenge_method: "S256",
|
|
17838
|
+
// offline + consent so Google issues a refresh_token on every connect
|
|
17839
|
+
access_type: "offline",
|
|
17840
|
+
prompt: "consent",
|
|
17841
|
+
state
|
|
17842
|
+
});
|
|
17843
|
+
return `https://accounts.google.com/o/oauth2/v2/auth?${params.toString()}`;
|
|
17844
|
+
}
|
|
17845
|
+
async exchangeCodeForTokens(code, codeVerifier, redirectUri) {
|
|
17846
|
+
return this.oauthHelper.exchangeCodeForTokens(this.config, this.id, code, codeVerifier, redirectUri);
|
|
17847
|
+
}
|
|
17848
|
+
};
|
|
17849
|
+
|
|
17705
17850
|
// src/providers/index.ts
|
|
17706
17851
|
var unimplementedProviders = [
|
|
17707
17852
|
{
|
|
@@ -17743,6 +17888,7 @@ function initializeProviders() {
|
|
|
17743
17888
|
new B1ChurchProvider(),
|
|
17744
17889
|
new DropboxProvider(),
|
|
17745
17890
|
new GoCurriculumProvider(),
|
|
17891
|
+
new GoogleDriveProvider(),
|
|
17746
17892
|
new BibleProjectProvider(),
|
|
17747
17893
|
new HighVoltageKidsProvider(),
|
|
17748
17894
|
new JesusFilmProvider(),
|
|
@@ -17787,7 +17933,7 @@ function getAvailableProviders(ids) {
|
|
|
17787
17933
|
}
|
|
17788
17934
|
|
|
17789
17935
|
// src/index.ts
|
|
17790
|
-
var VERSION = true ? "0.
|
|
17936
|
+
var VERSION = true ? "0.9.0" : "dev";
|
|
17791
17937
|
export {
|
|
17792
17938
|
APlayProvider,
|
|
17793
17939
|
B1ChurchProvider,
|