├── .babelrc ├── .flowconfig ├── .travis.yml ├── .vscode └── settings.json ├── README.md ├── assets └── images │ ├── icon_comments.png │ ├── icon_likes.png │ └── icon_views.png ├── index-styles.js ├── index.tsx ├── package.json └── screenshots └── screenshot.png /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | "transform-flow-strip-types" 4 | ], 5 | "presets": [ 6 | "react-native-stage-0/decorator-support" 7 | ] 8 | } -------------------------------------------------------------------------------- /.flowconfig: -------------------------------------------------------------------------------- 1 | [ignore] 2 | ; We fork some components by platform 3 | .*/*[.]android.js 4 | 5 | ; Ignore "BUCK" generated dirs 6 | /\.buckd/ 7 | 8 | ; Ignore unexpected extra "@providesModule" 9 | .*/node_modules/.*/node_modules/fbjs/.* 10 | 11 | ; Ignore duplicate module providers 12 | ; For RN Apps installed via npm, "Libraries" folder is inside 13 | ; "node_modules/react-native" but in the source repo it is in the root 14 | .*/Libraries/react-native/React.js 15 | .*/Libraries/react-native/ReactNative.js 16 | 17 | [include] 18 | 19 | [libs] 20 | node_modules/react-native/Libraries/react-native/react-native-interface.js 21 | node_modules/react-native/flow 22 | flow/ 23 | 24 | [options] 25 | module.system=haste 26 | 27 | experimental.strict_type_args=true 28 | 29 | munge_underscores=true 30 | 31 | module.name_mapper='^[./a-zA-Z0-9$_-]+\.\(bmp\|gif\|jpg\|jpeg\|png\|psd\|svg\|webp\|m4v\|mov\|mp4\|mpeg\|mpg\|webm\|aac\|aiff\|caf\|m4a\|mp3\|wav\|html\|pdf\)$' -> 'RelativeImageStub' 32 | 33 | suppress_type=$FlowIssue 34 | suppress_type=$FlowFixMe 35 | suppress_type=$FixMe 36 | 37 | suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(>=0\\.\\(3[0-7]\\|[1-2][0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\) 38 | suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(>=0\\.\\(3[0-7]\\|1[0-9]\\|[1-2][0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)?:? #[0-9]+ 39 | suppress_comment=\\(.\\|\n\\)*\\$FlowFixedInNextDeploy 40 | 41 | unsafe.enable_getters_and_setters=true 42 | 43 | [version] 44 | ^0.41.0 45 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "node" 4 | 5 | cache: 6 | yarn: true 7 | 8 | script: 9 | - npm test 10 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | // Place your settings in this file to overwrite default and user settings. 2 | { 3 | // Columns at which to show vertical rulers 4 | "editor.rulers": [ 5 | 80, 6 | 120 7 | ], 8 | "editor.renderWhitespace": "all", 9 | "javascript.validate.enable": false, 10 | "javascript.format.enable": false, 11 | "editor.formatOnSave": true, 12 | "prettier.printWidth": 80, 13 | "prettier.tabWidth": 2, 14 | "prettier.parser": "flow", 15 | "prettier.trailingComma": "all", 16 | "prettier.singleQuote": true, 17 | "flow.useNPMPackagedFlow": true, 18 | "flow.enabled": true, 19 | "flowide.useCodeSnippetsOnFunctionSuggest": true 20 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # react-native-embed-instagram 2 | 3 | Instagram Embed for React Native 4 | 5 | ## What is it for? 6 | Simple as that - we needed Instagram embeds in our application, and couldn't find anything decent on the internet. 7 | 8 | Feel free to contribute. 9 | 10 | ## How does it work? 11 | 12 | Use it to display standard Instagram embed "natively" (without WebView). 13 | 14 | ![Screenshot](https://github.com/Kobidl/react-native-embed-instagram/raw/master/screenshots/screenshot.png) 15 | 16 | ## Installation 17 | 18 | ### Installation 19 | 20 | ``` 21 | npm i --save react-native-embed-instagram 22 | ``` 23 | 24 | Example: 25 | 26 | ``` 27 | import InstagramEmbed from 'react-native-embed-instagram' 28 | 29 | 30 | 31 | ``` 32 | 33 | 34 | #### Config 35 | 36 | Property | Type | Default | Description 37 | --- | --- | --- | --- 38 | id | string | "" |The ID of the post 39 | style | object | {} | The container Style 40 | showAvatar | boolean | true | Show the author details 41 | showCaption | boolean | true | Show the post caption 42 | showStats | boolean | true | Show the post stats 43 | avatarStyle | object | {} | Avatar style 44 | nameStyle | object | {} | Author username style 45 | thumbnailStyle | object | {} | Thumbnail style 46 | renderCaption | fun | null | Render caption function 47 | 48 | 49 | ## License 50 | MIT. 51 | -------------------------------------------------------------------------------- /assets/images/icon_comments.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kobidl/react-native-embed-instagram/ec037852e7120347e49e708ff9b4763dd171bb02/assets/images/icon_comments.png -------------------------------------------------------------------------------- /assets/images/icon_likes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kobidl/react-native-embed-instagram/ec037852e7120347e49e708ff9b4763dd171bb02/assets/images/icon_likes.png -------------------------------------------------------------------------------- /assets/images/icon_views.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kobidl/react-native-embed-instagram/ec037852e7120347e49e708ff9b4763dd171bb02/assets/images/icon_views.png -------------------------------------------------------------------------------- /index-styles.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Instagram Embed component for React Native 3 | */ 4 | 5 | import React, { PureComponent } from 'react'; 6 | import { StyleSheet } from 'react-native'; 7 | 8 | export default StyleSheet.create({ 9 | container: { 10 | backgroundColor: 'white', 11 | borderColor: '#cccccc', 12 | borderWidth: 1, 13 | borderRadius: 3, 14 | }, 15 | headerContainer: { flexDirection: 'row', margin: 8, alignItems: 'center' }, 16 | avatar: { 17 | width: 40, 18 | height: 40, 19 | borderRadius: 20, 20 | borderWidth: 1, 21 | borderColor: '#cccccc', 22 | }, 23 | author: { marginLeft: 8, fontWeight: '500' }, 24 | titleContainer: { flexDirection: 'row', margin: 8 }, 25 | statsContainer: { 26 | flexDirection: 'row', 27 | marginVertical: 6, 28 | alignItems: 'center', 29 | }, 30 | statIcon: { width: 16, height: 14 }, 31 | statLabel: { fontWeight: '500', marginHorizontal: 8 }, 32 | }); 33 | -------------------------------------------------------------------------------- /index.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * Instagram Embed component for React Native 3 | */ 4 | 5 | import React from 'react'; 6 | import { View, Image, Text, ViewStyle, ImageStyle, TextStyle } from 'react-native'; 7 | 8 | import styles from './index-styles'; 9 | 10 | interface Props { 11 | id: string, 12 | style: ViewStyle, 13 | showAvatar: boolean, 14 | showCaption: boolean, 15 | showStats: boolean, 16 | avatarStyle: ImageStyle, 17 | nameStyle: TextStyle, 18 | thumbnailStyle: ImageStyle, 19 | renderCaption: (text: string) => {}, 20 | } 21 | 22 | interface State { 23 | response: any, 24 | height: number, 25 | width: number, 26 | avatar: string, 27 | likes: number | string, 28 | comments: number | string, 29 | thumbnail: string, 30 | } 31 | 32 | export default class InstagramEmbed extends React.Component { 33 | 34 | static defaultProps = { 35 | showAvatar: false, 36 | showCaption: false, 37 | showStats: false, 38 | style: {} 39 | } 40 | 41 | constructor(props) { 42 | super(props); 43 | this.state = { 44 | response: null, 45 | height: 240, 46 | width: 320, 47 | avatar: null, 48 | likes: 0, 49 | comments: 0, 50 | thumbnail: null, 51 | }; 52 | } 53 | 54 | _onLayout = layout => { 55 | this.setState({ 56 | height: layout.nativeEvent.layout.height, 57 | width: layout.nativeEvent.layout.width, 58 | }); 59 | }; 60 | 61 | /* 62 | * This is fairly experimental and probably not the best way to supplement 63 | * existing API (official) data with missing properties we need. 64 | */ 65 | _fetchComplementaryData = id => { 66 | 67 | if (!id) { 68 | return; 69 | } 70 | 71 | fetch(`https://www.instagram.com/p/${id}/embed/captioned/`) 72 | .then(response => response.text()) 73 | .then(responseText => { 74 | // The image from there not working ATM 75 | // let avatarUrl = ""; 76 | // let avatarRegex = /class=\"Avatar\"[^>]*>.*/s; 77 | // let avatarMatch = avatarRegex.exec(responseText); 78 | // if (avatarMatch && avatarMatch.length > 0) { 79 | // avatarUrl = avatarMatch[1]; 80 | // } else { 81 | // try { 82 | // avatarRegex = /class=\"Avatar InsideRing\"[^>]*>.*/s; 83 | // avatarMatch = avatarRegex.exec(responseText); 84 | // if (avatarMatch) { 85 | // avatarUrl = avatarMatch[0]; 86 | // avatarUrl = avatarUrl.substring(avatarUrl.indexOf("src=") + 5); 87 | // avatarUrl = avatarUrl.substring(0, avatarUrl.indexOf("\"")); 88 | // } 89 | // } catch (e) { } 90 | // } 91 | 92 | let likesRegex = /class=\"SocialProof\">[^>]*>([^l]*)/s; 93 | let likesMatch = likesRegex.exec(responseText); 94 | 95 | // let viewsRegex = /class=\"SocialProof\">[^>]*>([^l]*)/s; 96 | // let viewsMatch = viewsRegex.exec(responseText); 97 | 98 | let commentsRegex = /class=\"CaptionComments\">[^>]*>([^c]*)/s; 99 | let commentsMatch = commentsRegex.exec(responseText); 100 | 101 | let thumbnailRegex = /class=\"EmbeddedMediaImage\"[^>]*>.*/s; 102 | let thumbnailMatch = thumbnailRegex.exec(responseText); 103 | let thumbnailUrl = ""; 104 | if (thumbnailMatch) { 105 | try { 106 | thumbnailUrl = thumbnailMatch[0]; 107 | thumbnailUrl = thumbnailUrl.substring(thumbnailUrl.indexOf("src") + 5, thumbnailUrl.indexOf(">")); 108 | let thumbnailUrls = thumbnailUrl.split(' '); 109 | thumbnailUrl = thumbnailUrls[0].replace('"', ''); 110 | } catch (e) { } 111 | } 112 | 113 | this.setState({ 114 | thumbnail: thumbnailUrl ? thumbnailUrl : null, 115 | // avatar: avatarUrl ? avatarUrl : null, 116 | likes: likesMatch ? likesMatch[1].trim() : null, 117 | // views: viewsMatch ? viewsMatch[1] : null, 118 | comments: commentsMatch ? commentsMatch[1].replace("view all", "").trim() : null, 119 | }); 120 | }) 121 | .catch(error => { }); 122 | }; 123 | 124 | _fetchAvatar = (url) => { 125 | fetch(url).then(r => r.text()).then(r => { 126 | const property = 'og:image" content="'; 127 | const propertyIndex = r.indexOf(property); 128 | if (propertyIndex > -1) { 129 | let avatarUrl = r.substring(r.indexOf(property) + property.length); 130 | avatarUrl = avatarUrl.substring(0, avatarUrl.indexOf('"')); 131 | if (avatarUrl) { 132 | this.setState({ avatar: avatarUrl }); 133 | } 134 | } 135 | }); 136 | } 137 | 138 | componentDidMount = () => { 139 | const { id } = this.props; 140 | fetch(`https://api.instagram.com/oembed/?url=http://instagr.am/p/${id}/`) 141 | .then(response => response.json()) 142 | .then(responseJson => { 143 | if (this.props.showStats) 144 | this._fetchComplementaryData(id); 145 | if (this.props.showAvatar) 146 | this._fetchAvatar(responseJson.author_url); 147 | this.setState({ response: responseJson }); 148 | }) 149 | .catch(error => { 150 | this.setState({ response: null }); 151 | }); 152 | }; 153 | 154 | render() { 155 | let { style, showAvatar, showStats, avatarStyle, nameStyle, thumbnailStyle } = this.props; 156 | const { 157 | response, 158 | height, 159 | width, 160 | avatar, 161 | likes, 162 | comments, 163 | thumbnail 164 | } = this.state; 165 | 166 | if (!response) { 167 | return ; 168 | } 169 | 170 | return ( 171 | 180 | 181 | {showAvatar && 182 | {avatar && ( 183 | 189 | )} 190 | {response.author_name} 191 | } 192 | {(response.thumbnail_url || thumbnail) ? ( 193 | 200 | ) : <>} 201 | 202 | {showStats && 203 | {/* {!!views && ( 204 | 205 | 209 | {views} views 210 | 211 | )} */} 212 | {!!likes && ( 213 | 214 | 218 | {likes} likes 219 | 220 | )} 221 | {!!comments && ( 222 | 223 | 227 | {comments} comments 228 | 229 | )} 230 | } 231 | {this.renderCaption()} 232 | 233 | 234 | 235 | ); 236 | } 237 | 238 | renderCaption = () => { 239 | if (!this.props.showCaption || !this.state.response) return null; 240 | if (this.props.renderCaption) this.props.renderCaption(this.state.response.title); 241 | return ( 242 | {this.state.response.title} 243 | ) 244 | } 245 | } 246 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-native-embed-instagram", 3 | "version": "0.1.1", 4 | "keywords": [ 5 | "react", 6 | "instagram", 7 | "embed" 8 | ], 9 | "homepage": "https://github.com/Kobidl/react-native-embed-instagram", 10 | "repository": { 11 | "type": "git", 12 | "url": "https://github.com/Kobidl/react-native-embed-instagram.git" 13 | }, 14 | "private": false, 15 | "scripts": { 16 | "test": "jest --coverage --forceExit", 17 | "flow": "./node_modules/.bin/flow; test $? -eq 0 -o $? -eq 2" 18 | }, 19 | "dependencies": {}, 20 | "devDependencies": { 21 | "babel-jest": "^19.0.0", 22 | "babel-plugin-transform-flow-strip-types": "^6.22.0", 23 | "babel-preset-react-native-stage-0": "^1.0.1", 24 | "chai": "^3.5.0", 25 | "flow-bin": "^0.42.0", 26 | "imagemagick": "^0.1.3", 27 | "jest": "^19.0.2", 28 | "jest-cli": "^19.0.2", 29 | "react-test-renderer": "~15.4.0" 30 | }, 31 | "jest": { 32 | "verbose": true, 33 | "testRegex": "/__tests__/(.*)test_([a-z0-9_.-]+)\\.js$" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /screenshots/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kobidl/react-native-embed-instagram/ec037852e7120347e49e708ff9b4763dd171bb02/screenshots/screenshot.png --------------------------------------------------------------------------------