Search code examples
javascriptarraysapiloopstwitter

How can I iterate through this twitter api message list?


I'm currently using the Twitter API, specifically the https://api.twitter.com/1.1/direct_messages/events/list.json endpoint.

What the endpoint returns is an array of message objects.

The message object looks like so

{
  target: { recipient_id: '0002' },
  sender_id: '0001',
  source_app_id: '00000',
  message_data: {
    text: 'hello',
    entities: { hashtags: [], symbols: [], user_mentions: [], urls: [] }
  }
}

Based off that if I have an array of those objects then I would use a for loop to group the messages that have the same sender_id or recipient_id into a conversation array while also keeping track of which sender_id/recipient_id's have already been grouped so I don't have cloned conversations with the same users.

What I currently have is a double for loop where the first for loop grabs a pair of sender/recipient_id's and the second for loop checks for any other message object with matching id's to which are grouped into an array.

What I can't seem to do is trace which pair of id's have already been grouped. I'm leaning towards using a third for loop but I feel like there is a simpler solution.

EDIT: (MORE INFO) This is what I have currently.

 // Create conversations array
 convArr = [];

// see what the messages are
for (let i = 0; i < response.data.events.length; i++) {
   // if the next message has same recipient then skip
   sender_id = response.data.events[i].message_create.sender_id;
   recipient_id = 
      response.data.events[i].message_create.target.recipient_id;
   conversation = [];

   // Look for a certain matching sender and recipient id that matches original message
   for (let j = i; j < response.data.events.length; j++) {
      matchedSender = response.data.events[j].message_create.sender_id;
      matchedRecipient = 
      response.data.events[j].message_create.target.recipient_id;
      if((sender_id === matchedSender) | (sender_id === matchedRecipient) 
      &&
      (recipient_id === matchedSender) | (recipient_id === 
  matchedRecipient)) 
      {
     conversation.push(response.data.events[j]);
     } 
    }
   }
  convArr.push(conversation);
}

Solution

  • Based on the information you've given I think this can be done with a single reduce function. They key is to create an object whose keys represent unique conversations first, then output this as an array afterwards.

    Something like this:

    let messages = [ ... ] // Your messages inside here
    
    // We want to run a reducer on the messages array to get
    // unique conversations, then user Object.values to transform
    // this back into an array.
    let output = Object.values(messages.reduce((acc, message) => {
    
      // Convert both the sender and receiver ids to numbers.
      let sender = Number(message.sender_id);
      let receiver = Number(message.target.recipient_id);
    
      // We always want the conversation ID to be the same, regardless
      // of who the sender or receiver was, as long as it's the same
      // participants. The code below will always generate a string with
      // the smaller ID first, and the larger ID second, regardless of 
      // what role the users played in the message.
      let conversation_id = sender > receiver 
        ? `${receiver}_${sender}`
        : `${sender}_${receiver}`;
    
      if (acc[conversation_id]) {
        // If the conversation already exists, then we will just add this message
        // to the array of messages that make up the conversation
        acc[conversation_id].push(message);
      }
      else {
        // If the conversation doesn't exist, create a new array of messages
        // with this conversation ID and the first message initializing the array.
        acc[conversation_id] = [message];
      }
    
      // Return the updated accumulator, which will be used in the next
      // iteration.
      return acc;
    
    }, {}));
    

    As long as the sender_id and recipient_id are numbers, this should work.

    Edit

    Here is some simple explanation of the pieces that might not be clear.

    Object.values

    This function will operate differently depending on what argument you provide to it. But mainly, you will see this used to extract an array of values from an object (ie {}), rather than a string or an array, as they have their own methods to accomplish this.

    When talking about the values of an object, I'm referring to the value part of the key/value pairs. For example:

    let obj = {
      a: 1 // 'a' is the key, 1 is the value
      b: 2 // 'b' is the key, 2 is the value
    };
    
    let arrayOfValues = Object.values(obj); // Will return [1, 2]
    

    Array.reduce

    The array reduce function is a very powerful tool in the JavaScipt programming arsenal. It takes a function as an argument, and optionally an initial value for an accumulator. It's kind of like a stateful forEach, in that it runs a function on each item in an array, but maintains a state (the accumulator) across all iterations.

    Here's a simple reducer...

    let initialState = 'abc';
    let values = ['d', 'e', 'f']
    
    let result = values.reduce((acc, val) => {
      return acc + val;
    }, initialState);
    
    console.log(result) // 'abcdef';
    

    ..and the same functionality with a simple forEach loop

    let initialState = 'abc';
    let values = ['d', 'e', 'f'];
    let result = intialState;
    
    values.forEach(value => {
      result += value;
    });
    
    console.log(result) // 'abcdef';
    

    The only difference is that you the "state" data used in the loop, is contained and returned from a reduce function, where as in all other loops you need to manage state from a scope outside of the loop itself.

    ternary operator

    The ternary operator, or something similar exists in a large variety of languages. It is basically a quick way to write an if else statement, whose purpose is to decide on the value of a single variable. The expression after the ? part of the operator is like the if and the expression after the : part is like the else.

    Here's a simple ternary

    let twenty = 20;
    let isTwentyGreaterThanTen = twenty > 10 ? 'Yes' : 'No';
    
    console.log(isTwentyGreayerThanTen); // 'Yes'
    

    An the identical functionality with an if/else statement

    let twenty = 20;
    let isTwentyGreaterThanTen;
    
    if (twenty > 10) {
      isTwentyGreaterThanTen = 'Yes';
    }
    else {
      isTwentyGreaterThanTen = 'No';
    }
    
    console.log(isTwentyGreayerThanTen); // 'Yes'