@gentbajko/slopify 3.11.3 → 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.
@@ -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({ videos: z.array(extVideoBody).max(1000) });
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, for the extension to ask Studio whether
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: doneVideos(deps.db).map((video) => ({
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. A slot that has since become another video keeps it.
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 c.req.valid("json").videos) {
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
- return c.json({ forgotten });
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
- if (!confirmUpload(deps.db, projectId, short ?? null, videoId))
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,
@@ -0,0 +1,11 @@
1
+ # Slopify 3.11.4
2
+
3
+ Released 6 October 2026.
4
+
5
+ ## YouTube Studio extension (1.3.1): deleted videos are noticed for real
6
+
7
+ - **The deleted-video check now reads Studio signed in.** In 1.3.0 the extension read each video's page from its own side of the tab, where Studio answered as if signed out, so a video deleted in Studio was never noticed and stayed "On YouTube". It now reads the page as Studio itself does.
8
+ - **Check for deleted videos, whenever you want.** A new button beside the popup's project count checks at once which videos were deleted in Studio, long videos and shorts alike, instead of waiting for the two-hourly check. Read Studio now runs the same check too.
9
+ - **It says what it found.** Each check writes one line to the Studio tab's console: how many videos it checked, how many are there, deleted, missing or unread, and which it forgets.
10
+
11
+ Download the extension again from Settings → YouTube Studio and reload it.
@@ -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.
@@ -1,4 +1,16 @@
1
1
  [
2
+ {
3
+ "id": "3.11.5",
4
+ "title": "Slopify 3.11.5",
5
+ "version": "3.11.5",
6
+ "date": "2026-10-06"
7
+ },
8
+ {
9
+ "id": "3.11.4",
10
+ "title": "Slopify 3.11.4",
11
+ "version": "3.11.4",
12
+ "date": "2026-10-06"
13
+ },
2
14
  {
3
15
  "id": "3.11.3",
4
16
  "title": "Slopify 3.11.3",
@@ -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")
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gentbajko/slopify",
3
- "version": "3.11.3",
3
+ "version": "3.11.5",
4
4
  "description": "Turn a prompt into a finished video, locally, with your own provider keys",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Gent Bajko",
@@ -32,7 +32,7 @@
32
32
  "@fastify/busboy": "^3.2.2",
33
33
  "@hono/node-server": "^2.1.1",
34
34
  "@hono/zod-validator": "^0.9.1",
35
- "@modelcontextprotocol/sdk": "^1.30.1",
35
+ "@modelcontextprotocol/sdk": "^1.32.1",
36
36
  "fflate": "^0.8.3",
37
37
  "ffmpeg-static": "^5.3.0",
38
38
  "hono": "^4.13.7",