File size: 4,369 Bytes
5bab120
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
import { extract, normalizeURL } from "../url.js";
import { genericUserAgent } from "../../config.js";
import { createStream } from "../../stream/manage.js";
import { getRedirectingURL } from "../../misc/utils.js";

const SPOTLIGHT_VIDEO_REGEX = /<link data-react-helmet="true" rel="preload" href="([^"]+)" as="video"\/>/;
const NEXT_DATA_REGEX = /<script id="__NEXT_DATA__" type="application\/json">({.+})<\/script><\/body><\/html>$/;

async function getSpotlight(id) {
    const html = await fetch(`https://www.snapchat.com/spotlight/${id}`, {
        headers: { 'user-agent': genericUserAgent }
    }).then((r) => r.text()).catch(() => null);

    if (!html) {
        return { error: "fetch.fail" };
    }

    const videoURL = html.match(SPOTLIGHT_VIDEO_REGEX)?.[1];

    if (videoURL && new URL(videoURL).hostname.endsWith(".sc-cdn.net")) {
        return {
            urls: videoURL,
            filename: `snapchat_${id}.mp4`,
            audioFilename: `snapchat_${id}_audio`
        }
    }
}

async function getStory(username, storyId, alwaysProxy) {
    const html = await fetch(
        `https://www.snapchat.com/add/${username}${storyId ? `/${storyId}` : ''}`,
        { headers: { 'user-agent': genericUserAgent } }
    )
    .then((r) => r.text())
    .catch(() => null);

    if (!html) {
        return { error: "fetch.fail" };
    }

    const nextDataString = html.match(NEXT_DATA_REGEX)?.[1];
    if (nextDataString) {
        const data = JSON.parse(nextDataString);
        const storyIdParam = data.query.profileParams[1];

        if (storyIdParam && data.props.pageProps.story) {
            const story = data.props.pageProps.story.snapList.find((snap) => snap.snapId.value === storyIdParam);
            if (story) {
                if (story.snapMediaType === 0) {
                    return {
                        urls: story.snapUrls.mediaUrl,
                        filename: `snapchat_${storyId}.jpg`,
                        isPhoto: true
                    }
                }

                return {
                    urls: story.snapUrls.mediaUrl,
                    filename: `snapchat_${storyId}.mp4`,
                    audioFilename: `snapchat_${storyId}_audio`
                }
            }
        }

        const defaultStory = data.props.pageProps.curatedHighlights[0];
        if (defaultStory) {
            return {
                picker: defaultStory.snapList.map(snap => {
                    const snapType = snap.snapMediaType === 0 ? "photo" : "video";
                    const snapExt = snapType === "video" ? "mp4" : "jpg";
                    let snapUrl = snap.snapUrls.mediaUrl;

                    const proxy = createStream({
                        service: "snapchat",
                        type: "proxy",
                        u: snapUrl,
                        filename: `snapchat_${username}_${snap.timestampInSec.value}.${snapExt}`,
                    });

                    let thumbProxy;
                    if (snapType === "video") thumbProxy = createStream({
                        service: "snapchat",
                        type: "proxy",
                        u: snap.snapUrls.mediaPreviewUrl.value,
                    });

                    if (alwaysProxy) snapUrl = proxy;

                    return {
                        type: snapType,
                        url: snapUrl,
                        thumb: thumbProxy || proxy,
                    }
                })
            }
        }
    }
}

export default async function (obj) {
    let params = obj;
    if (obj.shortLink) {
        const link = await getRedirectingURL(`https://t.snapchat.com/${obj.shortLink}`);

        if (!link?.startsWith('https://www.snapchat.com/')) {
            return { error: "fetch.short_link" };
        }

        const extractResult = extract(normalizeURL(link));
        if (extractResult?.host !== 'snapchat') {
            return { error: "fetch.short_link" };
        }

        params = extractResult.patternMatch;
    }

    if (params.spotlightId) {
        const result = await getSpotlight(params.spotlightId);
        if (result) return result;
    } else if (params.username) {
        const result = await getStory(params.username, params.storyId, obj.alwaysProxy);
        if (result) return result;
    }

    return { error: "fetch.fail" };
}