randytarampi/me

View on GitHub
packages/service/src/lib/sources/instagram/source.js

Summary

Maintainability
F
3 days
Test Coverage
A
100%
import {Gallery, Photo, SizedPhoto} from "@randy.tarampi/js";
import fetch from "isomorphic-fetch";
import _ from "lodash";
import {DateTime} from "luxon";
import {AuthInfo} from "../../authInfo";
import CachedDataSource from "../../cachedDataSource";
import {filterPostForOrderingConditionsInSearchParams} from "../util";
import {InstagramAuthInfo} from "./authInfo";
import Instagram from "./service";
import {type} from "./util";

export class InstagramSource extends CachedDataSource {
    constructor(dataClient, cacheClient, authInfo) {
        authInfo = authInfo || new AuthInfo({token: process.env.INSTAGRAM_ACCESS_TOKEN});
        super(dataClient || new Instagram(authInfo.token), cacheClient);

        this.authInfo = authInfo;
    }

    static get AuthInfoClient() {
        return InstagramAuthInfo;
    }

    static get type() {
        return type;
    }

    get isEnabled() {
        return !!(this.authInfo && this.authInfo.token);
    }

    static instanceToRecord(postJson) {
        const {_ig: igJson} = postJson;

        if (igJson) {
            switch (igJson.media_type) {
                case "CAROUSEL_ALBUM":
                    return InstagramSource._jsonToGallery(postJson);

                case "IMAGE":
                    return InstagramSource._jsonToPhoto(postJson);
            }
        }

        if (!postJson.images) {
            return null;
        }

        return InstagramSource._oldJsonToPhoto(postJson);
    }

    static _oldJsonToPhoto(postJson) {
        const sizedPhotos = Object.keys(postJson.images).map(key => {
            const image = postJson.images[key];
            return SizedPhoto.fromJSON({...image, size: key});
        });

        const biggestOfficialPhoto = _.last(_.sortBy(sizedPhotos, ["width"]));

        return Photo.fromJS({
            raw: postJson,
            id: postJson.id,
            source: InstagramSource.type,
            datePublished: DateTime.fromMillis(parseInt(postJson.created_time, 10) * 1000),
            width: biggestOfficialPhoto.width,
            height: biggestOfficialPhoto.height,
            sizedPhotos,
            sourceUrl: postJson.link,
            title: postJson.location && postJson.location.name,
            body: postJson.caption && postJson.caption.text,
            creator: {
                id: postJson.user.id,
                username: postJson.user.username,
                name: postJson.user.full_name,
                url: `https://www.instagram.com/${postJson.user.username}`,
                image: postJson.user.profile_picture
            },
            tags: postJson.tags,
            locationCreated: postJson.location
                ? {
                    geo: {
                        latitude: postJson.location.latitude,
                        longitude: postJson.location.longitude
                    },
                    address: {
                        streetAddress: postJson.location.street_address
                    },
                    name: postJson.location.name
                }
                : null
        });
    }

    static _jsonToPhoto(postJson) {
        const {_ig: igJson, _graph: graphJson} = postJson;
        const graphJsonImages = graphJson.graphql.shortcode_media.display_resources;
        const sizedPhotos = Object.keys(graphJsonImages).map(key => {
            const {config_width: width, config_height: height, src: url, ...image} = graphJsonImages[key];
            return SizedPhoto.fromJSON({
                width,
                height,
                url,
                ...image,
                size: width
            });
        });

        const biggestOfficialPhoto = _.last(_.sortBy(sizedPhotos, ["width"]));

        return Photo.fromJS({
            raw: postJson,
            id: igJson.id,
            source: InstagramSource.type,
            datePublished: DateTime.fromMillis(parseInt(igJson.created_time, 10) * 1000),
            width: biggestOfficialPhoto.width,
            height: biggestOfficialPhoto.height,
            sizedPhotos,
            sourceUrl: igJson.link,
            title: igJson.location && igJson.location.name,
            body: igJson.caption && igJson.caption.text,
            creator: {
                id: igJson.user.id,
                username: igJson.user.username,
                name: igJson.user.full_name,
                url: `https://www.instagram.com/${igJson.user.username}`,
                image: igJson.user.profile_picture
            },
            tags: igJson.tags,
            locationCreated: igJson.location
                ? {
                    geo: {
                        latitude: igJson.location.latitude,
                        longitude: igJson.location.longitude
                    },
                    address: {
                        streetAddress: igJson.location.street_address
                    },
                    name: igJson.location.name
                }
                : null
        });
    }

    static _jsonToGallery(postJson) {
        const {_ig: igJson, _graph: graphJson} = postJson;

        return Gallery.fromJS({
            raw: postJson,
            id: igJson.id,
            source: InstagramSource.type,
            datePublished: DateTime.fromMillis(parseInt(igJson.created_time, 10) * 1000),
            sourceUrl: igJson.link,
            title: igJson.location && igJson.location.name,
            body: igJson.caption && igJson.caption.text,
            creator: {
                id: igJson.user.id,
                username: igJson.user.username,
                name: igJson.user.full_name,
                url: `https://www.instagram.com/${igJson.user.username}`,
                image: igJson.user.profile_picture
            },
            tags: igJson.tags,
            locationCreated: igJson.location
                ? {
                    geo: {
                        latitude: igJson.location.latitude,
                        longitude: igJson.location.longitude
                    },
                    address: {
                        streetAddress: igJson.location.street_address
                    },
                    name: igJson.location.name
                }
                : null,
            photos: graphJson.graphql.shortcode_media.edge_sidecar_to_children.edges
                .map(edgeJson => InstagramSource._jsonToPhoto({
                    _ig: igJson,
                    _graph: {
                        graphql: {
                            shortcode_media: edgeJson.node
                        }
                    }
                }))
        });
    }

    async allRecordsGetter(searchParams) {
        let posts = await this.recordsGetter(searchParams);

        if (posts.length) {
            const lastPost = posts[posts.length - 1];
            posts = posts.concat(await this.allRecordsGetter(
                searchParams
                    .set("all", true)
                    .set("afterId", lastPost.id)
            ));
        }

        return posts;
    }

    _highResolutionPhotoGetter(igJson) {
        return fetch(`${igJson.permalink}?__a=1`)
            .then(body => body.json())
            .then(graphJson => {
                return {
                    _ig: igJson,
                    _graph: graphJson
                };
            });
    }

    recordsGetter(searchParams) {
        return this.client.userSelfMedia(searchParams.Instagram)
            .then(mediaJson => Promise.all(
                mediaJson.data
                    .filter(postJson => filterPostForOrderingConditionsInSearchParams(this.constructor.instanceToRecord(postJson), searchParams))
                    .map(postJson => postJson && this._highResolutionPhotoGetter(postJson).then(igAndGraphJson => this.constructor.instanceToRecord(igAndGraphJson)))
            ))
            .then(_.compact);
    }

    recordGetter(photoId) {
        return this.client.media(photoId)
            .then(postJson => postJson && postJson.data && this._highResolutionPhotoGetter(postJson.data).then(post => this.constructor.instanceToRecord(post)));
    }
}

export default InstagramSource;