Browse Source

change send images

master
godo 9 months ago
parent
commit
b55aac8fc8
  1. 1
      frontend/components.d.ts
  2. 25
      frontend/src/components/builtin/FileList.vue
  3. 2
      frontend/src/components/computer/Computer.vue
  4. 102
      frontend/src/components/localchat/ChatContent.vue
  5. 20
      frontend/src/components/localchat/ChatEditor.vue
  6. 12
      frontend/src/components/localchat/ChatFoot.vue
  7. 2
      frontend/src/stores/choose.ts
  8. 288
      frontend/src/stores/localchat.ts
  9. 2
      godo/cmd/main.go
  10. 23
      godo/localchat/addr.go
  11. 23
      godo/localchat/check.go
  12. 41
      godo/localchat/image.go
  13. 29
      godo/webdav/auth.go

1
frontend/components.d.ts

@ -56,6 +56,7 @@ declare module 'vue' {
ElForm: typeof import('element-plus/es')['ElForm'] ElForm: typeof import('element-plus/es')['ElForm']
ElFormItem: typeof import('element-plus/es')['ElFormItem'] ElFormItem: typeof import('element-plus/es')['ElFormItem']
ElIcon: typeof import('element-plus/es')['ElIcon'] ElIcon: typeof import('element-plus/es')['ElIcon']
ElImage: typeof import('element-plus/es')['ElImage']
ElInput: typeof import('element-plus/es')['ElInput'] ElInput: typeof import('element-plus/es')['ElInput']
ElLink: typeof import('element-plus/es')['ElLink'] ElLink: typeof import('element-plus/es')['ElLink']
ElOption: typeof import('element-plus/es')['ElOption'] ElOption: typeof import('element-plus/es')['ElOption']

25
frontend/src/components/builtin/FileList.vue

@ -77,7 +77,6 @@ import { throttle } from '@/util/debounce';
import { dealSize } from '@/util/file'; import { dealSize } from '@/util/file';
import { Menu } from '@/system/menu/Menu'; import { Menu } from '@/system/menu/Menu';
import { useChooseStore } from "@/stores/choose"; import { useChooseStore } from "@/stores/choose";
import { log } from 'console';
const { openPropsWindow, copyFile, createLink, deleteFile } = useContextMenu(); const { openPropsWindow, copyFile, createLink, deleteFile } = useContextMenu();
const sys = useSystem(); const sys = useSystem();
const { startDrag, folderDrop } = useFileDrag(sys); const { startDrag, folderDrop } = useFileDrag(sys);
@ -124,8 +123,16 @@ function getName(item: any) {
} }
} }
function handleOnOpen(item: OsFileWithoutContent) { function handleOnOpen(item: OsFileWithoutContent) {
// props.onOpen(item);
// emitEvent('desktop.app.open');
chosenIndexs.value = [];
if (choose.ifShow && !item.isDirectory) {
choose.path.push(item.path)
choose.close()
} else {
props.onOpen(item); props.onOpen(item);
emitEvent('desktop.app.open'); emitEvent('desktop.app.open');
}
} }
function hadnleDrop(mouse: DragEvent, path: string) { function hadnleDrop(mouse: DragEvent, path: string) {
hoverIndex.value = -1; hoverIndex.value = -1;
@ -170,10 +177,10 @@ function onEditNameEnd() {
newPath newPath
); );
props.onRefresh(); props.onRefresh();
if(newPath.indexOf("Desktop") !== -1){ if (newPath.indexOf("Desktop") !== -1) {
sys.refershAppList() sys.refershAppList()
} }
} }
editIndex.value = -1; editIndex.value = -1;
} }
@ -320,8 +327,16 @@ function handleRightClick(mouse: MouseEvent, item: OsFileWithoutContent, index:
menuArr.push({ menuArr.push({
label: "选中发送", label: "选中发送",
click: () => { click: () => {
choose.path = item.path const paths: any = []
choose.close() chosenIndexs.value.forEach((index) => {
const item = props.fileList[index];
paths.push(item.path)
})
if (paths.length > 0) {
choose.path = paths
choose.close()
}
chosenIndexs.value = [];
}, },
}) })
} }

2
frontend/src/components/computer/Computer.vue

@ -248,7 +248,7 @@ async function onTreeOpen(path: string) {
if (file) { if (file) {
openFolder(file); openFolder(file);
} }
console.log(path) //console.log(path)
router_url.value = path; router_url.value = path;
} }

102
frontend/src/components/localchat/ChatContent.vue

