Swift 5/iOS 13+ – Google Cloud Functions 不允许我添加值

Swift 5/iOS 13+ – Google Cloud Functions won't let me add a value

我正在使用 Google Cloud Functions (GCF) 在 Instagram 上安排 posts(类似于 Hootsuite 的应用程序)。我刚刚集成了本地通知,我想将 ID(由我的“notificationManager”自动生成)添加到 post(发送到 Google 云的数据)。

基本上,它的工作方式是应用程序将数据发送到 GCF,GCF 负责将元素添加到 Firestore 数据库,但在多次尝试失败后,我似乎无法弄清楚如何添加附加值.我的目标是添加元素“notificationIdentifier”,但出于某种原因,GCF 不会注册它(我什至在日志中都看不到它!)。

这是 GCF 中的内容:

/* eslint-disable max-len */
/* eslint-disable */
const functions = require("firebase-functions");
const admin = require("firebase-admin");
const express = require("express");
const bodyParser = require("body-parser");
const axios = require("axios");
const FACEBOOK_GRAPH_API_VERSION = "v11.0";
const FACEBOOK_APP_ID = Undisclosed APP_ID;
const FACEBOOK_APP_SECRET = Undisclosed APP_SECRET;
// // Create and Deploy Your First Cloud Functions
// // https://firebase.google.com/docs/functions/write-firebase-functions
//
admin.initializeApp();

const app = express();
const main = express();
const db = admin.firestore();

/**
 * publish Instagram Media Object.
 * @param {string} container_id instagram media object creation id.
 * @param {string} accessToken user access token.
 * @return {Promise} Returns container status
 */
async function getContainerStatus(container_id, accessToken) {
  let status = "IN PROGRESS";
  let response;
  try {
    response = await axios.get(
      `https://graph.facebook.com/${FACEBOOK_GRAPH_API_VERSION}/${container_id}`,
      {
        params: {
          access_token: accessToken,
          fields: "status_code",
        },
      }
    );
  } catch (error) {
    console.log(error);
    return "ERROR";
  }
  // console.log(response.data.status_code, "status");
  status = response.data.status_code;
  return status;
}

/**
 * Get LongLive Token Expire in 60 days.
 * @param {string} accessToken user access token.
 * @return {Promise} Return Long Live token.
 */
function getLongLiveToken(accessToken) {
  return new Promise((resolve, reject) => {
    axios
      .get(
        `https://graph.facebook.com/${FACEBOOK_GRAPH_API_VERSION}/oauth/access_token`,
        {
          params: {
            grant_type: "fb_exchange_token",
            client_id: FACEBOOK_APP_ID,
            client_secret: FACEBOOK_APP_SECRET,
            fb_exchange_token: accessToken,
          },
        }
      )
      .then((response) => {
        resolve(response.data.access_token);
      })
      .catch((error) => {
        reject(error);
      });
  });
}

/**
 * Get Facebook Pages.
 * @param {string} accessToken user access token.
 * @return {Promise} Returns the facebook pages result.
 */
function getFacebookPages(accessToken) {
  return new Promise((resolve, reject) => {
    axios
      .get(
        `https://graph.facebook.com/${FACEBOOK_GRAPH_API_VERSION}/me/accounts`,
        {
          params: {
            access_token: accessToken,
          },
        }
      )
      .then((response) => {
        const data = response.data.data;
        resolve(data);
      })
      .catch((error) => {
        reject(error);
      });
  });
}

/**
 * Get Instagram Account From Facebook Pages.
 * @param {string} accessToken user access token.
 * @param {string} pageId Page ID.
 * @return {Promise} Returns the Instagram Account Id.
 */
function getInstagramAccountId(accessToken, pageId) {
  return new Promise((resolve, reject) => {
    axios
      .get(
        `https://graph.facebook.com/${FACEBOOK_GRAPH_API_VERSION}/${pageId}`,
        {
          params: {
            access_token: accessToken,
            fields: "instagram_business_account",
          },
        }
      )
      .then((response) => {
        if (response.data.instagram_business_account) {
          resolve({
            id: response.data.instagram_business_account.id,
          });
        } else {
          resolve({ error: "No instagram Business Account" });
        }
      })
      .catch((error) => {
        reject(error);
      });
  });
}

/**
 * Get Facebook Profile.
 * @param {string} accessToken user access token.
 * @return {Promise} Returns the Facebook Account Profile.
 */
