You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

自动下载Snapchat回忆的JS脚本报错求助:语法错误800A3EA

Snapchat回忆下载JS脚本报错排查与修复

问题重现

两年前从GitHub获取的Snapchat回忆自动下载脚本,此前运行正常,现突然失效,报错信息:

Error:
Line: 2
Char: 1
Error: Syntax error
Code: 800A3EA

无论调整代码顺序,始终提示首行首字符语法错误,原脚本代码如下:

// IMPORTS
const https = require("https");
const fs = require("fs");
const Queue = require("./concurrency.js");
const Progress = require("./progress.js");
const moment = require("moment");
const utimes = require("utimes").utimes;
const { Command } = require("commander");

// PARSE ARGUMENTS
const program = new Command();
program
    .description("A script to download Snapchat Memories

Example:
  node main.js -c 50 -f ./json/memories_history.json -o Downloads")
    .option("-c <number>", "Number of concurrent downloads", 30)
    .option("-f <path>", "Filepath to memories_history.json", "./json/memories_history.json")
    .option("-o <directory>", "Download directory", "Downloads");
program.parse();
const options = program.opts();

// CONFIG
const outputDir = options.o;
const maxConcurrentDownloads = options.c;
const jsonFile = (!options.f.startsWith("/") && !options.f.startsWith("./")) ? "./" + options.f : options.f;
const progressBarLength = 20;

// INIT
if (!jsonFile.startsWith("/") && !jsonFile.startsWith("./"))
    jsonFile = "./" + jsonFile
var downloads = require("./" + jsonFile)["Saved Media"];
var queue = new Queue(maxConcurrentDownloads);
var progress = new Progress(downloads.length, progressBarLength);

function main() {

    // Create download directory
    if (!fs.existsSync(outputDir)) fs.mkdirSync(outputDir);

    // Start downloads
    for (let i = 0; i < downloads.length; i++) {

        let [url, body] = downloads[i]["Download Link"].split('?', 2);
        const fileTime = moment.utc(downloads[i]["Date"], "YYYY-MM-DD HH:mm:ss Z")
        let fileName = getFileName(downloads[i], fileTime);

        // First get CDN download link
        queue.enqueue(() => getDownloadLink(url, body, fileName, fileTime))
            .then((result) => {
                progress.cdnLinkSucceeded(true);

                // Download the file
                queue.enqueue(() => downloadMemory(result[0], result[1], result[2]))
                    .then((success) => {
                        progress.downloadSucceeded(true);
                    })
                    .catch((err) => {
                        progress.downloadSucceeded(false);
                    })
            })
            .catch((err) => {
                progress.cdnLinkSucceeded(false);
            })
    }
}

function getFileName(download, fileTime) {
    var filename = fileTime.format("YYYY-MM-DD_HH-mm-ss")

    if (download["Media Type"] == "PHOTO")
        filename += ".jpg";
    else if (download["Media Type"] == "VIDEO")
        filename += ".mp4";

    return filename
}

const getDownloadLink = (url, body, filename, fileTime) => new Promise(resolve => {
    var parsedUrl = new URL(url);

    const options = {
        hostname: parsedUrl.hostname,
        path: parsedUrl.pathname,
        method: "POST",
        headers: {
            "Content-Type": "application/x-www-form-urlencoded",
        },
    };

    var req = https.request(options, (res) => {

        data = "";
        res.on("data", (chunk) => {
            data += chunk;
        });
        res.on("error", function () {
            reject("request error");
        })
        res.on("end", function () {
            if (res.statusCode == 200) {
                resolve([data, filename, fileTime]);
            }
            else {
                reject("status error");
            }
        });
    });
    req.write(body);
    req.end();
});


const downloadMemory = (downloadUrl, filename, fileTime) => new Promise(resolve => {

    // Check if there already exists a file with the same name/timestamp
    if (fs.existsSync(outputDir + "/" + filename)) {
        duplicates = 1;
        while (true) {
            var extensionPos = filename.lastIndexOf('.');
            var newFilename = filename.substring(0, extensionPos) + " (" + duplicates + ")" + filename.substring(extensionPos, filename.length);
            if (fs.existsSync(outputDir + "/" + newFilename))
                duplicates++;
            else {
                filename = newFilename;
                break;
            }
        }
    }

    // Create the file and write to it
    var file = fs.createWriteStream(outputDir + "/" + filename);

    https.get(downloadUrl, function (res) {
        res.pipe(file);
        res.on("error", function () {
            reject("request error");
        })
        res.on("end", function () {
            file.close();
            if (res.statusCode == 200) {
                // Update the file creation date
                utimes(file.path, {
                    btime: fileTime.valueOf(), // birthtime (Windows & Mac)
                    mtime: fileTime.valueOf() // modified time (Windows, Mac, Linux)
                }).then(() => {
                    resolve(true)
                }).catch(err => {
                    reject("failed to set file creation date:", err)
                })
            }
            else {
                reject("status error");
            }
        });
    });
});

main();


// Don"t look at this
process.on("uncaughtException", function (err) {
    if (err.code == "ECONNRESET")
        progress.downloadSucceeded(false);
});

错误原因

  1. 运行环境错误:错误代码800A3EA是Windows脚本宿主(WSH)的语法错误,说明你用Windows默认的WScript/cscript执行Node.js脚本,而非Node.js运行环境。WSH无法识别const、require等Node.js语法,因此报首行错误。
  2. 脚本自身语法问题:即使改用Node.js运行,原脚本也存在多处错误:
    • description方法的字符串包含换行,但未使用模板字符串包裹,导致语法错误
    • 两个Promise函数中使用reject但未在构造函数中声明该参数
    • 多个变量未用let/const声明,会成为全局变量
    • jsonFile被声明为const却被重新赋值,违反常量规则

修复步骤

1. 正确运行脚本

打开命令提示符,进入脚本所在目录,执行:

node main.js

禁止直接双击脚本,否则会默认用WSH打开。

2. 修正脚本语法问题

以下是修复后的完整代码:

// IMPORTS
const https = require("https");
const fs = require("fs");
const Queue = require("./concurrency.js");
const Progress = require("./progress.js");
const moment = require("moment");
const utimes = require("utimes").utimes;
const { Command } = require("commander");

// PARSE ARGUMENTS
const program = new Command();
program
    .description(`A script to download Snapchat Memories

Example:
  node main.js -c 50 -f ./json/memories_history.json -o Downloads`)
    .option("-c <number>", "Number of concurrent downloads", 30)
    .option("-f <path>", "Filepath to memories_history.json", "./json/memories_history.json")
    .option("-o <directory>", "Download directory", "Downloads");
program.parse();
const options = program.opts();

// CONFIG
const outputDir = options.o;
const maxConcurrentDownloads = options.c;
let jsonFile = (!options.f.startsWith("/") && !options.f.startsWith("./")) ? "./" + options.f : options.f;
const progressBarLength = 20;

// INIT
if (!jsonFile.startsWith("/") && !jsonFile.startsWith("./"))
    jsonFile = "./" + jsonFile;
const downloads = require(jsonFile)["Saved Media"];
const queue = new Queue(maxConcurrentDownloads);
const progress = new Progress(downloads.length, progressBarLength);

function main() {
    // Create download directory
    if (!fs.existsSync(outputDir)) fs.mkdirSync(outputDir);

    // Start downloads
    for (let i = 0; i < downloads.length; i++) {
        const [url, body] = downloads[i]["Download Link"].split('?', 2);
        const fileTime = moment.utc(downloads[i]["Date"], "YYYY-MM-DD HH:mm:ss Z");
        const fileName = getFileName(downloads[i], fileTime);

        // First get CDN download link
        queue.enqueue(() => getDownloadLink(url, body, fileName, fileTime))
            .then((result) => {
                progress.cdnLinkSucceeded(true);

                // Download the file
                queue.enqueue(() => downloadMemory(result[0], result[1], result[2]))
                    .then(() => progress.downloadSucceeded(true))
                    .catch(() => progress.downloadSucceeded(false));
            })
            .catch(() => progress.cdnLinkSucceeded(false));
    }
}

function getFileName(download, fileTime) {
    let filename = fileTime.format("YYYY-MM-DD_HH-mm-ss");

    if (download["Media Type"] === "PHOTO")
        filename += ".jpg";
    else if (download["Media Type"] === "VIDEO")
        filename += ".mp4";

    return filename;
}

const getDownloadLink = (url, body, filename, fileTime) => new Promise((resolve, reject) => {
    const parsedUrl = new URL(url);

    const options = {
        hostname: parsedUrl.hostname,
        path: parsedUrl.pathname,
        method: "POST",
        headers: {
            "Content-Type": "application/x-www-form-urlencoded",
        },
    };

    const req = https.request(options, (res) => {
        let data = "";
        res.on("data", (chunk) => {
            data += chunk;
        });
        res.on("error", () => reject("request error"));
        res.on("end", () => {
            if (res.statusCode === 200) {
                resolve([data, filename, fileTime]);
            } else {
                reject("status error");
            }
        });
    });
    req.write(body);
    req.end();
});

const downloadMemory = (downloadUrl, filename, fileTime) => new Promise((resolve, reject) => {
    // Check if there already exists a file with the same name/timestamp
    if (fs.existsSync(`${outputDir}/${filename}`)) {
        let duplicates = 1;
        while (true) {
            const extensionPos = filename.lastIndexOf('.');
            const newFilename = `${filename.substring(0, extensionPos)} (${duplicates})${filename.substring(extensionPos)}`;
            if (fs.existsSync(`${outputDir}/${newFilename}`)) {
                duplicates++;
            } else {
                filename = newFilename;
                break;
            }
        }
    }

    // Create the file and write to it
    const file = fs.createWriteStream(`${outputDir}/${filename}`);

    https.get(downloadUrl, (res) => {
        res.pipe(file);
        res.on("error", () => reject("request error"));
        res.on("end", () => {
            file.close();
            if (res.statusCode === 200) {
                // Update the file creation date
                utimes(file.path, {
                    btime: fileTime.valueOf(), // birthtime (Windows & Mac)
                    mtime: fileTime.valueOf() // modified time (Windows, Mac, Linux)
                }).then(() => resolve(true))
                  .catch(err => reject(`failed to set file creation date: ${err}`));
            } else {
                reject("status error");
            }
        });
    });
});

main();

// Don't look at this
process.on("uncaughtException", (err) => {
    if (err.code === "ECONNRESET")
        progress.downloadSucceeded(false);
});

额外说明

  • 确保已安装所有依赖包,执行npm install commander moment utimes(核心依赖)
  • 确认concurrency.js和progress.js文件存在于脚本同目录下

内容的提问来源于stack exchange,提问作者Fygar

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.22 22:47:02