@ -6,7 +6,7 @@ import { formatChatTime } from "@/util/common";
// import "vditor/dist/index.css"; // import "vditor/dist/index.css";
import { ElScrollbar } from "element-plus"; import { ElScrollbar } from "element-plus";
import { System } from "@/system"; import { System } from "@/system";
const sys:any = inject<System>("system"); const sys: any = inject<System>("system");
const store = useLocalChatStore(); const store = useLocalChatStore();
const messageContainerRef = ref<InstanceType<typeof ElScrollbar>>(); const messageContainerRef = ref<InstanceType<typeof ElScrollbar>>();
const messageInnerRef = ref<HTMLDivElement>(); const messageInnerRef = ref<HTMLDivElement>();
@ -20,23 +20,23 @@ const scrollToBottom = () => {
// messageContainerRef.value!.setScrollTop( // messageContainerRef.value!.setScrollTop(
// messageInnerRef.value!.clientHeight // messageInnerRef.value!.clientHeight
// ); // );
messageContainerRef.value.setScrollTop(messageInnerRef.value!.clientHeight); messageContainerRef.value.setScrollTop(messageInnerRef.value!.clientHeight);
} }
}); });
}; };
watch( watch(
() => store.msgList, () => store.msgList,
(_) => { (_) => {
if(!isScrool){ if (!isScrool) {
scrollToBottom(); scrollToBottom();
} }
}, },
{ {
deep: true, deep: true,
} }
); );
function replaceIconTags(text:any) { function replaceIconTags(text: any) {
// {**} // {**}
// console.log(text) // console.log(text)
// text = Vditor.md2html(text); // text = Vditor.md2html(text);
@ -44,7 +44,7 @@ function replaceIconTags(text:any) {
const regex = /\{\-(.*?)\-\}/g; const regex = /\{\-(.*?)\-\}/g;
// 使replace // 使replace
const replacedText = text.replace(regex, (_:any, p1:string) => { const replacedText = text.replace(regex, (_: any, p1: string) => {
// p1 // p1
return `<img src='/image/chat/emoji/${p1}.gif' style='width:30px;height:30px;' />`; return `<img src='/image/chat/emoji/${p1}.gif' style='width:30px;height:30px;' />`;
//return `![avatar](/image/chat/emoji/${p1}.gif)` //return `![avatar](/image/chat/emoji/${p1}.gif)`
@ -64,29 +64,35 @@ async function scroll({ scrollTop }: { scrollTop: number }) {
<template> <template>
<div class="chatContentContainer" v-if="store.chatTargetId > 0"> <div class="chatContentContainer" v-if="store.chatTargetId > 0">
<div class="message-area"> <div class="message-area">
<el-scrollbar <el-scrollbar max-height="100%" class="scrollbar-container" @scroll="scroll" ref="messageContainerRef">
max-height="100%"
class="scrollbar-container"
@scroll="scroll"
ref="messageContainerRef">
<div ref="messageInnerRef" class="message-wrap"> <div ref="messageInnerRef" class="message-wrap">
<div v-for="(item, index) in store.msgList" :key="index" :class="['message-block', item.isMe ? 'mine' : 'theirs']"> <div v-for="(item, index) in store.msgList" :key="index"
:class="['message-block', item.isMe ? 'mine' : 'theirs']">
<div class="avatar-container"> <div class="avatar-container">
<div class="icon-container"> <div class="icon-container">
<el-icon><component :is="item.isMe ? 'UserFilled' : 'Place'"/></el-icon> <el-icon>
<component :is="item.isMe ? 'UserFilled' : 'Place'" />
</el-icon>
</div> </div>
</div> </div>
<div class="content"> <div class="content">
<div <div v-if="item.type === 'text'" v-html="replaceIconTags(item.content)" class="message-content">
v-if="item.type === 'text'"
v-html="replaceIconTags(item.content)"
class="message-content">
</div> </div>
<div v-if="item.type === 'file'"> <div v-if="item.type === 'file'">
<div class="file-bubble"> <div class="file-bubble">
<div class="file-content" v-for="el in item.content" @click="sys.openFile(el.path)"> <div class="file-content" v-for="el in item.content" @click="sys.openFile(el.path)">
<div class="file-icon"><FileIcon :file="el" /></div> <div class="file-icon">
<div class="file-name">{{el.name}}</div> <FileIcon :file="el" />
</div>
<div class="file-name">{{ el.name }}</div>
</div>
</div>
</div>
<div v-if="item.type === 'image'">
<div class="file-bubble">
<div class="message-content" v-for="el in item.content">
<el-image style="width: 100px; height: 100px" :src="el" :zoom-rate="1.2" :max-scale="7"
:min-scale="0.2" :preview-src-list="item.content" :initial-index="4" fit="cover" />
</div> </div>
</div> </div>
</div> </div>
@ -100,7 +106,7 @@ async function scroll({ scrollTop }: { scrollTop: number }) {
</div> </div>
<div class="no-message-container" v-else> <div class="no-message-container" v-else>
<el-icon :size="180" color="#0078d7"> <el-icon :size="180" color="#0078d7">
<ChatDotRound /> <ChatDotRound />
</el-icon> </el-icon>
</div> </div>
</template> </template>
@ -115,7 +121,7 @@ $win10-light-grey: #f2f2f2;
} }
.message-area { .message-area {
height: 420px; height: 420px;
} }
@ -132,7 +138,7 @@ $win10-light-grey: #f2f2f2;
display: flex; display: flex;
margin-bottom: 10px; margin-bottom: 10px;
align-items: flex-end; // align-items: flex-end; //
flex-direction: row; flex-direction: row;
} }
@ -178,61 +184,75 @@ $win10-light-grey: #f2f2f2;
.mine { .mine {
// 'mine' // 'mine'
justify-content: flex-end; justify-content: flex-end;
.content { .content {
// 'mine' // 'mine'
// justify-content: flex-end; justify-content // justify-content: flex-end; justify-content
align-items: flex-end; // align-items: flex-end; //
} }
.message-content { .message-content {
background-color: $win10-blue; background-color: $win10-blue;
color: $win10-light-blue; color: $win10-light-blue;
border-radius: 12px 2px 2px 2px; // border-radius: 12px 2px 2px 2px; //
} }
.avatar-container { .avatar-container {
order: 1; order: 1;
margin-left: 10px; margin-left: 10px;
margin-top:-20px; margin-top: -20px;
} }
} }
.theirs { .theirs {
.content { .content {
// 使 // 使
align-items: flex-end; align-items: flex-end;
} }
.message-content { .message-content {
border-radius: 2px 12px 2px 2px; border-radius: 2px 12px 2px 2px;
} }
} }
.no-message-container { .no-message-container {
height: 100%; height: 100%;
margin: 120px auto; margin: 120px auto;
text-align: center; text-align: center;
justify-content: center; justify-content: center;
} }
.file-bubble { .file-bubble {
background-color: #f0f0f0; /* 背景色,可以根据需要调整 */ background-color: #f0f0f0;
border-radius: 10px; /* 圆角,让框看起来更柔和 */ /* 背景色,可以根据需要调整 */
padding: 10px; /* 内边距,给内容一些空间 */ border-radius: 10px;
margin-bottom: 10px; /* 气泡间的外边距,使它们看起来不紧凑 */ /* 圆角,让框看起来更柔和 */
padding: 10px;
/* 内边距,给内容一些空间 */
margin-bottom: 10px;
/* 气泡间的外边距,使它们看起来不紧凑 */
max-width: 100%; max-width: 100%;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); /* 添加阴影效果,增强立体感 */ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
/* 添加阴影效果,增强立体感 */
} }
.file-content { .file-content {
display: flex; display: flex;
align-items: center; align-items: center;
height:36px; height: 36px;
line-height:36px; line-height: 36px;
gap: 3px; gap: 3px;
} }
.file-content:hover { .file-content:hover {
background-color: #e0e0e0; /* 改变背景色,悬停时更浅或更深,根据设计调整 */ background-color: #e0e0e0;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); /* 增强阴影效果,使气泡在悬停时更加突出 */ /* 改变背景色,悬停时更浅或更深,根据设计调整 */
transition: all 0.3s ease; /* 添加过渡效果,使变化平滑 */ box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
/* 增强阴影效果,使气泡在悬停时更加突出 */
transition: all 0.3s ease;
/* 添加过渡效果,使变化平滑 */
} }
.file-icon { .file-icon {
width: 18px; width: 18px;
height: 18px; height: 18px;

20
frontend/src/components/localchat/ChatEditor.vue

@ -12,7 +12,7 @@
@keydown.enter="keyDown($event)" @keydown.enter="keyDown($event)"
v-model="store.sendInfo" /> v-model="store.sendInfo" />
<el-tooltip placement="top" content="按enter键发送,按ctrl+enter键换行"> <el-tooltip placement="top" content="按enter键发送,按ctrl+enter键换行">
<el-icon :size="22" class="win11-chat-send-button" @click="store.sendMsg()"> <el-icon :size="22" class="win11-chat-send-button" @click="store.sendMsg('text')">
<Promotion /> <Promotion />
</el-icon> </el-icon>
</el-tooltip> </el-tooltip>
@ -22,17 +22,18 @@
<script setup lang="ts"> <script setup lang="ts">
import { useLocalChatStore } from "@/stores/localchat" import { useLocalChatStore } from "@/stores/localchat"
import { notifyError } from "@/util/msg"; //import { notifyError } from "@/util/msg";
const store = useLocalChatStore() const store = useLocalChatStore()
// //
function keyDown(event: any) { function keyDown(event: any) {
if(store.sendInfo == '')return
if (event.ctrlKey && event.keyCode === 13) { if (event.ctrlKey && event.keyCode === 13) {
store.sendInfo = store.sendInfo + "\n" store.sendInfo = store.sendInfo + "\n"
} else if (event.keyCode === 13) { } else if (event.keyCode === 13) {
event.preventDefault() // event.preventDefault() //
send() store.sendMsg('text')
return false return false
} }
} }
@ -42,18 +43,13 @@ const handleDrop = (event:any) => {
const files = JSON.parse(frompathArrStr) as string[]; const files = JSON.parse(frompathArrStr) as string[];
if (files && files.length > 0) { if (files && files.length > 0) {
// //
console.log('Files dropped:', files); //console.log('Files dropped:', files);
store.uploadFile(files) store.sendInfo = files;
//store.uploadFile(files)
store.sendMsg('applyfile')
} }
}; };
function send(){
// if(!store.hostInfo || !store.hostInfo.ip){
// notifyError("Please wait for a moment");
// return;
// }
store.sendMsg()
}
</script> </script>

12
frontend/src/components/localchat/ChatFoot.vue

@ -64,16 +64,18 @@ const store = useLocalChatStore();
const choose = useChooseStore(); const choose = useChooseStore();
//const editor = ref(null) //const editor = ref(null)
const imgExt = ["png", "jpg", "jpeg", "gif", "bmp", "webp", "svg"]; const imgExt = ["png", "jpg", "jpeg", "gif", "bmp", "webp", "svg"];
const choosetype = ref('image')
// //
function selectIcon(icon: string) { function selectIcon(icon: string) {
store.sendInfo += store.sendInfo +=
"{-" + icon.replace("/image/chat/emoji/", "").replace(".gif", "") + "-}"; "{-" + icon.replace("/image/chat/emoji/", "").replace(".gif", "") + "-}";
} }
function selectImg() { function selectImg() {
choosetype.value = 'image'
choose.select("选择图片", imgExt); choose.select("选择图片", imgExt);
} }
function selectFile() { function selectFile() {
choosetype.value = 'applyfile'
choose.select("选择文件", "*"); choose.select("选择文件", "*");
} }
watch( watch(
@ -82,12 +84,10 @@ watch(
//console.log("userList :", newVal); //console.log("userList :", newVal);
const paths = toRaw(newVal) const paths = toRaw(newVal)
if(paths.length > 0){ if(paths.length > 0){
store.uploadFile(paths).then(() => { store.sendInfo = paths;
choose.path = [] choose.path = []
}) store.sendMsg(choosetype.value)
} }
}, },
{ deep: true } // deep: true { deep: true } // deep: true
); );

2
frontend/src/stores/choose.ts

@ -3,7 +3,7 @@ import { BrowserWindow } from "@/system";
import { ref } from 'vue'; import { ref } from 'vue';
export const useChooseStore = defineStore('chooseStore', () => { export const useChooseStore = defineStore('chooseStore', () => {
const win:any = ref() const win:any = ref()
const path:any = ref("") const path:any = ref([])
const ifShow = ref(false) const ifShow = ref(false)
const select = (title = '选择文件', fileExt:any) => { const select = (title = '选择文件', fileExt:any) => {
win.value = new BrowserWindow({ win.value = new BrowserWindow({

288
frontend/src/stores/localchat.ts

@ -4,16 +4,15 @@ import { ref, toRaw, inject } from "vue";
import { db } from './db' import { db } from './db'
import { System } from "@/system"; import { System } from "@/system";
import { getSystemConfig } from "@/system/config"; import { getSystemConfig } from "@/system/config";
import { isBase64, base64ToBuffer } from "@/util/file";
import { isValidIP } from "@/util/common"; import { isValidIP } from "@/util/common";
import { notifyError, notifySuccess } from "@/util/msg"; import { notifyError, notifySuccess } from "@/util/msg";
export const useLocalChatStore = defineStore('localChatStore', () => { export const useLocalChatStore = defineStore('localChatStore', () => {
const config = getSystemConfig(); const config = getSystemConfig();
const sys = inject<System>("system"); //const sys = inject<System>("system");
const userList: any = ref([]) const userList: any = ref([])
const msgList: any = ref([]) const msgList: any = ref([])
const contentList: any = ref([]) const contentList: any = ref([])
const OutUserList: any = ref([]) //const OutUserList: any = ref([])
const hostInfo: any = ref({}) const hostInfo: any = ref({})
const showChooseFile = ref(false) const showChooseFile = ref(false)
const currentPage = ref(1) const currentPage = ref(1)
@ -23,7 +22,7 @@ export const useLocalChatStore = defineStore('localChatStore', () => {
{ index: 2, lable: "用户列表", icon: "UserFilled", type: "info" }, { index: 2, lable: "用户列表", icon: "UserFilled", type: "info" },
]) ])
const navId = ref(1) const navId = ref(1)
const sendInfo = ref("") const sendInfo:any = ref()
const chatTargetId = ref(0) const chatTargetId = ref(0)
const chatTargetIp = ref("") const chatTargetIp = ref("")
const showAddUser = ref(false) const showAddUser = ref(false)
@ -49,6 +48,9 @@ export const useLocalChatStore = defineStore('localChatStore', () => {
msg.message = msg.message.replaceAll("\\n", "\n") msg.message = msg.message.replaceAll("\\n", "\n")
//console.log(msg) //console.log(msg)
addText(msg) addText(msg)
}
if (msg.type === "image"){
} }
if (msg.type === "fileSending"){ if (msg.type === "fileSending"){
@ -192,8 +194,8 @@ export const useLocalChatStore = defineStore('localChatStore', () => {
} }
} }
const getUserList = async () => { const getUserList = async () => {
const listAll = await db.getAll('chatuser') const list = await db.getAll('chatuser')
const list = [...listAll, ...OutUserList.value] //const list = [...listAll, ...OutUserList.value]
let uniqueIpMap = new Map<string, any>(); let uniqueIpMap = new Map<string, any>();
// 遍历 list 并添加 IP 地址到 Map 中 // 遍历 list 并添加 IP 地址到 Map 中
@ -338,168 +340,186 @@ export const useLocalChatStore = defineStore('localChatStore', () => {
await updateContentList(saveMsg) await updateContentList(saveMsg)
handleSelect(1) handleSelect(1)
} }
const sendMsg = async () => { const sendMsg = async (type:string) => {
if (chatTargetId.value < 1) { if (chatTargetId.value < 1) {
return return
} }
const content = toRaw(sendInfo.value)
let saves:any
if (type === 'image') {
const apiUrl = `${config.apiUrl}/localchat/viewimage?img=`
saves = content.map((d: any) => `${apiUrl}${encodeURIComponent(d)}`)
}else{
saves = content
}
const saveMsg: any = { const saveMsg: any = {
type: 'text', type: type,
targetId: chatTargetId.value, targetId: chatTargetId.value,
targetIp: chatTargetIp.value, targetIp: chatTargetIp.value,
content: sendInfo.value.trim(), content: saves,
createdAt: Date.now(), createdAt: Date.now(),
isMe: true, isMe: true,
isRead: false, isRead: false,
status: 'sending' status: 'sending'
} }
//console.log(saveMsg)
const msgId = await db.addOne('chatmsg', saveMsg) const msgId = await db.addOne('chatmsg', saveMsg)
//await getMsgList() //await getMsgList()
msgList.value.push(saveMsg) msgList.value.push(saveMsg)
const targetUser = userList.value.find((d: any) => d.ip === chatTargetIp.value) const targetUser = userList.value.find((d: any) => d.ip === chatTargetIp.value)
//console.log(targetUser) //console.log(targetUser)
if (targetUser.isOnline) { if (targetUser.isOnline) {
const postUrl = `${config.apiUrl}/localchat/message` let postUrl = `${config.apiUrl}/localchat/message`
if(type === 'applyfile'){
postUrl = `${config.apiUrl}/localchat/applyfile`
}
if(type === 'image'){
postUrl = `${config.apiUrl}/localchat/sendimage`
}
const messages = { const messages = {
type: 'text', type: type,
message: saveMsg.content, message: content,
ip: saveMsg.targetIp ip: saveMsg.targetIp
} }
const completion = await fetch(postUrl, { const completion = await fetch(postUrl, {
method: "POST", method: "POST",
body: JSON.stringify(messages), body: JSON.stringify(messages),
}) })
//console.log(completion)
if (!completion.ok) { if (!completion.ok) {
console.log(completion) console.log(completion)
notifyError("发送失败!")
} else { } else {
saveMsg.isRead = true saveMsg.isRead = true
saveMsg.status = 'sended' saveMsg.status = 'sended'
saveMsg.readAt = Date.now() saveMsg.readAt = Date.now()
await db.update('chatmsg', msgId, saveMsg) await db.update('chatmsg', msgId, saveMsg)
if(type === 'applyfile'){
notifySuccess("发送成功!")
}
} }
} }
sendInfo.value = "" sendInfo.value = ""
await updateContentList(saveMsg) await updateContentList(saveMsg)
} }
//上传文件资源 //上传文件资源
async function uploadFile(paths: any) { // async function uploadFile(paths: any) {
if (chatTargetId.value < 1) { // if (chatTargetId.value < 1) {
return // return
} // }
const targetUser = userList.value.find((d: any) => d.ip === chatTargetIp.value) // const targetUser = userList.value.find((d: any) => d.ip === chatTargetIp.value)
if (!targetUser.isOnline) { // if (!targetUser.isOnline) {
notifyError("The user is not online!"); // notifyError("The user is not online!");
return; // return;
} // }
if (!hostInfo.value || !hostInfo.value.ip) { // if (!hostInfo.value || !hostInfo.value.ip) {
notifyError("Please wait for a moment"); // notifyError("Please wait for a moment");
return; // return;
} // }
//console.log(paths) // //console.log(paths)
const formData = new FormData(); // const formData = new FormData();
const errstr: any = [] // const errstr: any = []
const files: any = [] // const files: any = []
for (let i = 0; i < paths.length; i++) { // for (let i = 0; i < paths.length; i++) {
const content = await sys?.fs.readFile(paths[i]); // const content = await sys?.fs.readFile(paths[i]);
let blobContent; // let blobContent;
if (!content || content == '') { // if (!content || content == '') {
errstr.push(paths[i] + " is empty") // errstr.push(paths[i] + " is empty")
continue // continue
} // }
if (content instanceof ArrayBuffer) { // if (content instanceof ArrayBuffer) {
blobContent = new Blob([content]); // blobContent = new Blob([content]);
} // }
else if (typeof content === 'string') { // else if (typeof content === 'string') {
if (isBase64(content)) { // if (isBase64(content)) {
const base64 = base64ToBuffer(content); // const base64 = base64ToBuffer(content);
blobContent = new Blob([base64]); // blobContent = new Blob([base64]);
} else { // } else {
blobContent = new Blob([content], { type: "text/plain;charset=utf-8" }); // blobContent = new Blob([content], { type: "text/plain;charset=utf-8" });
} // }
} // }
else { // else {
errstr.push(paths[i] + " type is error") // errstr.push(paths[i] + " type is error")
continue // continue
} // }
const fileName = paths[i].split("/").pop() // const fileName = paths[i].split("/").pop()
files.push({ // files.push({
name: fileName, // name: fileName,
path: paths[i], // path: paths[i],
ext: fileName.split(".").pop(), // ext: fileName.split(".").pop(),
}) // })
//files.push(blobContent); // //files.push(blobContent);
formData.append(`files`, blobContent, fileName); // formData.append(`files`, blobContent, fileName);
} // }
if (errstr.length > 0) { // if (errstr.length > 0) {
errstr.forEach((d: any) => { // errstr.forEach((d: any) => {
notifyError(d); // notifyError(d);
}) // })
return // return
} // }
//formData.append("files", files); // //formData.append("files", files);
formData.append("ip", hostInfo.value.ip); // formData.append("ip", hostInfo.value.ip);
formData.append("hostname", hostInfo.value.hostname); // formData.append("hostname", hostInfo.value.hostname);
//console.log(formData) // //console.log(formData)
const postUrl = `http://${targetUser.ip}:56780/localchat/upload` // const postUrl = `http://${targetUser.ip}:56780/localchat/upload`
const res = await fetch(postUrl, { // const res = await fetch(postUrl, {
method: "POST", // method: "POST",
body: formData, // body: formData,
}); // });
if (!res.ok) { // if (!res.ok) {
console.log(res); // console.log(res);
notifyError("Upload error!"); // notifyError("Upload error!");
return; // return;
} // }
const saveMsg: any = { // const saveMsg: any = {
type: 'file', // type: 'file',
targetId: targetUser.id, // targetId: targetUser.id,
targetIp: targetUser.ip, // targetIp: targetUser.ip,
content: files, // content: files,
reciperInfo: toRaw(targetUser), // reciperInfo: toRaw(targetUser),
createdAt: Date.now(), // createdAt: Date.now(),
isMe: false, // isMe: false,
isRead: true, // isRead: true,
status: 'reciped' // status: 'reciped'
} // }
//console.log(saveMsg) // //console.log(saveMsg)
await db.addOne('chatmsg', saveMsg) // await db.addOne('chatmsg', saveMsg)
msgList.value.push(saveMsg) // msgList.value.push(saveMsg)
notifySuccess("upload success!"); // notifySuccess("upload success!");
} // }
async function addUser(ip: string) { //async function addUser(ip: string) {
if (!isValidIP(ip)) { // if (!isValidIP(ip)) {
notifyError("请输入正确的IP地址"); // notifyError("请输入正确的IP地址");
return // return
} // }
const postUrl = `http://${ip}:56780/localchat/check` // const postUrl = `http://${ip}:56780/localchat/check`
const completion = await fetch(postUrl) // const completion = await fetch(postUrl)
if (!completion.ok) { // if (!completion.ok) {
notifyError('用户不在线') // notifyError('用户不在线')
} else { // } else {
const res = await completion.json() // const res = await completion.json()
const data = res.data // const data = res.data
data.createdAt = Date.now() // data.createdAt = Date.now()
data.updatedAt = Date.now() // data.updatedAt = Date.now()
data.isOnline = true // data.isOnline = true
data.username = data.hostname // data.username = data.hostname
if(ip != data.ip){ // if(ip != data.ip){
notifyError(`IP地址不一致,可能会存在不通的问题`) // notifyError(`IP地址不一致,可能会存在不通的问题`)
} // }
data.ip = ip // data.ip = ip
if (!OutUserList.value.some((item: any) => item.ip === data.ip)) { // if (!OutUserList.value.some((item: any) => item.ip === data.ip)) {
OutUserList.value.push(data); // OutUserList.value.push(data);
await getUserList() // await getUserList()
} // }
showAddUser.value = false // showAddUser.value = false
} // }
} //}
return { return {
userList, userList,
navList, navList,
@ -514,7 +534,7 @@ export const useLocalChatStore = defineStore('localChatStore', () => {
showChooseFile, showChooseFile,
pageSize, pageSize,
showAddUser, showAddUser,
OutUserList, //OutUserList,
init, init,
setUserList, setUserList,
getUserList, getUserList,
@ -523,23 +543,11 @@ export const useLocalChatStore = defineStore('localChatStore', () => {
sendMsg, sendMsg,
addText, addText,
addFile, addFile,
uploadFile, //uploadFile,
moreMsgList, moreMsgList,
refreshUserList, refreshUserList,
clearMsg, clearMsg,
addUser, //addUser,
handlerMessage handlerMessage
} }
}, {
persist: {
enabled: true,
strategies: [
{
storage: localStorage,
paths: [
"OutUserList"
]
}, // name 字段用localstorage存储
],
}
}) })

2
godo/cmd/main.go

@ -106,7 +106,7 @@ func OsStart() {
localchatRouter.HandleFunc("/applyfile", localchat.HandlerApplySendFile).Methods(http.MethodPost) localchatRouter.HandleFunc("/applyfile", localchat.HandlerApplySendFile).Methods(http.MethodPost)
localchatRouter.HandleFunc("/accessfile", localchat.HandlerAccessFile).Methods(http.MethodPost) localchatRouter.HandleFunc("/accessfile", localchat.HandlerAccessFile).Methods(http.MethodPost)
localchatRouter.HandleFunc("/sendimage", localchat.HandlerSendImg).Methods(http.MethodPost) localchatRouter.HandleFunc("/sendimage", localchat.HandlerSendImg).Methods(http.MethodPost)
localchatRouter.HandleFunc("/viewimage", localchat.HandleViewImg).Methods(http.MethodPost) localchatRouter.HandleFunc("/viewimage", localchat.HandleViewImg).Methods(http.MethodGet)
localchatRouter.HandleFunc("/setting", localchat.HandleAddr).Methods(http.MethodPost) localchatRouter.HandleFunc("/setting", localchat.HandleAddr).Methods(http.MethodPost)
localchatRouter.HandleFunc("/getsetting", localchat.HandleGetAddr).Methods(http.MethodGet) localchatRouter.HandleFunc("/getsetting", localchat.HandleGetAddr).Methods(http.MethodGet)
// 注册 WebDAV 路由 // 注册 WebDAV 路由

23
godo/localchat/addr.go

@ -22,29 +22,6 @@
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE. // SOFTWARE.
// MIT License
//
// Copyright (c) 2024 godoos.com
// Email: xpbb@qq.com
// GitHub: github.com/phpk/godoos
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
package localchat package localchat
import ( import (

23
godo/localchat/check.go

@ -22,29 +22,6 @@
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE. // SOFTWARE.
// MIT License
//
// Copyright (c) 2024 godoos.com
// Email: xpbb@qq.com
// GitHub: github.com/phpk/godoos
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
package localchat package localchat
import ( import (

41
godo/localchat/image.go

@ -25,15 +25,14 @@
package localchat package localchat
import ( import (
"bytes"
"encoding/json" "encoding/json"
"fmt" "fmt"
"godo/libs" "godo/libs"
"image"
"io" "io"
"log" "log"
"net" "net"
"net/http" "net/http"
"net/url"
"os" "os"
"path/filepath" "path/filepath"
"time" "time"
@ -61,21 +60,25 @@ func HandlerSendImg(w http.ResponseWriter, r *http.Request) {
log.Printf("GetOsDir error: %v", err) log.Printf("GetOsDir error: %v", err)
return return
} }
paths, ok := msg.Message.([]string) log.Printf("send image to %v", msg.Message)
log.Printf("Type of msg.Message: %T", msg.Message)
paths, ok := msg.Message.([]interface{})
log.Printf("paths: %v", paths)
if !ok { if !ok {
log.Printf("invalid message type") libs.ErrorMsg(w, "HandleMessage message error")
return return
} }
for _, p := range paths {
for _, v := range paths {
p, ok := v.(string)
if !ok {
continue
}
filePath := filepath.Join(basePath, p) filePath := filepath.Join(basePath, p)
// 处理多张图片 // 处理多张图片
if fileInfo, err := os.Stat(filePath); err == nil { if fileInfo, err := os.Stat(filePath); err == nil {
if !fileInfo.IsDir() { if !fileInfo.IsDir() {
if isImage(filePath) { // 检查是否为图片 sendImage(filePath, toIp, msg)
sendImage(filePath, toIp, msg)
} else {
log.Printf("文件 %s 不是图片", filePath)
}
} }
} else { } else {
continue continue
@ -83,14 +86,7 @@ func HandlerSendImg(w http.ResponseWriter, r *http.Request) {
} }
libs.SuccessMsg(w, nil, "图片发送成功") libs.SuccessMsg(w, nil, "图片发送成功")
} }
func isImage(filePath string) bool {
data, err := os.ReadFile(filePath)
if err != nil {
return false
}
img, _, err := image.DecodeConfig(bytes.NewReader(data))
return err == nil && img.Width > 0 && img.Height > 0
}
func sendImage(filePath string, toIp string, message UdpMessage) { func sendImage(filePath string, toIp string, message UdpMessage) {
// 打开文件 // 打开文件
file, err := os.Open(filePath) file, err := os.Open(filePath)
@ -212,15 +208,20 @@ func HandleViewImg(w http.ResponseWriter, r *http.Request) {
libs.ErrorMsg(w, "img is empty") libs.ErrorMsg(w, "img is empty")
return return
} }
decodedImgParam, err := url.QueryUnescape(img)
if err != nil {
log.Fatalf("Error unescaping image parameter: %v", err)
}
basePath, err := libs.GetOsDir() basePath, err := libs.GetOsDir()
if err != nil { if err != nil {
log.Printf("GetOsDir error: %v", err) log.Printf("GetOsDir error: %v", err)
return return
} }
filePath := filepath.Join(basePath, img) filePath := filepath.Join(basePath, decodedImgParam)
log.Printf("filePath: %s", filePath)
// 检查文件是否存在 // 检查文件是否存在
if _, err := os.Stat(filePath); os.IsNotExist(err) { if _, err := os.Stat(filePath); os.IsNotExist(err) {
http.NotFound(w, r) libs.ErrorMsg(w, "file not found")
return return
} }

29
godo/webdav/auth.go

@ -47,35 +47,6 @@ type Authorizer interface {
AddAuthenticator(key string, fn AuthFactory) AddAuthenticator(key string, fn AuthFactory)
} }
// An Authenticator implements a specific way to authorize requests.
// Each request is bound to a separate Authenticator instance.
//
// The authentication flow itself is broken down into `Authorize`
// and `Verify` steps. The former method runs before, and the latter
// runs after the `Request` is submitted.
// This makes it easy to encapsulate and control complex
// authentication challenges.
//
// Some authentication flows causing authentication round trips,
// which can be archived by returning the `redo` of the Verify
// method. `True` restarts the authentication process for the
// current action: A new `Request` is spawned, which must be
// authorized, sent, and re-verified again, until the action
// is successfully submitted.
// The preferred way is to handle the authentication ping-pong
// within `Verify`, and then `redo` with fresh credentials.
//
// The result of the `Verify` method can also trigger an
// `Authenticator` change by returning the `ErrAuthChanged`
// as an error. Depending on the `Authorizer` this may trigger
// an `Authenticator` negotiation.
//
// Set the `XInhibitRedirect` header to '1' in the `Authorize`
// method to get control over request redirection.
// Attention! You must handle the incoming request yourself.
//
// To store a shared session state the `Clone` method **must**
// return a new instance, initialized with the shared state.
type Authenticator interface { type Authenticator interface {
// Authorizes a request. Usually by adding some authorization headers. // Authorizes a request. Usually by adding some authorization headers.
Authorize(c *http.Client, rq *http.Request, path string) error Authorize(c *http.Client, rq *http.Request, path string) error

Loading…
Cancel
Save