function getFacebookProfile(accessToken) {
  return new Promise((resolve, reject) => {
    axios
      .get(`https://graph.facebook.com/${FACEBOOK_GRAPH_API_VERSION}/me`, {
        params: {
          access_token: accessToken,
          fields: "name,picture,email",
        },
      })
      .then((response) => {
        resolve({
          picture: response.data.picture.data.url,
          name: response.data.name,
          email: response.data.email,
          facebookUserId: response.data.id,
        });
      })
      .catch((error) => {
        reject(error);
      });
  });
}

/**
 * Get Instagram Profile.
 * @param {string} accessToken user access token.
 * @param {string} instagramAccountId Instagram Account ID.
 * @return {Promise} Returns the Instagram Account Profile.
 */
function getInstagramProfile(accessToken, instagramAccountId) {
  return new Promise((resolve, reject) => {
    axios
      .get(
        `https://graph.facebook.com/${FACEBOOK_GRAPH_API_VERSION}/${instagramAccountId}`,
        {
          params: {
            access_token: accessToken,
            fields: "name,username,profile_picture_url",
          },
        }
      )
      .then((response) => {
        resolve(response.data);
      })
      .catch((error) => {
        reject(error);
      });
  });
}

/**
 * Create Instagram Media Object.
 * @param {string} accessToken user access token.
 * @param {string} instagramAccountId Instagram Account ID.
 * @param {string} caption caption.
 * @param {string} mediaType Instagram Account ID.
 * @param {string} mediaUrl media url
 * @return {Promise} Returns created Instagram Media ID.
 */
function createInstagramMedia(
  accessToken,
  instagramAccountId,
  caption,
  mediaType,
  mediaUrl,
  tags,
) {
  return new Promise((resolve, reject) => {
    const params = {
      access_token: accessToken,
      caption: caption,
    };
    let user_tags = [];
    if(tags != null) {
      for (let i = 0; i < tags.length; i++) {
        const user_tag = {
          "username": tags[i],
          "x": Math.random(),
          "y": Math.random()
        }
        user_tags.push(user_tag);
      }
    }
    if (mediaType == "PICTURE") {
      params["image_url"] = mediaUrl;
      if(tags != null) {
        params["user_tags"] = user_tags;
      }      
    } else if (mediaType == "VIDEO") {
      params["video_url"] = mediaUrl;
      params["media_type"] = "VIDEO";
    } else {
      reject({ message: "Unknow media!" });
      return;
    }
    axios
      .post(
        `https://graph.facebook.com/${FACEBOOK_GRAPH_API_VERSION}/${instagramAccountId}/media`,
        params
      )
      .then(async (response) => {
        const container_id = response.data.id;
        let container_status = "IN_PROGRESS";
        while (container_status == "IN_PROGRESS") {
          container_status = await getContainerStatus(
            container_id,
            accessToken
          );
          console.log(container_status, "Container status");
        }
        // resolve(response.data);
        if (container_status == "ERROR") {
          reject({ error: "Container error!" });
        } else {
          resolve(response.data);
        }
      })
      .catch((error) => {
        console.log(error);
        reject(error);
      });
  });
}

/**
 * publish Instagram Media Object.
 * @param {string} accessToken user access token.
 * @param {string} instagramAcctId instagram media object creation id.
 * @param {string} mediaObjectId instagram media object creation id.
 * @return {Promise} Returns the Instagram Account Profile.
 */
function publishMedia(accessToken, instagramAcctId, mediaObjectId) {
  console.log(accessToken, " --> ", instagramAcctId, " --> ", mediaObjectId);
  return new Promise((resolve, reject) => {
    axios
      .post(
        `https://graph.facebook.com/${FACEBOOK_GRAPH_API_VERSION}/${instagramAcctId}/media_publish`,
        {
          access_token: accessToken,
          creation_id: mediaObjectId,
        }
      )
      .then((response) => {
        resolve(response.data);
      })
      .catch((error) => {
        console.log(error);
        reject(error);
      });
  }); 
}

/**
 * Facebook Login with uuid
 * body params {uuid, accessToken}
 */
