Added SocialManager, Updated Chat.
This commit is contained in:
parent
1de6fc980d
commit
69f5523454
@ -12,6 +12,7 @@ const ChatTab = () => {
|
|||||||
const [inputValue, setInputValue] = useState("");
|
const [inputValue, setInputValue] = useState("");
|
||||||
const [showSidebar, setShowSidebar] = useState(true);
|
const [showSidebar, setShowSidebar] = useState(true);
|
||||||
const [isMobile, setIsMobile] = useState(window.innerWidth <= 768);
|
const [isMobile, setIsMobile] = useState(window.innerWidth <= 768);
|
||||||
|
const [confirmUnfriend, setConfirmUnfriend] = useState(null);
|
||||||
|
|
||||||
const messagesEndRef = useRef(null);
|
const messagesEndRef = useRef(null);
|
||||||
|
|
||||||
@ -80,6 +81,14 @@ const ChatTab = () => {
|
|||||||
setSearchResults([]);
|
setSearchResults([]);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const removeFriend = () => {
|
||||||
|
if (confirmUnfriend) {
|
||||||
|
socket.emit("friend:remove", { friendId: confirmUnfriend.id });
|
||||||
|
if (activeChat === confirmUnfriend.id) setActiveChat("global");
|
||||||
|
setConfirmUnfriend(null);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const sendMessage = () => {
|
const sendMessage = () => {
|
||||||
if (!inputValue.trim() || !socket) return;
|
if (!inputValue.trim() || !socket) return;
|
||||||
socket.emit("chat:send_message", {
|
socket.emit("chat:send_message", {
|
||||||
@ -88,8 +97,6 @@ const ChatTab = () => {
|
|||||||
receiverId: activeChat === "global" ? null : activeChat,
|
receiverId: activeChat === "global" ? null : activeChat,
|
||||||
});
|
});
|
||||||
setInputValue("");
|
setInputValue("");
|
||||||
|
|
||||||
console.log(activeChat === "global" ? null : activeChat);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const selectChat = (id) => {
|
const selectChat = (id) => {
|
||||||
@ -105,6 +112,29 @@ const ChatTab = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`chat-container ${isMobile ? "mobile" : ""}`}>
|
<div className={`chat-container ${isMobile ? "mobile" : ""}`}>
|
||||||
|
{confirmUnfriend && (
|
||||||
|
<div className="modal-overlay">
|
||||||
|
<div className="modal-content">
|
||||||
|
<h3>TERMINATE_CONTACT</h3>
|
||||||
|
<p>
|
||||||
|
Are you sure you want to remove {confirmUnfriend.username} from
|
||||||
|
your contacts?
|
||||||
|
</p>
|
||||||
|
<div className="modal-actions">
|
||||||
|
<button className="confirm-btn" onClick={removeFriend}>
|
||||||
|
CONFIRM
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className="cancel-btn"
|
||||||
|
onClick={() => setConfirmUnfriend(null)}
|
||||||
|
>
|
||||||
|
CANCEL
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
<aside
|
<aside
|
||||||
className={`chat-sidebar ${isMobile && !showSidebar ? "hidden" : ""}`}
|
className={`chat-sidebar ${isMobile && !showSidebar ? "hidden" : ""}`}
|
||||||
>
|
>
|
||||||
@ -142,9 +172,11 @@ const ChatTab = () => {
|
|||||||
className={`chat-item ${activeChat === "global" ? "active" : ""}`}
|
className={`chat-item ${activeChat === "global" ? "active" : ""}`}
|
||||||
onClick={() => selectChat("global")}
|
onClick={() => selectChat("global")}
|
||||||
>
|
>
|
||||||
|
<div className="chat-item-main">
|
||||||
<i className="fas fa-globe"></i>
|
<i className="fas fa-globe"></i>
|
||||||
<span>GLOBAL_CHANNEL</span>
|
<span>GLOBAL_CHANNEL</span>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="friends-section-label">
|
<div className="friends-section-label">
|
||||||
<span className="label-text">CONTACTS</span>
|
<span className="label-text">CONTACTS</span>
|
||||||
@ -156,6 +188,9 @@ const ChatTab = () => {
|
|||||||
<div
|
<div
|
||||||
key={friend.id}
|
key={friend.id}
|
||||||
className={`chat-item ${activeChat === friend.id ? "active" : ""}`}
|
className={`chat-item ${activeChat === friend.id ? "active" : ""}`}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="chat-item-main"
|
||||||
onClick={() => selectChat(friend.id)}
|
onClick={() => selectChat(friend.id)}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
@ -163,6 +198,16 @@ const ChatTab = () => {
|
|||||||
></div>
|
></div>
|
||||||
<span>{friend.username}</span>
|
<span>{friend.username}</span>
|
||||||
</div>
|
</div>
|
||||||
|
<button
|
||||||
|
className="unfriend-btn"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
setConfirmUnfriend(friend);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<i className="fas fa-user-minus"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -14,6 +14,7 @@
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
transition: all 0.3s ease;
|
transition: all 0.3s ease;
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
|
background: rgba(10, 15, 24, 0.6);
|
||||||
}
|
}
|
||||||
|
|
||||||
.chat-main {
|
.chat-main {
|
||||||
@ -21,9 +22,303 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
background: rgba(0, 0, 0, 0.2);
|
background: rgba(0, 0, 0, 0.2);
|
||||||
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* МОБІЛЬНА ВЕРСІЯ */
|
/* SEARCH SECTION */
|
||||||
|
.search-section {
|
||||||
|
padding: 20px 15px 15px;
|
||||||
|
border-bottom: 1px solid var(--border-color);
|
||||||
|
position: relative;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-input-wrapper {
|
||||||
|
display: flex;
|
||||||
|
gap: 5px;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-input-wrapper input {
|
||||||
|
flex: 1;
|
||||||
|
background: #05080c;
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
color: #fff;
|
||||||
|
padding: 8px;
|
||||||
|
font-size: 12px;
|
||||||
|
font-family: "Geologica", sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-results-dropdown {
|
||||||
|
position: absolute;
|
||||||
|
top: 100%;
|
||||||
|
left: 15px;
|
||||||
|
right: 15px;
|
||||||
|
background: #0a0f18;
|
||||||
|
border: 1px solid var(--primary-color);
|
||||||
|
border-top: none;
|
||||||
|
max-height: 200px;
|
||||||
|
overflow-y: auto;
|
||||||
|
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.8);
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-result-item {
|
||||||
|
padding: 10px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 12px;
|
||||||
|
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-result-item:hover {
|
||||||
|
background: rgba(0, 212, 255, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* CHAT LIST & ITEMS */
|
||||||
|
.chats-list {
|
||||||
|
flex: 1;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.friends-section-label {
|
||||||
|
padding: 20px 15px 10px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
opacity: 0.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.label-text {
|
||||||
|
font-size: 10px;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.label-line {
|
||||||
|
flex: 1;
|
||||||
|
height: 1px;
|
||||||
|
background: var(--border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-item {
|
||||||
|
padding: 12px 15px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
cursor: pointer;
|
||||||
|
border-bottom: 1px solid rgba(255, 255, 255, 0.02);
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-item:hover {
|
||||||
|
background: rgba(255, 255, 255, 0.03);
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-item.active {
|
||||||
|
background: rgba(0, 212, 255, 0.1);
|
||||||
|
box-shadow: inset 4px 0 0 var(--primary-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-item-main {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-dot {
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
border-radius: 50%;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-dot.online {
|
||||||
|
background: #00ff88;
|
||||||
|
box-shadow: 0 0 5px #00ff88;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-dot.offline {
|
||||||
|
background: #444;
|
||||||
|
}
|
||||||
|
|
||||||
|
.unfriend-btn {
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
color: rgba(255, 255, 255, 0.2);
|
||||||
|
padding: 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-item:hover .unfriend-btn {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.unfriend-btn:hover {
|
||||||
|
color: #ff3e3e;
|
||||||
|
text-shadow: 0 0 8px rgba(255, 62, 62, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* CHAT MAIN AREA */
|
||||||
|
.chat-header {
|
||||||
|
padding: 15px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
border-bottom: 1px solid var(--border-color);
|
||||||
|
background: rgba(10, 15, 24, 0.8);
|
||||||
|
}
|
||||||
|
|
||||||
|
.active-chat-info {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.active-chat-info i {
|
||||||
|
color: var(--primary-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-messages {
|
||||||
|
flex: 1;
|
||||||
|
padding: 15px;
|
||||||
|
overflow-y: auto;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message {
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.msg-time {
|
||||||
|
color: rgba(255, 255, 255, 0.3);
|
||||||
|
margin-right: 8px;
|
||||||
|
font-family: monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
.msg-author {
|
||||||
|
color: var(--primary-color);
|
||||||
|
font-weight: bold;
|
||||||
|
margin-right: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message.system .msg-author {
|
||||||
|
color: #ff3e3e;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message.system .msg-text {
|
||||||
|
color: #888;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-input-area {
|
||||||
|
padding: 15px;
|
||||||
|
background: #05080c;
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
border-top: 1px solid var(--border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-input-area input {
|
||||||
|
flex: 1;
|
||||||
|
background: rgba(255, 255, 255, 0.05);
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
color: #fff;
|
||||||
|
padding: 10px 15px;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.send-btn {
|
||||||
|
background: transparent;
|
||||||
|
border: 1px solid var(--primary-color);
|
||||||
|
color: var(--primary-color);
|
||||||
|
padding: 0 20px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.send-btn:hover {
|
||||||
|
background: var(--primary-color);
|
||||||
|
color: #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* MODAL STYLES */
|
||||||
|
.modal-overlay {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background: rgba(0, 0, 0, 0.85);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
z-index: 100;
|
||||||
|
backdrop-filter: blur(4px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-content {
|
||||||
|
background: #0a0f18;
|
||||||
|
border: 1px solid #ff3e3e;
|
||||||
|
padding: 25px;
|
||||||
|
width: 90%;
|
||||||
|
max-width: 350px;
|
||||||
|
text-align: center;
|
||||||
|
box-shadow: 0 0 30px rgba(255, 62, 62, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-content h3 {
|
||||||
|
color: #ff3e3e;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
font-size: 14px;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-content p {
|
||||||
|
color: #aaa;
|
||||||
|
font-size: 13px;
|
||||||
|
margin-bottom: 25px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.confirm-btn,
|
||||||
|
.cancel-btn {
|
||||||
|
flex: 1;
|
||||||
|
padding: 10px;
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: bold;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.confirm-btn {
|
||||||
|
background: rgba(255, 62, 62, 0.1);
|
||||||
|
border: 1px solid #ff3e3e;
|
||||||
|
color: #ff3e3e;
|
||||||
|
}
|
||||||
|
|
||||||
|
.confirm-btn:hover {
|
||||||
|
background: #ff3e3e;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cancel-btn {
|
||||||
|
background: rgba(255, 255, 255, 0.05);
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* MOBILE RESPONSIVENESS */
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
.chat-sidebar {
|
.chat-sidebar {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@ -57,41 +352,19 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Стилі заголовків та вводу */
|
/* Контейнер елемента списку */
|
||||||
.search-section {
|
|
||||||
padding: 20px 15px 15px;
|
|
||||||
border-bottom: 1px solid var(--border-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.search-input-wrapper {
|
|
||||||
display: flex;
|
|
||||||
gap: 5px;
|
|
||||||
margin-top: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.search-input-wrapper input {
|
|
||||||
flex: 1;
|
|
||||||
background: #05080c;
|
|
||||||
border: 1px solid var(--border-color);
|
|
||||||
color: #fff;
|
|
||||||
padding: 8px;
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.add-friend-btn {
|
|
||||||
background: var(--primary-color);
|
|
||||||
border: none;
|
|
||||||
width: 35px;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.chat-item {
|
.chat-item {
|
||||||
padding: 15px 20px;
|
padding: 12px 15px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 12px;
|
justify-content: space-between;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
border-bottom: 1px solid rgba(255, 255, 255, 0.02);
|
border-bottom: 1px solid rgba(255, 255, 255, 0.02);
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-item:hover {
|
||||||
|
background: rgba(255, 255, 255, 0.03);
|
||||||
}
|
}
|
||||||
|
|
||||||
.chat-item.active {
|
.chat-item.active {
|
||||||
@ -99,84 +372,42 @@
|
|||||||
box-shadow: inset 4px 0 0 var(--primary-color);
|
box-shadow: inset 4px 0 0 var(--primary-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.chat-header {
|
.chat-item-main {
|
||||||
padding: 15px;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
border-bottom: 1px solid var(--border-color);
|
gap: 12px;
|
||||||
background: rgba(10, 15, 24, 0.8);
|
|
||||||
}
|
|
||||||
|
|
||||||
.chat-messages {
|
|
||||||
flex: 1;
|
flex: 1;
|
||||||
padding: 15px;
|
|
||||||
overflow-y: auto;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.chat-input-area {
|
.unfriend-btn {
|
||||||
padding: 15px;
|
|
||||||
background: #05080c;
|
|
||||||
display: flex;
|
|
||||||
gap: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.chat-input-area input {
|
|
||||||
flex: 1;
|
|
||||||
background: rgba(255, 255, 255, 0.05);
|
|
||||||
border: 1px solid var(--border-color);
|
|
||||||
color: #fff;
|
|
||||||
padding: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.send-btn {
|
|
||||||
background: transparent;
|
background: transparent;
|
||||||
border: 1px solid var(--primary-color);
|
border: none;
|
||||||
color: var(--primary-color);
|
color: rgba(255, 255, 255, 0.3);
|
||||||
padding: 0 15px;
|
padding: 8px;
|
||||||
}
|
|
||||||
|
|
||||||
.search-section {
|
|
||||||
position: relative;
|
|
||||||
z-index: 10;
|
|
||||||
}
|
|
||||||
|
|
||||||
.search-results-dropdown {
|
|
||||||
position: absolute;
|
|
||||||
top: 100%;
|
|
||||||
left: 15px;
|
|
||||||
right: 15px;
|
|
||||||
background: #0a0f18;
|
|
||||||
border: 1px solid var(--primary-color);
|
|
||||||
border-top: none;
|
|
||||||
max-height: 200px;
|
|
||||||
overflow-y: auto;
|
|
||||||
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.5);
|
|
||||||
}
|
|
||||||
|
|
||||||
.search-result-item {
|
|
||||||
padding: 10px;
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-size: 12px;
|
transition: all 0.2s;
|
||||||
color: #fff;
|
opacity: 0;
|
||||||
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.search-result-item:hover {
|
@media (min-width: 769px) {
|
||||||
background: rgba(0, 212, 255, 0.1);
|
.chat-item:hover .unfriend-btn {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.search-result-item i {
|
@media (max-width: 768px) {
|
||||||
color: var(--primary-color);
|
.unfriend-btn {
|
||||||
|
opacity: 1;
|
||||||
|
color: rgba(255, 255, 255, 0.5);
|
||||||
|
padding: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.message.system .msg-author {
|
.chat-item {
|
||||||
|
padding: 15px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.unfriend-btn:hover {
|
||||||
color: #ff3e3e;
|
color: #ff3e3e;
|
||||||
}
|
text-shadow: 0 0 8px rgba(255, 62, 62, 0.4);
|
||||||
|
|
||||||
.message.system .msg-text {
|
|
||||||
color: #aaa;
|
|
||||||
font-style: italic;
|
|
||||||
}
|
}
|
||||||
|
|||||||
84
game-server/src/game/SocialManager.js
Normal file
84
game-server/src/game/SocialManager.js
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
const { Player, Friend } = require("../models");
|
||||||
|
const notificationManager = require("./NotificationManager");
|
||||||
|
const { Op } = require("sequelize");
|
||||||
|
|
||||||
|
class SocialManager {
|
||||||
|
constructor() {
|
||||||
|
this.io = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
init(io) {
|
||||||
|
this.io = io;
|
||||||
|
}
|
||||||
|
|
||||||
|
async searchPlayers(query, excludeId) {
|
||||||
|
return await Player.findAll({
|
||||||
|
where: {
|
||||||
|
username: { [Op.like]: `%${query}%` },
|
||||||
|
id: { [Op.ne]: excludeId },
|
||||||
|
},
|
||||||
|
limit: 5,
|
||||||
|
attributes: ["id", "username", "level"],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async sendFriendRequest(sender, targetId) {
|
||||||
|
await notificationManager.send({
|
||||||
|
playerId: targetId,
|
||||||
|
type: "friend_request",
|
||||||
|
title: "NEW FRIEND REQUEST",
|
||||||
|
message: `${sender.username} wants to add you as a friend.`,
|
||||||
|
data: { fromId: sender.id },
|
||||||
|
priority: "normal",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async acceptFriendRequest(myId, friendId, notificationId) {
|
||||||
|
const exists = await Friend.findOne({
|
||||||
|
where: { playerId: myId, friendId: friendId },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!exists) {
|
||||||
|
await Friend.bulkCreate([
|
||||||
|
{ playerId: myId, friendId: friendId },
|
||||||
|
{ playerId: friendId, friendId: myId },
|
||||||
|
]);
|
||||||
|
|
||||||
|
await notificationManager.delete(notificationId, myId);
|
||||||
|
|
||||||
|
await this.broadcastFriendListUpdate(myId);
|
||||||
|
await this.broadcastFriendListUpdate(friendId);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getFriendList(playerId) {
|
||||||
|
const player = await Player.findByPk(playerId, {
|
||||||
|
include: [
|
||||||
|
{
|
||||||
|
model: Player,
|
||||||
|
as: "Friends",
|
||||||
|
attributes: ["id", "username", "level"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
return player?.Friends || [];
|
||||||
|
}
|
||||||
|
|
||||||
|
async broadcastFriendListUpdate(playerId) {
|
||||||
|
if (!this.io) return;
|
||||||
|
|
||||||
|
const list = await this.getFriendList(playerId);
|
||||||
|
const targetSocket = [...this.io.sockets.sockets.values()].find(
|
||||||
|
(s) => s.user?.id === playerId,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (targetSocket) {
|
||||||
|
targetSocket.emit("friend:list", list);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = new SocialManager();
|
||||||
@ -12,6 +12,7 @@ const app = express();
|
|||||||
const economyService = require("./game/EconomyService.js");
|
const economyService = require("./game/EconomyService.js");
|
||||||
const NotificationManager = require("./game/NotificationManager.js");
|
const NotificationManager = require("./game/NotificationManager.js");
|
||||||
const AdminManager = require("./game/AdminManager.js");
|
const AdminManager = require("./game/AdminManager.js");
|
||||||
|
const SocialManager = require("./game/SocialManager.js");
|
||||||
|
|
||||||
app.use(
|
app.use(
|
||||||
cors({
|
cors({
|
||||||
@ -85,6 +86,7 @@ server.listen(config.port, async () => {
|
|||||||
NotificationManager.init(io);
|
NotificationManager.init(io);
|
||||||
AdminManager.init(io);
|
AdminManager.init(io);
|
||||||
economyService.init(io);
|
economyService.init(io);
|
||||||
|
SocialManager.init(io);
|
||||||
await registerInApi();
|
await registerInApi();
|
||||||
setInterval(sendHeartbeat, HEARTBEAT_INTERVAL);
|
setInterval(sendHeartbeat, HEARTBEAT_INTERVAL);
|
||||||
console.log(`Server running on ${config.host}. PORT: ${config.port}`);
|
console.log(`Server running on ${config.host}. PORT: ${config.port}`);
|
||||||
|
|||||||
44
game-server/src/sockets/handlers/socialHandler.js
Normal file
44
game-server/src/sockets/handlers/socialHandler.js
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
const socialManager = require("../../game/SocialManager");
|
||||||
|
|
||||||
|
module.exports = (io, socket) => {
|
||||||
|
if (!socialManager.io) socialManager.init(io);
|
||||||
|
|
||||||
|
socket.on("player:search", async ({ query }) => {
|
||||||
|
try {
|
||||||
|
const players = await socialManager.searchPlayers(query, socket.user.id);
|
||||||
|
socket.emit("player:search_results", players);
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on("friend:add", async ({ friendId }) => {
|
||||||
|
try {
|
||||||
|
await socialManager.sendFriendRequest(socket.user, friendId);
|
||||||
|
} catch (e) {
|
||||||
|
socket.emit("error", { message: "FAILED_TO_SEND_REQUEST" });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on("friend:accept", async ({ friendId, notificationId }) => {
|
||||||
|
try {
|
||||||
|
await socialManager.acceptFriendRequest(
|
||||||
|
socket.user.id,
|
||||||
|
friendId,
|
||||||
|
notificationId,
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
socket.emit("error", { message: "FAILED_TO_ACCEPT_FRIEND" });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on("friend:get_list", async () => {
|
||||||
|
try {
|
||||||
|
const list = await socialManager.getFriendList(socket.user.id);
|
||||||
|
socket.emit("friend:list", list);
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
@ -5,8 +5,8 @@ const craftingHandler = require("./handlers/craftingHandler");
|
|||||||
const adminHandler = require("./handlers/adminHandler");
|
const adminHandler = require("./handlers/adminHandler");
|
||||||
const dungeonHandler = require("./handlers/dungeonHandler");
|
const dungeonHandler = require("./handlers/dungeonHandler");
|
||||||
const chatHandler = require("./handlers/chatHandler");
|
const chatHandler = require("./handlers/chatHandler");
|
||||||
const friendHandler = require("./handlers/friendHandler");
|
|
||||||
const notificationHandler = require("./handlers/notificationHandler");
|
const notificationHandler = require("./handlers/notificationHandler");
|
||||||
|
const socialHandler = require("./handlers/socialHandler");
|
||||||
|
|
||||||
const initSockets = (io) => {
|
const initSockets = (io) => {
|
||||||
io.use(socketAuth);
|
io.use(socketAuth);
|
||||||
@ -18,7 +18,7 @@ const initSockets = (io) => {
|
|||||||
adminHandler(io, socket);
|
adminHandler(io, socket);
|
||||||
dungeonHandler(io, socket);
|
dungeonHandler(io, socket);
|
||||||
chatHandler(io, socket);
|
chatHandler(io, socket);
|
||||||
friendHandler(io, socket);
|
socialHandler(io, socket);
|
||||||
notificationHandler(io, socket);
|
notificationHandler(io, socket);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user