Skip to content

Commit

Permalink
Few Changes
Browse files Browse the repository at this point in the history
  • Loading branch information
tlh26 committed Oct 20, 2024
1 parent 3a04999 commit ab165b0
Show file tree
Hide file tree
Showing 5 changed files with 117 additions and 51 deletions.
2 changes: 1 addition & 1 deletion backend/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ const WebSocket = require('ws');

app.use(
cors({
origin: ["http://localhost:8081", "exp://192.168.225.19:8081", 'chrome-extension://gghoblbehihhmceogmigccldhmnnfeoc', "https://*.netflix.com/*"],
origin: ["http://localhost:8081", "exp://192.168.225.19:8081", 'chrome-extension://mbmbholeiljjaabgbpajlfalcbnciona', "https://www.netflix.com/*"],
methods: ["GET", "POST", "PUT", "DELETE"],
credentials: true,
})
Expand Down
113 changes: 79 additions & 34 deletions backend/src/Recommender/recommender.service.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,10 @@ const getUserFavoriteGenres = async (userId) => {
const session = driver.session();
try {
const result = await session.run(
'MATCH (u:User {id: $userId}) RETURN u.favorite_genres AS favoriteGenres',
'MATCH (u:User {uid: $userId}) RETURN u.favouriteGenres AS favoriteGenres',
{ userId }
);

// Assuming favorite_genres is stored as a list in Neo4j
if (result.records.length > 0) {
const favoriteGenres = result.records[0].get('favoriteGenres');
return favoriteGenres; // This should return an array of genre IDs
Expand All @@ -38,26 +37,71 @@ const getUserFavoriteGenres = async (userId) => {
}
};

// Don't forget to close the driver when your application is shutting down
// New function to get user interaction data (watched movies, liked movies, etc.)
const getUserInteractionData = async (userId) => {
const session = driver.session();
try {
const result = await session.run(
'MATCH (u:User {uid: $userId})-[:REVIEW]->(m:Movie) RETURN m.movieId AS movieId, m.rating AS rating',
{ userId }
);

return result.records.map(record => ({
movieId: record.get('movieId'),
rating: record.get('rating') || null
}));
} catch (error) {
console.error('Error fetching user interaction data:', error);
throw new Error('Failed to retrieve user interactions');
} finally {
await session.close();
}
};

const closeNeo4jConnection = async () => {
await driver.close();
};

const checkAndSetupIndex = async () => {
//await setupIndex();
//await indexMovies(); // Ensure movies are indexed after setup
// await setupIndex();
// await indexMovies(); // Ensure movies are indexed after setup
};
const genresMap = {
28: 'Action',
12: 'Adventure',
16: 'Animation',
35: 'Comedy',
80: 'Crime',
99: 'Documentary',
18: 'Drama',
10751: 'Family',
14: 'Fantasy',
36: 'History',
27: 'Horror',
10402: 'Music',
9648: 'Mystery',
10749: 'Romance',
878: 'Science Fiction',
10770: 'TV Movie',
53: 'Thriller',
10752: 'War',
37: 'Western'
};


// Modified recommendation function to leverage collaborative filtering with similarity scaling
const recommendMoviesByTMDBId = async (tmdbId, userId) => {
try {
await checkAndSetupIndex();

const tmdbMovie = await getMovieDetails(tmdbId);
const userFavoriteGenres = await getUserFavoriteGenres(userId);
const userInteractions = await getUserInteractionData(userId); // Get watched/liked movies

// Collect genre IDs for querying
const genreQuery = [...tmdbMovie.genres.map(genre => genre.id), ...userFavoriteGenres];
const genreQuery = [
...tmdbMovie.genres.map(genre => genresMap[genre.id]), // Convert TMDB movie genres to names
...userFavoriteGenres.map(genreId => genresMap[genreId]) // Convert favorite genre IDs to names
].filter(Boolean); // Ensure no undefined values if an ID doesn't exist in the map

console.log("Searching movies in Elasticsearch with genre query:", genreQuery);

Expand All @@ -66,27 +110,17 @@ const recommendMoviesByTMDBId = async (tmdbId, userId) => {
body: {
query: {
bool: {
filter: [
{
range: {
'popularity': {
'gte': 10 // Popularity must be greater than 5
}
}
}
],
should: [
{ match: { overview: tmdbMovie.overview } },
{ terms: { 'genre_ids': genreQuery } }
{ terms: { 'genre_names': genreQuery } }
],

must_not: [
{ match: { id: tmdbId } }
],
minimum_should_match: 1
}
},
size: 100
size: 400
},
});

Expand All @@ -98,11 +132,7 @@ const recommendMoviesByTMDBId = async (tmdbId, userId) => {
index: 'movies',
body: {
query: {
range: {
popularity: {
gt: 10 // Popularity must be greater than 5
}
}
match_all: {} // Remove popularity condition and fetch all movies
}
},
size: 100
Expand All @@ -118,8 +148,6 @@ const recommendMoviesByTMDBId = async (tmdbId, userId) => {
const allMoviesFeatures = allMovies.map(combineFeatures);
const combinedFeatures = [tmdbMovieFeatures, ...allMoviesFeatures];

console.log("Combined features for all movies: ", combinedFeatures);

// Vectorize the movie features
const movieVectors = vectorizeMovies(combinedFeatures);
const [tmdbMovieVector, ...allMoviesVectors] = movieVectors;
Expand All @@ -134,17 +162,35 @@ const recommendMoviesByTMDBId = async (tmdbId, userId) => {
};
});

// Collaborative filtering: Give a boost to movies that the user has liked or watched
const enhancedSimilarities = cosineSimilarities.map(rec => {
const userInteraction = userInteractions.find(interaction => interaction.movieId === rec.movie.id);
if (userInteraction) {
rec.similarity += 0.1 * (userInteraction.rating || 1); // Boost based on rating
}
return rec;
});

// Normalize the similarity scores based on the number of movies to recommend
const numRecommendations = 15;
const maxSimilarity = Math.max(...enhancedSimilarities.map(rec => rec.similarity)); // Get max similarity for scaling

const scaledSimilarities = enhancedSimilarities.map(rec => {
rec.similarity = (rec.similarity / maxSimilarity) * (1 / numRecommendations); // Scale similarity
return rec;
});

// Sort the movies by similarity in descending order
cosineSimilarities.sort((a, b) => b.similarity - a.similarity);
scaledSimilarities.sort((a, b) => b.similarity - a.similarity);

// Return the top 15 highest similarity movies
const topRecommendations = cosineSimilarities
.slice(0, 15)
const topRecommendations = scaledSimilarities
.slice(0, numRecommendations)
.map(rec => ({
id: rec.movie.id,
title: rec.movie.title,
posterUrl: `https://image.tmdb.org/t/p/w500${rec.movie.poster_path}`,
similarity: (rec.similarity * 100).toFixed(2)
similarity: (((rec.similarity * 100).toFixed(2)*10).toFixed(2)) // Adjust similarity scaling for display
}));

console.log("Top recommendations: ", topRecommendations);
Expand All @@ -161,11 +207,10 @@ const combineFeatures = (movie) => {
const overview = movie.overview || '';
const title = movie.title || '';
const director = movie.director || '';
const cast = Array.isArray(movie.cast) ? movie.cast.join(' ') : '';
const music = movie.music || '';
// const cast = Array.isArray(movie.cast) ? movie.cast.join(' ') : '';
// const music = movie.music || '';

// Combine all available features into a single string
return `${overview} ${genres} ${title} ${director} ${cast} ${music}`.trim();
return `${overview} ${genres} ${title} ${director}`.trim();
};

module.exports = { recommendMoviesByTMDBId };
25 changes: 19 additions & 6 deletions backend/src/Room/WatchParty/party.controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ const partyService = require('./party.service');
// Controller for starting a watch party
exports.startWatchParty = async (req, res) => {
console.log('Starting watch party -> controller');
res.setHeader('Access-Control-Allow-Origin', 'chrome-extension://gghoblbehihhmceogmigccldhmnnfeoc');
res.setHeader('Access-Control-Allow-Origin', 'chrome-extension://mbmbholeiljjaabgbpajlfalcbnciona');
const { username, roomShortCode, partyCode } = req.body;

if (!partyCode || !roomShortCode || !username) {
Expand All @@ -27,7 +27,7 @@ exports.startWatchParty = async (req, res) => {
// Controller for joining a watch party
exports.joinWatchParty = async (req, res) => {
const { username, partyCode } = req.body;
res.setHeader('Access-Control-Allow-Origin', 'chrome-extension://gghoblbehihhmceogmigccldhmnnfeoc');
res.setHeader('Access-Control-Allow-Origin', 'chrome-extension://mbmbholeiljjaabgbpajlfalcbnciona');
if (!username || !partyCode) {
return res.status(400).json({ success: false, message: 'Username and Party Code are required' });
}
Expand All @@ -54,25 +54,38 @@ exports.joinWatchParty = async (req, res) => {
// Fetch chat messages for a watch party
exports.getWatchPartyChatMessages = async (req, res) => {
console.log('Get Watch Party-controller');
res.setHeader('Access-Control-Allow-Origin', 'chrome-extension://gghoblbehihhmceogmigccldhmnnfeoc');
res.setHeader('Access-Control-Allow-Origin', 'chrome-extension://mbmbholeiljjaabgbpajlfalcbnciona');
res.setHeader('Access-Control-Allow-Origin', 'https://www.netflix.com');

const { partyCode } = req.params;
console.log("Controller party Code-> ", partyCode);

try {
const { roomId } = await partyService.getWatchPartyByCode(partyCode); // Ensure this function exists in the service
const messages = await partyService.getWatchPartyMessages(roomId);

if (messages.success) {
res.status(200).json(messages);
if (messages.messages.length === 0) {
console.log("LOOK?");
return res.status(200).json({ success: true, message: "No messages available in the chat room." });
}
return res.status(200).json(messages); // Return the messages if available
} else {
return res.status(500).json({ success: false, error: 'Failed to fetch chat messages' });
}

} catch (error) {
console.error('Error fetching chat messages:', error);
res.status(500).json({ error: 'Failed to fetch chat messages' });
res.status(500).json({ success: false, error: 'Failed to fetch chat messages' });
}
};


// Send a chat message to a watch party
exports.sendWatchPartyChatMessage = async (req, res) => {
res.setHeader('Access-Control-Allow-Origin', 'chrome-extension://gghoblbehihhmceogmigccldhmnnfeoc');
res.setHeader('Access-Control-Allow-Origin', 'chrome-extension://mbmbholeiljjaabgbpajlfalcbnciona');
res.setHeader('Access-Control-Allow-Origin', 'https://www.netflix.com');

const { partyCode } = req.params;
const { username, text, id: messageId } = req.body; // Include messageId

Expand Down
8 changes: 4 additions & 4 deletions backend/src/utils/vectorizeMovies.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,22 +12,22 @@ function enhancedTokenize(text, n = 1) {
return n === 1 ? filteredTokens : natural.NGrams.ngrams(filteredTokens, n).map(ngram => ngram.join(' '));
}

function vectorizeMovies(combinedFeatures) {
function vectorizeMovies(combinedFeatures, n = 1) {
const tfidf = new TfIdf();
const termSet = new Set();

// First pass: Add each movie's combined features to the TfIdf instance
combinedFeatures.forEach(features => {
if (features.trim().length > 0) { // Check for non-empty features
const tokens = enhancedTokenize(features, 1); // Use unigrams
const tokens = enhancedTokenize(features, n); // Use n-grams (can be unigrams, bigrams, etc.)
tfidf.addDocument(tokens.join(' ')); // Rejoin tokens for TfIdf
tokens.forEach(term => {
termSet.add(term);
termSet.add(term); // Collect terms for vocabulary
});
}
});

const vocabulary = Array.from(termSet);
const vocabulary = Array.from(termSet); // Create the final vocabulary array
const movieVectors = combinedFeatures.map((features, index) => {
const vector = new Array(vocabulary.length).fill(0);
const terms = tfidf.listTerms(index);
Expand Down
20 changes: 14 additions & 6 deletions frontend/Chrome Extension/js/content.js
Original file line number Diff line number Diff line change
Expand Up @@ -997,8 +997,16 @@ async function sendMessage(partyCode, username, text) {
function addChatMessage(username, text, isUser, timestamp) {
const chatMessage = document.createElement("p");

// Format the timestamp
const formattedTime = new Date(timestamp).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" });
// Check if the timestamp is valid
const messageTimestamp = new Date(timestamp);
const isValidDate = !isNaN(messageTimestamp.getTime()); // Checks if the timestamp is valid

// If the timestamp is not valid, use the current time
const finalTimestamp = isValidDate ? messageTimestamp : new Date();

// Format the timestamp as HH:MM
const formattedTime = finalTimestamp.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" });

chatMessage.textContent = `[${username}] ${text} - ${formattedTime}`; // Append the timestamp to the message
chatMessage.className = "chat-message"; // Assign the base chat-message class

Expand Down Expand Up @@ -1071,10 +1079,10 @@ async function initChat() {
}
});
// Set an interval to fetch chat messages every 5 seconds
setInterval(() => {
fetchChatMessages(userDetails.username, userDetails.partyCode);
//createChatInputContainer(userDetails);
}, 5000); // Adjust interval as necessary (5000ms = 5 seconds)
// setInterval(() => {
// fetchChatMessages(userDetails.username, userDetails.partyCode);
// //createChatInputContainer(userDetails);
// }, 5000); // Adjust interval as necessary (5000ms = 5 seconds)
} catch (error) {
console.error('Initialization error:', error);
}
Expand Down

0 comments on commit ab165b0

Please sign in to comment.