app.post("/register", async (req, res) => {
  const uuid = req.body.uuid;
  const accessToken = req.body.accessToken;
  const instagramAccts = [];
  let longLiveToken, userData, pages;
  try {
    longLiveToken = await getLongLiveToken(accessToken);
  } catch (error) {
    console.log(error);
    return res.status(400).json({
      error: "Failed to create LongLive Token",
    });
  }

  try {
    userData = await getFacebookProfile(accessToken);
  } catch (error) {
    console.log(error);
    return res.status(400).json({
      error: "Failed to get Facebook profile",
    });
  }

  try {
    pages = await getFacebookPages(accessToken);
  } catch (error) {
    console.log(error);
    return res.status(400).json({
      error: "Failed to get Facebook pages",
    });
  }

  if (pages.length > 0) {
    for (let i = 0; i < pages.length; i++) {
      try {
        const data = await getInstagramAccountId(accessToken, pages[i].id);
        if (!data.error) {
          const igProfile = await getInstagramProfile(accessToken, data.id);
          // const tags = await db
          //   .collection("tags")
          //   .where("tag", igProfile["username"])
          //   .get();

          // if (tags.docs.length < 1) {
          //   await db.collection("tags").add({
          //     tag: igProfile["username"],
          //   });
          // }
          igProfile["isActive"] = true;
          if (i == 0) {
            igProfile["isPrimary"] = true;
          } else {
            igProfile["isPrimary"] = false;
          }
          instagramAccts.push(igProfile);
        }
      } catch (error) {
        console.log(error);
        return res.status(400).json({
          error: "Failed to get Instagram accounts",
        });
      }
    }
  }

  try {
    await db.collection("users").doc(uuid).set({
      longLiveToken: longLiveToken,
      picture: userData.picture,
      name: userData.name,
      email: userData.email,
      facebookUserId: userData.facebookUserId,
      accessToken: accessToken,
      uuid: uuid,
      instagramAccts: instagramAccts,
    });
  } catch (error) {
    console.log(error);
    return res.status(400).json({
      error: "Failed to save user info to firestore",
    });
  }

  return res.status(200).json({
    message: "Success",
    instagramAccts: instagramAccts,
  });
});

app.post("/getInstagramAccounts", async (req, res) => {
  const uuid = req.body.uuid;
  const user = await db.collection("users").doc(uuid).get();
  const userData = user.data();
  const accessToken = userData.longLiveToken;
  const instagramAccts = [];
  let pages;

  try {
    pages = await getFacebookPages(accessToken);
  } catch (error) {
    console.log(error);
    return res.status(400).json({
      error: "Failed to get Facebook pages",
    });
  }

  if (pages.length > 0) {
    for (let i = 0; i < pages.length; i++) {
      try {
        const data = await getInstagramAccountId(accessToken, pages[i].id);
        if (!data.error) {
          const igProfile = await getInstagramProfile(accessToken, data.id);
          if (i == 0) {
            igProfile["isActive"] = true;
          } else {
            igProfile["isActive"] = false;
          }
          instagramAccts.push(igProfile);
        }
      } catch (error) {
        console.log(error);
        return res.status(400).json({
          error: "Failed to get Instagram accounts",
        });
      }
    }
  }
  try {
    await db
      .collection("users")
      .doc(uuid)
      .update({ instagramAccts: instagramAccts });
  } catch (error) {
    console.log(error);
    return res.status(500).json({
      error: "Failed to update instagram accounts",
    });
  }
  return res.status(200).json({ instagramAccts: instagramAccts });
});

/**
 * Schedule Instagram Media Object to publish.
 * body params {time, mediaType, uuid, media, instagramAcctId}
 */
app.post("/schedule", async (req, res) => {
  const uuid = req.body.uuid;
  const time = req.body.time;
  const mediaType = req.body.mediaType;
  const media = req.body.media; // url string array
  const tags = req.body.tags; // array of strings
  const longitude = req.body.longitude;
  const latitude = req.body.latitude;
  const instagramAcctId = req.body.instagramAcctId;
  const caption = req.body.caption;
  const thumbnail = req.body.thumbnail;
  const timeStamp = req.body.timeStamp;
  const notificationIdentifier = req.notificationIdentifier;

  try {
    const postData = await db.collection("posts").add({
      uuid: uuid,
      time: time,
      mediaType: mediaType,
      media: media,
      instagramAcctId: instagramAcctId,
      published: false,
      caption: caption,
      tags: tags ? tags : null,
      thumbnail: thumbnail ? thumbnail : null,
      longitude: longitude,
      latitude: latitude,
      timeStamp: timeStamp,
      notificationIdentifier: notificationIdentifier ? notificationIdentifier : null,

    });
    const ref = await postData.get();
    await db.collection("posts").doc(ref.id).update({ id: ref.id });
    res.status(200).json({
      message: "Success!",
    });
  } catch (error) {
    console.log(error);
    res.status(500).json(error.message);
  }
});

