How to define if #mentioned is subscriber? TMI.js - javascript

Trying to make little bot for my friend's twitch and want to make it define if "#mentioned" is a sub.
I decided to test if it define mods first but it always says "2"
client.on("chat", function (channel, user, message, self) {
if (message.toLowerCase().startsWith("!love")) {
var loved = message.split(' ')[1];
if (loved.mod || loved['user-type'] === 'mod'){
client.action(channel, "1 ");  //says sth
      }
else {
client.action(channel, "2 ");//says sth different
      }
}

user.mod
Is only available for user objects that are returned by the message handler. If you want to check if a mentioned user is a mod you have to request the chat list
function getAllChatters(channel, _attempts) {
return rp({
uri: `https://tmi.twitch.tv/group/user/${channel.replace('#','')}/chatters`,
json: true
})
.then(data => {
return Object.entries(data.chatters)
.reduce((p, [ type, list ]) => p.concat(list.map(name => {
return {name, type};
})), []);
})
.catch(err => {
if(_attempts < 3) {
return getChatters(channel, _attempts + 1);
}
throw err;
})
}
Then filter it for only mods
function getModsOnline(channel) {
return getAllChatters(channel)
.then(data => {
var mods = data.filter(chatter => chatter.type == 'moderators' || chatter.type == 'broadcaster').map(chatter => chatter.name);
return mods;
})
}
And then see if the user is in the list
function isModOnline(channel, username) {
// This will only return true if the mod is online in chat aka in the chat list
return getMods(channel)
.then(mods => {
return mods.includes(username);
})
}
Keep in mind that
This is for getting mods, not subscribers
It will only work for mods that are currently in chat, that list tends to update rather slowly
You need to work with promises here so
const rp = require('request-promise');
and to use the value
```
isModOnline(channel, username)
.then(value => {
// Code to work with the value here
console.log(`User is mod: ${value}`);
})

Related

using async methods inside of array.map() in javascript

I want to go through an array of elements and then for each element check if a theme exists with the "findOne()" function and if not create a new theme and save it and if that theme exists just apply some changes and then save and i want it to only continue after the theme is saved but .
internalRoutes.post('/add_questions', (req, res) => {
let elements = req.body
var count = 0;
elements.map((element) => {
let q = new Question({ category: element.category, body: element.body, level: element.level })
q.save().then(question => {
Theme.findOne({ name: element.theme }).then(theme => {
if (!theme) {
let new_theme = new Theme({ name: element.theme, level: element.level, questions: [], first_question: null })
new_theme.questions.push(question);
new_theme.first_question = question._id;
new_theme.save().then(t => {
console.log('new theeeeemmmmmmeeeeee')
})
}
else {
theme.questions.push(question);
theme.save().then(t => {
console.log('yeeeerrrrrrrrrrrrrecognized');
})
}
})
.catch(err => {
res.status(400).json("couldn't locate theme")
})
}).catch(err => {
res.status(400).json("couldn't save question")
})
count++;
})
if (count === elements.length) {
res.status(200).json({ message: "questions added successfully" })
}
else res.status(500).json('error')
})
here's my current code , i tried many ways such as async await functions or try catch blocks but never seem to solve this does anyone know where the issue could be ?
You probably want some sort of Promise.all implementation, so you know all of the promises you fired did return.
Your example code is fairly complex, so if you could simplify it I would be able to give you a more specific approach for your case.
const promisedElements = Promise.all(elements.map(async () => { ... })).then(...)

Is there a way to use a observable returning function for each element of another observable array?

I get an Observable<Group[]> from my Firebase collection.
In this Group class is an id which I wanna use to retrieve another dataset array from Firebase, which would be messages for each unique group Observable<Message[]>.(each group has its own chat: Message[])
And it want to return an observable which hold an array of a new Type:
return { ...group, messages: Message[] } as GroupWithMessages
the final goal should be Observable<GroupWithMessages[]>
getGroupWithChat(): Observable<GroupWithMessages[]> {
const groupColl = this.getGroups(); // Observable<Group[]>
const messages = groupColl.pipe(
map(groups => {
return groups.map(meet => {
const messages = this.getMessagesFor(group.uid);
return { messages:messages, ...group} as GroupWithMessages
});
})
);
return messages;
}
}
and here the Message function
getMessagesFor(id: string): Observable<Message[]> {
return this.afs.collection<Message>(`meets/${id} /messages`).valueChanges();
}
sadly that doesnt work because when i create the new Obj I cannot bind messages:messages because messages ist vom typ Observable<Message[]>
I hope that cleares things
UPDATE:
my main problem now comes down to this:
getGroupsWithMessages() {
this.getJoinedGroups()
.pipe(
mergeMap(groups =>
from(groups).pipe(
mergeMap(group => {
return this.getMessagesFor(group.uid).pipe(
map(messages => {
return { ...group, messages } as GroupIdMess;
})
);
}),
tap(x => console.log('reaching here: ', x)),
toArray(),
tap(x => console.log('not reaching here = completed: ', x))
)
),
tap(x => console.log('not reaching here: ', x))
)
.subscribe(x => console.log('not reaching here: ', x));
}
when i call that function my console.log is as follows:
Not sure if I follow what you're doing here but the logic look like you'd want:
getGroupWithChat() {
return this.getGroups.pipe(map(groups=> {
return groups.map(group => this.getMessagesFor(group.uid));
})).subscribe(); // trigger "hot" observable
}
Let me know if I can help further after you clarify.
UPDATE:
So it looks like you need to get the UID of the group before making the call to get the GroupMessages[]?
get Group: Observable
call getMessagesFor(Group.uid)
this example gets groups result$ then
concatMap uses groups result$ to make the messages query
this.getGroups().pipe(
concatMap((group: Group) => this.getMessagesFor(group.uid))
).subscribe((messages: GroupWithMessages[]) => {
console.log(messages);
});
You may still want to map them together but it seems like you know how to do that. concatMap waits for the first to finish, then makes the second call which you need.
Is this closer?
Use forkJoin to wait for messages to be received for all groups. Then map the result of forkJoin to an array of GroupWithMessages like this -
getGroupWithChat(): Observable<GroupWithMessages[]> {
return this.getGroups()
.pipe(
switchMap(groups => {
const messagesForAllGroups$ = groups.map(group => this.getMessagesFor(group.uid));
return forkJoin(messagesForAllGroups$)
.pipe(
map(joined => {
//joined has response like -
//[messagesArrayForGroup0, messagesArrayForGroup1, messagesArrayForGroup2....];
const messagesByGroup = Array<GroupWithMessages>();
groups.forEach((group, index) => {
//assuming that GroupWithMessages has group and messages properties.
const gm = new GroupWithMessages();
gm.group = group;
gm.messages = joined[index];
messagesByGroup.push(gm);
});
return messagesByGroup;
})
)
})
)
}
I usually do that by splitting Observable<any[]> to Observable<any> and then mergeMap the results to inner Observable.
Something like this should work:
getMessagesFor(id: string): Observable<number> {
return of(1);
}
getGroups(): Observable<string[]> {
return of(["1", "2"]);
}
getGroupWithChat() {
this.getGroups().pipe(
mergeMap(groups => from(groups)), // Split the stream into individual group elements instead of an array
mergeMap(group => {
return this.getMessagesFor(group).pipe(
map(messages => {
return Object.assign(group, messages);
})
);
})
);
}
Edit:
Consider BehaviorSubject. It doesn't complete at all:
const behSub: BehaviorSubject<number[]> = new BehaviorSubject([1, 2, 3]);
setTimeout(() => {
behSub.next([4, 5, 6]);
}, 5000);
behSub
.pipe(
mergeMap(arr =>
from(arr).pipe(
tap(), // Do something with individual items, like mergeMap to messages
toArray() // Go back to array
)
)
)
.subscribe(console.log, null, () => {
console.log('Complete');
});

Error: You must return a Promise in your transaction()-callback

My script is throwing the following error when returning the result of the assync firestore set function:
You must return a Promise in your transaction()-callback.
According to firebase documentation about transactions, set function return a transaction itself.
Here a simplified copy of my code.
var myDoc = {
field1: "v1"
};
var docRef = db
.collection("docs")
.doc("1");
return db
.runTransaction(t => {
return t
.set(docRef, chat, {merge:false}); //has i understand, this should return a transaction object but the error says otherwise.
})
.then( doc => {
response.send();
})
.catch(err => {
...;
})
I am still new to Nodejs and not very familiar with chaining assyncs methods, so i must be doing some obvious error here.
Haven't used firestore transaction but I have used firebase transactions. You can try following
return db
.runTransaction(t => {
return t.set(docRef, chat, {merge:false})
.then(data => {
return Promise.resolve('transaction complete');
})
.then( doc => {
response.send();
})
.catch(err => {
...;
})
and the method that encloses your whole code must be returning promise as you have written return db.runTransaction(t => {....})
so if that is not needed then use
var transaction = db.runTransaction(t => {...});
Just do this:
return db
.runTransaction(t => {
t.set(docRef, chat, {merge:false});
return Promise.resolve(); // Add this line.
})
.then( doc => {
response.send();
})
.catch(err => {
...;
})
If the t.set() failed, it won't go to the resolve() anyway.

node.js pg-promise and pagination from API

Looking at https://github.com/vitaly-t/pg-promise/wiki/Data-Imports there's a very detailed doc on how to use it for importing.
However while that works for the demoed scenario I don't know how to apply it on my case.
When I do my web call, I get the actual JSON data and a paramter in the header which gives me a value for the next page (could be a date or String or a number value).
In the example, it says:
db.tx('massive-insert', t => {
return t.sequence(index => {
return getNextData(index)
.then(data => {
if (data) {
const insert = pgp.helpers.insert(data, cs);
return t.none(insert);
}
});
});
})
.then(data => {
console.log('Total batches:', data.total, ', Duration:', data.duration);
})
.catch(error => {
console.log(error);
});
In this case, sequence(index will use index which seems to increment +1.
But in my case,
function getNextData(nextPage) {
//get the data for nextPage
.....
//get the nextPage if exists for future use
nextPage = response.next;
resolve(data);
}
My question is, how can I replace index with nextPage in this example, as each new Promise needs to use the nextPage from previous one.
LATER EDIT: And if I want to fetch info from a certain value of nextPageInfo?
For instance:
db.any('Select value from table')
.then(function(value) {
var data = value; //not working
db.tx('massive-insert', t => {
return t.sequence((index, data) => {
return getNextData(index, data)
.then(a => {
if (a) {
const insert = pgp.helpers.insert(a.data, cs);
return t.none(insert).then(() => a.nextPageInfo);
}
})
});
})
.then(data => {
// COMMIT has been executed
console.log('Total batches:', data.total, ', Duration:', data.duration);
})
.catch(error => {
// ROLLBACK has been executed
console.log(error);
})
}
Following this question, I have extended article Data Imports with the new extras section, which gives you exactly the example that you need. The example copied from the article:
function getNextData(t, index, nextPageInfo) {
// t = database transaction protocol
// NOTE: nextPageInfo = undefined when index = 0
return new Promise((resolve, reject) {
/* pull the next data, according to nextPageInfo */
/* do reject(error) on an error, to ROLLBACK changes */
if(/* there is still data left*/) {
// if whateverNextDetails = undefined, the data will be inserted,
// but the sequence will end there (as success).
resolve({data, nextPageInfo: whateverNextDetails});
} else {
resolve(null);
}
});
}
db.tx('massive-insert', t => {
return t.sequence((index, data) => {
return getNextData(t, index, data)
.then(a => {
if (a) {
const insert = pgp.helpers.insert(a.data, cs);
return t.none(insert).then(() => a.nextPageInfo);
}
})
});
})
.then(data => {
// COMMIT has been executed
console.log('Total batches:', data.total, ', Duration:', data.duration);
})
.catch(error => {
// ROLLBACK has been executed
console.log(error);
});
Please note that since we are chaining the result from getNextData to the value of nextPageInfo, then if its value is undefined, it will do the next insert, but then will end the sequence (as success).

RXJS filter on another observable

In my code I am getting a list of users from MS Graph, but this lists all users. I want to emit only those users that are a member of a specified group. So I have set up a filter:
private loadUsers() {
console.log(`Loading users...`);
this.graph.get$(`users`)
.map(resp => resp.json().value)
.flatMap(user => user)
.do(user => {
console.log(`Got user: ${user['displayName']}`);
})
.filter(user => {
// we only want users who are members of the TrackerContact group
const userId: string = user['id'];
console.log(`Checking user ${userId}`);
return this.userIsTrackerContact(user); // <<= PROBLEM HERE!
})
.subscribe(_ => {
console.log(`Completed user fetch!`);
});
}
The problem is in order to find out what groups a user is a member of, I must make another REST call back to MS Graph:
private userIsTrackerContact(user): Observable<boolean> {
// get the groups this user is a member of
return this.graph.get$(`users/${user['id']}/memberOf`)
.map(resp => resp.json().value)
.do(groups => {
console.log(`${user['displayName']} is a member of ${groups.length} groups:`);
groups.forEach((group, idx) => {
console.log(` [${idx+1}]: ${group['displayName']}`);
})
})
.map(groups => {
const found = groups.find((group) => group['id'] === this.authConfig.azureGroups.trackerContact)
return !isUndefined(found);
})
}
It does not seem to me that it's possible to filter an Observable based on another observable. The predicate test must be a simple true/false test.
How can I filter an Observable based on the results of another Observable?
.concatAll() might be better:
this.graph.get$(`users`)
.map(resp => resp.json().value)
.flatMap(user => user)
.do(user => {
console.log(`Got user: ${user['displayName']}`);
})
.filter(user => {
// we only want users who are members of the TrackerContact group
const userId: string = user['id'];
console.log(`Checking user ${userId}`);
return this.userIsTrackerContact(user); // <<= PROBLEM HERE!
})
.concatAll()
.subscribe(...)
I believe you should be able to merge the async observables then filter.
Rx.Observable.merge(this.graph.get$(`users`)
.map(resp => resp.json().value)
.flatMap(user => user)
.do(user => {
console.log(`Got user: ${user['displayName']}`);
})
.map(user => {
// we only want users who are members of the TrackerContact group
const userId: string = user['id'];
console.log(`Checking user ${userId}`);
return this.userIsTrackerContact(user); // <<= PROBLEM HERE!
}))
.filter(x => x)
.subscribe(_ => console.info('success'));
You can change your predicate set a property on user isGroupMember. Then filter(x => x.isGroupMember). Then you have the user objects in the subscribe method.

Categories