├── screenshots
└── screenshot.png
├── assets
└── images
│ ├── icon_likes.png
│ ├── icon_views.png
│ └── icon_comments.png
├── .travis.yml
├── .babelrc
├── .vscode
└── settings.json
├── index-styles.js
├── package.json
├── README.md
├── .flowconfig
└── index.tsx
/screenshots/screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Kobidl/react-native-embed-instagram/HEAD/screenshots/screenshot.png
--------------------------------------------------------------------------------
/assets/images/icon_likes.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Kobidl/react-native-embed-instagram/HEAD/assets/images/icon_likes.png
--------------------------------------------------------------------------------
/assets/images/icon_views.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Kobidl/react-native-embed-instagram/HEAD/assets/images/icon_views.png
--------------------------------------------------------------------------------
/assets/images/icon_comments.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Kobidl/react-native-embed-instagram/HEAD/assets/images/icon_comments.png
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - "node"
4 |
5 | cache:
6 | yarn: true
7 |
8 | script:
9 | - npm test
10 |
--------------------------------------------------------------------------------
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "plugins": [
3 | "transform-flow-strip-types"
4 | ],
5 | "presets": [
6 | "react-native-stage-0/decorator-support"
7 | ]
8 | }
--------------------------------------------------------------------------------
/.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 | }
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 | 
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 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------