app.post("/update-schedule", async (req, res) => {
  const id = req.body.id;
  const time = req.body.time;
  const tags = req.body.tags; // array of strings
  const longitude = req.body.longitude;
  const latitude = req.body.latitude;
  const instagramAcctId = req.body.instagramAcctId;
  const caption = req.body.caption;
  const timeStamp = req.body.timeStamp;
  const notificationIdentifier  = req.body.notificationIdentifier;
  const updateData = {};
  if (time) {
    updateData["time"] = time;
  }
  if (tags) {
    updateData["tags"] = tags;
  }
  if (longitude) {
    updateData["longitude"] = longitude;
  }
  if (latitude) {
    updateData["latitude"] = latitude;
  }
  if (instagramAcctId) {
    updateData["instagramAcctId"] = instagramAcctId;
  }
  if (caption) {
    updateData["caption"] = caption;
  }
  if (timeStamp) {
    updateData["timeStamp"] = timeStamp;
  }
  if (notificationID) {
      updateData["notificationIdentifier"] = notificationIdentifier;
  }

  try {
    await db.collection("posts").doc(id).update(updateData);
    res.status(200).json({
      message: "Update success!",
    });
  } catch (error) {
    console.log(error);
    res.status(500).json(error.message);
  }
});

app.post("/remove-schedule", async (req, res) => {
  const id = req.body.id;

  try {
    const postData = await db.collection("posts").doc(id).get();
    if (postData.exists) {
      await db.collection("posts").doc(id).delete();
      res.status(200).json({
        message: "Remove success!",
      });
    } else {
      res.status(400).json({
        message: "There is no such post!",
      });
    }
  } catch (error) {
    console.log(error);
    res.status(500).json(error.message);
  }
});

app.post("/get-posts-by-date", async (req, res) => {
  const uuid = req.body.uuid;
  let posts_by_date = [];
  try {
    const posts = await db
      .collection("posts")
      .where("published", "==", false)
      .where("uuid", "==", uuid)
      .orderBy("time")
      .get();
    for (let i = 0; i < posts.docs.length; i++) {
      const postData = posts.docs[i].data();
      console.log(new Date(postData["time"]).toLocaleDateString());
      const localDate = new Date(postData["time"]).toLocaleDateString();
      if (posts_by_date[localDate]) {
        posts_by_date[localDate].push(postData);
      } else {
        posts_by_date[localDate] = [];
        posts_by_date[localDate].push(postData);
      }
    }
    console.log(posts_by_date, "posts by date");
    return res.status(200).json({
      posts: posts_by_date,
    });
  } catch (error) {
    console.log(error);
    return res.status(500).json(error.message);
  }
});

exports.scheduledFunction = functions.pubsub
  .schedule("* * * * *")
  .onRun((context) => {
    console.log("This will be run every 1 minute!");
    db.collection("posts")
      .where("published", "==", false)
      .get()
      .then((querySnapshot) => {
        querySnapshot.forEach(async (doc) => {
          console.log(doc.id, " => ", doc.data());
          const post = doc.data();
          const now = new Date();
          const publishDate = new Date(post.time);
          if (publishDate <= now) {
            //publish media object.
            const uuid = post.uuid;
            const userRef = await db.collection("users").doc(uuid).get();
            const user = userRef.data();
            const longLiveToken = user.longLiveToken;
            const instagramAcctId = post.instagramAcctId;
            const medias = post.media;
            //const notificationIdentifier = post.notificationIdentifier;

            for (let i = 0; i < medias.length; i++) {
              try {
                const mediaObjects = await createInstagramMedia(
                  longLiveToken,
                  instagramAcctId,
                  post.caption,
                  post.mediaType,
                  medias[i],
                  post.tags,
                 // notificationIdentifier
                );
                const mediaObjectId = mediaObjects.id;
                await publishMedia(
                  longLiveToken,
                  instagramAcctId,
                  mediaObjectId
                );
              } catch (error) {
                console.log(error);
                return;
              }
            }
            await db
              .collection("posts")
              .doc(doc.id)
              .update({ published: true });
          }
        });
      })
      .catch((error) => {
        console.log("Error getting documents: ", error);
      });

    return null;
  });

main.use("/v1", app);
main.use(bodyParser.json());
main.use(bodyParser.urlencoded({ extended: true }));

exports.api = functions.https.onRequest(main);

数据是这样发送的:

