@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.cjs
CHANGED
|
@@ -202,7 +202,8 @@ var DeviceFlowHelper = class {
|
|
|
202
202
|
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 }) });
|
|
203
203
|
if (response.ok) {
|
|
204
204
|
const data = await response.json();
|
|
205
|
-
|
|
205
|
+
const auth = toAuthData(data, { scope: config.scopes.join(" ") });
|
|
206
|
+
return data.plan_type_id ? { ...auth, planTypeId: data.plan_type_id } : auth;
|
|
206
207
|
}
|
|
207
208
|
const errorData = await response.json();
|
|
208
209
|
switch (errorData.error) {
|
|
@@ -710,7 +711,7 @@ async function convertAddOnToFile(addOn) {
|
|
|
710
711
|
} else {
|
|
711
712
|
return null;
|
|
712
713
|
}
|
|
713
|
-
return { type: "file", id: addOn.id, title: addOn.name, mediaType, thumbnail: addOn.image, url, downloadUrl: url, seconds, loopVideo: video?.loopVideo || false };
|
|
714
|
+
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 };
|
|
714
715
|
}
|
|
715
716
|
function buildSectionActionsMap(actionsResponse, lessonImage, feedResponse) {
|
|
716
717
|
const sectionActionsMap = /* @__PURE__ */ new Map();
|
|
@@ -1235,7 +1236,7 @@ var SECTION_TYPES = /* @__PURE__ */ new Set(["item", "section", "lessonSection",
|
|
|
1235
1236
|
var ACTION_TYPES = /* @__PURE__ */ new Set(["action", "lessonAction", "providerPresentation", "lessonAddOn", "addon", "providerFile", "file"]);
|
|
1236
1237
|
function collectFilesFromNode(node) {
|
|
1237
1238
|
const files = [];
|
|
1238
|
-
if (node.downloadUrl) {
|
|
1239
|
+
if (node.downloadUrl && (node.itemType === "file" || !node.children?.length)) {
|
|
1239
1240
|
files.push({
|
|
1240
1241
|
id: node.id,
|
|
1241
1242
|
name: node.label || "",
|
|
@@ -17753,6 +17754,150 @@ var GoCurriculumProvider = class extends BaseProvider {
|
|
|
17753
17754
|
}
|
|
17754
17755
|
};
|
|
17755
17756
|
|
|
17757
|
+
// src/providers/googledrive/GoogleDriveProvider.ts
|
|
17758
|
+
var FOLDER_MIME = "application/vnd.google-apps.folder";
|
|
17759
|
+
var MAX_PAGINATION_CALLS2 = 10;
|
|
17760
|
+
var LEAF_CHECK_MAX_FOLDERS = 50;
|
|
17761
|
+
var GoogleDriveProvider = class extends BaseProvider {
|
|
17762
|
+
constructor() {
|
|
17763
|
+
super(...arguments);
|
|
17764
|
+
this.oauthHelper = new OAuthHelper();
|
|
17765
|
+
this.id = "googledrive";
|
|
17766
|
+
this.name = "Google Drive";
|
|
17767
|
+
this.logos = {
|
|
17768
|
+
light: "https://ssl.gstatic.com/images/branding/product/2x/drive_2020q4_48dp.png",
|
|
17769
|
+
dark: "https://ssl.gstatic.com/images/branding/product/2x/drive_2020q4_48dp.png"
|
|
17770
|
+
};
|
|
17771
|
+
this.config = {
|
|
17772
|
+
id: "googledrive",
|
|
17773
|
+
name: "Google Drive",
|
|
17774
|
+
apiBase: "https://www.googleapis.com/drive/v3",
|
|
17775
|
+
// Token endpoint host; the user-facing consent screen is accounts.google.com (see buildAuthUrlFromChallenge)
|
|
17776
|
+
oauthBase: "https://oauth2.googleapis.com",
|
|
17777
|
+
// TODO owner action: Google Cloud OAuth web client id (drive.readonly scope) — fill before publishing
|
|
17778
|
+
clientId: "",
|
|
17779
|
+
// Google requires client_secret even for PKCE public clients; injected server-side via setProviderSecret("googledrive", ...)
|
|
17780
|
+
get clientSecret() {
|
|
17781
|
+
return getProviderSecret("googledrive");
|
|
17782
|
+
},
|
|
17783
|
+
scopes: ["https://www.googleapis.com/auth/drive.readonly"]
|
|
17784
|
+
};
|
|
17785
|
+
this.requiresAuth = true;
|
|
17786
|
+
this.authTypes = ["oauth_pkce"];
|
|
17787
|
+
this.capabilities = {
|
|
17788
|
+
browse: true,
|
|
17789
|
+
playlist: true,
|
|
17790
|
+
instructions: true,
|
|
17791
|
+
mediaLicensing: false
|
|
17792
|
+
};
|
|
17793
|
+
}
|
|
17794
|
+
// Paths are Drive folder ids ("/<id>"), not names; root is "" or "/"
|
|
17795
|
+
folderIdFromPath(path) {
|
|
17796
|
+
const trimmed = (path || "").replace(/^\//, "");
|
|
17797
|
+
return trimmed || "root";
|
|
17798
|
+
}
|
|
17799
|
+
isValidId(id) {
|
|
17800
|
+
return /^[\w-]+$/.test(id);
|
|
17801
|
+
}
|
|
17802
|
+
async listFiles(query, auth) {
|
|
17803
|
+
if (!auth) {
|
|
17804
|
+
console.error("[GoogleDrive] No auth token provided");
|
|
17805
|
+
return [];
|
|
17806
|
+
}
|
|
17807
|
+
const all = [];
|
|
17808
|
+
let pageToken;
|
|
17809
|
+
for (let i = 0; i < MAX_PAGINATION_CALLS2; i++) {
|
|
17810
|
+
const params = new URLSearchParams({
|
|
17811
|
+
q: query,
|
|
17812
|
+
fields: "nextPageToken,files(id,name,mimeType,thumbnailLink,webContentLink,parents)",
|
|
17813
|
+
orderBy: "folder,name_natural",
|
|
17814
|
+
pageSize: "1000",
|
|
17815
|
+
supportsAllDrives: "true",
|
|
17816
|
+
includeItemsFromAllDrives: "true"
|
|
17817
|
+
});
|
|
17818
|
+
if (pageToken) params.set("pageToken", pageToken);
|
|
17819
|
+
const response = await this.apiRequest(`/files?${params.toString()}`, auth);
|
|
17820
|
+
if (!response) break;
|
|
17821
|
+
all.push(...response.files || []);
|
|
17822
|
+
pageToken = response.nextPageToken;
|
|
17823
|
+
if (!pageToken) break;
|
|
17824
|
+
}
|
|
17825
|
+
return all;
|
|
17826
|
+
}
|
|
17827
|
+
async childItems(folderId, auth) {
|
|
17828
|
+
const entries = await this.listFiles(`'${folderId}' in parents and trashed=false`, auth);
|
|
17829
|
+
const folders = entries.filter((e) => e.mimeType === FOLDER_MIME);
|
|
17830
|
+
const mediaFiles = entries.filter((e) => e.mimeType.startsWith("video/") || e.mimeType.startsWith("image/"));
|
|
17831
|
+
return { folders, mediaFiles };
|
|
17832
|
+
}
|
|
17833
|
+
/** One batched query marks which sibling folders have subfolders; null = unknown (isLeaf omitted). */
|
|
17834
|
+
async leafIds(folders, auth) {
|
|
17835
|
+
if (folders.length === 0 || folders.length > LEAF_CHECK_MAX_FOLDERS) return null;
|
|
17836
|
+
const parentClause = folders.map((f) => `'${f.id}' in parents`).join(" or ");
|
|
17837
|
+
const subfolders = await this.listFiles(`mimeType='${FOLDER_MIME}' and trashed=false and (${parentClause})`, auth);
|
|
17838
|
+
const parentsWithSubfolders = new Set(subfolders.flatMap((s) => s.parents || []));
|
|
17839
|
+
return new Set(folders.filter((f) => !parentsWithSubfolders.has(f.id)).map((f) => f.id));
|
|
17840
|
+
}
|
|
17841
|
+
toContentFile(file) {
|
|
17842
|
+
const url = file.webContentLink || `https://drive.google.com/uc?id=${file.id}&export=download`;
|
|
17843
|
+
return {
|
|
17844
|
+
type: "file",
|
|
17845
|
+
id: file.id,
|
|
17846
|
+
title: file.name,
|
|
17847
|
+
mediaType: detectMediaType(file.name, file.mimeType),
|
|
17848
|
+
thumbnail: file.thumbnailLink,
|
|
17849
|
+
url,
|
|
17850
|
+
downloadUrl: url
|
|
17851
|
+
};
|
|
17852
|
+
}
|
|
17853
|
+
async browse(path, auth) {
|
|
17854
|
+
const folderId = this.folderIdFromPath(path);
|
|
17855
|
+
if (!this.isValidId(folderId)) return [];
|
|
17856
|
+
const { folders, mediaFiles } = await this.childItems(folderId, auth);
|
|
17857
|
+
const leaves = await this.leafIds(folders, auth);
|
|
17858
|
+
const folderItems = folders.map((f) => ({ type: "folder", id: f.id, title: f.name, path: `/${f.id}`, isLeaf: leaves ? leaves.has(f.id) : void 0 }));
|
|
17859
|
+
return [...folderItems, ...mediaFiles.map((f) => this.toContentFile(f))];
|
|
17860
|
+
}
|
|
17861
|
+
async getPlaylist(path, auth, _resolution) {
|
|
17862
|
+
const folderId = this.folderIdFromPath(path);
|
|
17863
|
+
if (folderId === "root" || !this.isValidId(folderId)) return null;
|
|
17864
|
+
const { mediaFiles } = await this.childItems(folderId, auth);
|
|
17865
|
+
return mediaFiles.length > 0 ? mediaFiles.map((f) => this.toContentFile(f)) : null;
|
|
17866
|
+
}
|
|
17867
|
+
async getInstructions(path, auth) {
|
|
17868
|
+
const files = await this.getPlaylist(path, auth);
|
|
17869
|
+
if (!files) return null;
|
|
17870
|
+
const meta = await this.apiRequest(`/files/${this.folderIdFromPath(path)}?fields=name&supportsAllDrives=true`, auth);
|
|
17871
|
+
const folderName = meta?.name || "Google Drive";
|
|
17872
|
+
return filesToInstructions(folderName, files, { id: "googledrive-section", label: folderName });
|
|
17873
|
+
}
|
|
17874
|
+
generateCodeVerifier() {
|
|
17875
|
+
return this.oauthHelper.generateCodeVerifier();
|
|
17876
|
+
}
|
|
17877
|
+
async buildAuthUrl(codeVerifier, redirectUri, state) {
|
|
17878
|
+
const codeChallenge = await this.oauthHelper.generateCodeChallenge(codeVerifier);
|
|
17879
|
+
return { url: this.buildAuthUrlFromChallenge(codeChallenge, redirectUri, state || this.id), challengeMethod: "S256" };
|
|
17880
|
+
}
|
|
17881
|
+
buildAuthUrlFromChallenge(codeChallenge, redirectUri, state) {
|
|
17882
|
+
const params = new URLSearchParams({
|
|
17883
|
+
response_type: "code",
|
|
17884
|
+
client_id: this.config.clientId,
|
|
17885
|
+
redirect_uri: redirectUri,
|
|
17886
|
+
scope: this.config.scopes.join(" "),
|
|
17887
|
+
code_challenge: codeChallenge,
|
|
17888
|
+
code_challenge_method: "S256",
|
|
17889
|
+
// offline + consent so Google issues a refresh_token on every connect
|
|
17890
|
+
access_type: "offline",
|
|
17891
|
+
prompt: "consent",
|
|
17892
|
+
state
|
|
17893
|
+
});
|
|
17894
|
+
return `https://accounts.google.com/o/oauth2/v2/auth?${params.toString()}`;
|
|
17895
|
+
}
|
|
17896
|
+
async exchangeCodeForTokens(code, codeVerifier, redirectUri) {
|
|
17897
|
+
return this.oauthHelper.exchangeCodeForTokens(this.config, this.id, code, codeVerifier, redirectUri);
|
|
17898
|
+
}
|
|
17899
|
+
};
|
|
17900
|
+
|
|
17756
17901
|
// src/providers/index.ts
|
|
17757
17902
|
var unimplementedProviders = [
|
|
17758
17903
|
{
|
|
@@ -17794,6 +17939,7 @@ function initializeProviders() {
|
|
|
17794
17939
|
new B1ChurchProvider(),
|
|
17795
17940
|
new DropboxProvider(),
|
|
17796
17941
|
new GoCurriculumProvider(),
|
|
17942
|
+
new GoogleDriveProvider(),
|
|
17797
17943
|
new BibleProjectProvider(),
|
|
17798
17944
|
new HighVoltageKidsProvider(),
|
|
17799
17945
|
new JesusFilmProvider(),
|
|
@@ -17838,7 +17984,7 @@ function getAvailableProviders(ids) {
|
|
|
17838
17984
|
}
|
|
17839
17985
|
|
|
17840
17986
|
// src/index.ts
|
|
17841
|
-
var VERSION = true ? "0.
|
|
17987
|
+
var VERSION = true ? "0.9.0" : "dev";
|
|
17842
17988
|
// Annotate the CommonJS export names for ESM import in node:
|
|
17843
17989
|
0 && (module.exports = {
|
|
17844
17990
|
APlayProvider,
|