diff --git a/backend/src/main/java/com/rit/portal/controller/CollabController.java b/backend/src/main/java/com/rit/portal/controller/CollabController.java index 63b977c..9c67879 100644 --- a/backend/src/main/java/com/rit/portal/controller/CollabController.java +++ b/backend/src/main/java/com/rit/portal/controller/CollabController.java @@ -101,7 +101,7 @@ public class CollabController { request.setApplicationsCount(request.getApplicationsCount() + 1); collabRequestRepository.save(request); - // Notify Telegram/Discord intermediary bot asynchronously + // Notify Telegram intermediary bot asynchronously new Thread(() -> { try { Map payload = new HashMap<>(); @@ -112,7 +112,6 @@ public class CollabController { payload.put("author_name", request.getAuthorName()); payload.put("contact_info", request.getContactInfo()); payload.put("telegram_chat_id", request.getTelegramChatId()); - payload.put("discord_user_id", request.getDiscordUserId()); payload.put("applicant_name", savedApp.getApplicantName()); payload.put("applicant_email", savedApp.getApplicantEmail()); payload.put("applicant_dept", savedApp.getApplicantDept()); @@ -172,11 +171,6 @@ public class CollabController { return collabRequestRepository.findByTelegramChatIdAndStatusOrderByCreatedAtDesc(chatId, "OPEN"); } - @GetMapping("/active/discord/{userId}") - public List getActiveRequestsByDiscord(@PathVariable String userId) { - return collabRequestRepository.findByDiscordUserIdAndStatusOrderByCreatedAtDesc(userId, "OPEN"); - } - @DeleteMapping("/{id}") public ResponseEntity deleteOrCancelCollabRequest(@PathVariable Integer id) { return collabRequestRepository.findById(id).map(req -> { diff --git a/backend/src/main/java/com/rit/portal/entity/CollabRequest.java b/backend/src/main/java/com/rit/portal/entity/CollabRequest.java index c74b4cc..af9059c 100644 --- a/backend/src/main/java/com/rit/portal/entity/CollabRequest.java +++ b/backend/src/main/java/com/rit/portal/entity/CollabRequest.java @@ -46,9 +46,6 @@ public class CollabRequest { @Column(name = "telegram_chat_id") private Long telegramChatId; - @Column(name = "discord_user_id") - private String discordUserId; - @Column(name = "status") @Builder.Default private String status = "OPEN"; diff --git a/backend/src/main/java/com/rit/portal/repository/CollabRequestRepository.java b/backend/src/main/java/com/rit/portal/repository/CollabRequestRepository.java index 6b337d0..997e41a 100644 --- a/backend/src/main/java/com/rit/portal/repository/CollabRequestRepository.java +++ b/backend/src/main/java/com/rit/portal/repository/CollabRequestRepository.java @@ -10,5 +10,4 @@ public interface CollabRequestRepository extends JpaRepository findAllByOrderByCreatedAtDesc(); List findByAuthorEmailOrderByCreatedAtDesc(String authorEmail); List findByTelegramChatIdAndStatusOrderByCreatedAtDesc(Long telegramChatId, String status); - List findByDiscordUserIdAndStatusOrderByCreatedAtDesc(String discordUserId, String status); } diff --git a/public/map.png b/public/map.png index ac1d795..cf71b5d 100644 Binary files a/public/map.png and b/public/map.png differ diff --git a/src/components/Navbar/Navbar.tsx b/src/components/Navbar/Navbar.tsx index 9822efa..3e47142 100644 --- a/src/components/Navbar/Navbar.tsx +++ b/src/components/Navbar/Navbar.tsx @@ -1,34 +1,77 @@ import { useState, useEffect, useRef } from 'react'; import { Link, useLocation } from 'react-router-dom'; import { motion, AnimatePresence } from 'framer-motion'; -import { Menu, X, LogIn, LogOut, ShieldCheck, ChevronDown, Rocket, Sparkles } from 'lucide-react'; -import { NAV_LINKS } from '@/constants'; +import { + Menu, + X, + LogIn, + LogOut, + ShieldCheck, + ChevronDown, + Map, + Bus, + UserCheck, + MessageCircle, + Rocket, + BookOpen, + Wrench, + Bot, + Users, + Code2, + Trophy, + Home as HomeIcon, +} from 'lucide-react'; import { useAuth } from '@/context/AuthContext'; +interface NavItem { + label: string; + path: string; + icon?: any; +} + +const PRIMARY_LINKS: NavItem[] = [ + { label: 'Home', path: '/', icon: HomeIcon }, + { label: 'Notes', path: '/notes', icon: BookOpen }, + { label: 'Toolkit', path: '/toolkit', icon: Wrench }, + { label: 'Chatbot', path: '/ai-assistant', icon: Bot }, + { label: 'Clubs & Centers', path: '/events', icon: Users }, + { label: 'Dev Collab', path: '/collab', icon: Code2 }, + { label: 'LeetCode', path: '/leetcode', icon: Trophy }, +]; + +const MORE_LINKS: NavItem[] = [ + { label: 'Campus Map', path: '/campus', icon: Map }, + { label: 'Bus Routes', path: '/bus-routes', icon: Bus }, + { label: 'Faculty Directory', path: '/faculty', icon: UserCheck }, + { label: 'Community', path: '/community', icon: MessageCircle }, + { label: 'RAISE Incubator', path: '/raise', icon: Rocket }, +]; + +const ALL_MOBILE_LINKS: NavItem[] = [...PRIMARY_LINKS, ...MORE_LINKS]; + export default function Navbar() { - const [isScrolled, setIsScrolled] = useState(false); const [isMobileOpen, setIsMobileOpen] = useState(false); const [isProfileOpen, setIsProfileOpen] = useState(false); + const [isMoreOpen, setIsMoreOpen] = useState(false); const location = useLocation(); const { user, isAuthenticated, isVerifiedStudent, loginWithGoogle, logout } = useAuth(); const profileRef = useRef(null); - - useEffect(() => { - const handleScroll = () => setIsScrolled(window.scrollY > 20); - window.addEventListener('scroll', handleScroll); - return () => window.removeEventListener('scroll', handleScroll); - }, []); + const moreRef = useRef(null); useEffect(() => { setIsMobileOpen(false); + setIsMoreOpen(false); }, [location.pathname]); - // Close profile dropdown when clicking outside + // Close dropdowns when clicking outside useEffect(() => { const handleClickOutside = (e: MouseEvent) => { if (profileRef.current && !profileRef.current.contains(e.target as Node)) { setIsProfileOpen(false); } + if (moreRef.current && !moreRef.current.contains(e.target as Node)) { + setIsMoreOpen(false); + } }; document.addEventListener('mousedown', handleClickOutside); return () => document.removeEventListener('mousedown', handleClickOutside); @@ -36,84 +79,240 @@ export default function Navbar() { const checkIsActive = (linkPath: string) => { const [pathName] = linkPath.split('?'); - return location.pathname === pathName; + if (pathName === '/') return location.pathname === '/'; + return location.pathname.startsWith(pathName); }; + const isMoreActive = MORE_LINKS.some((link) => checkIsActive(link.path)); + return ( <> - {/* Fixed Header Layout Wrapper */} -
- {/* Grey Rectangular Capsule Container (Logo + Links ending with LeetCode) */} + {/* Unified Floating Navbar Container */} +
- {/* Logo - RIT Event Hub Style */} - - {/* Logo Image */} + {/* Left: Logo & College Brand */} + -
- + rit -
-
- RAJALAKSHMI - INSTITUTE OF - TECHNOLOGY +
+
+ Rajalakshmi + Institute of + Technology
-
+ - {/* Desktop Nav - Ends with LeetCode */} -
- {NAV_LINKS.map((link) => { + {/* Center: Desktop Navigation Links */} +
+ {PRIMARY_LINKS.map((link) => { const isActive = checkIsActive(link.path); return ( {isActive && ( )} {link.label} ); })} + + {/* "More" Dropdown Menu */} +
+ + + + {isMoreOpen && ( + + {MORE_LINKS.map((item) => { + const IconComponent = item.icon; + const active = checkIsActive(item.path); + return ( + setIsMoreOpen(false)} + className={`flex items-center gap-2.5 px-3 py-2 rounded-xl text-xs font-medium transition-all ${ + active + ? 'bg-gradient-to-r from-[#FF6B00] to-[#F97316] text-white font-bold' + : 'text-slate-300 hover:text-white hover:bg-white/10' + }`} + style={{ fontFamily: 'Plus Jakarta Sans, sans-serif' }} + > + {IconComponent && } + {item.label} + + ); + })} + + )} + +
- {/* Mobile Toggle */} -
+ {/* Right: Auth Profile / Sign In */} +
+ {isAuthenticated && user ? ( +
+ + + {/* Profile Dropdown */} + + {isProfileOpen && ( + +
+
+ {user.pictureUrl ? ( + {user.name} + ) : ( +
+ {user.name.charAt(0).toUpperCase()} +
+ )} +
+

{user.name}

+

{user.email}

+
+
+ {isVerifiedStudent ? ( +
+ + Verified RIT Student +
+ ) : ( +
+ + Sign in with @ritchennai.edu.in + +
+ )} +
+
+ +
+
+ )} +
+
+ ) : ( + + + Sign In + + )} +
+ + {/* Mobile Hamburger Toggle */} +
setIsMobileOpen(!isMobileOpen)} - className="w-9 h-9 rounded-xl flex items-center justify-center text-slate-200 hover:text-white bg-slate-800/60 hover:bg-slate-700/60 border border-slate-700/50 transition-all active:scale-95" + className="w-9 h-9 rounded-xl flex items-center justify-center text-slate-200 hover:text-white bg-slate-800/80 border border-white/10 transition-all active:scale-95" aria-label="Toggle menu" > {isMobileOpen ? : } @@ -128,189 +327,69 @@ export default function Navbar() { animate={{ height: 'auto', opacity: 1 }} exit={{ height: 0, opacity: 0 }} transition={{ duration: 0.25 }} - className="absolute left-0 top-full mt-2 w-full overflow-hidden md:hidden max-h-[80vh] overflow-y-auto z-50" + className="absolute left-0 top-full mt-2 w-full overflow-hidden lg:hidden max-h-[82vh] overflow-y-auto z-50 rounded-2xl border border-white/10 bg-slate-950/95 backdrop-blur-2xl shadow-2xl" > -
- {NAV_LINKS.map((link) => { - const isActive = checkIsActive(link.path); - return ( - setIsMobileOpen(false)} - className="px-3.5 py-3 rounded-xl text-xs font-semibold uppercase tracking-wider flex items-center justify-center text-center transition-all min-h-[44px]" - style={{ - fontFamily: 'Poppins, sans-serif', - color: isActive ? '#FFFFFF' : '#CBD5E1', - backgroundColor: isActive ? '#F97316' : 'rgba(255, 255, 255, 0.05)', - border: isActive ? '1px solid #FB923C' : '1px solid rgba(255, 255, 255, 0.08)', - }} - > - {link.label} - - ); - })} - - {/* Mobile RAISE Button */} - setIsMobileOpen(false)} - className="col-span-2 mt-1 flex items-center justify-center gap-2 px-4 py-2.5 rounded-xl text-white text-xs font-bold bg-gradient-to-r from-rose-500 to-pink-600 border border-rose-400/40 shadow-md shadow-rose-500/20 transition-all active:scale-95" - style={{ fontFamily: 'Poppins, sans-serif' }} - > - - Pitch your Idea - RAISE Incubator - +
+
+ {ALL_MOBILE_LINKS.map((link) => { + const isActive = checkIsActive(link.path); + const IconComp = link.icon; + return ( + setIsMobileOpen(false)} + className={`px-3 py-2.5 rounded-xl text-xs font-semibold flex items-center gap-2 transition-all ${ + isActive + ? 'bg-gradient-to-r from-[#FF6B00] to-[#F97316] text-white shadow-sm' + : 'bg-white/5 text-slate-200 hover:bg-white/10 border border-white/5' + }`} + style={{ fontFamily: 'Plus Jakarta Sans, sans-serif' }} + > + {IconComp && } + {link.label} + + ); + })} +
{/* Mobile Auth Button */} - {!isAuthenticated ? ( - - ) : ( - - )} +
+ {!isAuthenticated ? ( + + ) : ( + + )} +
)} +
- {/* Separate Top Right Section: Sign In on Top, RAISE Incubator Below */} - - {/* Sign Option (Top Right, Centered) */} - {isAuthenticated && user ? ( -
- - - {/* Profile Dropdown */} - - {isProfileOpen && ( - -
-
- {user.pictureUrl ? ( - {user.name} - ) : ( -
- {user.name.charAt(0).toUpperCase()} -
- )} -
-

{user.name}

-

{user.email}

-
-
- {isVerifiedStudent ? ( -
- - Verified RIT Student -
- ) : ( -
- - Sign in with @ritchennai.edu.in for verified badge - -
- )} -
-
- -
-
- )} -
-
- ) : ( - - - Sign In - - )} - - {/* RAISE Incubator Module (Below Sign Option) */} - - - Pitch your Idea - RAISE Incubator - -
-
- - {/* Spacer */} -
+ {/* Spacer to prevent content overlap */} +
); } diff --git a/src/constants/index.ts b/src/constants/index.ts index 6edc132..7de26d8 100644 --- a/src/constants/index.ts +++ b/src/constants/index.ts @@ -116,6 +116,15 @@ export const FEATURES: Feature[] = [ color: '#F59E0B', bgColor: '#FFFBEB', }, + { + id: 'raise', + icon: 'Rocket', + title: 'RAISE Incubator', + description: 'Pitch startup ideas, get incubation support, seed funding opportunities, and entrepreneurial mentoring.', + path: '/raise', + color: '#EC4899', + bgColor: '#FDF2F8', + }, ]; // ─── Bus Routes ────────────────────────────────────────────────────────────── @@ -3106,7 +3115,7 @@ export const CLUBS_DATA: Club[] = [ vicePresidentName: 'Harish A (CSE)', coordinatorName: 'Pradakshina Rajesh Bhavani (AI&DS)', year: 'Senior Year', - contactEmail: '', + contactEmail: 'nationalservicescheme@ritchennai.edu.in', contactPhone: '+91 8668130804', icon: 'HeartHandshake', logoUrl: '/clubs/nss.png', @@ -3125,7 +3134,7 @@ export const CLUBS_DATA: Club[] = [ vicePresidentName: 'Supeckshana M (CSE)', coordinatorName: 'Dr. S. Niranjana (VLSI)', year: 'Senior Year', - contactEmail: '', + contactEmail: 'ubarit@ritchennai.edu.in', contactPhone: '+91 7305642992', icon: 'Users', logoUrl: '/clubs/unnat_bharat.png', @@ -3144,7 +3153,7 @@ export const CLUBS_DATA: Club[] = [ vicePresidentName: 'Danish (VLSI)', coordinatorName: 'Rotaract Faculty Lead', year: 'Senior Year', - contactEmail: '', + contactEmail: 'rotaract@ritchennai.edu.in', contactPhone: '+91 7200696226', icon: 'HeartHandshake', logoUrl: '/clubs/rotaract.png', @@ -3163,7 +3172,7 @@ export const CLUBS_DATA: Club[] = [ vicePresidentName: 'Julinsa (CSE)', coordinatorName: 'Niraimathy Marthandan (Biotech)', year: 'Senior Year', - contactEmail: '', + contactEmail: 'youthredcross@ritchennai.edu.in', contactPhone: '+91 9344061221', icon: 'Heart', logoUrl: '/clubs/nss.png', @@ -3182,7 +3191,7 @@ export const CLUBS_DATA: Club[] = [ vicePresidentName: 'Indhuvadhani M (ECE)', coordinatorName: 'Ms. N. Suganthi (ECE)', year: 'Third Year', - contactEmail: '', + contactEmail: 'womenempowermentcell@ritchennai.edu.in', contactPhone: '+91 6385227710', icon: 'Sparkles', logoUrl: '/clubs/wec.png', @@ -3201,7 +3210,7 @@ export const CLUBS_DATA: Club[] = [ vicePresidentName: 'Pradakshina RB', coordinatorName: 'YUVA Faculty Lead', year: 'Senior Year', - contactEmail: '', + contactEmail: 'ciiyiclub@ritchennai.edu.in', contactPhone: '+91 9677043565', icon: 'Rocket', logoUrl: '/clubs/yuva.png', @@ -3220,7 +3229,7 @@ export const CLUBS_DATA: Club[] = [ vicePresidentName: 'Keerthana S (III CSE)', coordinatorName: 'Dr. J. Anitha (H&S)', year: 'III Year, CSE', - contactEmail: '', + contactEmail: 'wistemclub@ritchennai.edu.in', contactPhone: '+91 9600110150', icon: 'Atom', logoUrl: '/clubs/wistem.png', @@ -3240,7 +3249,7 @@ export const CLUBS_DATA: Club[] = [ vicePresidentName: 'Barath S (AIML)', coordinatorName: 'Prof. Avudai Selvi (CSE)', year: 'Senior Year', - contactEmail: '', + contactEmail: 'techspark@ritchennai.edu.in', contactPhone: '+91 8667466390', icon: 'Zap', logoUrl: '/clubs/techspark.png', @@ -3259,7 +3268,7 @@ export const CLUBS_DATA: Club[] = [ vicePresidentName: 'Sanjay Y (III ECE)', coordinatorName: 'Dr. A. Thilagavathy Vinoth (H&S)', year: 'Third Year', - contactEmail: '', + contactEmail: 'infinitus@ritchennai.edu.in', contactPhone: '+91 8838552068', icon: 'Calculator', logoUrl: '/clubs/infinitus.png', @@ -3278,7 +3287,7 @@ export const CLUBS_DATA: Club[] = [ vicePresidentName: 'Abhi Harish (III VLSI)', coordinatorName: 'Mr. Mohan Ram (AP/ECE)', year: 'III Year, VLSI', - contactEmail: '', + contactEmail: 'steam@ritchennai.edu.in', contactPhone: '+91 7550271227', icon: 'Printer', logoUrl: '/clubs/steam.png', @@ -3297,7 +3306,7 @@ export const CLUBS_DATA: Club[] = [ vicePresidentName: 'Jonathi (III AIDS)', coordinatorName: 'Prof. Shri Janani P (CSE)', year: 'III Year, CSE', - contactEmail: '', + contactEmail: 'culturalcell@ritchennai.edu.in', contactPhone: '+91 9025726185', icon: 'Sparkles', logoUrl: '/clubs/artist_league.png', @@ -3316,7 +3325,7 @@ export const CLUBS_DATA: Club[] = [ vicePresidentName: 'Sarvesh V (III VLSI)', coordinatorName: 'Mr. Keerthana Pandiyan (Biotech)', year: 'III Year, AIML', - contactEmail: '', + contactEmail: 'vaarithitamilclub@ritchennai.edu.in', contactPhone: '+91 8248701027', icon: 'BookOpen', logoUrl: '/clubs/vaarithi.png', @@ -3335,7 +3344,7 @@ export const CLUBS_DATA: Club[] = [ vicePresidentName: 'Farren Jeanvil', coordinatorName: 'Ms. Roopavathi', year: 'Third Year', - contactEmail: '', + contactEmail: 'nippon@ritchennai.edu.in', contactPhone: '+91 9042294493', icon: 'Globe', logoUrl: '/clubs/nippon.png', @@ -3354,7 +3363,7 @@ export const CLUBS_DATA: Club[] = [ vicePresidentName: 'Thirumalai (III VLSI)', coordinatorName: 'Ms. Fouzia Sulthana (AIDS)', year: 'III Year, ECE', - contactEmail: '', + contactEmail: 'podxrit@ritchennai.edu.in', contactPhone: '+91 9962840247', icon: 'Mic', logoUrl: '/clubs/podx.png', diff --git a/src/pages/AIAssistant/AIAssistant.tsx b/src/pages/AIAssistant/AIAssistant.tsx index 62c85b9..ff88662 100644 --- a/src/pages/AIAssistant/AIAssistant.tsx +++ b/src/pages/AIAssistant/AIAssistant.tsx @@ -139,19 +139,6 @@ export default function AIAssistant() { Telegram Bot - - - Discord Bot - = { dept: ['Homi Jehangir Bhabha Block', 'Nikola Tesla Block', 'C Block', 'Steve Jobs Block', 'Alan Turing Block', 'Green Building'], library: ['Green Building'], labs: ['MEC Lab', 'EPL Lab', 'Steve Jobs Block', 'Alan Turing Block', 'Nikola Tesla Block', 'C Block'], - hostel: ['Girls Hostel', 'Boys Mess'], - canteen: ['Canteen', 'Girls Mess', 'Boys Mess', 'Staff Mess', 'Arcade'], + hostel: ['Girls Hostel', 'Girls Mess'], + canteen: ['Canteen', 'Girls Mess', 'Arcade'], auditorium: ['Steve Jobs Block', 'Campus Centre'], sports: ['Playground'], }; diff --git a/src/pages/DevCollab/DevCollab.tsx b/src/pages/DevCollab/DevCollab.tsx index 1a0ffd5..b7042c4 100644 --- a/src/pages/DevCollab/DevCollab.tsx +++ b/src/pages/DevCollab/DevCollab.tsx @@ -24,7 +24,6 @@ export interface CollabRequestItem { acceptedCount?: number; contactInfo?: string; telegramChatId?: number; - discordUserId?: string; status: string; applicationsCount: number; createdAt: string; diff --git a/src/pages/Home/Home.tsx b/src/pages/Home/Home.tsx index 75d5888..7c567d2 100644 --- a/src/pages/Home/Home.tsx +++ b/src/pages/Home/Home.tsx @@ -90,19 +90,6 @@ export default function Home() { Ask Assistant - - - - - Pitch your Idea - RAISE - - - diff --git a/src/pages/Notes/Notes.tsx b/src/pages/Notes/Notes.tsx index 0e89465..3d818e3 100644 --- a/src/pages/Notes/Notes.tsx +++ b/src/pages/Notes/Notes.tsx @@ -309,7 +309,7 @@ const CS_TOOLS_CATEGORIES = [ bg: '#FEFCE8', tools: [ { name: 'Documentation', desc: 'Confluence, Notion, MkDocs, and Docusaurus.', url: 'https://docusaurus.io/' }, - { name: 'Team Communication', desc: 'Slack, Microsoft Teams, Discord, and Mattermost.', url: 'https://slack.com/' }, + { name: 'Team Communication', desc: 'Slack, Microsoft Teams, and Mattermost.', url: 'https://slack.com/' }, { name: 'Project Management', desc: 'Jira, Trello, Asana, and Linear ticket tracking.', url: 'https://linear.app/' }, { name: 'Whiteboarding', desc: 'Miro, FigJam, Excalidraw, and Lucidchart.', url: 'https://excalidraw.com/' }, { name: 'Knowledge Management', desc: 'Obsidian, Notion, and Slite workspace wiki.', url: 'https://obsidian.md/' } diff --git a/src/pages/Toolkit/Toolkit.tsx b/src/pages/Toolkit/Toolkit.tsx index abb7b2e..365b98b 100644 --- a/src/pages/Toolkit/Toolkit.tsx +++ b/src/pages/Toolkit/Toolkit.tsx @@ -278,7 +278,7 @@ const CS_TOOLS_CATEGORIES = [ bg: '#FEFCE8', tools: [ { name: 'Documentation', desc: 'Confluence, Notion, MkDocs, and Docusaurus.', url: 'https://docusaurus.io/' }, - { name: 'Team Communication', desc: 'Slack, Microsoft Teams, Discord, and Mattermost.', url: 'https://slack.com/' }, + { name: 'Team Communication', desc: 'Slack, Microsoft Teams, and Mattermost.', url: 'https://slack.com/' }, { name: 'Project Management', desc: 'Jira, Trello, Asana, and Linear ticket tracking.', url: 'https://linear.app/' }, { name: 'Whiteboarding', desc: 'Miro, FigJam, Excalidraw, and Lucidchart.', url: 'https://excalidraw.com/' }, { name: 'Knowledge Management', desc: 'Obsidian, Notion, and Slite workspace wiki.', url: 'https://obsidian.md/' } @@ -766,7 +766,20 @@ export default function Toolkit() { const [calculationType, setCalculationType] = useState<'gpa' | 'internal' | null>(null); const [gpaStep, setGpaStep] = useState<1 | 2>(1); const [calculatedScore, setCalculatedScore] = useState(null); - const [subjects, setSubjects] = useState([ + interface SubjectItem { + name: string; + credits: number; + grade: string; + cat1: string; + cat2: string; + assignment: string; + attendance: string; + isElective?: boolean; + electiveSlot?: number; + electiveCode?: string; + } + + const [subjects, setSubjects] = useState([ { name: 'Subject 1', credits: 4, grade: 'O', cat1: '45', cat2: '48', assignment: '10', attendance: '95' }, { name: 'Subject 2', credits: 3, grade: 'A+', cat1: '42', cat2: '44', assignment: '9', attendance: '92' }, { name: 'Subject 3', credits: 3, grade: 'A', cat1: '38', cat2: '40', assignment: '8', attendance: '88' }, diff --git a/telegram-bot/config.json b/telegram-bot/config.json index 5716b56..3d052ea 100644 --- a/telegram-bot/config.json +++ b/telegram-bot/config.json @@ -1,8 +1,6 @@ { "community_bot_token": "", "telegram_bot_token": "", - "helper_chat_ids": [971749136, 5567776672,1873240361], - "discord_bot_token": "", - "discord_helper_user_ids": [789393727641878568], + "helper_chat_ids": [971749136, 5567776672, 1873240361], "spring_backend_url": "http://localhost:8085" } diff --git a/telegram-bot/requirements.txt b/telegram-bot/requirements.txt index 24fd1e2..c85c38d 100644 --- a/telegram-bot/requirements.txt +++ b/telegram-bot/requirements.txt @@ -2,4 +2,3 @@ requests>=2.28.0 fastapi>=0.95.0 uvicorn>=0.20.0 pydantic>=2.0 -discord.py>=2.3.0 diff --git a/telegram-bot/telegram_bot.py b/telegram-bot/telegram_bot.py index 3e2eae5..398f257 100644 --- a/telegram-bot/telegram_bot.py +++ b/telegram-bot/telegram_bot.py @@ -6,7 +6,6 @@ import time import logging import requests import asyncio -import discord from typing import Optional from fastapi import FastAPI, HTTPException from fastapi.middleware.cors import CORSMiddleware @@ -53,7 +52,6 @@ def load_config(): config = load_config() COMMUNITY_BOT_TOKEN = os.environ.get("COMMUNITY_BOT_TOKEN") or config.get("community_bot_token", "8859374355:AAH0dhwstkTBhRerRTjzmb2RG2fjPbigzvo") BOT_TOKEN = os.environ.get("TELEGRAM_BOT_TOKEN") or config.get("telegram_bot_token") -DISCORD_TOKEN = os.environ.get("DISCORD_BOT_TOKEN") or config.get("discord_bot_token") BACKEND_URL = os.environ.get("SPRING_BACKEND_URL") or config.get("spring_backend_url", "http://localhost:8085") # Database Setup @@ -611,292 +609,8 @@ def telegram_polling_thread(): logging.error(f"Error in 24/7 chatbot polling loop: {e}") time.sleep(5) -# Discord Action View for Collaboration Applications -class DiscordCollabActionView(discord.ui.View): - def __init__(self, application_id: int, applicant_contact: str): - super().__init__(timeout=86400) - self.application_id = application_id - self.applicant_contact = applicant_contact - - @discord.ui.button(label="Accept Collaboration", style=discord.ButtonStyle.success, emoji="✅") - async def accept_button(self, interaction: discord.Interaction, button: discord.ui.Button): - try: - requests.put(f"{BACKEND_URL}/api/collab/applications/{self.application_id}/status?status=ACCEPTED", timeout=5) - await interaction.response.send_message( - f"✅ **Collaboration Request Accepted!**\nDirect contact details: **{self.applicant_contact}**", - ephemeral=False - ) - for item in self.children: - item.disabled = True - await interaction.message.edit(view=self) - except Exception as e: - await interaction.response.send_message(f"❌ Error updating status: {e}", ephemeral=True) - - @discord.ui.button(label="Decline Request", style=discord.ButtonStyle.danger, emoji="❌") - async def decline_button(self, interaction: discord.Interaction, button: discord.ui.Button): - try: - requests.put(f"{BACKEND_URL}/api/collab/applications/{self.application_id}/status?status=REJECTED", timeout=5) - await interaction.response.send_message("❌ **Collaboration Request Declined.**", ephemeral=False) - for item in self.children: - item.disabled = True - await interaction.message.edit(view=self) - except Exception as e: - await interaction.response.send_message(f"❌ Error updating status: {e}", ephemeral=True) - -async def broadcast_discord_collab_application(application_id: int, project_idea: str, tag: str, applicant_name: str, applicant_dept: str, applicant_year: str, applicant_contact: str, message: str, user_ids: list): - formatted_msg = ( - f"🤝 **New Collaboration Request for your Project!**\n\n" - f"📌 **Project Idea:** {project_idea}\n" - f"🏷️ **Tag:** `{tag}`\n\n" - f"👤 **Applicant:** {applicant_name} ({applicant_dept}, {applicant_year})\n" - f"💬 **Message:** {message or 'No message provided'}\n" - f"📱 **Contact Info:** {applicant_contact}\n\n" - f"Click a button below to respond:" - ) - view = DiscordCollabActionView(application_id, applicant_contact) - for user_id_val in user_ids: - try: - user_id = int(user_id_val) - user = await discord_client.fetch_user(user_id) - if user: - await user.send(content=formatted_msg, view=view) - logging.info(f"Sent Discord collab notification DM to user {user_id}") - except Exception as e: - logging.error(f"Failed to send Discord collab DM to {user_id_val}: {e}") - -# Discord Remove Select View -class DiscordCollabRemoveSelect(discord.ui.Select): - def __init__(self, active_requests: list): - options = [] - for req in active_requests: - req_id = str(req["id"]) - snippet = req.get("projectIdea", "Project")[:45] - options.append(discord.SelectOption( - label=f"Cancel: {snippet}", - value=req_id, - description=f"Tag: {req.get('tag')} | Dept: {req.get('department')}", - emoji="❌" - )) - super().__init__(placeholder="Select a request to remove...", min_values=1, max_values=1, options=options) - - async def callback(self, interaction: discord.Interaction): - req_id = self.values[0] - try: - res = requests.delete(f"{BACKEND_URL}/api/collab/{req_id}", timeout=5) - if res.status_code in [200, 204]: - await interaction.response.send_message("✅ **Collaboration Request cancelled and removed from RIT Dev Hub.**", ephemeral=False) - else: - await interaction.response.send_message("❌ Failed to cancel request.", ephemeral=True) - except Exception as e: - await interaction.response.send_message(f"❌ Error: {e}", ephemeral=True) - -class DiscordCollabRemoveView(discord.ui.View): - def __init__(self, active_requests: list): - super().__init__(timeout=180) - self.add_item(DiscordCollabRemoveSelect(active_requests)) - -# Discord Native Interactive UI Components (Modal & Dropdown View) -class CollabModal(discord.ui.Modal, title="Post Collaboration Request"): - author_name = discord.ui.TextInput(label="1) Your Name", placeholder="e.g. Priyan Sharma", required=True) - dept_and_year = discord.ui.TextInput(label="2) Dept & Year", placeholder="e.g. CSE, 1st Year", required=True, default="CSE, 1st Year") - collaborators_needed = discord.ui.TextInput(label="3) Collaborators Needed", placeholder="e.g. 2", required=True, default="1") - project_idea = discord.ui.TextInput(label="4) Project Idea & Details", style=discord.TextStyle.paragraph, placeholder="Describe your project idea...", required=True) - github_link = discord.ui.TextInput(label="5) GitHub Link (Optional)", placeholder="https://github.com/...", required=False) - - def __init__(self, tag: str): - super().__init__() - self.selected_tag = tag - - async def on_submit(self, interaction: discord.Interaction): - try: - num_needed = int(self.collaborators_needed.value) - except ValueError: - num_needed = 1 - - dept_val = "CSE" - year_val = "1st Year" - if "," in self.dept_and_year.value: - parts = [p.strip() for p in self.dept_and_year.value.split(",", 1)] - dept_val = parts[0] - year_val = parts[1] - else: - dept_val = self.dept_and_year.value.strip() - - payload = { - "authorName": self.author_name.value, - "department": dept_val, - "year": year_val, - "collaboratorsNeeded": num_needed, - "tag": self.selected_tag, - "projectIdea": self.project_idea.value, - "githubLink": self.github_link.value or None, - "contactInfo": f"Discord: {interaction.user.name}", - "discordUserId": str(interaction.user.id) - } - try: - res = requests.post(f"{BACKEND_URL}/api/collab", json=payload, timeout=10) - if res.status_code in [200, 201]: - await interaction.response.send_message( - f"🎉 **Collaboration Request posted live to RIT Dev Hub!**\n" - f"👤 **Author:** {self.author_name.value} ({dept_val}, {year_val})\n" - f"📌 **Project:** {self.project_idea.value}\n" - f"🏷️ **Tag:** `{self.selected_tag}`\n" - f"👥 **Collaborators Needed:** `{num_needed}`", - ephemeral=False - ) - else: - await interaction.response.send_message(f"❌ Failed to save request (Status: {res.status_code})", ephemeral=True) - except Exception as e: - await interaction.response.send_message(f"❌ Error connecting to backend: {e}", ephemeral=True) - -class CollabTagSelect(discord.ui.Select): - def __init__(self): - options = [ - discord.SelectOption( - label="Co-developing from scratch", - value="looking for co-developing a project from scratch", - description="Build a brand new project from scratch together", - emoji="🚀" - ), - discord.SelectOption( - label="Beta Testers needed", - value="looking for beta testers", - description="Test early builds and provide user feedback", - emoji="🧪" - ), - discord.SelectOption( - label="Open-Source Contributors", - value="looking for Open-source Collaborators/Contributers", - description="Open repository seeking PRs and contributors", - emoji="🌐" - ), - ] - super().__init__(placeholder="Scroll down to select a tag for your request...", min_values=1, max_values=1, options=options) - - async def callback(self, interaction: discord.Interaction): - selected_tag = self.values[0] - modal = CollabModal(tag=selected_tag) - await interaction.response.send_modal(modal) - -class CollabView(discord.ui.View): - def __init__(self): - super().__init__(timeout=180) - self.add_item(CollabTagSelect()) - -# Discord Bot Client Setup -intents = discord.Intents.default() -intents.messages = True -intents.message_content = True - -discord_client = discord.Client(intents=intents) -discord_loop = None - -@discord_client.event -async def on_ready(): - logging.info(f"Discord Bot logged in as {discord_client.user}!") - -@discord_client.event -async def on_message(message): - if message.author == discord_client.user: - return - - is_dm = isinstance(message.channel, discord.DMChannel) - is_mention = discord_client.user in message.mentions - - if not is_dm and not is_mention: - return - - content = message.content - if is_mention: - mention_str = f"<@{discord_client.user.id}>" - mention_nick_str = f"<@!{discord_client.user.id}>" - content = content.replace(mention_str, "").replace(mention_nick_str, "").strip() - - # Discord /collab & /remove commands disabled as requested - if content.lower().startswith("/collab") or content.lower().startswith("/remove") or content.lower().startswith("/cancel"): - await message.reply("ℹ️ **Dev Hub collaboration requests via Discord are currently disabled.** Please use the RIT Dev Hub website (https://rit-services.in/dev-collab) or the Telegram bot (@Ritchatbot_bot).") - return - - # Check if this message is a reply to a question DM sent to a helper - if message.reference and message.reference.message_id: - question_id = get_question_id(message.author.id, message.reference.message_id) - if question_id: - author_name = message.author.display_name or message.author.name or "Senior Helper" - logging.info(f"Submitting answer for question {question_id} by Discord helper '{author_name}'") - backend_endpoint = f"{BACKEND_URL}/api/questions/{question_id}/answers" - answer_payload = { - "body": content, - "author": author_name - } - try: - def call_backend(): - return requests.post(backend_endpoint, json=answer_payload, timeout=10) - - loop = asyncio.get_event_loop() - res = await loop.run_in_executor(None, call_backend) - if res.status_code in [200, 201]: - await message.reply("✅ **Answer posted successfully to the Q&A board!**") - else: - await message.reply(f"❌ **Failed to post answer to backend.** (Status: {res.status_code})") - except Exception as e: - logging.error(f"Error calling backend endpoint {backend_endpoint}: {e}") - await message.reply(f"❌ **Connection error to backend.** ({e})") - return - - # Direct query fallback to Go chatbot service - if not content.strip(): - return - - logging.info(f"Querying Go chatbot service for Discord user {message.author.id}: '{content}'") - chatbot_service_url = "http://localhost:8081/api/chat" - try: - def call_chatbot(): - return requests.post(chatbot_service_url, json={"message": content}, timeout=10) - - loop = asyncio.get_event_loop() - res = await loop.run_in_executor(None, call_chatbot) - - if res.status_code == 200: - ans_data = res.json() - bot_response = ans_data.get("answer", "I am having trouble processing that question.") - await message.reply(bot_response) - else: - logging.error(f"Go chatbot API returned status code {res.status_code}") - await message.reply("⚠️ The RIT Chatbot service is currently experiencing issues. Please try again later.") - except Exception as e: - logging.error(f"Failed to connect to Go chatbot service: {e}") - await message.reply("⚠️ I cannot connect to the RIT Chatbot database right now. Please make sure the service is online.") - -async def broadcast_discord_question(question_id: int, title: str, body: str, author: str, user_ids: list): - formatted_msg = ( - f"❓ **New Student Question!**\n\n" - f"👤 **Author:** {author}\n" - f"📌 **Topic:** {title}\n" - f"📝 **Details:** {body}\n\n" - f"💬 **Reply directly to this message to submit your answer.**" - ) - for user_id_val in user_ids: - try: - user_id = int(user_id_val) - user = await discord_client.fetch_user(user_id) - if user: - msg = await user.send(formatted_msg) - save_mapping(user_id, msg.id, question_id) - logging.info(f"Sent Discord DM to helper {user_id}") - except Exception as e: - logging.error(f"Failed to send Discord DM to helper {user_id_val}: {e}") - -async def run_discord_bot(): - global discord_loop - logging.info("Starting Discord bot...") - discord_loop = asyncio.get_running_loop() - try: - await discord_client.start(DISCORD_TOKEN) - except Exception as e: - logging.error(f"Discord Bot failed to run: {e}") - # FastAPI Web Server Setup -app = FastAPI(title="RIT Telegram & Discord Intermediary Bot HTTP Server") +app = FastAPI(title="RIT Telegram Intermediary Bot HTTP Server") app.add_middleware( CORSMiddleware, @@ -920,7 +634,6 @@ class CollabApplicationPayload(BaseModel): author_name: str contact_info: Optional[str] = None telegram_chat_id: Optional[int] = None - discord_user_id: Optional[str] = None applicant_name: str applicant_dept: str applicant_year: str @@ -932,7 +645,7 @@ def send_question(payload: QuestionPayload): current_config = load_config() comm_token = os.environ.get("COMMUNITY_BOT_TOKEN") or current_config.get("community_bot_token", COMMUNITY_BOT_TOKEN) - # 1. Telegram Broadcast via Senior Helper Community Bot + # Telegram Broadcast via Senior Helper Community Bot telegram_helpers = current_config.get("helper_chat_ids", []) telegram_sent = 0 if telegram_helpers and comm_token: @@ -951,27 +664,9 @@ def send_question(payload: QuestionPayload): save_mapping(chat_id, message_id, payload.question_id) telegram_sent += 1 - # 2. Discord Broadcast - discord_helpers = current_config.get("discord_helper_user_ids", []) - discord_sent = 0 - if discord_helpers and DISCORD_TOKEN: - logging.info(f"Broadcasting question {payload.question_id} to {len(discord_helpers)} Discord helpers.") - if discord_loop: - try: - asyncio.run_coroutine_threadsafe( - broadcast_discord_question(payload.question_id, payload.title, payload.body, payload.author, discord_helpers), - discord_loop - ) - discord_sent = len(discord_helpers) - except Exception as e: - logging.error(f"Error scheduling Discord broadcast: {e}") - else: - logging.warning("Discord loop not running. Skipping Discord broadcast.") - return { "status": "success", - "telegram_delivered_to": telegram_sent, - "discord_queued_for": discord_sent + "telegram_delivered_to": telegram_sent } @app.post("/send_collab_application") @@ -980,9 +675,8 @@ def send_collab_application(payload: CollabApplicationPayload): bot_token = os.environ.get("TELEGRAM_BOT_TOKEN") or current_config.get("telegram_bot_token") telegram_sent = False - discord_sent = False - # 1. Telegram Notification strictly to Person A (the project author) + # Telegram Notification strictly to Person A (the project author) chat_id = payload.telegram_chat_id if chat_id: @@ -1009,13 +703,9 @@ def send_collab_application(payload: CollabApplicationPayload): else: logging.info(f"Skipping Telegram notification for collab application {payload.application_id}: No telegram_chat_id provided for author.") - # 2. Discord Broadcast - Disabled as requested - discord_sent = False - return { "status": "success", - "telegram_sent": telegram_sent, - "discord_sent": discord_sent + "telegram_sent": telegram_sent } async def run_uvicorn(): @@ -1030,14 +720,7 @@ async def main(): chat_thread = threading.Thread(target=telegram_polling_thread, daemon=True) chat_thread.start() - tasks = [] - if DISCORD_TOKEN: - tasks.append(run_discord_bot()) - else: - logging.warning("Discord Bot Token is empty. Skipping Discord bot startup.") - - tasks.append(run_uvicorn()) - await asyncio.gather(*tasks) + await run_uvicorn() if __name__ == "__main__": try: