Skip to content

Commit 831b225

Browse files
authored
Merge pull request #1 from Ringrev/main
update from stream
2 parents 97ba9c5 + 5f78b7e commit 831b225

File tree

11 files changed

+623
-129
lines changed

11 files changed

+623
-129
lines changed

examples/seed/src/instagram.rs

Lines changed: 210 additions & 61 deletions
Large diffs are not rendered by default.

examples/seed/src/lib.rs

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,8 @@ enum Msg {
7575
ConfigFetched(fetch::Result<Config>),
7676
GetAccount,
7777
GetAccountSuccess(Data<Accounts>),
78-
78+
GetMeDetails,
79+
GetMeDetailsSuccess(Me),
7980
AccessTokenInformation,
8081
AccessTokenInfoData(AccessTokenInformation),
8182

@@ -95,6 +96,7 @@ fn update(msg: Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
9596
Msg::ConfigFetched(Err(fetch_error)) => error!("Config fetch failed! Be sure to have config.json at the root of your project with client_id and redirect_uri", fetch_error),
9697

9798
Msg::GetAccount => {
99+
orders.send_msg(Msg::GetMeDetails);
98100
if let Some(user_access_tokens) = model.user_tokens.clone() {
99101
let user_tokens = user_access_tokens;
100102
let client = Client::new(user_tokens, "".to_string());
@@ -111,6 +113,26 @@ fn update(msg: Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
111113
}
112114
}
113115

116+
Msg::GetMeDetails => {
117+
if let Some(user_access_tokens) = model.user_tokens.clone() {
118+
let user_tokens = user_access_tokens;
119+
let user_token = user_tokens.long_lived_token.clone();
120+
let client = Client::new(user_tokens, "".to_string());
121+
orders.perform_cmd(async {
122+
// we are interested in the page long live token, therefore we called the long
123+
// live methed by passing "long_live_token" to the method
124+
client
125+
.me_by_short_or_long_live_token("short_live".to_string())
126+
.details()
127+
.await
128+
.map_or_else(Msg::ResponseFailed, Msg::GetMeDetailsSuccess)
129+
});
130+
}
131+
}
132+
Msg:: GetMeDetailsSuccess(resp) => {
133+
134+
}
135+
114136
Msg:: GetAccountSuccess(accounts) => {
115137
model.accounts = Some(accounts.clone());
116138
model.facebook.accounts= Some(accounts.clone());

src/graph/client.rs

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ use crate::prelude::search::PagesSearchAPI;
1111
use crate::prelude::video::VideoApi;
1212
use seed::{prelude::*, *};
1313
use std::option::Option::Some;
14+
use crate::graph::instagram::media::InstagramMediaApi;
1415

1516
/// Client Struct for making calls to Facebook Graph
1617
#[derive(Debug)]
@@ -30,6 +31,7 @@ pub struct Client {
3031
impl Default for Client {
3132
fn default() -> Self {
3233
let graph = "https://graph.facebook.com/v11.0/NODE/EDGE".to_string();
34+
// let insta_graph = "https://graph.instagram.com/v11.0/NODE/EDGE".to_string();
3335

3436
Self {
3537
graph,
@@ -70,6 +72,10 @@ impl Client {
7072
self
7173
}
7274

75+
pub fn base_url (self) -> String{
76+
self.graph
77+
}
78+
7379
/// This method is used to pass user data/crediteniatls to the ME method
7480
/// which will be used to reach the ME API.
7581
/// since facebbok allows two access token ( short and long live ) to used
@@ -126,12 +132,18 @@ impl Client {
126132
InstagramApi::new(self.page_access_token, base_url)
127133
}
128134

129-
pub fn instagram(self, instagarm_id: String) -> InstagramPostApi {
130-
let base_url = self.graph.replace("NODE", &instagarm_id);
135+
pub fn instagram_publish(self, instagram_id: String) -> InstagramPostApi {
136+
let base_url = self.graph.replace("NODE", &instagram_id);
131137

132138
InstagramPostApi::new(self.page_access_token, base_url)
133139
}
134140

141+
pub fn instagram_media_container(self, media_container_id: String) -> InstagramMediaApi{
142+
let base_url = self.graph.replace("NODE", &media_container_id);
143+
144+
InstagramMediaApi::new(self.page_access_token, base_url)
145+
}
146+
135147
pub fn search_pages(self) -> PagesSearchAPI {
136148
let base_url = self.graph.replace("NODE/EDGE", "pages/search");
137149
PagesSearchAPI::new(base_url, self.page_access_token)

src/graph/instagram/account/mod.rs

Lines changed: 102 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
1+
//! This Api is used to get instagram business account for any given facebook page
2+
//! <https://developers.facebook.com/docs/instagram-api/reference/page>
3+
14
use seed::fetch::fetch;
25
use seed::prelude::{Method, Request};
3-
46
use seed::{prelude::*, *};
57
use serde::{Deserialize, Serialize};
8+
use seed::prelude::js_sys::encode_uri;
9+
use urlencoding::encode;
610

711
#[derive(Deserialize, Clone, Debug, Serialize)]
812
pub struct InstaAccount {
@@ -16,11 +20,13 @@ pub struct InstaAccountId {
1620
pub id: String,
1721
}
1822

23+
#[derive( Clone)]
1924
pub struct InstagramApi {
2025
page_access_token: String,
2126
base_url: String,
2227
}
2328

29+
2430
impl InstagramApi {
2531
pub fn new(page_access_token: String, base_url: String) -> InstagramApi {
2632
InstagramApi {
@@ -29,7 +35,9 @@ impl InstagramApi {
2935
}
3036
}
3137

32-
pub async fn insta_account(self) -> seed::fetch::Result<InstaAccount> {
38+
/// This method is use to get instagram business account id.
39+
/// for reference check <https://developers.facebook.com/docs/instagram-api/reference/page>
40+
pub async fn account_id(self) -> seed::fetch::Result<InstaAccount> {
3341
let base_url = self.base_url.replace("EDGE", "?");
3442
let url = base_url
3543
+ "fields=instagram_business_account"
@@ -39,4 +47,96 @@ impl InstagramApi {
3947
let request = Request::new(url).method(Method::Get);
4048
fetch(request).await?.json::<InstaAccount>().await
4149
}
50+
51+
/// This method is used to get instagram business account with its details (name, user, id ,etc).
52+
/// It accepts the instagram page id.
53+
/// for reference check <https://developers.facebook.com/docs/instagram-api/reference/ig-user>
54+
pub async fn account_details(self) -> seed::fetch::Result<InstagramAccount> {
55+
let mut url = self.base_url.replace("EDGE", "?");
56+
let url_fields = Fields::default().build_url_with_fields(); // build urlenconded url withe regired fields
57+
58+
let mut request_url = url
59+
+ "fields="
60+
+url_fields.as_str()
61+
+ "&access_token="
62+
+ &self.page_access_token;
63+
let request = Request::new(request_url).method(Method::Get);
64+
fetch(request).await?.json::<InstagramAccount>().await
65+
}
4266
}
67+
68+
69+
#[derive(Deserialize, Clone, Debug, Serialize)]
70+
pub struct InstagramAccount {
71+
//https://developers.facebook.com/docs/instagram-api/reference/ig-user/
72+
//This is a public fields, which means it can be returned if available.
73+
// biography: String,
74+
75+
///This is a public fields, which means it can be returned if available.
76+
pub id: String,
77+
78+
///This is not a public fields, which means it may be returned depending on the user setting.
79+
pub ig_id: u32,
80+
81+
///This is a public fields, which means it can be returned if available.
82+
pub followers_count: u32,
83+
84+
///This is not a public fields, which means it may be returned depending on the user setting.
85+
pub follows_count : u32,
86+
87+
///This is not a public fields, which means it may be returned depending on the user setting.
88+
pub media_count: u32,
89+
90+
///This is not a public fields, which means it may be returned depending on the user setting.
91+
pub name: String,
92+
93+
//This is not a public fields, which means it may be returned depending on the user setting.
94+
// profile_picture_url: String,
95+
96+
///This is not a public fields, which means it may be returned depending on the user setting.
97+
pub username : String,
98+
99+
//This is not a public fields, which means it may be returned depending on the user setting.
100+
// website : String,
101+
}
102+
pub struct Fields {
103+
pub(crate) fields: Vec<String>,
104+
}
105+
106+
impl Default for Fields {
107+
/// These parameters are used as fields which are passed in as a query
108+
/// parameters to the get post request and feeds request
109+
fn default() -> Self {
110+
let field_list = vec![
111+
"biography",
112+
"id",
113+
"ig_id",
114+
"followers_count",
115+
"follows_count",
116+
"media_count",
117+
"name",
118+
"profile_picture_url",
119+
"username",
120+
"website",
121+
];
122+
123+
let fields = field_list.iter().map(|&field| field.into()).collect();
124+
Self { fields }
125+
}
126+
}
127+
128+
impl Fields {
129+
pub fn build_url_with_fields(self) -> String {
130+
let mut url_fields= "".to_string();
131+
let fields_count = Fields::default().fields.len();
132+
for (count, field) in Fields::default().fields.into_iter().enumerate() {
133+
if count < fields_count - 1 {
134+
url_fields = url_fields+ &field + ",";
135+
} else {
136+
url_fields= url_fields+ &field; // remove the comma in the last filed
137+
}
138+
}
139+
url_fields = String::from(encode(url_fields.as_str())); // encode the url
140+
url_fields
141+
}
142+
}

src/graph/instagram/media/mod.rs

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
//! This mode Represents Instagram api for Photo, Video, Story, Album, or Instagram TV media. Reels are not supported.
2+
//! It allow´ you to get media details ( comments, like, etc).
3+
//! for details check <https://developers.facebook.com/docs/instagram-api/reference/ig-media>.
4+
5+
use seed::fetch::fetch;
6+
use seed::prelude::{Method, Request};
7+
use seed::{prelude::*, *};
8+
use serde::{Deserialize, Serialize};
9+
use urlencoding::encode;
10+
use std::{thread, time};
11+
use crate::graph::client::Client;
12+
13+
#[derive(Deserialize, Clone, Serialize)]
14+
pub struct InstagramMediaApi {
15+
access_token: String,
16+
base_url: String,
17+
}
18+
19+
impl InstagramMediaApi {
20+
pub fn new(access_token: String, base_url: String) -> InstagramMediaApi {
21+
InstagramMediaApi {
22+
access_token,
23+
base_url,
24+
}
25+
}
26+
/// this method allow´s you to post on a give media container.
27+
// for details check <https://developers.facebook.com/docs/instagram-api/reference/ig-media/comments>
28+
pub async fn post_comments(
29+
self,
30+
comment_message:String,
31+
) -> seed::fetch::Result<InstaMediaContainerId> {
32+
let self_data = self.clone();
33+
let base_url = self.base_url.replace("EDGE", "comments");
34+
let comment = comment_message ;
35+
let message = encode(&comment);
36+
let url = base_url.to_string()
37+
+ "?message="
38+
+ (&message).as_ref()
39+
+ "&access_token="
40+
+ &self_data.access_token;
41+
let request = Request::new(url).method(Method::Post);
42+
fetch(request).await?.json::<InstaMediaContainerId>().await
43+
}
44+
45+
pub async fn data(self) -> seed::fetch::Result<MediaContainerData> {
46+
let mut url = self.base_url.replace("EDGE", "?fields=");
47+
48+
let mut fields_count = Fields::default().fields.len();
49+
for (count, field) in Fields::default().fields.into_iter().enumerate() {
50+
if count < fields_count - 1 {
51+
url = url+ &field + ",";
52+
} else {
53+
url= url+ &field; // remove the comma in the last filed
54+
}
55+
}
56+
url = url
57+
+ "&access_token="
58+
+ &self.access_token;
59+
60+
61+
let request = Request::new(url).method(Method::Get);
62+
fetch(request).await?.json::<MediaContainerData>().await
63+
}
64+
65+
/// This method allows you to check the status for a given media, this is important to check before
66+
/// calling the publish_media method.
67+
/// for details check <https://developers.facebook.com/docs/instagram-api/reference/ig-container#reading>
68+
pub async fn status(self)
69+
-> seed::fetch::Result<MediaContainerStatus> {
70+
let base_url = self.base_url.replace("EDGE", "?fields=status_code");
71+
let url = base_url
72+
+ "&access_token="
73+
+ &self.access_token;
74+
75+
let request = Request::new(url).method(Method::Get);
76+
fetch(request).await?.json::<MediaContainerStatus>().await
77+
}
78+
}
79+
80+
#[derive(Deserialize, Debug, Clone, Serialize)]
81+
pub struct MediaContainerStatus {
82+
pub status_code:String
83+
}
84+
85+
#[derive(Deserialize, Debug, Clone, Default, Serialize)]
86+
pub struct MediaContainerData {
87+
media_type:String,
88+
media_url: String,
89+
owner: Owner,
90+
timestamp: String,
91+
username: String,
92+
permalink: String,
93+
like_count: String,
94+
comments_count: String,
95+
caption : String
96+
}
97+
98+
#[derive(Deserialize, Debug, Clone, Default, Serialize)]
99+
pub struct InstaMediaContainerId {
100+
pub id: String,
101+
}
102+
103+
#[derive(Deserialize, Debug, Clone, Default, Serialize)]
104+
pub struct Owner {
105+
id: String
106+
}
107+
108+
pub struct Fields {
109+
pub(crate) fields: Vec<String>,
110+
}
111+
112+
impl Default for Fields {
113+
/// This parameters are used as fields which are passed in as a query
114+
/// parameters to the get post request and feeds request
115+
fn default() -> Self {
116+
let field_list = vec![
117+
"caption",
118+
"id",
119+
"ig_id",
120+
"comments_count",
121+
"follows_count",
122+
"like_count",
123+
"media_product_type",
124+
"media_type",
125+
"media_url",
126+
"owner",
127+
"permalink",
128+
"thumbnail_url",
129+
"timestamp " ,
130+
"username",
131+
"video_title",
132+
];
133+
134+
let fields = field_list.iter().map(|&field| field.into()).collect();
135+
Self { fields }
136+
}
137+
}

src/graph/instagram/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
pub mod account;
22
pub mod publish;
3+
pub mod media;

0 commit comments

Comments
 (0)