@gentbajko/slopify 3.11.4 → 3.11.5
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/edge/http/studio.js +45 -25
- package/dist/extension/slopify-studio-chrome.zip +0 -0
- package/dist/extension/slopify-studio-firefox.zip +0 -0
- package/dist/patch-notes/3.11.5.md +9 -0
- package/dist/patch-notes/index.json +6 -0
- package/dist/slices/studio/videos.js +4 -0
- package/package.json +1 -1
package/dist/edge/http/studio.js
CHANGED
|
@@ -26,7 +26,7 @@ import { parseStudioExport, readReport, saveReport } from "../../slices/studio/r
|
|
|
26
26
|
import { statsFromReport } from "../../slices/studio/report-stats.js";
|
|
27
27
|
import { autoCommentKey, bearerToken, exportViewProblem, exportViews, isExtensionOrigin, pairStudioExtension, readChannelPlaylists, readExportView, readStudioPlaylists, resetStudioPairing, saveExportView, saveProjectPlaylists, saveRealFootage, saveStudioPlaylists, studioPairing, studioPlaylistsMax, studioPlaylistsProblem, studioRequestAllowed, } from "../../slices/studio/settings.js";
|
|
28
28
|
import { abResults, abVariantSchema, projectStats, saveAbResult, saveStats, } from "../../slices/studio/stats.js";
|
|
29
|
-
import { confirmUpload, doneVideos, forgetVideo, projectVideos, recordVideo, setChecks, setTaskState, videoIdOf, videoIdPattern, videoOf, waitingTasks, } from "../../slices/studio/videos.js";
|
|
29
|
+
import { confirmUpload, doneVideos, forgetVideo, projectVideos, recordedVideos, recordVideo, setChecks, setTaskState, videoIdOf, videoIdPattern, videoOf, waitingTasks, } from "../../slices/studio/videos.js";
|
|
30
30
|
import { setAsideProjects, uploadedProjects } from "../../slices/uploads/repo.js";
|
|
31
31
|
import { onInvalid, problem, titleOf } from "./problem.js";
|
|
32
32
|
const id = z
|
|
@@ -74,7 +74,10 @@ const extVideoBody = z.object({
|
|
|
74
74
|
short: shortField,
|
|
75
75
|
videoId: z.string().regex(videoIdPattern),
|
|
76
76
|
});
|
|
77
|
-
const goneBody = z.object({
|
|
77
|
+
const goneBody = z.object({
|
|
78
|
+
videos: z.array(extVideoBody).max(1000),
|
|
79
|
+
confirmed: z.array(extVideoBody).max(1000).optional(),
|
|
80
|
+
});
|
|
78
81
|
const videoLinkBody = z.object({ short: shortField, link: z.string().max(500) });
|
|
79
82
|
const slotBody = z.object({
|
|
80
83
|
slot: z.object({ row: z.string().min(1).max(20), longAt: z.string().max(40) }).nullable(),
|
|
@@ -809,37 +812,50 @@ export function studioRoutes(deps) {
|
|
|
809
812
|
})),
|
|
810
813
|
});
|
|
811
814
|
})
|
|
812
|
-
// Every video Slopify takes to be on YouTube,
|
|
813
|
-
// each still exists.
|
|
815
|
+
// Every video Slopify takes to be on YouTube, and every upload it saw start, for the
|
|
816
|
+
// extension to ask Studio whether each still exists and which Studio has scheduled.
|
|
814
817
|
.get("/ext/recorded-videos", (c) => {
|
|
815
818
|
allowOrigin(c, false);
|
|
816
819
|
if (!extAllowed(c))
|
|
817
820
|
return refused(c);
|
|
818
821
|
const channels = projectChannels(deps.db);
|
|
819
822
|
return c.json({
|
|
820
|
-
videos:
|
|
823
|
+
videos: recordedVideos(deps.db).map((video) => ({
|
|
821
824
|
projectId: video.projectId,
|
|
822
825
|
short: video.short,
|
|
823
826
|
videoId: video.videoId,
|
|
827
|
+
uploadState: video.uploadState,
|
|
824
828
|
channelId: channels.get(video.projectId) ?? null,
|
|
825
829
|
})),
|
|
826
830
|
});
|
|
827
831
|
})
|
|
828
832
|
// Videos Studio says were deleted: each is forgotten, as Deleted on YouTube does, so it
|
|
829
|
-
// can be uploaded again.
|
|
833
|
+
// can be uploaded again. Uploads Studio has scheduled or published whose confirmation
|
|
834
|
+
// the upload dialog missed are confirmed. A slot that has since become another video
|
|
835
|
+
// keeps it.
|
|
830
836
|
.post("/ext/gone", zValidator("json", goneBody, onInvalid), (c) => {
|
|
831
837
|
allowOrigin(c, false);
|
|
832
838
|
if (!extAllowed(c))
|
|
833
839
|
return refused(c);
|
|
840
|
+
const body = c.req.valid("json");
|
|
834
841
|
let forgotten = 0;
|
|
835
|
-
for (const { projectId, short, videoId } of
|
|
842
|
+
for (const { projectId, short, videoId } of body.videos) {
|
|
836
843
|
if (videoOf(deps.db, projectId, short ?? null)?.videoId !== videoId)
|
|
837
844
|
continue;
|
|
838
845
|
forgetVideo(deps.db, projectId, short ?? null);
|
|
839
846
|
deps.hub.emit(projectId, { type: "project.updated", projectId });
|
|
840
847
|
forgotten++;
|
|
841
848
|
}
|
|
842
|
-
|
|
849
|
+
let confirmed = 0;
|
|
850
|
+
for (const { projectId, short, videoId } of body.confirmed ?? []) {
|
|
851
|
+
const video = videoOf(deps.db, projectId, short ?? null);
|
|
852
|
+
if (video?.videoId !== videoId || video.uploadState !== "filled")
|
|
853
|
+
continue;
|
|
854
|
+
confirmVideoUpload(deps, projectId, short ?? null, videoId);
|
|
855
|
+
deps.hub.emit(projectId, { type: "project.updated", projectId });
|
|
856
|
+
confirmed++;
|
|
857
|
+
}
|
|
858
|
+
return c.json({ forgotten, confirmed });
|
|
843
859
|
})
|
|
844
860
|
.post("/ext/stats", zValidator("json", statsBody, onInvalid), (c) => {
|
|
845
861
|
allowOrigin(c, false);
|
|
@@ -896,23 +912,7 @@ export function studioRoutes(deps) {
|
|
|
896
912
|
if (!extAllowed(c))
|
|
897
913
|
return refused(c);
|
|
898
914
|
const { projectId, short, videoId } = c.req.valid("json");
|
|
899
|
-
|
|
900
|
-
recordVideo(deps.db, projectId, short ?? null, videoId, deps.clock.now().toISOString());
|
|
901
|
-
// The Details touches (a short's related video, the long video's end screen and
|
|
902
|
-
// captions) wait for the extension now, and the comment for the video to be public.
|
|
903
|
-
const confirmedPack = uploadPack(deps, projectId);
|
|
904
|
-
const confirmedItem = confirmedPack.ok
|
|
905
|
-
? packItem(confirmedPack.pack, short ?? undefined)
|
|
906
|
-
: undefined;
|
|
907
|
-
if (confirmedItem !== undefined &&
|
|
908
|
-
(confirmedItem.relatedVideoId !== undefined ||
|
|
909
|
-
confirmedItem.endScreenVideoId !== undefined ||
|
|
910
|
-
confirmedItem.captions !== undefined))
|
|
911
|
-
setTaskState(deps.db, "finish", projectId, short ?? null, "waiting", null);
|
|
912
|
-
if (confirmedItem?.kind === "video" &&
|
|
913
|
-
confirmedItem.pinnedComment !== undefined &&
|
|
914
|
-
readSetting(deps.db, autoCommentKey) === "on")
|
|
915
|
-
setTaskState(deps.db, "comment", projectId, short ?? null, "waiting", null);
|
|
915
|
+
confirmVideoUpload(deps, projectId, short ?? null, videoId);
|
|
916
916
|
return c.json({ confirmed: true });
|
|
917
917
|
})
|
|
918
918
|
.get("/ext/files/:projectId/:asset", zValidator("param", fileParam, onInvalid), (c) => {
|
|
@@ -942,6 +942,26 @@ export function studioRoutes(deps) {
|
|
|
942
942
|
});
|
|
943
943
|
}));
|
|
944
944
|
}
|
|
945
|
+
// Studio said the upload was scheduled or published: it is on YouTube, and the Details touches
|
|
946
|
+
// (a short's related video, the long video's end screen and captions) wait for the extension,
|
|
947
|
+
// the pinned comment for the video to be public.
|
|
948
|
+
function confirmVideoUpload(deps, projectId, short, videoId) {
|
|
949
|
+
if (!confirmUpload(deps.db, projectId, short, videoId))
|
|
950
|
+
recordVideo(deps.db, projectId, short, videoId, deps.clock.now().toISOString());
|
|
951
|
+
const confirmedPack = uploadPack(deps, projectId);
|
|
952
|
+
const confirmedItem = confirmedPack.ok
|
|
953
|
+
? packItem(confirmedPack.pack, short ?? undefined)
|
|
954
|
+
: undefined;
|
|
955
|
+
if (confirmedItem !== undefined &&
|
|
956
|
+
(confirmedItem.relatedVideoId !== undefined ||
|
|
957
|
+
confirmedItem.endScreenVideoId !== undefined ||
|
|
958
|
+
confirmedItem.captions !== undefined))
|
|
959
|
+
setTaskState(deps.db, "finish", projectId, short, "waiting", null);
|
|
960
|
+
if (confirmedItem?.kind === "video" &&
|
|
961
|
+
confirmedItem.pinnedComment !== undefined &&
|
|
962
|
+
readSetting(deps.db, autoCommentKey) === "on")
|
|
963
|
+
setTaskState(deps.db, "comment", projectId, short, "waiting", null);
|
|
964
|
+
}
|
|
945
965
|
function unknownProject(c) {
|
|
946
966
|
return problem(c, {
|
|
947
967
|
status: 404,
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# Slopify 3.11.5
|
|
2
|
+
|
|
3
|
+
Released 6 October 2026.
|
|
4
|
+
|
|
5
|
+
## YouTube Studio extension (1.3.2): uploads Studio scheduled count as uploaded
|
|
6
|
+
|
|
7
|
+
- **An upload whose confirmation was missed is caught up.** The extension marks an upload as on YouTube when Studio's "Video scheduled" or "Video published" message shows. When that was missed (the dialog closed early, or the next short opened), the upload stayed at "Upload again", and its captions, end screen and pinned comment never followed. The Studio check (Check for deleted videos, Read Studio now, or every two hours) now also reads these uploads: one Studio has scheduled or published counts as uploaded and its Details touches follow; a draft stays as it is.
|
|
8
|
+
|
|
9
|
+
Download the extension again from Settings → YouTube Studio and reload it.
|
|
@@ -75,6 +75,10 @@ export function waitingTasks(db, task) {
|
|
|
75
75
|
.map(rowOf);
|
|
76
76
|
}
|
|
77
77
|
// Every video on YouTube, for the numbers the extension reads from Studio.
|
|
78
|
+
// Every video recorded, on YouTube or only started (`filled`), newest first.
|
|
79
|
+
export function recordedVideos(db) {
|
|
80
|
+
return db.prepare("SELECT * FROM youtube_videos ORDER BY recorded_at DESC").all().map(rowOf);
|
|
81
|
+
}
|
|
78
82
|
export function doneVideos(db) {
|
|
79
83
|
return db
|
|
80
84
|
.prepare("SELECT * FROM youtube_videos WHERE upload_state='done' ORDER BY recorded_at DESC")
|