private func schedulePost(uuid: String, time: Date, mediaType: String, media: [String], caption: String, tags:[String], location: CLLocation?, thumbImageUrl: String) {
       
       var newCap: [String] = []
       newCap.append(caption)
       newCap.insert(contentsOf: tags, at: newCap.endIndex)
       let newCaption = newCap.joined(separator: " ")
       print (newCaption)
       
       AuthManager.shared.loadUser()
       guard let instagramAccountId = AuthManager.shared.currentUser?.id else {return}
       let timeStamp = NSDate().timeIntervalSince1970
       if !self.TrueStory {
           StoryManager().addNewTask("POST", "Post", time, self.ImageURL)
           print("Scheduled Post" + self.ImageURL)
         //  let notificationID = notificationIdentifier
           //tags.insert(caption, at: tags.firstIndex)
           let param = ["uuid": uuid, "time": time, "mediaType": mediaType, "media": media, "instagramAcctId": instagramAccountId, "caption": newCaption, "tags": "", "latitude": location?.coordinate.latitude ?? "", "longitude":location?.coordinate.longitude ?? "", "thumbnail":thumbImageUrl, "timeStamp":timeStamp, "notificationIdentifier":notificationIdentifier] as [String : Any]
           print ("The PARAMS are: ")
           print (param)
           ServerApi.shared.scheduleIGPosts(param: param, success: {response in
               print(response)
               ProgressHUD.dismiss()
               AppManager.shared.isPostScheduled = true
               AppManager.shared.showNext()
           //  NotificationCenter.default.post(name: .PostWasSuccessfullyScheduled, object: nil, userInfo: ["posted": true])
               
           }, failure: {(error) in
               print(error)
               self.showAlert(error.description)
               ProgressHUD.dismiss()
           })
       } else if self.TrueStory {
           StoryManager().addNewTask("STORY", "Story", time, self.ImageURL)
           print("Scheduled Story" + self.ImageURL)
          // let notificationID = notificationIdentifier
           let param = ["uuid": uuid, "time": time, "mediaType": mediaType, "media": media, "instagramAcctId": instagramAccountId, "caption": caption, "tags": "", "latitude": location?.coordinate.latitude ?? "", "longitude":location?.coordinate.longitude ?? "", "thumbnail":thumbImageUrl, "timeStamp":timeStamp, "notificationIdentifier":notificationIdentifier] as [String : Any]
           // "notificationID":notificationIdentifier
           ServerApi.shared.scheduleIGPosts(param: param, success: {response in
               print(response)
               ProgressHUD.dismiss()
               AppManager.shared.isPostScheduled = true
               AppManager.shared.showNext()
            //   NotificationCenter.default.post(name: .PostWasSuccessfullyScheduled, object: nil, userInfo: ["posted": true])
               
           }, failure: {(error) in
               print(error)
               self.showAlert(error.description)
               ProgressHUD.dismiss()
           })
       }
     
   } 

还有供您娱乐的“ScheduleIGPost”功能:

import Foundation
import SwiftyJSON
import Alamofire
struct AppUrls {
    static let baseUrl = URL that I wont disclose
    static let registerIGAccounts = baseUrl + "register"
    static let scheduleIGPosts = baseUrl + "schedule"
    static let updateIGPosts = baseUrl + "update-schedule"
    static let removeIGPosts = baseUrl + "remove-schedule"
}
class ServerApi {
    static let shared = ServerApi()

  func scheduleIGPosts(param: [String: Any], success: @escaping(JSON) -> Void, failure: @escaping(JSON) -> Void) {
        ApiWrapper.requestPOSTURLWithoutToken(AppUrls.scheduleIGPosts, params: param, success: {(response) in
            print(JSON(response))
            success(JSON(response))
        }, failure: { (error) in
            let err = JSON(error)
            print(err)
            failure(err)
        })
    }

我确实检查并查看“param”在发送到 GCF 时是否包含“notificationIdentifier”,确实如此,但是如果我检查 GCF 中的日志,我可以看到除了“notificationIdentifier”之外应该包含的所有内容。例如,当我用“notificationIdentifier”替换“tags”时,它就起作用了。

我不确定 Firestore/Firebase 是否有元素限制以及为什么会出现这种情况。老实说,我在网上找不到太多资料。

如果我不清楚,请随时提出任何问题,因为我往往如此!

这很简单地解决了,该应用程序正在使用一个名为“API”的云函数,该函数在访问“计划”函数之前使用。所以更改了值(或者更确切地说是添加了值),现在一切正常。