├── .dockerignore ├── .gitattributes ├── .github └── workflows │ ├── build.yml │ └── build_test.yml ├── .gitignore ├── Dockerfile ├── Dockerfile.dev ├── Dockerfile_full_s6 ├── LICENSE ├── README.md ├── README_EN.md ├── build.bat ├── build.sh ├── cmd └── apimain.go ├── conf ├── admin │ └── hello.html └── config.yaml ├── config ├── cache.go ├── config.go ├── gin.go ├── gorm.go ├── jwt.go ├── ldap.go ├── logger.go ├── oauth.go ├── oss.go ├── proxy.go ├── redis.go └── rustdesk.go ├── debian ├── changelog ├── compat ├── control.tpl ├── copyright ├── rules ├── rustdesk-api-server.install ├── rustdesk-api-server.postinst ├── rustdesk-api-server.postrm ├── rustdesk-api-server.prerm └── source │ └── format ├── docker-compose-dev.yaml ├── docker-compose.yaml ├── docker-dev.sh ├── docs ├── admin │ ├── admin_docs.go │ ├── admin_swagger.json │ └── admin_swagger.yaml ├── admin_webclient.png ├── api │ ├── api_docs.go │ ├── api_swagger.json │ └── api_swagger.yaml ├── api_swag.png ├── en_img │ ├── admin_webclient.png │ ├── pc_ab.png │ ├── pc_gr.png │ ├── pc_login.png │ ├── rustdesk_command_advance.png │ ├── rustdesk_command_simple.png │ ├── web_admin.png │ ├── web_admin_gr.png │ ├── web_admin_oauth.png │ ├── web_admin_user.png │ └── web_resetpwd.png ├── init_admin_pwd.png ├── pc_ab.png ├── pc_gr.png ├── pc_login.png ├── rustdesk_command_advance.png ├── rustdesk_command_simple.png ├── web_admin.png ├── web_admin_gr.png ├── web_admin_oauth.png ├── web_admin_user.png ├── web_resetpwd.png └── webclientv2.png ├── generate_api.go ├── generate_run.go ├── global ├── apiValidator.go ├── global.go └── i18n.go ├── go.mod ├── http ├── controller │ ├── admin │ │ ├── addressBook.go │ │ ├── addressBookCollection.go │ │ ├── addressBookCollectionRule.go │ │ ├── audit.go │ │ ├── config.go │ │ ├── deviceGroup.go │ │ ├── file.go │ │ ├── group.go │ │ ├── login.go │ │ ├── loginLog.go │ │ ├── my │ │ │ ├── addressBook.go │ │ │ ├── addressBookCollection.go │ │ │ ├── addressBookCollectionRule.go │ │ │ ├── loginLog.go │ │ │ ├── peer.go │ │ │ ├── shareRecord.go │ │ │ └── tag.go │ │ ├── oauth.go │ │ ├── peer.go │ │ ├── rustdesk.go │ │ ├── shareRecord.go │ │ ├── tag.go │ │ ├── user.go │ │ └── userToken.go │ ├── api │ │ ├── ab.go │ │ ├── audit.go │ │ ├── group.go │ │ ├── index.go │ │ ├── login.go │ │ ├── ouath.go │ │ ├── peer.go │ │ ├── user.go │ │ └── webClient.go │ └── web │ │ └── index.go ├── http.go ├── middleware │ ├── admin.go │ ├── admin_privilege.go │ ├── cors.go │ ├── jwt.go │ ├── limiter.go │ ├── logger.go │ └── rustauth.go ├── request │ ├── admin │ │ ├── addressBook.go │ │ ├── audit.go │ │ ├── group.go │ │ ├── login.go │ │ ├── oauth.go │ │ ├── peer.go │ │ ├── shareRecord.go │ │ ├── tag.go │ │ └── user.go │ └── api │ │ ├── audit.go │ │ ├── oauth.go │ │ ├── peer.go │ │ └── user.go ├── response │ ├── admin │ │ └── user.go │ ├── api │ │ ├── ab.go │ │ ├── peer.go │ │ ├── user.go │ │ └── webClient.go │ └── response.go ├── router │ ├── admin.go │ ├── api.go │ └── router.go ├── run.go └── run_win.go ├── lib ├── cache │ ├── cache.go │ ├── cache_test.go │ ├── file.go │ ├── file_test.go │ ├── memory.go │ ├── memory_test.go │ ├── redis.go │ ├── redis_test.go │ ├── simple_cache.go │ └── simple_cache_test.go ├── jwt │ ├── jwt.go │ └── jwt_test.go ├── lock │ ├── local.go │ ├── local_test.go │ └── lock.go ├── logger │ └── logger.go ├── orm │ ├── mysql.go │ └── sqlite.go └── upload │ ├── local.go │ └── oss.go ├── model ├── addressBook.go ├── audit.go ├── custom_types │ ├── auto_json.go │ └── auto_time.go ├── group.go ├── loginLog.go ├── model.go ├── oauth.go ├── peer.go ├── serverCmd.go ├── shareRecord.go ├── tag.go ├── user.go ├── userThird.go ├── userToken.go └── version.go ├── resources ├── i18n │ ├── en.toml │ ├── es.toml │ ├── fr.toml │ ├── ko.toml │ ├── ru.toml │ ├── zh_CN.toml │ └── zh_TW.toml ├── public │ └── upload │ │ └── .gitkeep ├── templates │ ├── oauth_fail.html │ └── oauth_success.html ├── version ├── web │ ├── .gitignore │ ├── .last_build_id │ ├── assets │ │ ├── AssetManifest.json │ │ ├── FontManifest.json │ │ ├── NOTICES │ │ ├── assets │ │ │ ├── android.png │ │ │ ├── gestures.ttf │ │ │ ├── insecure.png │ │ │ ├── insecure_relay.png │ │ │ ├── linux.png │ │ │ ├── mac.png │ │ │ ├── secure.png │ │ │ ├── secure_relay.png │ │ │ └── win.png │ │ ├── fonts │ │ │ └── MaterialIcons-Regular.otf │ │ └── packages │ │ │ ├── cupertino_icons │ │ │ └── assets │ │ │ │ └── CupertinoIcons.ttf │ │ │ └── wakelock_web │ │ │ └── assets │ │ │ └── no_sleep.js │ ├── favicon.svg │ ├── flutter_service_worker.js │ ├── icons │ │ ├── Icon-192.png │ │ ├── Icon-512.png │ │ ├── Icon-maskable-192.png │ │ └── Icon-maskable-512.png │ ├── index.html │ ├── js │ │ ├── .gitattributes │ │ ├── .gitignore │ │ ├── .yarnrc.yml │ │ ├── dist │ │ │ ├── index.css │ │ │ ├── index.html │ │ │ ├── index.js │ │ │ └── vendor.js │ │ ├── gen_js_from_hbb.py │ │ ├── index.html │ │ ├── package-lock.json │ │ ├── package.json │ │ ├── src │ │ │ ├── codec.js │ │ │ ├── common.ts │ │ │ ├── connection.ts │ │ │ ├── gen_js_from_hbb.ts │ │ │ ├── globals.js │ │ │ ├── ljw.js │ │ │ ├── main.ts │ │ │ ├── message.ts │ │ │ ├── rendezvous.ts │ │ │ ├── style.css │ │ │ ├── ui.js │ │ │ ├── vite-env.d.ts │ │ │ └── websock.ts │ │ ├── ts_proto.py │ │ ├── tsconfig.json │ │ ├── vite.config.js │ │ └── yarn.lock │ ├── libopus.js │ ├── libopus.wasm │ ├── libs │ │ ├── firebase-analytics.js │ │ └── firebase-app.js │ ├── main.dart.js │ ├── manifest.json │ ├── ogvjs-1.8.6 │ │ ├── COPYING │ │ ├── COPYING-dav1d.txt │ │ ├── COPYING-ogg.txt │ │ ├── COPYING-opus.txt │ │ ├── COPYING-theora.txt │ │ ├── COPYING-vorbis.txt │ │ ├── LICENSE-nestegg.txt │ │ ├── LICENSE-vpx.txt │ │ ├── PATENTS-vpx.txt │ │ ├── README.md │ │ ├── ogv-decoder-audio-opus-wasm.js │ │ ├── ogv-decoder-audio-opus-wasm.wasm │ │ ├── ogv-decoder-audio-vorbis-wasm.js │ │ ├── ogv-decoder-audio-vorbis-wasm.wasm │ │ ├── ogv-decoder-video-av1-mt-wasm.js │ │ ├── ogv-decoder-video-av1-mt-wasm.wasm │ │ ├── ogv-decoder-video-av1-mt-wasm.worker.js │ │ ├── ogv-decoder-video-av1-simd-mt-wasm.js │ │ ├── ogv-decoder-video-av1-simd-mt-wasm.wasm │ │ ├── ogv-decoder-video-av1-simd-mt-wasm.worker.js │ │ ├── ogv-decoder-video-av1-simd-wasm.js │ │ ├── ogv-decoder-video-av1-simd-wasm.wasm │ │ ├── ogv-decoder-video-av1-wasm.js │ │ ├── ogv-decoder-video-av1-wasm.wasm │ │ ├── ogv-decoder-video-theora-wasm.js │ │ ├── ogv-decoder-video-theora-wasm.wasm │ │ ├── ogv-decoder-video-vp8-mt-wasm.js │ │ ├── ogv-decoder-video-vp8-mt-wasm.wasm │ │ ├── ogv-decoder-video-vp8-mt-wasm.worker.js │ │ ├── ogv-decoder-video-vp8-wasm.js │ │ ├── ogv-decoder-video-vp8-wasm.wasm │ │ ├── ogv-decoder-video-vp9-mt-wasm.js │ │ ├── ogv-decoder-video-vp9-mt-wasm.wasm │ │ ├── ogv-decoder-video-vp9-mt-wasm.worker.js │ │ ├── ogv-decoder-video-vp9-simd-mt-wasm.js │ │ ├── ogv-decoder-video-vp9-simd-mt-wasm.wasm │ │ ├── ogv-decoder-video-vp9-simd-mt-wasm.worker.js │ │ ├── ogv-decoder-video-vp9-simd-wasm.js │ │ ├── ogv-decoder-video-vp9-simd-wasm.wasm │ │ ├── ogv-decoder-video-vp9-wasm.js │ │ ├── ogv-decoder-video-vp9-wasm.wasm │ │ ├── ogv-demuxer-ogg-wasm.js │ │ ├── ogv-demuxer-ogg-wasm.wasm │ │ ├── ogv-demuxer-webm-wasm.js │ │ ├── ogv-demuxer-webm-wasm.wasm │ │ ├── ogv-es2017.js │ │ ├── ogv-support.js │ │ ├── ogv-version.js │ │ ├── ogv-worker-audio.js │ │ ├── ogv-worker-video.js │ │ └── ogv.js │ ├── start-server.bat │ ├── version.json │ ├── web_deps.tar.gz │ ├── yarn.lock │ ├── yuv-canvas-1.2.6.js │ ├── yuv.js │ └── yuv.wasm └── web2 │ ├── assets │ ├── AssetManifest.bin │ ├── AssetManifest.json │ ├── FontManifest.json │ ├── assets │ │ ├── actions.svg │ │ ├── actions_mobile.svg │ │ ├── address_book.ttf │ │ ├── android.svg │ │ ├── arrow.svg │ │ ├── auth-apple.svg │ │ ├── auth-auth0.svg │ │ ├── auth-azure.svg │ │ ├── auth-default.svg │ │ ├── auth-facebook.svg │ │ ├── auth-github.svg │ │ ├── auth-gitlab.svg │ │ ├── auth-google.svg │ │ ├── auth-okta.svg │ │ ├── call_end.svg │ │ ├── call_wait.svg │ │ ├── chat.svg │ │ ├── chat2.svg │ │ ├── checkbox-outline.svg │ │ ├── chevron_up_chevron_down.svg │ │ ├── close.svg │ │ ├── device_group.ttf │ │ ├── display.svg │ │ ├── dots.svg │ │ ├── file.svg │ │ ├── file_transfer.svg │ │ ├── folder.svg │ │ ├── folder_new.svg │ │ ├── fullscreen.svg │ │ ├── fullscreen_exit.svg │ │ ├── gestures.ttf │ │ ├── home.svg │ │ ├── icon.svg │ │ ├── insecure.svg │ │ ├── insecure_relay.svg │ │ ├── kb_layout_iso.svg │ │ ├── kb_layout_not_iso.svg │ │ ├── keyboard.svg │ │ ├── linux.svg │ │ ├── mac.svg │ │ ├── message_24dp_5F6368.svg │ │ ├── more.ttf │ │ ├── peer_searchbar.ttf │ │ ├── pinned.svg │ │ ├── rec.svg │ │ ├── record_screen.svg │ │ ├── refresh.svg │ │ ├── scam.png │ │ ├── screen.svg │ │ ├── search.svg │ │ ├── secure.svg │ │ ├── secure_relay.svg │ │ ├── tabbar.ttf │ │ ├── transfer.svg │ │ ├── trash.svg │ │ ├── unpinned.svg │ │ ├── voice_call.svg │ │ ├── voice_call_waiting.svg │ │ └── win.svg │ ├── fonts │ │ └── MaterialIcons-Regular.otf │ ├── packages │ │ ├── dash_chat_2 │ │ │ └── assets │ │ │ │ ├── placeholder.png │ │ │ │ └── profile_placeholder.png │ │ ├── flex_color_picker │ │ │ └── assets │ │ │ │ └── opacity.png │ │ ├── wakelock_plus │ │ │ └── assets │ │ │ │ └── no_sleep.js │ │ └── window_manager │ │ │ └── images │ │ │ ├── ic_chrome_close.png │ │ │ ├── ic_chrome_maximize.png │ │ │ ├── ic_chrome_minimize.png │ │ │ └── ic_chrome_unmaximize.png │ └── shaders │ │ └── ink_sparkle.frag │ ├── favicon.svg │ ├── ffmpeg-core.js │ ├── ffmpeg-core.wasm │ ├── ffmpeg.js │ ├── flutter_service_worker.js │ ├── icons │ ├── Icon-192.png │ └── libopus.wasm │ ├── index.html │ ├── js │ └── dist │ │ ├── index.js │ │ ├── lang.js │ │ ├── ljw.js │ │ └── vendor.js │ ├── libopus.js │ ├── libopus.wasm │ ├── libs │ ├── firebase-analytics.js │ ├── firebase-app.js │ └── stream │ │ ├── StreamSaver.min.js │ │ └── ponyfill.min.js │ ├── main.dart.js │ └── manifest.json ├── runtime └── cache │ └── .gitkeep ├── service ├── addressBook.go ├── app.go ├── app_test.go ├── audit.go ├── group.go ├── ldap.go ├── loginLog.go ├── oauth.go ├── peer.go ├── serverCmd.go ├── service.go ├── shareRecord.go ├── tag.go └── user.go ├── systemd └── rustdesk-api.service └── utils ├── captcha.go ├── login_limiter.go ├── login_limiter_test.go └── tools.go /.dockerignore: -------------------------------------------------------------------------------- 1 | # Ignore Docker Compose configuration files 2 | docker-compose.yaml 3 | docker-compose-dev.yaml 4 | 5 | # Ignore development Dockerfile 6 | Dockerfile 7 | Dockerfile.dev 8 | docker-dev.sh 9 | 10 | # Ignore the data directory 11 | data/ 12 | 13 | # Ignore version control system directories 14 | .git/ 15 | 16 | # Ignore log and temporary files 17 | *.log 18 | *.tmp 19 | *.swp 20 | 21 | # Ignore editor/IDE configuration files 22 | .vscode/ 23 | .idea/ 24 | 25 | # Ignore binaries and build cache 26 | release/ 27 | bin/ 28 | *.exe 29 | *.out -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | resources/web/**/* linguist-vendored 2 | resources/web2/**/* linguist-vendored -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | runtime/* 3 | !runtime 4 | !runtime/cache/.gitkeep 5 | go.sum 6 | resources/admin 7 | release 8 | data -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine 2 | 3 | ARG BUILDARCH 4 | WORKDIR /app 5 | RUN apk add --no-cache tzdata 6 | COPY ./${BUILDARCH}/release /app/ 7 | VOLUME /app/data 8 | 9 | EXPOSE 21114 10 | CMD ["./apimain"] 11 | -------------------------------------------------------------------------------- /Dockerfile_full_s6: -------------------------------------------------------------------------------- 1 | FROM rustdesk/rustdesk-server-s6:latest AS server 2 | 3 | FROM alpine 4 | 5 | ARG BUILDARCH 6 | WORKDIR /app 7 | RUN apk add --no-cache tzdata 8 | COPY ./${BUILDARCH}/release /app/ 9 | 10 | COPY --from=server /init /init 11 | COPY --from=server /etc/s6-overlay /etc/s6-overlay 12 | COPY --from=server /package /package 13 | COPY --from=server /usr/bin/healthcheck.sh /usr/bin/healthcheck.sh 14 | COPY --from=server /usr/bin/hbbr /usr/bin/hbbr 15 | COPY --from=server /usr/bin/hbbs /usr/bin/hbbs 16 | COPY --from=server /usr/bin/rustdesk-utils /usr/bin/rustdesk-utils 17 | COPY --from=server /command /command 18 | 19 | RUN \ 20 | mkdir -p /etc/s6-overlay/s6-rc.d/api && \ 21 | echo -e "key-secret\nhbbs" > /etc/s6-overlay/s6-rc.d/api/dependencies && \ 22 | echo "longrun" > /etc/s6-overlay/s6-rc.d/api/type && \ 23 | echo "#!/command/with-contenv sh" > /etc/s6-overlay/s6-rc.d/api/run && \ 24 | echo "cd /app" >> /etc/s6-overlay/s6-rc.d/api/run && \ 25 | echo "./apimain" >> /etc/s6-overlay/s6-rc.d/api/run && \ 26 | touch /etc/s6-overlay/s6-rc.d/user/contents.d/api && \ 27 | echo "/package/admin/s6/command/s6-svstat /run/s6-rc/servicedirs/api || exit 1" >> /usr/bin/healthcheck.sh && \ 28 | ln -s /run /var/run 29 | 30 | ENV RELAY=relay.example.com 31 | ENV ENCRYPTED_ONLY=0 32 | 33 | VOLUME /data 34 | VOLUME /app/data 35 | 36 | EXPOSE 21114 21115 21116 21116/udp 21117 21118 21119 37 | 38 | ENTRYPOINT ["/init"] 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024-present Lejianwen and contributors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /build.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | rmdir /s /q release 3 | go env -w GO111MODULE=on 4 | go env -w GOPROXY=https://goproxy.cn,direct 5 | go env -w CGO_ENABLED=1 6 | go env -w GOOS=windows 7 | go env -w GOARCH=amd64 8 | swag init -g cmd/apimain.go --output docs/api --instanceName api --exclude http/controller/admin 9 | swag init -g cmd/apimain.go --output docs/admin --instanceName admin --exclude http/controller/api 10 | go build -o release/apimain.exe cmd/apimain.go 11 | xcopy resources release\resources /E /I /Y 12 | xcopy docs release\docs /E /I /Y 13 | xcopy conf release\conf /E /I /Y 14 | mkdir release\runtime 15 | mkdir release\data -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | # Automatically get the current environment's GOARCH; if not defined, use the detected system architecture 5 | GOARCH=${GOARCH:-$(go env GOARCH)} 6 | DOCS="true" 7 | # Safely remove the old release directory 8 | rm -rf release 9 | 10 | # Set Go environment variables 11 | go env -w GO111MODULE=on 12 | go env -w GOPROXY=https://goproxy.cn,direct 13 | go env -w CGO_ENABLED=1 14 | go env -w GOOS=linux 15 | go env -w GOARCH=${GOARCH} 16 | 17 | 18 | # Generate Swagger documentation if DOCS is not empty 19 | if [ -n "${DOCS}" ]; then 20 | # Check if swag is installed 21 | if ! command -v swag &> /dev/null; then 22 | echo "swag command not found. Please install it using:" 23 | echo "go install github.com/swaggo/swag/cmd/swag@latest" 24 | echo "Skipping Swagger documentation generation due to missing swag tool." 25 | else 26 | echo "Generating Swagger documentation..." 27 | swag init -g cmd/apimain.go --output docs/api --instanceName api --exclude http/controller/admin 28 | swag init -g cmd/apimain.go --output docs/admin --instanceName admin --exclude http/controller/api 29 | fi 30 | else 31 | echo "Skipping Swagger documentation generation due to DOCS is empty." 32 | fi 33 | 34 | # Compile the Go code and output it to the release directory 35 | go build -o release/apimain cmd/apimain.go 36 | 37 | # Copy resource files to the release directory 38 | cp -ar resources release/ 39 | cp -ar docs release/ 40 | cp -ar conf release/ 41 | 42 | # Create necessary directory structures 43 | mkdir -p release/data 44 | mkdir -p release/runtime 45 | 46 | echo "Build and setup completed successfully." -------------------------------------------------------------------------------- /conf/admin/hello.html: -------------------------------------------------------------------------------- 1 | ### 👏👏👏 你好 ***{{username}}***, 欢迎使用 [RustDesk API](https://github.com/lejianwen/rustdesk-api) -------------------------------------------------------------------------------- /config/cache.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | type Cache struct { 4 | Type string 5 | RedisAddr string `mapstructure:"redis-addr"` 6 | RedisPwd string `mapstructure:"redis-pwd"` 7 | RedisDb int `mapstructure:"redis-db"` 8 | FileDir string `mapstructure:"file-dir"` 9 | } 10 | -------------------------------------------------------------------------------- /config/gin.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | type Gin struct { 4 | ApiAddr string `mapstructure:"api-addr"` 5 | AdminAddr string `mapstructure:"admin-addr"` 6 | Mode string 7 | ResourcesPath string `mapstructure:"resources-path"` 8 | TrustProxy string `mapstructure:"trust-proxy"` 9 | } 10 | -------------------------------------------------------------------------------- /config/gorm.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | const ( 4 | TypeSqlite = "sqlite" 5 | TypeMysql = "mysql" 6 | ) 7 | 8 | type Gorm struct { 9 | Type string `mapstructure:"type"` 10 | MaxIdleConns int `mapstructure:"max-idle-conns"` 11 | MaxOpenConns int `mapstructure:"max-open-conns"` 12 | } 13 | 14 | type Mysql struct { 15 | Addr string `mapstructure:"addr"` 16 | Username string `mapstructure:"username"` 17 | Password string `mapstructure:"password"` 18 | Dbname string `mapstructure:"dbname"` 19 | } 20 | -------------------------------------------------------------------------------- /config/jwt.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import "time" 4 | 5 | type Jwt struct { 6 | Key string `mapstructure:"key"` 7 | ExpireDuration time.Duration `mapstructure:"expire-duration"` 8 | } 9 | -------------------------------------------------------------------------------- /config/ldap.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | type LdapUser struct { 4 | BaseDn string `mapstructure:"base-dn"` // The base DN of the user for searching 5 | EnableAttr string `mapstructure:"enable-attr"` // The attribute name of the user for enabling, in AD it is "userAccountControl", empty means no enable attribute, all users are enabled 6 | EnableAttrValue string `mapstructure:"enable-attr-value"` // The value of the enable attribute when the user is enabled. If you are using AD, just leave it random str, it will be ignored. 7 | Filter string `mapstructure:"filter"` 8 | Username string `mapstructure:"username"` 9 | Email string `mapstructure:"email"` 10 | FirstName string `mapstructure:"first-name"` 11 | LastName string `mapstructure:"last-name"` 12 | Sync bool `mapstructure:"sync"` // Will sync the user's information to the internal database 13 | AdminGroup string `mapstructure:"admin-group"` // Which group is the admin group 14 | } 15 | 16 | // type LdapGroup struct { 17 | // BaseDn string `mapstructure:"base-dn"` // The base DN of the group for searching 18 | // Name string `mapstructure:"name"` // The attribute name of the group 19 | // Filter string `mapstructure:"filter"` 20 | // Admin string `mapstructure:"admin"` // Which group is the admin group 21 | // Member string `mapstructure:"member"` // How to get the member of the group: member, uniqueMember, or memberOf (default: member) 22 | // Mode string `mapstructure:"mode"` 23 | // Map map[string]string `mapstructure:"map"` // If mode is "map", map the LDAP group to the internal group 24 | // } 25 | 26 | type Ldap struct { 27 | Enable bool `mapstructure:"enable"` 28 | Url string `mapstructure:"url"` 29 | TlsCaFile string `mapstructure:"tls-ca-file"` 30 | TlsVerify bool `mapstructure:"tls-verify"` 31 | BaseDn string `mapstructure:"base-dn"` 32 | BindDn string `mapstructure:"bind-dn"` 33 | BindPassword string `mapstructure:"bind-password"` 34 | User LdapUser `mapstructure:"user"` 35 | // Group LdapGroup `mapstructure:"group"` 36 | } 37 | -------------------------------------------------------------------------------- /config/logger.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | type Logger struct { 4 | Path string 5 | Level string 6 | ReportCaller bool `mapstructure:"report-caller"` 7 | } 8 | -------------------------------------------------------------------------------- /config/oauth.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | type GithubOauth struct { 4 | ClientId string `mapstructure:"client-id"` 5 | ClientSecret string `mapstructure:"client-secret"` 6 | RedirectUrl string `mapstructure:"redirect-url"` 7 | } 8 | 9 | type GoogleOauth struct { 10 | ClientId string `mapstructure:"client-id"` 11 | ClientSecret string `mapstructure:"client-secret"` 12 | RedirectUrl string `mapstructure:"redirect-url"` 13 | } 14 | 15 | type OidcOauth struct { 16 | Issuer string `mapstructure:"issuer"` 17 | ClientId string `mapstructure:"client-id"` 18 | ClientSecret string `mapstructure:"client-secret"` 19 | RedirectUrl string `mapstructure:"redirect-url"` 20 | } 21 | -------------------------------------------------------------------------------- /config/oss.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | type Oss struct { 4 | AccessKeyId string `mapstructure:"access-key-id"` 5 | AccessKeySecret string `mapstructure:"access-key-secret"` 6 | Host string `mapstructure:"host"` 7 | CallbackUrl string `mapstructure:"callback-url"` 8 | ExpireTime int64 `mapstructure:"expire-time"` 9 | MaxByte int64 `mapstructure:"max-byte"` 10 | } 11 | -------------------------------------------------------------------------------- /config/proxy.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | type Proxy struct { 4 | Enable bool `mapstructure:"enable"` 5 | Host string `mapstructure:"host"` 6 | } 7 | -------------------------------------------------------------------------------- /config/redis.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | type Redis struct { 4 | Addr string 5 | Password string 6 | Db int 7 | } 8 | -------------------------------------------------------------------------------- /config/rustdesk.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "os" 5 | ) 6 | 7 | const ( 8 | DefaultIdServerPort = 21116 9 | DefaultRelayServerPort = 21117 10 | ) 11 | 12 | type Rustdesk struct { 13 | IdServer string `mapstructure:"id-server"` 14 | IdServerPort int `mapstructure:"-"` 15 | RelayServer string `mapstructure:"relay-server"` 16 | RelayServerPort int `mapstructure:"-"` 17 | ApiServer string `mapstructure:"api-server"` 18 | Key string `mapstructure:"key"` 19 | KeyFile string `mapstructure:"key-file"` 20 | Personal int `mapstructure:"personal"` 21 | //webclient-magic-queryonline 22 | WebclientMagicQueryonline int `mapstructure:"webclient-magic-queryonline"` 23 | WsHost string `mapstructure:"ws-host"` 24 | } 25 | 26 | func (rd *Rustdesk) LoadKeyFile() { 27 | // Load key file 28 | if rd.Key != "" { 29 | return 30 | } 31 | if rd.KeyFile != "" { 32 | // Load key from file 33 | b, err := os.ReadFile(rd.KeyFile) 34 | if err != nil { 35 | return 36 | } 37 | rd.Key = string(b) 38 | return 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /debian/changelog: -------------------------------------------------------------------------------- 1 | rustdesk-api-server (1.3.6) UNRELEASED; urgency=medium 2 | 3 | * Update the version to 1.3.6 to match the client. 4 | 5 | -- rustdesk-api Tue, 24 Dec 2024 13:48:34 +0800 -------------------------------------------------------------------------------- /debian/compat: -------------------------------------------------------------------------------- 1 | 10 2 | -------------------------------------------------------------------------------- /debian/control.tpl: -------------------------------------------------------------------------------- 1 | Source: rustdesk-api-server 2 | Section: net 3 | Priority: optional 4 | Maintainer: ymwl 5 | Build-Depends: debhelper (>= 10), pkg-config 6 | Standards-Version: 4.5.0 7 | Homepage: https://github.com/lejianwen/rustdesk-api/ 8 | 9 | Package: rustdesk-api-server 10 | Architecture: {{ ARCH }} 11 | Depends: systemd ${misc:Depends} 12 | Description: RustDesk api server 13 | RustDesk api server, it is free and open source. 14 | -------------------------------------------------------------------------------- /debian/copyright: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024-present Lejianwen and contributors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /debian/rules: -------------------------------------------------------------------------------- 1 | #!/usr/bin/make -f 2 | %: 3 | dh $@ 4 | 5 | override_dh_builddeb: 6 | dh_builddeb -- -Zgzip 7 | -------------------------------------------------------------------------------- /debian/rustdesk-api-server.install: -------------------------------------------------------------------------------- 1 | bin/rustdesk-api usr/bin 2 | systemd/rustdesk-api.service lib/systemd/system 3 | conf var/lib/rustdesk-api 4 | data var/lib/rustdesk-api 5 | resources var/lib/rustdesk-api 6 | runtime var/lib/rustdesk-api -------------------------------------------------------------------------------- /debian/rustdesk-api-server.postinst: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | SERVICE=rustdesk-api.service 5 | 6 | if [ "$1" = "configure" ]; then 7 | mkdir -p /var/log/rustdesk-api 8 | fi 9 | 10 | case "$1" in 11 | configure|abort-upgrade|abort-deconfigure|abort-remove) 12 | mkdir -p /var/lib/rustdesk-api/ 13 | deb-systemd-helper unmask "${SERVICE}" >/dev/null || true 14 | if deb-systemd-helper --quiet was-enabled "${SERVICE}"; then 15 | deb-systemd-invoke enable "${SERVICE}" >/dev/null || true 16 | else 17 | deb-systemd-invoke update-state "${SERVICE}" >/dev/null || true 18 | fi 19 | systemctl --system daemon-reload >/dev/null || true 20 | if [ -n "$2" ]; then 21 | deb-systemd-invoke restart "${SERVICE}" >/dev/null || true 22 | else 23 | deb-systemd-invoke start "${SERVICE}" >/dev/null || true 24 | fi 25 | ;; 26 | esac 27 | 28 | exit 0 29 | -------------------------------------------------------------------------------- /debian/rustdesk-api-server.postrm: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | SERVICE=rustdesk-api.service 5 | 6 | systemctl --system daemon-reload >/dev/null || true 7 | 8 | if [ "$1" = "purge" ]; then 9 | rm -rf /var/log/rustdesk-api/rustdesk-api.* 10 | deb-systemd-helper purge "${SERVICE}" >/dev/null || true 11 | deb-systemd-helper unmask "${SERVICE}" >/dev/null || true 12 | fi 13 | 14 | if [ "$1" = "remove" ]; then 15 | deb-systemd-helper mask "${SERVICE}" >/dev/null || true 16 | fi 17 | 18 | exit 0 19 | -------------------------------------------------------------------------------- /debian/rustdesk-api-server.prerm: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | SERVICE=rustdesk-api.service 5 | 6 | case "$1" in 7 | remove|deconfigure) 8 | deb-systemd-invoke stop "${SERVICE}" >/dev/null || true 9 | deb-systemd-invoke disable "${SERVICE}" >/dev/null || true 10 | ;; 11 | esac 12 | 13 | exit 0 14 | -------------------------------------------------------------------------------- /debian/source/format: -------------------------------------------------------------------------------- 1 | 3.0 (native) -------------------------------------------------------------------------------- /docker-compose-dev.yaml: -------------------------------------------------------------------------------- 1 | services: 2 | rustdesk-api: 3 | build: 4 | context: . 5 | dockerfile: Dockerfile.dev 6 | args: 7 | COUNTRY: CN 8 | FREONTEND_GIT_REPO: https://github.com/lejianwen/rustdesk-api-web.git 9 | FRONTEND_GIT_BRANCH: master 10 | # image: lejianwen/rustdesk-api 11 | container_name: rustdesk-api 12 | environment: 13 | - TZ=Asia/Shanghai 14 | - RUSTDESK_API_RUSTDESK_ID_SERVER=192.168.1.66:21116 15 | - RUSTDESK_API_RUSTDESK_RELAY_SERVER=192.168.1.66:21117 16 | - RUSTDESK_API_RUSTDESK_API_SERVER=http://127.0.0.1:21114 17 | - RUSTDESK_API_RUSTDESK_KEY=123456789 18 | ports: 19 | - 21114:21114 20 | volumes: 21 | - ./data/rustdesk/api:/app/data #将数据库挂载出来方便备份 22 | - ./conf:/app/conf # config 23 | # - ./resources:/app/resources # 静态资源 24 | restart: unless-stopped -------------------------------------------------------------------------------- /docker-compose.yaml: -------------------------------------------------------------------------------- 1 | services: 2 | rustdesk-api: 3 | image: lejianwen/rustdesk-api 4 | container_name: rustdesk-api 5 | environment: 6 | - TZ=Asia/Shanghai 7 | - RUSTDESK_API_RUSTDESK_ID_SERVER=192.168.1.66:21116 8 | - RUSTDESK_API_RUSTDESK_RELAY_SERVER=192.168.1.66:21117 9 | - RUSTDESK_API_RUSTDESK_API_SERVER=http://127.0.0.1:21114 10 | - RUSTDESK_API_RUSTDESK_KEY=123456789 11 | ports: 12 | - 21114:21114 13 | volumes: 14 | - ./data/rustdesk/api:/app/data # database 15 | # - ./conf:/app/conf # config 16 | # - ./resources:/app/resources # 静态资源 17 | restart: unless-stopped -------------------------------------------------------------------------------- /docker-dev.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | # Define Docker Compose file and cache option 5 | COMPOSE_FILE_NAME="docker-compose-dev.yaml" 6 | CACHE="" 7 | # Uncomment the next line to enable no-cache option 8 | # CACHE="--no-cache" 9 | 10 | # Define the base Docker Compose command 11 | DCS="docker compose -f ${COMPOSE_FILE_NAME}" 12 | 13 | # Function to build and start services 14 | build_and_run() { 15 | echo "Building services..." 16 | if ! $DCS build ${CACHE}; then 17 | echo "Error: Failed to build services" 18 | exit 1 19 | fi 20 | 21 | echo "Starting services..." 22 | if ! $DCS up -d; then 23 | echo "Error: Failed to start services" 24 | exit 1 25 | fi 26 | echo "Services started successfully" 27 | echo "If you want to stop the services, run" 28 | echo "docker compose -f ${COMPOSE_FILE_NAME} down" 29 | 30 | echo "If you want to see the logs, run" 31 | echo "docker compose -f ${COMPOSE_FILE_NAME} logs -f" 32 | } 33 | 34 | # Execute build and start function 35 | build_and_run -------------------------------------------------------------------------------- /docs/admin_webclient.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lejianwen/rustdesk-api/7a5d141ce8039267b3ea0138950ae2b64d50f86e/docs/admin_webclient.png -------------------------------------------------------------------------------- /docs/api_swag.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lejianwen/rustdesk-api/7a5d141ce8039267b3ea0138950ae2b64d50f86e/docs/api_swag.png -------------------------------------------------------------------------------- /docs/en_img/admin_webclient.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lejianwen/rustdesk-api/7a5d141ce8039267b3ea0138950ae2b64d50f86e/docs/en_img/admin_webclient.png -------------------------------------------------------------------------------- /docs/en_img/pc_ab.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lejianwen/rustdesk-api/7a5d141ce8039267b3ea0138950ae2b64d50f86e/docs/en_img/pc_ab.png -------------------------------------------------------------------------------- /docs/en_img/pc_gr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lejianwen/rustdesk-api/7a5d141ce8039267b3ea0138950ae2b64d50f86e/docs/en_img/pc_gr.png -------------------------------------------------------------------------------- /docs/en_img/pc_login.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lejianwen/rustdesk-api/7a5d141ce8039267b3ea0138950ae2b64d50f86e/docs/en_img/pc_login.png -------------------------------------------------------------------------------- /docs/en_img/rustdesk_command_advance.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lejianwen/rustdesk-api/7a5d141ce8039267b3ea0138950ae2b64d50f86e/docs/en_img/rustdesk_command_advance.png -------------------------------------------------------------------------------- /docs/en_img/rustdesk_command_simple.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lejianwen/rustdesk-api/7a5d141ce8039267b3ea0138950ae2b64d50f86e/docs/en_img/rustdesk_command_simple.png -------------------------------------------------------------------------------- /docs/en_img/web_admin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lejianwen/rustdesk-api/7a5d141ce8039267b3ea0138950ae2b64d50f86e/docs/en_img/web_admin.png -------------------------------------------------------------------------------- /docs/en_img/web_admin_gr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lejianwen/rustdesk-api/7a5d141ce8039267b3ea0138950ae2b64d50f86e/docs/en_img/web_admin_gr.png -------------------------------------------------------------------------------- /docs/en_img/web_admin_oauth.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lejianwen/rustdesk-api/7a5d141ce8039267b3ea0138950ae2b64d50f86e/docs/en_img/web_admin_oauth.png -------------------------------------------------------------------------------- /docs/en_img/web_admin_user.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lejianwen/rustdesk-api/7a5d141ce8039267b3ea0138950ae2b64d50f86e/docs/en_img/web_admin_user.png -------------------------------------------------------------------------------- /docs/en_img/web_resetpwd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lejianwen/rustdesk-api/7a5d141ce8039267b3ea0138950ae2b64d50f86e/docs/en_img/web_resetpwd.png -------------------------------------------------------------------------------- /docs/init_admin_pwd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lejianwen/rustdesk-api/7a5d141ce8039267b3ea0138950ae2b64d50f86e/docs/init_admin_pwd.png -------------------------------------------------------------------------------- /docs/pc_ab.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lejianwen/rustdesk-api/7a5d141ce8039267b3ea0138950ae2b64d50f86e/docs/pc_ab.png -------------------------------------------------------------------------------- /docs/pc_gr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lejianwen/rustdesk-api/7a5d141ce8039267b3ea0138950ae2b64d50f86e/docs/pc_gr.png -------------------------------------------------------------------------------- /docs/pc_login.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lejianwen/rustdesk-api/7a5d141ce8039267b3ea0138950ae2b64d50f86e/docs/pc_login.png -------------------------------------------------------------------------------- /docs/rustdesk_command_advance.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lejianwen/rustdesk-api/7a5d141ce8039267b3ea0138950ae2b64d50f86e/docs/rustdesk_command_advance.png -------------------------------------------------------------------------------- /docs/rustdesk_command_simple.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lejianwen/rustdesk-api/7a5d141ce8039267b3ea0138950ae2b64d50f86e/docs/rustdesk_command_simple.png -------------------------------------------------------------------------------- /docs/web_admin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lejianwen/rustdesk-api/7a5d141ce8039267b3ea0138950ae2b64d50f86e/docs/web_admin.png -------------------------------------------------------------------------------- /docs/web_admin_gr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lejianwen/rustdesk-api/7a5d141ce8039267b3ea0138950ae2b64d50f86e/docs/web_admin_gr.png -------------------------------------------------------------------------------- /docs/web_admin_oauth.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lejianwen/rustdesk-api/7a5d141ce8039267b3ea0138950ae2b64d50f86e/docs/web_admin_oauth.png -------------------------------------------------------------------------------- /docs/web_admin_user.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lejianwen/rustdesk-api/7a5d141ce8039267b3ea0138950ae2b64d50f86e/docs/web_admin_user.png -------------------------------------------------------------------------------- /docs/web_resetpwd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lejianwen/rustdesk-api/7a5d141ce8039267b3ea0138950ae2b64d50f86e/docs/web_resetpwd.png -------------------------------------------------------------------------------- /docs/webclientv2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lejianwen/rustdesk-api/7a5d141ce8039267b3ea0138950ae2b64d50f86e/docs/webclientv2.png -------------------------------------------------------------------------------- /generate_api.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | //go:generate swag init -g cmd/apimain.go --output docs/api --instanceName api --exclude http/controller/admin 4 | //go:generate swag init -g cmd/apimain.go --output docs/admin --instanceName admin --exclude http/controller/api 5 | //go:generate go run cmd/apimain.go 6 | -------------------------------------------------------------------------------- /generate_run.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | //go:generate go run cmd/apimain.go 4 | -------------------------------------------------------------------------------- /global/global.go: -------------------------------------------------------------------------------- 1 | package global 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | ut "github.com/go-playground/universal-translator" 6 | "github.com/go-playground/validator/v10" 7 | "github.com/go-redis/redis/v8" 8 | "github.com/lejianwen/rustdesk-api/v2/config" 9 | "github.com/lejianwen/rustdesk-api/v2/lib/cache" 10 | "github.com/lejianwen/rustdesk-api/v2/lib/jwt" 11 | "github.com/lejianwen/rustdesk-api/v2/lib/lock" 12 | "github.com/lejianwen/rustdesk-api/v2/lib/upload" 13 | "github.com/lejianwen/rustdesk-api/v2/utils" 14 | "github.com/nicksnyder/go-i18n/v2/i18n" 15 | "github.com/sirupsen/logrus" 16 | "github.com/spf13/viper" 17 | "gorm.io/gorm" 18 | ) 19 | 20 | var ( 21 | DB *gorm.DB 22 | Logger *logrus.Logger 23 | ConfigPath string = "" 24 | Config config.Config 25 | Viper *viper.Viper 26 | Redis *redis.Client 27 | Cache cache.Handler 28 | Validator struct { 29 | Validate *validator.Validate 30 | UT *ut.UniversalTranslator 31 | VTrans ut.Translator 32 | ValidStruct func(*gin.Context, interface{}) []string 33 | ValidVar func(ctx *gin.Context, field interface{}, tag string) []string 34 | } 35 | Oss *upload.Oss 36 | Jwt *jwt.Jwt 37 | Lock lock.Locker 38 | Localizer func(lang string) *i18n.Localizer 39 | LoginLimiter *utils.LoginLimiter 40 | ) 41 | -------------------------------------------------------------------------------- /global/i18n.go: -------------------------------------------------------------------------------- 1 | package global 2 | 3 | import ( 4 | "github.com/BurntSushi/toml" 5 | "github.com/nicksnyder/go-i18n/v2/i18n" 6 | "golang.org/x/text/language" 7 | "os" 8 | ) 9 | 10 | func InitI18n() { 11 | bundle := i18n.NewBundle(language.English) 12 | bundle.RegisterUnmarshalFunc("toml", toml.Unmarshal) 13 | //读取global.Config.Gin.ResourcesPath下的所有语言文件 14 | dir := Config.Gin.ResourcesPath + "/i18n" 15 | fileInfos, err := os.ReadDir(dir) 16 | if err != nil { 17 | panic(err) 18 | } 19 | for _, fileInfo := range fileInfos { 20 | //如果文件名不是.toml结尾 21 | if fileInfo.IsDir() || fileInfo.Name()[len(fileInfo.Name())-5:] != ".toml" { 22 | continue 23 | } 24 | bundle.LoadMessageFile(Config.Gin.ResourcesPath + "/i18n/" + fileInfo.Name()) 25 | } 26 | Localizer = func(lang string) *i18n.Localizer { 27 | if lang == "" { 28 | lang = Config.Lang 29 | } 30 | if lang == "en" { 31 | return i18n.NewLocalizer(bundle, "en") 32 | } else { 33 | return i18n.NewLocalizer(bundle, lang, "en") 34 | } 35 | } 36 | 37 | //personUnreadEmails := localizer.MustLocalize(&i18n.LocalizeConfig{ 38 | // DefaultMessage: &i18n.Message{ 39 | // ID: "PersonUnreadEmails", 40 | // }, 41 | // PluralCount: 6, 42 | // TemplateData: map[string]interface{}{ 43 | // "Name": "LE", 44 | // "PluralCount": 6, 45 | // }, 46 | //}) 47 | //personUnreadEmails, err := global.Localizer.LocalizeMessage(&i18n.Message{ 48 | // ID: "ParamsError", 49 | //}) 50 | //fmt.Println(err, personUnreadEmails) 51 | 52 | } 53 | -------------------------------------------------------------------------------- /http/controller/admin/file.go: -------------------------------------------------------------------------------- 1 | package admin 2 | 3 | import ( 4 | "fmt" 5 | "github.com/gin-gonic/gin" 6 | "github.com/lejianwen/rustdesk-api/v2/global" 7 | "github.com/lejianwen/rustdesk-api/v2/http/response" 8 | "github.com/lejianwen/rustdesk-api/v2/lib/upload" 9 | "os" 10 | "time" 11 | ) 12 | 13 | type File struct { 14 | } 15 | 16 | // OssToken 文件 17 | // @Tags 文件 18 | // @Summary 获取ossToken 19 | // @Description 获取ossToken 20 | // @Accept json 21 | // @Produce json 22 | // @Success 200 {object} response.Response 23 | // @Failure 500 {object} response.Response 24 | // @Router /admin/file/oss_token [get] 25 | // @Security token 26 | func (f *File) OssToken(c *gin.Context) { 27 | token := global.Oss.GetPolicyToken("") 28 | response.Success(c, token) 29 | } 30 | 31 | type FileBack struct { 32 | upload.CallbackBaseForm 33 | Url string `json:"url"` 34 | } 35 | 36 | // Notify 上传成功后回调 37 | func (f *File) Notify(c *gin.Context) { 38 | 39 | res := global.Oss.Verify(c.Request) 40 | if !res { 41 | response.Fail(c, 101, "权限错误") 42 | return 43 | } 44 | fm := &FileBack{} 45 | if err := c.ShouldBind(fm); err != nil { 46 | fmt.Println(err) 47 | } 48 | fm.Url = global.Config.Oss.Host + "/" + fm.Filename 49 | response.Success(c, fm) 50 | 51 | } 52 | 53 | // Upload 上传文件到本地 54 | // @Tags 文件 55 | // @Summary 上传文件到本地 56 | // @Description 上传文件到本地 57 | // @Accept multipart/form-data 58 | // @Produce json 59 | // @Param file formData file true "上传文件示例" 60 | // @Success 200 {object} response.Response 61 | // @Failure 500 {object} response.Response 62 | // @Router /admin/file/upload [post] 63 | // @Security token 64 | func (f *File) Upload(c *gin.Context) { 65 | file, _ := c.FormFile("file") 66 | timePath := time.Now().Format("20060102") + "/" 67 | webPath := "/upload/" + timePath 68 | path := global.Config.Gin.ResourcesPath + webPath 69 | dst := path + file.Filename 70 | err := os.MkdirAll(path, os.ModePerm) 71 | if err != nil { 72 | return 73 | } 74 | // 上传文件至指定目录 75 | err = c.SaveUploadedFile(file, dst) 76 | if err != nil { 77 | return 78 | } 79 | // 返回文件web地址 80 | response.Success(c, gin.H{ 81 | "url": webPath + file.Filename, 82 | }) 83 | } 84 | -------------------------------------------------------------------------------- /http/controller/admin/my/peer.go: -------------------------------------------------------------------------------- 1 | package my 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "github.com/lejianwen/rustdesk-api/v2/http/request/admin" 6 | "github.com/lejianwen/rustdesk-api/v2/http/response" 7 | "github.com/lejianwen/rustdesk-api/v2/service" 8 | "gorm.io/gorm" 9 | "time" 10 | ) 11 | 12 | type Peer struct { 13 | } 14 | 15 | // List 列表 16 | // @Tags 我的设备 17 | // @Summary 设备列表 18 | // @Description 设备列表 19 | // @Accept json 20 | // @Produce json 21 | // @Param page query int false "页码" 22 | // @Param page_size query int false "页大小" 23 | // @Param time_ago query int false "时间" 24 | // @Param id query string false "ID" 25 | // @Param hostname query string false "主机名" 26 | // @Param uuids query string false "uuids 用逗号分隔" 27 | // @Success 200 {object} response.Response{data=model.PeerList} 28 | // @Failure 500 {object} response.Response 29 | // @Router /admin/my/peer/list [get] 30 | // @Security token 31 | func (ct *Peer) List(c *gin.Context) { 32 | query := &admin.PeerQuery{} 33 | if err := c.ShouldBindQuery(query); err != nil { 34 | response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error()) 35 | return 36 | } 37 | u := service.AllService.UserService.CurUser(c) 38 | res := service.AllService.PeerService.List(query.Page, query.PageSize, func(tx *gorm.DB) { 39 | tx.Where("user_id = ?", u.Id) 40 | if query.TimeAgo > 0 { 41 | lt := time.Now().Unix() - int64(query.TimeAgo) 42 | tx.Where("last_online_time < ?", lt) 43 | } 44 | if query.TimeAgo < 0 { 45 | lt := time.Now().Unix() + int64(query.TimeAgo) 46 | tx.Where("last_online_time > ?", lt) 47 | } 48 | if query.Id != "" { 49 | tx.Where("id like ?", "%"+query.Id+"%") 50 | } 51 | if query.Hostname != "" { 52 | tx.Where("hostname like ?", "%"+query.Hostname+"%") 53 | } 54 | if query.Uuids != "" { 55 | tx.Where("uuid in (?)", query.Uuids) 56 | } 57 | }) 58 | response.Success(c, res) 59 | } 60 | -------------------------------------------------------------------------------- /http/controller/api/index.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | requstform "github.com/lejianwen/rustdesk-api/v2/http/request/api" 6 | "github.com/lejianwen/rustdesk-api/v2/http/response" 7 | "github.com/lejianwen/rustdesk-api/v2/model" 8 | "github.com/lejianwen/rustdesk-api/v2/service" 9 | "net/http" 10 | "time" 11 | ) 12 | 13 | type Index struct { 14 | } 15 | 16 | // Index 首页 17 | // @Tags 首页 18 | // @Summary 首页 19 | // @Description 首页 20 | // @Accept json 21 | // @Produce json 22 | // @Success 200 {object} response.Response 23 | // @Failure 500 {object} response.Response 24 | // @Router / [get] 25 | func (i *Index) Index(c *gin.Context) { 26 | response.Success( 27 | c, 28 | "Hello Gwen", 29 | ) 30 | } 31 | 32 | // Heartbeat 心跳 33 | // @Tags 首页 34 | // @Summary 心跳 35 | // @Description 心跳 36 | // @Accept json 37 | // @Produce json 38 | // @Success 200 {object} nil 39 | // @Failure 500 {object} response.Response 40 | // @Router /heartbeat [post] 41 | func (i *Index) Heartbeat(c *gin.Context) { 42 | info := &requstform.PeerInfoInHeartbeat{} 43 | err := c.ShouldBindJSON(info) 44 | if err != nil { 45 | c.JSON(http.StatusOK, gin.H{}) 46 | return 47 | } 48 | if info.Uuid == "" { 49 | c.JSON(http.StatusOK, gin.H{}) 50 | return 51 | } 52 | peer := service.AllService.PeerService.FindByUuid(info.Uuid) 53 | if peer == nil || peer.RowId == 0 { 54 | c.JSON(http.StatusOK, gin.H{}) 55 | return 56 | } 57 | //如果在40s以内则不更新 58 | if time.Now().Unix()-peer.LastOnlineTime >= 30 { 59 | upp := &model.Peer{RowId: peer.RowId, LastOnlineTime: time.Now().Unix(), LastOnlineIp: c.ClientIP()} 60 | service.AllService.PeerService.Update(upp) 61 | } 62 | c.JSON(http.StatusOK, gin.H{}) 63 | } 64 | 65 | // Version 版本 66 | // @Tags 首页 67 | // @Summary 版本 68 | // @Description 版本 69 | // @Accept json 70 | // @Produce json 71 | // @Success 200 {object} response.Response 72 | // @Failure 500 {object} response.Response 73 | // @Router /version [get] 74 | func (i *Index) Version(c *gin.Context) { 75 | //读取resources/version文件 76 | v := service.AllService.AppService.GetAppVersion() 77 | response.Success( 78 | c, 79 | v, 80 | ) 81 | } 82 | -------------------------------------------------------------------------------- /http/controller/api/peer.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "github.com/gin-gonic/gin/binding" 6 | requstform "github.com/lejianwen/rustdesk-api/v2/http/request/api" 7 | "github.com/lejianwen/rustdesk-api/v2/http/response" 8 | "github.com/lejianwen/rustdesk-api/v2/service" 9 | "net/http" 10 | ) 11 | 12 | type Peer struct { 13 | } 14 | 15 | // SysInfo 16 | // @Tags 地址 17 | // @Summary 提交系统信息 18 | // @Description 提交系统信息 19 | // @Accept json 20 | // @Produce json 21 | // @Param body body requstform.PeerForm true "系统信息表单" 22 | // @Success 200 {string} string "SYSINFO_UPDATED,ID_NOT_FOUND" 23 | // @Failure 500 {object} response.ErrorResponse 24 | // @Router /sysinfo [post] 25 | func (p *Peer) SysInfo(c *gin.Context) { 26 | f := &requstform.PeerForm{} 27 | err := c.ShouldBindBodyWith(f, binding.JSON) 28 | if err != nil { 29 | response.Error(c, response.TranslateMsg(c, "ParamsError")+err.Error()) 30 | return 31 | } 32 | fpe := f.ToPeer() 33 | pe := service.AllService.PeerService.FindByUuid(f.Uuid) 34 | if pe.RowId == 0 { 35 | pe = f.ToPeer() 36 | pe.UserId = service.AllService.UserService.FindLatestUserIdFromLoginLogByUuid(pe.Uuid) 37 | err = service.AllService.PeerService.Create(pe) 38 | if err != nil { 39 | response.Error(c, response.TranslateMsg(c, "OperationFailed")+err.Error()) 40 | return 41 | } 42 | } else { 43 | if pe.UserId == 0 { 44 | pe.UserId = service.AllService.UserService.FindLatestUserIdFromLoginLogByUuid(pe.Uuid) 45 | } 46 | fpe.RowId = pe.RowId 47 | fpe.UserId = pe.UserId 48 | err = service.AllService.PeerService.Update(fpe) 49 | if err != nil { 50 | response.Error(c, response.TranslateMsg(c, "OperationFailed")+err.Error()) 51 | return 52 | } 53 | } 54 | //SYSINFO_UPDATED 上传成功 55 | //ID_NOT_FOUND 下次心跳会上传 56 | //直接响应文本 57 | c.String(http.StatusOK, "SYSINFO_UPDATED") 58 | } 59 | 60 | func (p *Peer) SysInfoVer(c *gin.Context) { 61 | //读取resources/version文件 62 | v := service.AllService.AppService.GetAppVersion() 63 | c.String(http.StatusOK, v) 64 | } 65 | -------------------------------------------------------------------------------- /http/controller/api/user.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | apiResp "github.com/lejianwen/rustdesk-api/v2/http/response/api" 6 | "github.com/lejianwen/rustdesk-api/v2/service" 7 | "net/http" 8 | ) 9 | 10 | type User struct { 11 | } 12 | 13 | // currentUser 当前用户 14 | // @Tags 用户 15 | // @Summary 用户信息 16 | // @Description 用户信息 17 | // @Accept json 18 | // @Produce json 19 | // @Success 200 {object} apiResp.UserPayload 20 | // @Failure 500 {object} response.Response 21 | // @Router /currentUser [get] 22 | // @Security token 23 | //func (u *User) currentUser(c *gin.Context) { 24 | // user := service.AllService.UserService.CurUser(c) 25 | // up := (&apiResp.UserPayload{}).FromName(user) 26 | // c.JSON(http.StatusOK, up) 27 | //} 28 | 29 | // Info 用户信息 30 | // @Tags 用户 31 | // @Summary 用户信息 32 | // @Description 用户信息 33 | // @Accept json 34 | // @Produce json 35 | // @Success 200 {object} apiResp.UserPayload 36 | // @Failure 500 {object} response.Response 37 | // @Router /currentUser [get] 38 | // @Security token 39 | func (u *User) Info(c *gin.Context) { 40 | user := service.AllService.UserService.CurUser(c) 41 | up := (&apiResp.UserPayload{}).FromUser(user) 42 | c.JSON(http.StatusOK, up) 43 | } 44 | -------------------------------------------------------------------------------- /http/controller/web/index.go: -------------------------------------------------------------------------------- 1 | package web 2 | 3 | import ( 4 | "fmt" 5 | "github.com/gin-gonic/gin" 6 | "github.com/lejianwen/rustdesk-api/v2/global" 7 | ) 8 | 9 | type Index struct { 10 | } 11 | 12 | func (i *Index) Index(c *gin.Context) { 13 | c.Redirect(302, "/_admin/") 14 | } 15 | 16 | func (i *Index) ConfigJs(c *gin.Context) { 17 | apiServer := global.Config.Rustdesk.ApiServer 18 | magicQueryonline := global.Config.Rustdesk.WebclientMagicQueryonline 19 | tmp := fmt.Sprintf(`localStorage.setItem('api-server', '%v'); 20 | const ws2_prefix = 'wc-'; 21 | localStorage.setItem(ws2_prefix+'api-server', '%v'); 22 | 23 | window.webclient_magic_queryonline = %d; 24 | window.ws_host = '%v'; 25 | `, apiServer, apiServer, magicQueryonline, global.Config.Rustdesk.WsHost) 26 | // tmp := ` 27 | //localStorage.setItem('api-server', "` + apiServer + `") 28 | //const ws2_prefix = 'wc-' 29 | //localStorage.setItem(ws2_prefix+'api-server', "` + apiServer + `") 30 | // 31 | //window.webclient_magic_queryonline = ` + magicQueryonline + `` 32 | 33 | c.Header("Content-Type", "application/javascript") 34 | c.String(200, tmp) 35 | } 36 | -------------------------------------------------------------------------------- /http/http.go: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "github.com/lejianwen/rustdesk-api/v2/global" 6 | "github.com/lejianwen/rustdesk-api/v2/http/middleware" 7 | "github.com/lejianwen/rustdesk-api/v2/http/router" 8 | "github.com/sirupsen/logrus" 9 | "net/http" 10 | "strings" 11 | ) 12 | 13 | func ApiInit() { 14 | gin.SetMode(global.Config.Gin.Mode) 15 | g := gin.New() 16 | 17 | //[WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value. 18 | //Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details. 19 | if global.Config.Gin.TrustProxy != "" { 20 | pro := strings.Split(global.Config.Gin.TrustProxy, ",") 21 | err := g.SetTrustedProxies(pro) 22 | if err != nil { 23 | panic(err) 24 | } 25 | } 26 | 27 | if global.Config.Gin.Mode == gin.ReleaseMode { 28 | //修改gin Recovery日志 输出为logger的输出点 29 | if global.Logger != nil { 30 | gin.DefaultErrorWriter = global.Logger.WriterLevel(logrus.ErrorLevel) 31 | } 32 | } 33 | g.NoRoute(func(c *gin.Context) { 34 | c.String(http.StatusNotFound, "404 not found") 35 | }) 36 | g.Use(middleware.Logger(), middleware.Limiter(), gin.Recovery()) 37 | router.WebInit(g) 38 | router.Init(g) 39 | router.ApiInit(g) 40 | Run(g, global.Config.Gin.ApiAddr) 41 | } 42 | -------------------------------------------------------------------------------- /http/middleware/admin.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "github.com/lejianwen/rustdesk-api/v2/http/response" 6 | "github.com/lejianwen/rustdesk-api/v2/service" 7 | ) 8 | 9 | // BackendUserAuth 后台权限验证中间件 10 | func BackendUserAuth() gin.HandlerFunc { 11 | return func(c *gin.Context) { 12 | 13 | //测试先关闭 14 | token := c.GetHeader("api-token") 15 | if token == "" { 16 | response.Fail(c, 403, "请先登录") 17 | c.Abort() 18 | return 19 | } 20 | user, ut := service.AllService.UserService.InfoByAccessToken(token) 21 | if user.Id == 0 { 22 | response.Fail(c, 403, "请先登录") 23 | c.Abort() 24 | return 25 | } 26 | 27 | if !service.AllService.UserService.CheckUserEnable(user) { 28 | c.JSON(401, gin.H{ 29 | "error": "Unauthorized", 30 | }) 31 | c.Abort() 32 | return 33 | } 34 | 35 | c.Set("curUser", user) 36 | c.Set("token", token) 37 | //如果时间小于1天,token自动续期 38 | service.AllService.UserService.AutoRefreshAccessToken(ut) 39 | 40 | c.Next() 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /http/middleware/admin_privilege.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "github.com/lejianwen/rustdesk-api/v2/http/response" 6 | "github.com/lejianwen/rustdesk-api/v2/service" 7 | ) 8 | 9 | // AdminPrivilege ... 10 | func AdminPrivilege() gin.HandlerFunc { 11 | return func(c *gin.Context) { 12 | u := service.AllService.UserService.CurUser(c) 13 | 14 | if !service.AllService.UserService.IsAdmin(u) { 15 | response.Fail(c, 403, "无权限") 16 | c.Abort() 17 | return 18 | } 19 | 20 | c.Next() 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /http/middleware/cors.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "net/http" 6 | ) 7 | 8 | // Cors 跨域 9 | func Cors() gin.HandlerFunc { 10 | return func(c *gin.Context) { 11 | origin := c.GetHeader("Origin") 12 | //fmt.Println("origin", origin) 13 | c.Header("Access-Control-Allow-Origin", origin) 14 | c.Header("Access-Control-Allow-Headers", "api-token,content-type,authorization ") 15 | c.Header("Access-Control-Allow-Methods", c.Request.Method) 16 | c.Header("Access-Control-Allow-Credentials", "true") 17 | if c.Request.Method == "OPTIONS" { 18 | c.AbortWithStatus(http.StatusNoContent) 19 | return 20 | } 21 | c.Next() 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /http/middleware/jwt.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "github.com/lejianwen/rustdesk-api/v2/global" 6 | "github.com/lejianwen/rustdesk-api/v2/http/response" 7 | "github.com/lejianwen/rustdesk-api/v2/service" 8 | ) 9 | 10 | func JwtAuth() gin.HandlerFunc { 11 | return func(c *gin.Context) { 12 | //测试先关闭 13 | token := c.GetHeader("api-token") 14 | if token == "" { 15 | response.Fail(c, 403, "请先登录") 16 | c.Abort() 17 | return 18 | } 19 | uid, err := global.Jwt.ParseToken(token) 20 | if err != nil { 21 | response.Fail(c, 403, "请先登录") 22 | c.Abort() 23 | return 24 | } 25 | if uid == 0 { 26 | response.Fail(c, 403, "请先登录") 27 | c.Abort() 28 | return 29 | } 30 | 31 | user := service.AllService.UserService.InfoById(uid) 32 | //user := &model.User{ 33 | // Id: uid, 34 | // Username: "测试用户", 35 | //} 36 | if user.Id == 0 { 37 | response.Fail(c, 403, "请先登录") 38 | c.Abort() 39 | return 40 | } 41 | if !service.AllService.UserService.CheckUserEnable(user) { 42 | response.Fail(c, 101, "你已被禁用") 43 | c.Abort() 44 | return 45 | } 46 | c.Set("curUser", user) 47 | 48 | c.Next() 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /http/middleware/limiter.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "github.com/lejianwen/rustdesk-api/v2/global" 6 | "github.com/lejianwen/rustdesk-api/v2/http/response" 7 | "net/http" 8 | ) 9 | 10 | func Limiter() gin.HandlerFunc { 11 | return func(c *gin.Context) { 12 | loginLimiter := global.LoginLimiter 13 | clientIp := c.ClientIP() 14 | banned, _ := loginLimiter.CheckSecurityStatus(clientIp) 15 | if banned { 16 | response.Fail(c, http.StatusLocked, response.TranslateMsg(c, "Banned")) 17 | c.Abort() 18 | return 19 | } 20 | c.Next() 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /http/middleware/logger.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "github.com/lejianwen/rustdesk-api/v2/global" 6 | "github.com/sirupsen/logrus" 7 | ) 8 | 9 | // Logger 日志中间件 10 | func Logger() gin.HandlerFunc { 11 | return func(c *gin.Context) { 12 | global.Logger.WithFields( 13 | logrus.Fields{ 14 | "uri": c.Request.URL, 15 | "ip": c.ClientIP(), 16 | "method": c.Request.Method, 17 | }).Debug("Request") 18 | c.Next() 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /http/middleware/rustauth.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "github.com/lejianwen/rustdesk-api/v2/global" 6 | "github.com/lejianwen/rustdesk-api/v2/service" 7 | ) 8 | 9 | func RustAuth() gin.HandlerFunc { 10 | return func(c *gin.Context) { 11 | //fmt.Println(c.Request.URL, c.Request.Header) 12 | //获取HTTP_AUTHORIZATION 13 | token := c.GetHeader("Authorization") 14 | if token == "" { 15 | c.JSON(401, gin.H{ 16 | "error": "Unauthorized", 17 | }) 18 | c.Abort() 19 | return 20 | } 21 | if len(token) <= 7 { 22 | c.JSON(401, gin.H{ 23 | "error": "Unauthorized", 24 | }) 25 | c.Abort() 26 | return 27 | } 28 | //提取token,格式是Bearer {token} 29 | //这里只是简单的提取 30 | token = token[7:] 31 | 32 | //验证token 33 | 34 | //检查是否设置了jwt key 35 | if len(global.Jwt.Key) > 0 { 36 | uid, _ := service.AllService.UserService.VerifyJWT(token) 37 | if uid == 0 { 38 | c.JSON(401, gin.H{ 39 | "error": "Unauthorized", 40 | }) 41 | c.Abort() 42 | return 43 | } 44 | } 45 | 46 | user, ut := service.AllService.UserService.InfoByAccessToken(token) 47 | if user.Id == 0 { 48 | c.JSON(401, gin.H{ 49 | "error": "Unauthorized", 50 | }) 51 | c.Abort() 52 | return 53 | } 54 | if !service.AllService.UserService.CheckUserEnable(user) { 55 | c.JSON(401, gin.H{ 56 | "error": "Unauthorized", 57 | }) 58 | c.Abort() 59 | return 60 | } 61 | 62 | c.Set("curUser", user) 63 | c.Set("token", token) 64 | 65 | service.AllService.UserService.AutoRefreshAccessToken(ut) 66 | 67 | c.Next() 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /http/request/admin/audit.go: -------------------------------------------------------------------------------- 1 | package admin 2 | 3 | type AuditQuery struct { 4 | PeerId string `form:"peer_id"` 5 | FromPeer string `form:"from_peer"` 6 | PageQuery 7 | } 8 | 9 | type AuditConnLogIds struct { 10 | Ids []uint `json:"ids" validate:"required"` 11 | } 12 | type AuditFileLogIds struct { 13 | Ids []uint `json:"ids" validate:"required"` 14 | } 15 | -------------------------------------------------------------------------------- /http/request/admin/group.go: -------------------------------------------------------------------------------- 1 | package admin 2 | 3 | import "github.com/lejianwen/rustdesk-api/v2/model" 4 | 5 | type GroupForm struct { 6 | Id uint `json:"id"` 7 | Name string `json:"name" validate:"required"` 8 | Type int `json:"type"` 9 | } 10 | 11 | func (gf *GroupForm) FromGroup(group *model.Group) *GroupForm { 12 | gf.Id = group.Id 13 | gf.Name = group.Name 14 | gf.Type = group.Type 15 | return gf 16 | } 17 | 18 | func (gf *GroupForm) ToGroup() *model.Group { 19 | group := &model.Group{} 20 | group.Id = gf.Id 21 | group.Name = gf.Name 22 | group.Type = gf.Type 23 | return group 24 | } 25 | 26 | type DeviceGroupForm struct { 27 | Id uint `json:"id"` 28 | Name string `json:"name" validate:"required"` 29 | } 30 | 31 | func (gf *DeviceGroupForm) ToDeviceGroup() *model.DeviceGroup { 32 | group := &model.DeviceGroup{} 33 | group.Id = gf.Id 34 | group.Name = gf.Name 35 | return group 36 | } 37 | -------------------------------------------------------------------------------- /http/request/admin/login.go: -------------------------------------------------------------------------------- 1 | package admin 2 | 3 | type Login struct { 4 | Username string `json:"username" validate:"required" label:"用户名"` 5 | Password string `json:"password,omitempty" validate:"required" label:"密码"` 6 | Platform string `json:"platform" label:"平台"` 7 | Captcha string `json:"captcha,omitempty" label:"验证码"` 8 | CaptchaId string `json:"captcha_id,omitempty"` 9 | } 10 | 11 | type LoginLogQuery struct { 12 | UserId int `form:"user_id"` 13 | IsMy int `form:"is_my"` 14 | PageQuery 15 | } 16 | type LoginTokenQuery struct { 17 | UserId int `form:"user_id"` 18 | PageQuery 19 | } 20 | 21 | type LoginLogIds struct { 22 | Ids []uint `json:"ids" validate:"required"` 23 | } 24 | -------------------------------------------------------------------------------- /http/request/admin/oauth.go: -------------------------------------------------------------------------------- 1 | package admin 2 | 3 | import ( 4 | "github.com/lejianwen/rustdesk-api/v2/model" 5 | ) 6 | 7 | type BindOauthForm struct { 8 | Op string `json:"op" binding:"required"` 9 | } 10 | 11 | type OauthConfirmForm struct { 12 | Code string `json:"code" binding:"required"` 13 | } 14 | type UnBindOauthForm struct { 15 | Op string `json:"op" binding:"required"` 16 | } 17 | type OauthForm struct { 18 | Id uint `json:"id"` 19 | Op string `json:"op" validate:"omitempty"` 20 | OauthType string `json:"oauth_type" validate:"required"` 21 | Issuer string `json:"issuer" validate:"omitempty,url"` 22 | Scopes string `json:"scopes" validate:"omitempty"` 23 | ClientId string `json:"client_id" validate:"required"` 24 | ClientSecret string `json:"client_secret" validate:"required"` 25 | RedirectUrl string `json:"redirect_url" validate:"required"` 26 | AutoRegister *bool `json:"auto_register"` 27 | PkceEnable *bool `json:"pkce_enable"` 28 | PkceMethod string `json:"pkce_method"` 29 | } 30 | 31 | func (of *OauthForm) ToOauth() *model.Oauth { 32 | oa := &model.Oauth{ 33 | Op: of.Op, 34 | OauthType: of.OauthType, 35 | ClientId: of.ClientId, 36 | ClientSecret: of.ClientSecret, 37 | RedirectUrl: of.RedirectUrl, 38 | AutoRegister: of.AutoRegister, 39 | Issuer: of.Issuer, 40 | Scopes: of.Scopes, 41 | PkceEnable: of.PkceEnable, 42 | PkceMethod: of.PkceMethod, 43 | } 44 | oa.Id = of.Id 45 | return oa 46 | } 47 | -------------------------------------------------------------------------------- /http/request/admin/peer.go: -------------------------------------------------------------------------------- 1 | package admin 2 | 3 | import "github.com/lejianwen/rustdesk-api/v2/model" 4 | 5 | type PeerForm struct { 6 | RowId uint `json:"row_id" ` 7 | Id string `json:"id"` 8 | Cpu string `json:"cpu"` 9 | Hostname string `json:"hostname"` 10 | Memory string `json:"memory"` 11 | Os string `json:"os"` 12 | Username string `json:"username"` 13 | Uuid string `json:"uuid"` 14 | Version string `json:"version"` 15 | GroupId uint `json:"group_id"` 16 | } 17 | 18 | type PeerBatchDeleteForm struct { 19 | RowIds []uint `json:"row_ids" validate:"required"` 20 | } 21 | 22 | // ToPeer 23 | func (f *PeerForm) ToPeer() *model.Peer { 24 | return &model.Peer{ 25 | RowId: f.RowId, 26 | Id: f.Id, 27 | Cpu: f.Cpu, 28 | Hostname: f.Hostname, 29 | Memory: f.Memory, 30 | Os: f.Os, 31 | Username: f.Username, 32 | Uuid: f.Uuid, 33 | Version: f.Version, 34 | GroupId: f.GroupId, 35 | } 36 | } 37 | 38 | type PeerQuery struct { 39 | PageQuery 40 | TimeAgo int `json:"time_ago" form:"time_ago"` 41 | Id string `json:"id" form:"id"` 42 | Hostname string `json:"hostname" form:"hostname"` 43 | Uuids string `json:"uuids" form:"uuids"` 44 | Ip string `json:"ip" form:"ip"` 45 | Username string `json:"username" form:"username"` 46 | } 47 | 48 | type SimpleDataQuery struct { 49 | Ids []string `json:"ids" form:"ids"` 50 | } 51 | -------------------------------------------------------------------------------- /http/request/admin/shareRecord.go: -------------------------------------------------------------------------------- 1 | package admin 2 | 3 | type ShareRecordQuery struct { 4 | UserId uint `json:"user_id" form:"user_id"` 5 | PageQuery 6 | } 7 | 8 | type ShareRecordForm struct { 9 | Id uint `json:"id" form:"id"` 10 | UserId uint `json:"user_id" form:"user_id"` 11 | } 12 | 13 | type PeerShareRecordBatchDeleteForm struct { 14 | Ids []uint `json:"ids" validate:"required"` 15 | } 16 | -------------------------------------------------------------------------------- /http/request/admin/tag.go: -------------------------------------------------------------------------------- 1 | package admin 2 | 3 | import "github.com/lejianwen/rustdesk-api/v2/model" 4 | 5 | type TagForm struct { 6 | Id uint `json:"id"` 7 | Name string `json:"name" validate:"required"` 8 | Color uint `json:"color" validate:"required"` 9 | UserId uint `json:"user_id"` 10 | CollectionId uint `json:"collection_id"` 11 | } 12 | 13 | func (f *TagForm) FromTag(group *model.Tag) *TagForm { 14 | f.Id = group.Id 15 | f.Name = group.Name 16 | f.Color = group.Color 17 | f.UserId = group.UserId 18 | f.CollectionId = group.CollectionId 19 | return f 20 | } 21 | 22 | func (f *TagForm) ToTag() *model.Tag { 23 | i := &model.Tag{} 24 | i.Id = f.Id 25 | i.Name = f.Name 26 | i.Color = f.Color 27 | i.UserId = f.UserId 28 | i.CollectionId = f.CollectionId 29 | return i 30 | } 31 | 32 | type TagQuery struct { 33 | UserId int `form:"user_id"` 34 | IsMy int `form:"is_my"` 35 | CollectionId *int `form:"collection_id"` 36 | PageQuery 37 | } 38 | -------------------------------------------------------------------------------- /http/request/api/audit.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "encoding/json" 5 | "github.com/lejianwen/rustdesk-api/v2/global" 6 | "github.com/lejianwen/rustdesk-api/v2/model" 7 | "strconv" 8 | ) 9 | 10 | type AuditConnForm struct { 11 | Action string `json:"action"` 12 | ConnId int64 `json:"conn_id"` 13 | Id string `json:"id"` 14 | Peer []string `json:"peer"` 15 | Ip string `json:"ip"` 16 | SessionId float64 `json:"session_id"` 17 | Type int `json:"type"` 18 | Uuid string `json:"uuid"` 19 | } 20 | 21 | func (a *AuditConnForm) ToAuditConn() *model.AuditConn { 22 | fp := "" 23 | fn := "" 24 | if len(a.Peer) >= 1 { 25 | fp = a.Peer[0] 26 | if len(a.Peer) == 2 { 27 | fn = a.Peer[1] 28 | } 29 | } 30 | ssid := strconv.FormatFloat(a.SessionId, 'f', -1, 64) 31 | return &model.AuditConn{ 32 | Action: a.Action, 33 | ConnId: a.ConnId, 34 | PeerId: a.Id, 35 | FromPeer: fp, 36 | FromName: fn, 37 | Ip: a.Ip, 38 | SessionId: ssid, 39 | Type: a.Type, 40 | Uuid: a.Uuid, 41 | } 42 | } 43 | 44 | type AuditFileForm struct { 45 | Id string `json:"id"` 46 | Info string `json:"info"` 47 | IsFile bool `json:"is_file"` 48 | Path string `json:"path"` 49 | PeerId string `json:"peer_id"` 50 | Type int `json:"type"` 51 | Uuid string `json:"uuid"` 52 | } 53 | type AuditFileInfo struct { 54 | Ip string `json:"ip"` 55 | Name string `json:"name"` 56 | Num int `json:"num"` 57 | } 58 | 59 | func (a *AuditFileForm) ToAuditFile() *model.AuditFile { 60 | fi := &AuditFileInfo{} 61 | err := json.Unmarshal([]byte(a.Info), fi) 62 | if err != nil { 63 | global.Logger.Warn("ToAuditFile", err) 64 | } 65 | 66 | return &model.AuditFile{ 67 | PeerId: a.Id, 68 | Info: a.Info, 69 | IsFile: a.IsFile, 70 | FromPeer: a.PeerId, 71 | Path: a.Path, 72 | Type: a.Type, 73 | Uuid: a.Uuid, 74 | FromName: fi.Name, 75 | Ip: fi.Ip, 76 | Num: fi.Num, 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /http/request/api/oauth.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | type OidcAuthRequest struct { 4 | DeviceInfo DeviceInfoInLogin `json:"deviceInfo" label:"设备信息"` 5 | Id string `json:"id" label:"id"` 6 | Op string `json:"op" label:"op"` 7 | Uuid string `json:"uuid" label:"uuid"` 8 | } 9 | 10 | type OidcAuthQuery struct { 11 | Code string `json:"code" form:"code" label:"code"` 12 | Id string `json:"id" form:"id" label:"id"` 13 | Uuid string `json:"uuid" form:"uuid" label:"uuid"` 14 | } 15 | -------------------------------------------------------------------------------- /http/request/api/user.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | /* 4 | * 5 | 6 | message LoginRequest { 7 | string username = 1; 8 | bytes password = 2; 9 | string my_id = 4; 10 | string my_name = 5; 11 | OptionMessage option = 6; 12 | oneof union { 13 | FileTransfer file_transfer = 7; 14 | PortForward port_forward = 8; 15 | } 16 | bool video_ack_required = 9; 17 | uint64 session_id = 10; 18 | string version = 11; 19 | OSLogin os_login = 12; 20 | string my_platform = 13; 21 | bytes hwid = 14; 22 | } 23 | */ 24 | 25 | type DeviceInfoInLogin struct { 26 | Name string `json:"name" label:"name"` 27 | Os string `json:"os" label:"os"` 28 | Type string `json:"type" label:"type"` 29 | } 30 | 31 | type LoginForm struct { 32 | AutoLogin bool `json:"autoLogin" label:"自动登录"` 33 | DeviceInfo DeviceInfoInLogin `json:"deviceInfo" label:"设备信息"` 34 | Id string `json:"id" label:"id"` 35 | Type string `json:"type" label:"type"` 36 | Uuid string `json:"uuid" label:"uuid"` 37 | Username string `json:"username" validate:"required,gte=2,lte=32" label:"用户名"` 38 | Password string `json:"password,omitempty" validate:"gte=4,lte=32" label:"密码"` 39 | } 40 | 41 | type UserListQuery struct { 42 | Page uint `json:"page" form:"page" validate:"required" label:"页码"` 43 | PageSize uint `json:"pageSize" form:"pageSize" validate:"required" label:"每页数量"` 44 | Status int `json:"status" form:"status" label:"状态"` 45 | Accessible string `json:"accessible" form:"accessible"` 46 | } 47 | 48 | type PeerListQuery struct { 49 | Page uint `json:"page" form:"page" validate:"required" label:"页码"` 50 | PageSize uint `json:"pageSize" form:"pageSize" validate:"required" label:"每页数量"` 51 | Status int `json:"status" form:"status" label:"状态"` 52 | Accessible string `json:"accessible" form:"accessible"` 53 | } 54 | -------------------------------------------------------------------------------- /http/response/admin/user.go: -------------------------------------------------------------------------------- 1 | package admin 2 | 3 | import "github.com/lejianwen/rustdesk-api/v2/model" 4 | 5 | type LoginPayload struct { 6 | Username string `json:"username"` 7 | Email string `json:"email"` 8 | Avatar string `json:"avatar"` 9 | Token string `json:"token"` 10 | RouteNames []string `json:"route_names"` 11 | Nickname string `json:"nickname"` 12 | } 13 | 14 | func (lp *LoginPayload) FromUser(user *model.User) { 15 | lp.Username = user.Username 16 | lp.Email = user.Email 17 | lp.Avatar = user.Avatar 18 | lp.Nickname = user.Nickname 19 | } 20 | 21 | type UserOauthItem struct { 22 | Op string `json:"op"` 23 | Status int `json:"status"` 24 | } 25 | -------------------------------------------------------------------------------- /http/response/api/ab.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import "github.com/lejianwen/rustdesk-api/v2/model" 4 | 5 | type AbList struct { 6 | Peers []*model.AddressBook `json:"peers,omitempty"` 7 | Tags []string `json:"tags,omitempty"` 8 | TagColors string `json:"tag_colors,omitempty"` 9 | } 10 | 11 | type SharedProfilesPayload struct { 12 | Guid string `json:"guid"` 13 | Name string `json:"name"` 14 | Owner string `json:"owner"` 15 | Note string `json:"note"` 16 | Rule int `json:"rule"` 17 | } 18 | -------------------------------------------------------------------------------- /http/response/api/peer.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import "github.com/lejianwen/rustdesk-api/v2/model" 4 | 5 | /* 6 | GroupPeerPayload 7 | https://github.com/rustdesk/rustdesk/blob/master/flutter/lib/common/hbbs/hbbs.dart#L64 8 | 9 | String id = ''; 10 | Map info = {}; 11 | int? status; 12 | String user = ''; 13 | String user_name = ''; 14 | String note = ''; 15 | 16 | PeerPayload.fromJson(Map json) 17 | : id = json['id'] ?? '', 18 | info = (json['info'] is Map) ? json['info'] : {}, 19 | status = json['status'], 20 | user = json['user'] ?? '', 21 | user_name = json['user_name'] ?? '', 22 | note = json['note'] ?? ''; 23 | 24 | static Peer toPeer(GroupPeerPayload p) { 25 | return Peer.fromJson({ 26 | "id": p.id, 27 | 'loginName': p.user_name, 28 | "username": p.info['username'] ?? '', 29 | "platform": _platform(p.info['os']), 30 | "hostname": p.info['device_name'], 31 | }); 32 | } 33 | */ 34 | type GroupPeerPayload struct { 35 | Id string `json:"id"` 36 | Info *PeerPayloadInfo `json:"info"` 37 | Status int `json:"status"` 38 | User string `json:"user"` 39 | UserName string `json:"user_name"` 40 | Note string `json:"note"` 41 | DeviceGroupName string `json:"device_group_name"` 42 | } 43 | type PeerPayloadInfo struct { 44 | DeviceName string `json:"device_name"` 45 | Os string `json:"os"` 46 | Username string `json:"username"` 47 | } 48 | 49 | func (gpp *GroupPeerPayload) FromAddressBook(a *model.AddressBook, username string) { 50 | gpp.Id = a.Id 51 | os := a.Platform 52 | if a.Platform == "Mac OS" { 53 | os = "MacOS" 54 | } 55 | gpp.Info = &PeerPayloadInfo{ 56 | DeviceName: a.Hostname, 57 | Os: os, 58 | Username: a.Username, 59 | } 60 | gpp.UserName = username 61 | } 62 | 63 | func (gpp *GroupPeerPayload) FromPeer(p *model.Peer, username string, dGroupName string) { 64 | gpp.Id = p.Id 65 | gpp.Info = &PeerPayloadInfo{ 66 | DeviceName: p.Hostname, 67 | Os: p.Os, 68 | Username: p.Username, 69 | } 70 | gpp.Note = "" 71 | gpp.UserName = username 72 | gpp.DeviceGroupName = dGroupName 73 | } 74 | -------------------------------------------------------------------------------- /http/response/api/user.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import "github.com/lejianwen/rustdesk-api/v2/model" 4 | 5 | /* 6 | pub enum UserStatus { 7 | Disabled = 0, 8 | Normal = 1, 9 | Unverified = -1, 10 | } 11 | */ 12 | 13 | /* 14 | UserPayload 15 | String name = ”; 16 | String email = ”; 17 | String note = ”; 18 | UserStatus status; 19 | bool isAdmin = false; 20 | */ 21 | type UserPayload struct { 22 | Name string `json:"name"` 23 | Email string `json:"email"` 24 | Note string `json:"note"` 25 | IsAdmin *bool `json:"is_admin"` 26 | Status int `json:"status"` 27 | Info map[string]interface{} `json:"info"` 28 | } 29 | 30 | func (up *UserPayload) FromUser(user *model.User) *UserPayload { 31 | up.Name = user.Username 32 | up.Email = user.Email 33 | up.IsAdmin = user.IsAdmin 34 | up.Status = int(user.Status) 35 | up.Info = map[string]interface{}{} 36 | return up 37 | } 38 | 39 | /* 40 | class HttpType { 41 | static const kAuthReqTypeAccount = "account"; 42 | static const kAuthReqTypeMobile = "mobile"; 43 | static const kAuthReqTypeSMSCode = "sms_code"; 44 | static const kAuthReqTypeEmailCode = "email_code"; 45 | static const kAuthReqTypeTfaCode = "tfa_code"; 46 | 47 | static const kAuthResTypeToken = "access_token"; 48 | static const kAuthResTypeEmailCheck = "email_check"; 49 | static const kAuthResTypeTfaCheck = "tfa_check"; 50 | } 51 | */ 52 | type LoginRes struct { 53 | Type string `json:"type"` 54 | AccessToken string `json:"access_token"` 55 | User UserPayload `json:"user"` 56 | Secret string `json:"secret,omitempty"` 57 | TfaType string `json:"tfa_type,omitempty"` 58 | } 59 | -------------------------------------------------------------------------------- /http/response/api/webClient.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "github.com/lejianwen/rustdesk-api/v2/model" 5 | "time" 6 | ) 7 | 8 | type WebClientPeerPayload struct { 9 | ViewStyle string `json:"view-style"` 10 | Tm int64 `json:"tm"` 11 | Info WebClientPeerInfoPayload `json:"info"` 12 | Tmppwd string `json:"tmppwd"` 13 | } 14 | 15 | type WebClientPeerInfoPayload struct { 16 | Username string `json:"username"` 17 | Hostname string `json:"hostname"` 18 | Platform string `json:"platform"` 19 | Hash string `json:"hash"` 20 | Id string `json:"id"` 21 | } 22 | 23 | func (wcpp *WebClientPeerPayload) FromAddressBook(a *model.AddressBook) { 24 | wcpp.ViewStyle = "shrink" 25 | //24小时前 26 | wcpp.Tm = time.Now().Add(-time.Hour * 24).UnixNano() 27 | wcpp.Info = WebClientPeerInfoPayload{ 28 | Username: a.Username, 29 | Hostname: a.Hostname, 30 | Platform: a.Platform, 31 | Hash: a.Hash, 32 | } 33 | } 34 | 35 | func (wcpp *WebClientPeerPayload) FromShareRecord(sr *model.ShareRecord) { 36 | wcpp.ViewStyle = "shrink" 37 | //24小时前 38 | wcpp.Tm = time.Now().UnixNano() 39 | wcpp.Tmppwd = sr.Password 40 | wcpp.Info = WebClientPeerInfoPayload{ 41 | Username: "", 42 | Hostname: "", 43 | Platform: "", 44 | Id: sr.PeerId, 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /http/router/router.go: -------------------------------------------------------------------------------- 1 | package router 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "github.com/lejianwen/rustdesk-api/v2/global" 6 | "github.com/lejianwen/rustdesk-api/v2/http/controller/web" 7 | "net/http" 8 | ) 9 | 10 | func WebInit(g *gin.Engine) { 11 | i := &web.Index{} 12 | g.GET("/", i.Index) 13 | 14 | if global.Config.App.WebClient == 1 { 15 | g.GET("/webclient-config/index.js", i.ConfigJs) 16 | } 17 | 18 | if global.Config.App.WebClient == 1 { 19 | g.StaticFS("/webclient", http.Dir(global.Config.Gin.ResourcesPath+"/web")) 20 | g.StaticFS("/webclient2", http.Dir(global.Config.Gin.ResourcesPath+"/web2")) 21 | } 22 | g.StaticFS("/_admin", http.Dir(global.Config.Gin.ResourcesPath+"/admin")) 23 | } 24 | -------------------------------------------------------------------------------- /http/run.go: -------------------------------------------------------------------------------- 1 | //go:build !windows 2 | 3 | package http 4 | 5 | import ( 6 | "github.com/fvbock/endless" 7 | "github.com/gin-gonic/gin" 8 | ) 9 | 10 | func Run(g *gin.Engine, addr string) { 11 | endless.ListenAndServe(addr, g) 12 | } 13 | -------------------------------------------------------------------------------- /http/run_win.go: -------------------------------------------------------------------------------- 1 | //go:build windows 2 | 3 | package http 4 | 5 | import ( 6 | "github.com/gin-gonic/gin" 7 | ) 8 | 9 | func Run(g *gin.Engine, addr string) { 10 | g.Run(addr) 11 | } 12 | -------------------------------------------------------------------------------- /lib/cache/cache.go: -------------------------------------------------------------------------------- 1 | package cache 2 | 3 | import ( 4 | "encoding/json" 5 | ) 6 | 7 | type Handler interface { 8 | Get(key string, value interface{}) error 9 | Set(key string, value interface{}, exp int) error 10 | Gc() error 11 | } 12 | 13 | // MaxTimeOut 最大超时时间 14 | 15 | const ( 16 | TypeMem = "memory" 17 | TypeRedis = "redis" 18 | TypeFile = "file" 19 | MaxTimeOut = 365 * 24 * 3600 20 | ) 21 | 22 | func New(typ string) Handler { 23 | var cache Handler 24 | switch typ { 25 | case TypeFile: 26 | cache = NewFileCache() 27 | case TypeRedis: 28 | cache = new(RedisCache) 29 | case TypeMem: // memory 30 | cache = NewMemoryCache(0) 31 | default: 32 | cache = NewMemoryCache(0) 33 | } 34 | return cache 35 | } 36 | 37 | func EncodeValue(value interface{}) (string, error) { 38 | /*if v, ok := value.(string); ok { 39 | return v, nil 40 | } 41 | if v, ok := value.([]byte); ok { 42 | return string(v), nil 43 | }*/ 44 | b, err := json.Marshal(value) 45 | if err != nil { 46 | return "", err 47 | } 48 | return string(b), nil 49 | } 50 | 51 | func DecodeValue(value string, rtv interface{}) error { 52 | //判断rtv的类型是否是string,如果是string,直接赋值并返回 53 | /*switch rtv.(type) { 54 | case *string: 55 | *(rtv.(*string)) = value 56 | return nil 57 | case *[]byte: 58 | *(rtv.(*[]byte)) = []byte(value) 59 | return nil 60 | //struct 61 | case *interface{}: 62 | err := json.Unmarshal(([]byte)(value), rtv) 63 | return err 64 | default: 65 | err := json.Unmarshal(([]byte)(value), rtv) 66 | return err 67 | } 68 | */ 69 | err := json.Unmarshal(([]byte)(value), rtv) 70 | return err 71 | } 72 | -------------------------------------------------------------------------------- /lib/cache/cache_test.go: -------------------------------------------------------------------------------- 1 | package cache 2 | 3 | import ( 4 | "fmt" 5 | "github.com/go-redis/redis/v8" 6 | "reflect" 7 | "testing" 8 | ) 9 | 10 | func TestSimpleCache(t *testing.T) { 11 | 12 | type st struct { 13 | A string 14 | B string 15 | } 16 | 17 | items := map[string]interface{}{} 18 | items["a"] = "b" 19 | items["b"] = "c" 20 | 21 | ab := &st{ 22 | A: "a", 23 | B: "b", 24 | } 25 | items["ab"] = *ab 26 | 27 | a := items["a"] 28 | fmt.Println(a) 29 | 30 | b := items["b"] 31 | fmt.Println(b) 32 | 33 | ab.A = "aa" 34 | ab2 := st{} 35 | ab2 = (items["ab"]).(st) 36 | fmt.Println(ab2, reflect.TypeOf(ab2)) 37 | 38 | } 39 | 40 | func TestFileCacheSet(t *testing.T) { 41 | fc := New("file") 42 | err := fc.Set("123", "ddd", 0) 43 | if err != nil { 44 | fmt.Println(err.Error()) 45 | t.Fatalf("写入失败") 46 | } 47 | } 48 | 49 | func TestFileCacheGet(t *testing.T) { 50 | fc := New("file") 51 | err := fc.Set("123", "45156", 300) 52 | if err != nil { 53 | t.Fatalf("写入失败") 54 | } 55 | res := "" 56 | err = fc.Get("123", &res) 57 | if err != nil { 58 | t.Fatalf("读取失败") 59 | } 60 | fmt.Println("res", res) 61 | } 62 | 63 | func TestRedisCacheSet(t *testing.T) { 64 | rc := NewRedis(&redis.Options{ 65 | Addr: "192.168.1.168:6379", 66 | Password: "", // no password set 67 | DB: 0, // use default DB 68 | }) 69 | err := rc.Set("123", "ddd", 0) 70 | if err != nil { 71 | fmt.Println(err.Error()) 72 | t.Fatalf("写入失败") 73 | } 74 | } 75 | 76 | func TestRedisCacheGet(t *testing.T) { 77 | rc := NewRedis(&redis.Options{ 78 | Addr: "192.168.1.168:6379", 79 | Password: "", // no password set 80 | DB: 0, // use default DB 81 | }) 82 | err := rc.Set("123", "451156", 300) 83 | if err != nil { 84 | t.Fatalf("写入失败") 85 | } 86 | res := "" 87 | err = rc.Get("123", &res) 88 | if err != nil { 89 | t.Fatalf("读取失败") 90 | } 91 | fmt.Println("res", res) 92 | } 93 | -------------------------------------------------------------------------------- /lib/cache/file.go: -------------------------------------------------------------------------------- 1 | package cache 2 | 3 | import ( 4 | "crypto/md5" 5 | "fmt" 6 | "os" 7 | "sync" 8 | "time" 9 | ) 10 | 11 | type FileCache struct { 12 | mu sync.Mutex 13 | locks map[string]*sync.Mutex 14 | Dir string 15 | } 16 | 17 | func (fc *FileCache) getLock(key string) *sync.Mutex { 18 | fc.mu.Lock() 19 | defer fc.mu.Unlock() 20 | if fc.locks == nil { 21 | fc.locks = make(map[string]*sync.Mutex) 22 | } 23 | if _, ok := fc.locks[key]; !ok { 24 | fc.locks[key] = new(sync.Mutex) 25 | } 26 | return fc.locks[key] 27 | } 28 | 29 | func (c *FileCache) Get(key string, value interface{}) error { 30 | data, _ := c.getValue(key) 31 | err := DecodeValue(data, value) 32 | return err 33 | } 34 | 35 | // 获取值,如果文件不存在或者过期,返回空,过滤掉错误 36 | func (c *FileCache) getValue(key string) (string, error) { 37 | f := c.fileName(key) 38 | fileInfo, err := os.Stat(f) 39 | if err != nil { 40 | //文件不存在 41 | return "", nil 42 | } 43 | difT := time.Now().Sub(fileInfo.ModTime()) 44 | if difT >= 0 { 45 | os.Remove(f) 46 | return "", nil 47 | } 48 | data, err := os.ReadFile(f) 49 | if err != nil { 50 | return "", nil 51 | } 52 | return string(data), nil 53 | } 54 | 55 | // 保存值 56 | func (c *FileCache) saveValue(key string, value string, exp int) error { 57 | f := c.fileName(key) 58 | lock := c.getLock(f) 59 | lock.Lock() 60 | defer lock.Unlock() 61 | 62 | err := os.WriteFile(f, ([]byte)(value), 0644) 63 | if err != nil { 64 | return err 65 | } 66 | if exp <= 0 { 67 | exp = MaxTimeOut 68 | } 69 | expFromNow := time.Now().Add(time.Duration(exp) * time.Second) 70 | err = os.Chtimes(f, expFromNow, expFromNow) 71 | return err 72 | } 73 | 74 | func (c *FileCache) Set(key string, value interface{}, exp int) error { 75 | str, err := EncodeValue(value) 76 | if err != nil { 77 | return err 78 | } 79 | 80 | err = c.saveValue(key, str, exp) 81 | return err 82 | } 83 | 84 | func (c *FileCache) SetDir(path string) { 85 | c.Dir = path 86 | } 87 | 88 | func (c *FileCache) fileName(key string) string { 89 | f := c.Dir + string(os.PathSeparator) + fmt.Sprintf("%x", md5.Sum([]byte(key))) 90 | return f 91 | } 92 | 93 | func (c *FileCache) Gc() error { 94 | //检查文件过期时间,并删除 95 | return nil 96 | } 97 | 98 | func NewFileCache() *FileCache { 99 | return &FileCache{ 100 | locks: make(map[string]*sync.Mutex), 101 | Dir: os.TempDir(), 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /lib/cache/file_test.go: -------------------------------------------------------------------------------- 1 | package cache 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "testing" 7 | ) 8 | 9 | func TestFileSet(t *testing.T) { 10 | fc := NewFileCache() 11 | err := fc.Set("123", "ddd", 0) 12 | if err != nil { 13 | fmt.Println(err.Error()) 14 | t.Fatalf("写入失败") 15 | } 16 | } 17 | 18 | func TestFileGet(t *testing.T) { 19 | fc := NewFileCache() 20 | res := "" 21 | err := fc.Get("123", &res) 22 | if err != nil { 23 | fmt.Println(err.Error()) 24 | t.Fatalf("读取失败") 25 | } 26 | fmt.Println("res", res) 27 | } 28 | func TestFileSetGet(t *testing.T) { 29 | fc := NewFileCache() 30 | err := fc.Set("key1", "ddd", 0) 31 | res := "" 32 | err = fc.Get("key1", &res) 33 | if err != nil { 34 | fmt.Println(err.Error()) 35 | t.Fatalf("读取失败") 36 | } 37 | fmt.Println("res", res) 38 | } 39 | func TestFileGetJson(t *testing.T) { 40 | fc := NewFileCache() 41 | old := &r{ 42 | A: "a", B: "b", 43 | } 44 | fc.Set("123", old, 0) 45 | res := &r{} 46 | err2 := fc.Get("123", res) 47 | fmt.Println("res", res) 48 | if err2 != nil { 49 | t.Fatalf("读取失败" + err2.Error()) 50 | } 51 | } 52 | func TestFileSetGetJson(t *testing.T) { 53 | fc := NewFileCache() 54 | 55 | old_rr := &rr{AA: "aa", BB: "bb"} 56 | old := &r{ 57 | A: "a", B: "b", 58 | R: old_rr, 59 | } 60 | err := fc.Set("123", old, 300) 61 | if err != nil { 62 | t.Fatalf("写入失败") 63 | } 64 | //old_rr.AA = "aaa" 65 | fmt.Println("old_rr", old) 66 | 67 | res := &r{} 68 | err2 := fc.Get("123", res) 69 | fmt.Println("res", res) 70 | if err2 != nil { 71 | t.Fatalf("读取失败" + err2.Error()) 72 | } 73 | if !reflect.DeepEqual(res, old) { 74 | t.Fatalf("读取错误") 75 | } 76 | 77 | } 78 | 79 | func BenchmarkSet(b *testing.B) { 80 | fc := NewFileCache() 81 | b.ResetTimer() 82 | for i := 0; i < b.N; i++ { 83 | fc.Set("123", "{dsv}", 1000) 84 | } 85 | } 86 | 87 | func BenchmarkGet(b *testing.B) { 88 | fc := NewFileCache() 89 | b.ResetTimer() 90 | v := "" 91 | for i := 0; i < b.N; i++ { 92 | fc.Get("123", &v) 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /lib/cache/redis.go: -------------------------------------------------------------------------------- 1 | package cache 2 | 3 | import ( 4 | "context" 5 | "github.com/go-redis/redis/v8" 6 | "time" 7 | ) 8 | 9 | var ctx = context.Background() 10 | 11 | type RedisCache struct { 12 | rdb *redis.Client 13 | } 14 | 15 | func RedisCacheInit(conf *redis.Options) *RedisCache { 16 | c := &RedisCache{} 17 | c.rdb = redis.NewClient(conf) 18 | return c 19 | } 20 | 21 | func (c *RedisCache) Get(key string, value interface{}) error { 22 | data, err := c.rdb.Get(ctx, key).Result() 23 | if err != nil { 24 | return err 25 | } 26 | err1 := DecodeValue(data, value) 27 | return err1 28 | } 29 | 30 | func (c *RedisCache) Set(key string, value interface{}, exp int) error { 31 | str, err := EncodeValue(value) 32 | if err != nil { 33 | return err 34 | } 35 | if exp <= 0 { 36 | exp = MaxTimeOut 37 | } 38 | _, err1 := c.rdb.Set(ctx, key, str, time.Duration(exp)*time.Second).Result() 39 | return err1 40 | } 41 | 42 | func (c *RedisCache) Gc() error { 43 | return nil 44 | } 45 | 46 | func NewRedis(conf *redis.Options) *RedisCache { 47 | cache := RedisCacheInit(conf) 48 | return cache 49 | } 50 | -------------------------------------------------------------------------------- /lib/cache/redis_test.go: -------------------------------------------------------------------------------- 1 | package cache 2 | 3 | import ( 4 | "fmt" 5 | "github.com/go-redis/redis/v8" 6 | "reflect" 7 | "testing" 8 | ) 9 | 10 | func TestRedisSet(t *testing.T) { 11 | //rc := New("redis") 12 | rc := RedisCacheInit(&redis.Options{ 13 | Addr: "192.168.1.168:6379", 14 | Password: "", // no password set 15 | DB: 0, // use default DB 16 | }) 17 | err := rc.Set("123", "ddd", 0) 18 | if err != nil { 19 | fmt.Println(err.Error()) 20 | t.Fatalf("写入失败") 21 | } 22 | } 23 | 24 | func TestRedisGet(t *testing.T) { 25 | rc := RedisCacheInit(&redis.Options{ 26 | Addr: "192.168.1.168:6379", 27 | Password: "", // no password set 28 | DB: 0, // use default DB 29 | }) 30 | err := rc.Set("123", "451156", 300) 31 | if err != nil { 32 | t.Fatalf("写入失败") 33 | } 34 | res := "" 35 | err = rc.Get("123", &res) 36 | if err != nil { 37 | t.Fatalf("读取失败") 38 | } 39 | fmt.Println("res", res) 40 | } 41 | 42 | func TestRedisGetJson(t *testing.T) { 43 | rc := RedisCacheInit(&redis.Options{ 44 | Addr: "192.168.1.168:6379", 45 | Password: "", // no password set 46 | DB: 0, // use default DB 47 | }) 48 | type r struct { 49 | Aa string `json:"a"` 50 | B string `json:"c"` 51 | } 52 | old := &r{ 53 | Aa: "ab", B: "cdc", 54 | } 55 | err := rc.Set("1233", old, 300) 56 | if err != nil { 57 | t.Fatalf("写入失败") 58 | } 59 | 60 | res := &r{} 61 | err2 := rc.Get("1233", res) 62 | if err2 != nil { 63 | t.Fatalf("读取失败") 64 | } 65 | if !reflect.DeepEqual(res, old) { 66 | t.Fatalf("读取错误") 67 | } 68 | fmt.Println(res, res.Aa) 69 | } 70 | 71 | func BenchmarkRSet(b *testing.B) { 72 | rc := RedisCacheInit(&redis.Options{ 73 | Addr: "192.168.1.168:6379", 74 | Password: "", // no password set 75 | DB: 0, // use default DB 76 | }) 77 | b.ResetTimer() 78 | for i := 0; i < b.N; i++ { 79 | rc.Set("123", "{dsv}", 1000) 80 | } 81 | } 82 | 83 | func BenchmarkRGet(b *testing.B) { 84 | rc := RedisCacheInit(&redis.Options{ 85 | Addr: "192.168.1.168:6379", 86 | Password: "", // no password set 87 | DB: 0, // use default DB 88 | }) 89 | b.ResetTimer() 90 | v := "" 91 | for i := 0; i < b.N; i++ { 92 | rc.Get("123", &v) 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /lib/cache/simple_cache.go: -------------------------------------------------------------------------------- 1 | package cache 2 | 3 | import ( 4 | "errors" 5 | "reflect" 6 | "sync" 7 | ) 8 | 9 | // 此处实现了一个简单的缓存,用于测试 10 | // SimpleCache is a simple cache implementation 11 | type SimpleCache struct { 12 | data map[string]interface{} 13 | mu sync.Mutex 14 | maxBytes int64 15 | usedBytes int64 16 | } 17 | 18 | func (s *SimpleCache) Get(key string, value interface{}) error { 19 | s.mu.Lock() 20 | defer s.mu.Unlock() 21 | 22 | // 使用反射将存储的值设置到传入的指针变量中 23 | val := reflect.ValueOf(value) 24 | if val.Kind() != reflect.Ptr { 25 | return errors.New("value must be a pointer") 26 | } 27 | v, ok := s.data[key] 28 | if !ok { 29 | //设为空值 30 | val.Elem().Set(reflect.Zero(val.Elem().Type())) 31 | return nil 32 | } 33 | 34 | vval := reflect.ValueOf(v) 35 | if val.Elem().Type() != vval.Type() { 36 | //设为空值 37 | val.Elem().Set(reflect.Zero(val.Elem().Type())) 38 | return nil 39 | } 40 | 41 | val.Elem().Set(reflect.ValueOf(v)) 42 | return nil 43 | } 44 | 45 | func (s *SimpleCache) Set(key string, value interface{}, exp int) error { 46 | s.mu.Lock() 47 | defer s.mu.Unlock() 48 | // 检查传入的值是否是指针,如果是则取其值 49 | val := reflect.ValueOf(value) 50 | if val.Kind() == reflect.Ptr { 51 | val = val.Elem() 52 | } 53 | 54 | s.data[key] = val.Interface() 55 | return nil 56 | } 57 | func (s *SimpleCache) Gc() error { 58 | return nil 59 | } 60 | 61 | func NewSimpleCache() *SimpleCache { 62 | return &SimpleCache{ 63 | data: make(map[string]interface{}), 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /lib/cache/simple_cache_test.go: -------------------------------------------------------------------------------- 1 | package cache 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | func TestSimpleCache_Set(t *testing.T) { 9 | s := NewSimpleCache() 10 | err := s.Set("key", "value", 0) 11 | if err != nil { 12 | t.Fatalf("写入失败") 13 | } 14 | err = s.Set("key", 111, 0) 15 | if err != nil { 16 | t.Fatalf("写入失败") 17 | } 18 | } 19 | 20 | func TestSimpleCache_Get(t *testing.T) { 21 | s := NewSimpleCache() 22 | err := s.Set("key", "value", 0) 23 | value := "" 24 | err = s.Get("key", &value) 25 | fmt.Println("value", value) 26 | if err != nil { 27 | t.Fatalf("读取失败") 28 | } 29 | 30 | err = s.Set("key1", 11, 0) 31 | value1 := 0 32 | err = s.Get("key1", &value1) 33 | fmt.Println("value1", value1) 34 | if err != nil { 35 | t.Fatalf("读取失败") 36 | } 37 | 38 | err = s.Set("key2", []byte{'a', 'b'}, 0) 39 | value2 := []byte{} 40 | err = s.Get("key2", &value2) 41 | fmt.Println("value2", string(value2)) 42 | if err != nil { 43 | t.Fatalf("读取失败") 44 | } 45 | 46 | err = s.Set("key3", 33.33, 0) 47 | var value3 int 48 | err = s.Get("key3", &value3) 49 | fmt.Println("value3", value3) 50 | if err != nil { 51 | t.Fatalf("读取失败") 52 | } 53 | 54 | } 55 | 56 | type r struct { 57 | A string `json:"a"` 58 | B string `json:"b"` 59 | R *rr `json:"r"` 60 | } 61 | type r2 struct { 62 | A string `json:"a"` 63 | B string `json:"b"` 64 | } 65 | type rr struct { 66 | AA string `json:"aa"` 67 | BB string `json:"bb"` 68 | } 69 | 70 | func TestSimpleCache_GetStruct(t *testing.T) { 71 | s := NewSimpleCache() 72 | 73 | old_rr := &rr{ 74 | AA: "aa", BB: "bb", 75 | } 76 | 77 | old := &r{ 78 | A: "ab", B: "cdc", 79 | R: old_rr, 80 | } 81 | err := s.Set("key", old, 300) 82 | if err != nil { 83 | t.Fatalf("写入失败") 84 | } 85 | 86 | res := &r{} 87 | err2 := s.Get("key", res) 88 | fmt.Println("res", res) 89 | if err2 != nil { 90 | t.Fatalf("读取失败" + err2.Error()) 91 | 92 | } 93 | 94 | //修改原始值,看后面是否会变化 95 | old.A = "aa" 96 | old_rr.AA = "aaa" 97 | fmt.Println("old", old) 98 | res2 := &r{} 99 | err3 := s.Get("key", res2) 100 | fmt.Println("res2", res2, res2.R.AA, res2.R.BB) 101 | if err3 != nil { 102 | t.Fatalf("读取失败" + err3.Error()) 103 | 104 | } 105 | //if reflect.DeepEqual(res, old) { 106 | // t.Fatalf("读取错误") 107 | //} 108 | } 109 | -------------------------------------------------------------------------------- /lib/jwt/jwt.go: -------------------------------------------------------------------------------- 1 | package jwt 2 | 3 | import ( 4 | "fmt" 5 | "github.com/golang-jwt/jwt/v5" 6 | "time" 7 | ) 8 | 9 | type Jwt struct { 10 | Key []byte 11 | TokenExpireDuration time.Duration 12 | } 13 | 14 | type UserClaims struct { 15 | UserId uint `json:"user_id"` 16 | jwt.RegisteredClaims 17 | } 18 | 19 | func NewJwt(key string, tokenExpireDuration time.Duration) *Jwt { 20 | return &Jwt{ 21 | Key: []byte(key), 22 | TokenExpireDuration: tokenExpireDuration, 23 | } 24 | } 25 | 26 | func (s *Jwt) GenerateToken(userId uint) string { 27 | if len(s.Key) == 0 { 28 | fmt.Println("jwt key is nil") 29 | return "" 30 | } 31 | t := jwt.NewWithClaims(jwt.SigningMethodHS256, 32 | UserClaims{ 33 | UserId: userId, 34 | RegisteredClaims: jwt.RegisteredClaims{ 35 | ExpiresAt: jwt.NewNumericDate(time.Now().Add(s.TokenExpireDuration)), 36 | }, 37 | }) 38 | token, err := t.SignedString(s.Key) 39 | if err != nil { 40 | fmt.Printf("jwt token generate error: %v", err) 41 | return "" 42 | } 43 | return token 44 | } 45 | 46 | func (s *Jwt) ParseToken(tokenString string) (uint, error) { 47 | token, err := jwt.ParseWithClaims(tokenString, &UserClaims{}, func(token *jwt.Token) (interface{}, error) { 48 | return s.Key, nil 49 | }) 50 | if err != nil { 51 | return 0, err 52 | } 53 | if claims, ok := token.Claims.(*UserClaims); ok && token.Valid { 54 | return claims.UserId, nil 55 | } 56 | return 0, err 57 | } 58 | -------------------------------------------------------------------------------- /lib/lock/local.go: -------------------------------------------------------------------------------- 1 | package lock 2 | 3 | import ( 4 | "sync" 5 | ) 6 | 7 | type Local struct { 8 | Locks *sync.Map 9 | } 10 | 11 | func (l *Local) Lock(key string) { 12 | lock := l.GetLock(key) 13 | lock.Lock() 14 | } 15 | 16 | func (l *Local) UnLock(key string) { 17 | lock, ok := l.Locks.Load(key) 18 | if ok { 19 | lock.(*sync.Mutex).Unlock() 20 | } 21 | } 22 | 23 | func (l *Local) GetLock(key string) *sync.Mutex { 24 | lock, _ := l.Locks.LoadOrStore(key, &sync.Mutex{}) 25 | return lock.(*sync.Mutex) 26 | } 27 | 28 | func NewLocal() *Local { 29 | return &Local{ 30 | Locks: &sync.Map{}, 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /lib/lock/local_test.go: -------------------------------------------------------------------------------- 1 | package lock 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | "testing" 7 | ) 8 | 9 | func TestLocal_GetLock(t *testing.T) { 10 | l := NewLocal() 11 | wg := sync.WaitGroup{} 12 | wg.Add(3) 13 | var l1 *sync.Mutex 14 | var l2 *sync.Mutex 15 | var l3 *sync.Mutex 16 | i := 0 17 | go func() { 18 | l1 = l.GetLock("key") 19 | fmt.Println("l1", l1, i) 20 | l1.Lock() 21 | fmt.Println("l1", i) 22 | i++ 23 | l1.Unlock() 24 | wg.Done() 25 | }() 26 | go func() { 27 | l2 = l.GetLock("key") 28 | fmt.Println("l2", l2, i) 29 | l2.Lock() 30 | fmt.Println("l2", i) 31 | i++ 32 | l2.Unlock() 33 | wg.Done() 34 | }() 35 | go func() { 36 | l3 = l.GetLock("key") 37 | fmt.Println("l3", l3, i) 38 | l3.Lock() 39 | fmt.Println("l3", i) 40 | i++ 41 | l3.Unlock() 42 | wg.Done() 43 | }() 44 | wg.Wait() 45 | 46 | fmt.Println(l1, l2, l3) 47 | fmt.Println(l1 == l2, l2 == l3) 48 | fmt.Println(&sync.Mutex{} == &sync.Mutex{}) 49 | } 50 | 51 | func TestLocal_Lock(t *testing.T) { 52 | l := NewLocal() 53 | wg := sync.WaitGroup{} 54 | m := 10 55 | wg.Add(m) 56 | i := 0 57 | for j := 0; j < m; j++ { 58 | go func() { 59 | l.Lock("key") 60 | //fmt.Println(j, i) 61 | i++ 62 | fmt.Println(j, i) 63 | l.UnLock("key") 64 | wg.Done() 65 | }() 66 | } 67 | 68 | wg.Wait() 69 | fmt.Println(i) 70 | 71 | } 72 | func TestSyncMap(t *testing.T) { 73 | m := sync.Map{} 74 | wg := sync.WaitGroup{} 75 | wg.Add(3) 76 | go func() { 77 | v, ok := m.LoadOrStore("key", 1) 78 | fmt.Println(1, v, ok) 79 | wg.Done() 80 | }() 81 | go func() { 82 | v, ok := m.LoadOrStore("key", 2) 83 | fmt.Println(2, v, ok) 84 | wg.Done() 85 | }() 86 | go func() { 87 | v, ok := m.LoadOrStore("key", 3) 88 | fmt.Println(3, v, ok) 89 | wg.Done() 90 | }() 91 | wg.Wait() 92 | } 93 | -------------------------------------------------------------------------------- /lib/lock/lock.go: -------------------------------------------------------------------------------- 1 | package lock 2 | 3 | import "sync" 4 | 5 | type Locker interface { 6 | GetLock(key string) *sync.Mutex 7 | Lock(key string) 8 | UnLock(key string) 9 | } 10 | -------------------------------------------------------------------------------- /lib/logger/logger.go: -------------------------------------------------------------------------------- 1 | package logger 2 | 3 | import ( 4 | nested "github.com/antonfisher/nested-logrus-formatter" 5 | log "github.com/sirupsen/logrus" 6 | "io" 7 | "os" 8 | ) 9 | 10 | const ( 11 | DebugMode = "debug" 12 | ReleaseMode = "release" 13 | ) 14 | 15 | type Config struct { 16 | Path string 17 | Level string 18 | ReportCaller bool 19 | } 20 | 21 | func New(c *Config) *log.Logger { 22 | log.SetFormatter(&nested.Formatter{ 23 | // HideKeys: true, 24 | TimestampFormat: "[2006-01-02 15:04:05]", 25 | NoColors: true, 26 | NoFieldsColors: true, 27 | //FieldsOrder: []string{"name", "age"}, 28 | }) 29 | 30 | // 日志文件 31 | f := c.Path 32 | var write io.Writer 33 | if f != "" { 34 | fwriter, err := os.OpenFile(f, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644) 35 | if err != nil { 36 | panic("open log file fail!") 37 | } 38 | write = io.MultiWriter(fwriter, os.Stdout) 39 | } else { 40 | write = os.Stdout 41 | } 42 | 43 | log.SetOutput(write) 44 | 45 | log.SetReportCaller(c.ReportCaller) 46 | 47 | level, err2 := log.ParseLevel(c.Level) 48 | if err2 != nil { 49 | level = log.DebugLevel 50 | } 51 | log.SetLevel(level) 52 | 53 | return log.StandardLogger() 54 | } 55 | -------------------------------------------------------------------------------- /lib/orm/mysql.go: -------------------------------------------------------------------------------- 1 | package orm 2 | 3 | import ( 4 | "fmt" 5 | "github.com/lejianwen/rustdesk-api/v2/global" 6 | "gorm.io/driver/mysql" 7 | "gorm.io/gorm" 8 | "gorm.io/gorm/logger" 9 | "time" 10 | ) 11 | 12 | type MysqlConfig struct { 13 | Dns string 14 | MaxIdleConns int 15 | MaxOpenConns int 16 | } 17 | 18 | func NewMysql(mysqlConf *MysqlConfig) *gorm.DB { 19 | db, err := gorm.Open(mysql.New(mysql.Config{ 20 | DSN: mysqlConf.Dns, // DSN data source name 21 | DefaultStringSize: 256, // string 类型字段的默认长度 22 | //DisableDatetimePrecision: true, // 禁用 datetime 精度,MySQL 5.6 之前的数据库不支持 23 | //DontSupportRenameIndex: true, // 重命名索引时采用删除并新建的方式,MySQL 5.7 之前的数据库和 MariaDB 不支持重命名索引 24 | //DontSupportRenameColumn: true, // 用 `change` 重命名列,MySQL 8 之前的数据库和 MariaDB 不支持重命名列 25 | //SkipInitializeWithVersion: false, // 根据当前 MySQL 版本自动配置 26 | }), &gorm.Config{ 27 | DisableForeignKeyConstraintWhenMigrating: true, 28 | Logger: logger.New( 29 | global.Logger, // io writer 30 | logger.Config{ 31 | SlowThreshold: time.Second, // Slow SQL threshold 32 | LogLevel: logger.Warn, // Log level 33 | IgnoreRecordNotFoundError: true, // Ignore ErrRecordNotFound error for logger 34 | ParameterizedQueries: true, // Don't include params in the SQL log 35 | Colorful: true, 36 | }, 37 | ), 38 | }) 39 | if err != nil { 40 | fmt.Println(err) 41 | } 42 | sqlDB, err2 := db.DB() 43 | if err2 != nil { 44 | fmt.Println(err2) 45 | } 46 | // SetMaxIdleConns 设置空闲连接池中连接的最大数量 47 | sqlDB.SetMaxIdleConns(mysqlConf.MaxIdleConns) 48 | 49 | // SetMaxOpenConns 设置打开数据库连接的最大数量。 50 | sqlDB.SetMaxOpenConns(mysqlConf.MaxOpenConns) 51 | 52 | return db 53 | } 54 | -------------------------------------------------------------------------------- /lib/orm/sqlite.go: -------------------------------------------------------------------------------- 1 | package orm 2 | 3 | import ( 4 | "fmt" 5 | "github.com/lejianwen/rustdesk-api/v2/global" 6 | "gorm.io/driver/sqlite" 7 | "gorm.io/gorm" 8 | "gorm.io/gorm/logger" 9 | "time" 10 | ) 11 | 12 | type SqliteConfig struct { 13 | MaxIdleConns int 14 | MaxOpenConns int 15 | } 16 | 17 | func NewSqlite(sqliteConf *SqliteConfig) *gorm.DB { 18 | db, err := gorm.Open(sqlite.Open("./data/rustdeskapi.db"), &gorm.Config{ 19 | DisableForeignKeyConstraintWhenMigrating: true, 20 | Logger: logger.New( 21 | global.Logger, // io writer 22 | logger.Config{ 23 | SlowThreshold: time.Second, // Slow SQL threshold 24 | LogLevel: logger.Warn, // Log level 25 | IgnoreRecordNotFoundError: true, // Ignore ErrRecordNotFound error for logger 26 | ParameterizedQueries: true, // Don't include params in the SQL log 27 | Colorful: true, 28 | }, 29 | ), 30 | }) 31 | if err != nil { 32 | fmt.Println(err) 33 | } 34 | sqlDB, err2 := db.DB() 35 | if err2 != nil { 36 | fmt.Println(err2) 37 | } 38 | // SetMaxIdleConns 设置空闲连接池中连接的最大数量 39 | sqlDB.SetMaxIdleConns(sqliteConf.MaxIdleConns) 40 | 41 | // SetMaxOpenConns 设置打开数据库连接的最大数量。 42 | sqlDB.SetMaxOpenConns(sqliteConf.MaxOpenConns) 43 | 44 | return db 45 | } 46 | -------------------------------------------------------------------------------- /lib/upload/local.go: -------------------------------------------------------------------------------- 1 | package upload 2 | 3 | type Local struct { 4 | } 5 | -------------------------------------------------------------------------------- /model/audit.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | const ( 4 | AuditActionNew = "new" 5 | AuditActionClose = "close" 6 | ) 7 | 8 | type AuditConn struct { 9 | IdModel 10 | Action string `json:"action" gorm:"default:'';not null;"` 11 | ConnId int64 `json:"conn_id" gorm:"default:0;not null;index"` 12 | PeerId string `json:"peer_id" gorm:"default:'';not null;index"` 13 | FromPeer string `json:"from_peer" gorm:"default:'';not null;"` 14 | FromName string `json:"from_name" gorm:"default:'';not null;"` 15 | Ip string `json:"ip" gorm:"default:'';not null;"` 16 | SessionId string `json:"session_id" gorm:"default:'';not null;"` 17 | Type int `json:"type" gorm:"default:0;not null;"` 18 | Uuid string `json:"uuid" gorm:"default:'';not null;"` 19 | CloseTime int64 `json:"close_time" gorm:"default:0;not null;"` 20 | TimeModel 21 | } 22 | 23 | type AuditConnList struct { 24 | AuditConns []*AuditConn `json:"list"` 25 | Pagination 26 | } 27 | 28 | type AuditFile struct { 29 | IdModel 30 | FromPeer string `json:"from_peer" gorm:"default:'';not null;index"` 31 | Info string `json:"info" gorm:"default:'';not null;"` 32 | IsFile bool `json:"is_file" gorm:"default:0;not null;"` 33 | Path string `json:"path" gorm:"default:'';not null;"` 34 | PeerId string `json:"peer_id" gorm:"default:'';not null;index"` 35 | Type int `json:"type" gorm:"default:0;not null;"` 36 | Uuid string `json:"uuid" gorm:"default:'';not null;"` 37 | Ip string `json:"ip" gorm:"default:'';not null;"` 38 | Num int `json:"num" gorm:"default:0;not null;"` 39 | FromName string `json:"from_name" gorm:"default:'';not null;"` 40 | TimeModel 41 | } 42 | 43 | type AuditFileList struct { 44 | AuditFiles []*AuditFile `json:"list"` 45 | Pagination 46 | } 47 | -------------------------------------------------------------------------------- /model/custom_types/auto_json.go: -------------------------------------------------------------------------------- 1 | package custom_types 2 | 3 | import ( 4 | "database/sql/driver" 5 | "encoding/json" 6 | "errors" 7 | "fmt" 8 | ) 9 | 10 | // AutoJson 数据类型 11 | type AutoJson json.RawMessage 12 | 13 | func (j *AutoJson) Scan(value interface{}) error { 14 | 15 | var strValue string 16 | switch v := value.(type) { 17 | case []byte: 18 | strValue = string(v) 19 | case string: 20 | strValue = v 21 | default: 22 | return errors.New(fmt.Sprintf("Failed Scan AutoJson value: %v", value)) 23 | } 24 | bytes := []byte(strValue) 25 | //bytes, ok := value.([]byte) 26 | //if !ok { 27 | // return errors.New(fmt.Sprint("Failed Scan AutoJson value:", value)) 28 | //} 29 | 30 | if bytes == nil || len(bytes) == 0 { 31 | *j = AutoJson(json.RawMessage{'[', ']'}) 32 | return nil 33 | } 34 | result := &json.RawMessage{} 35 | err := json.Unmarshal(bytes, result) 36 | //解析json错误 返回空 37 | if err != nil { 38 | *j = AutoJson(json.RawMessage{'[', ']'}) 39 | return nil 40 | } 41 | *j = AutoJson(*result) 42 | return err 43 | } 44 | func (j AutoJson) Value() (driver.Value, error) { 45 | bytes, err := json.RawMessage(j).MarshalJSON() 46 | return string(bytes), err 47 | } 48 | func (j AutoJson) MarshalJSON() ([]byte, error) { 49 | b, err := json.RawMessage(j).MarshalJSON() 50 | if err != nil { 51 | return nil, err 52 | } 53 | return b, err 54 | } 55 | 56 | func (j *AutoJson) UnmarshalJSON(b []byte) error { 57 | result := json.RawMessage{} 58 | err := result.UnmarshalJSON(b) 59 | *j = AutoJson(result) 60 | return err 61 | } 62 | 63 | func (j AutoJson) String() string { 64 | s, _ := j.MarshalJSON() 65 | return (string)(s) 66 | } 67 | -------------------------------------------------------------------------------- /model/custom_types/auto_time.go: -------------------------------------------------------------------------------- 1 | package custom_types 2 | 3 | import ( 4 | "database/sql/driver" 5 | "time" 6 | ) 7 | 8 | // AutoTime 自定义时间格式 9 | type AutoTime time.Time 10 | 11 | func (mt AutoTime) Value() (driver.Value, error) { 12 | var zeroTime time.Time 13 | t := time.Time(mt) 14 | if t.UnixNano() == zeroTime.UnixNano() { 15 | return nil, nil 16 | } 17 | return t, nil 18 | } 19 | 20 | func (mt AutoTime) MarshalJSON() ([]byte, error) { 21 | //b := make([]byte, 0, len("2006-01-02 15:04:05")+2) 22 | b := time.Time(mt).AppendFormat([]byte{}, "\"2006-01-02 15:04:05\"") 23 | return b, nil 24 | } 25 | -------------------------------------------------------------------------------- /model/group.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | const ( 4 | GroupTypeDefault = 1 // 默认 5 | GroupTypeShare = 2 // 共享 6 | ) 7 | 8 | type Group struct { 9 | IdModel 10 | Name string `json:"name" gorm:"default:'';not null;"` 11 | Type int `json:"type" gorm:"default:1;not null;"` 12 | TimeModel 13 | } 14 | 15 | type GroupList struct { 16 | Groups []*Group `json:"list"` 17 | Pagination 18 | } 19 | 20 | type DeviceGroup struct { 21 | IdModel 22 | Name string `json:"name" gorm:"default:'';not null;"` 23 | TimeModel 24 | } 25 | 26 | type DeviceGroupList struct { 27 | DeviceGroups []*DeviceGroup `json:"list"` 28 | Pagination 29 | } 30 | -------------------------------------------------------------------------------- /model/loginLog.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | type LoginLog struct { 4 | IdModel 5 | UserId uint `json:"user_id" gorm:"default:0;not null;"` 6 | Client string `json:"client"` //webadmin,webclient,app, 7 | DeviceId string `json:"device_id"` 8 | Uuid string `json:"uuid"` 9 | Ip string `json:"ip"` 10 | Type string `json:"type"` //account,oauth 11 | Platform string `json:"platform"` //windows,linux,mac,android,ios 12 | UserTokenId uint `json:"user_token_id" gorm:"default:0;not null;"` 13 | IsDeleted uint `json:"is_deleted" gorm:"default:0;not null;"` 14 | TimeModel 15 | } 16 | 17 | const ( 18 | LoginLogClientWebAdmin = "webadmin" 19 | LoginLogClientWeb = "webclient" 20 | LoginLogClientApp = "app" 21 | ) 22 | 23 | const ( 24 | LoginLogTypeAccount = "account" 25 | LoginLogTypeOauth = "oauth" 26 | ) 27 | 28 | const ( 29 | IsDeletedNo = 0 30 | IsDeletedYes = 1 31 | ) 32 | 33 | type LoginLogList struct { 34 | LoginLogs []*LoginLog `json:"list"` 35 | Pagination 36 | } 37 | -------------------------------------------------------------------------------- /model/model.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "github.com/lejianwen/rustdesk-api/v2/model/custom_types" 5 | ) 6 | 7 | type StatusCode int 8 | 9 | const ( 10 | COMMON_STATUS_ENABLE StatusCode = 1 //通用状态 启用 11 | COMMON_STATUS_DISABLED StatusCode = 2 //通用状态 禁用 12 | ) 13 | 14 | type IdModel struct { 15 | Id uint `gorm:"primaryKey" json:"id"` 16 | } 17 | type TimeModel struct { 18 | CreatedAt custom_types.AutoTime `json:"created_at" gorm:"type:timestamp;"` 19 | UpdatedAt custom_types.AutoTime `json:"updated_at" gorm:"type:timestamp;"` 20 | } 21 | 22 | // Pagination 23 | type Pagination struct { 24 | Page int64 `form:"page" json:"page"` 25 | Total int64 `form:"total" json:"total"` 26 | PageSize int64 `form:"page_size" json:"page_size"` 27 | } 28 | -------------------------------------------------------------------------------- /model/peer.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | type Peer struct { 4 | RowId uint `json:"row_id" gorm:"primaryKey;"` 5 | Id string `json:"id" gorm:"default:'';not null;index"` 6 | Cpu string `json:"cpu" gorm:"default:'';not null;"` 7 | Hostname string `json:"hostname" gorm:"default:'';not null;"` 8 | Memory string `json:"memory" gorm:"default:'';not null;"` 9 | Os string `json:"os" gorm:"default:'';not null;"` 10 | Username string `json:"username" gorm:"default:'';not null;"` 11 | Uuid string `json:"uuid" gorm:"default:'';not null;index"` 12 | Version string `json:"version" gorm:"default:'';not null;"` 13 | UserId uint `json:"user_id" gorm:"default:0;not null;index"` 14 | User *User `json:"user,omitempty"` 15 | LastOnlineTime int64 `json:"last_online_time" gorm:"default:0;not null;"` 16 | LastOnlineIp string `json:"last_online_ip" gorm:"default:'';not null;"` 17 | GroupId uint `json:"group_id" gorm:"default:0;not null;index"` 18 | TimeModel 19 | } 20 | 21 | type PeerList struct { 22 | Peers []*Peer `json:"list"` 23 | Pagination 24 | } 25 | -------------------------------------------------------------------------------- /model/shareRecord.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | type ShareRecord struct { 4 | IdModel 5 | UserId uint `json:"user_id" gorm:"default:0;not null;index"` 6 | PeerId string `json:"peer_id" gorm:"default:'';not null;index"` 7 | ShareToken string `json:"share_token" gorm:"default:'';not null;index"` 8 | PasswordType string `json:"password_type" gorm:"default:'';not null;"` 9 | Password string `json:"password" gorm:"default:'';not null;"` 10 | Expire int64 `json:"expire" gorm:"default:0;not null;"` 11 | TimeModel 12 | } 13 | 14 | // ShareRecordList 分享记录列表 15 | type ShareRecordList struct { 16 | ShareRecords []*ShareRecord `json:"list,omitempty"` 17 | Pagination 18 | } 19 | -------------------------------------------------------------------------------- /model/tag.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | type Tag struct { 4 | IdModel 5 | Name string `json:"name" gorm:"default:'';not null;"` 6 | UserId uint `json:"user_id" gorm:"default:0;not null;index"` 7 | Color uint `json:"color" gorm:"default:0;not null;"` //color 是flutter的颜色值,从0x00000000 到 0xFFFFFFFF; 前两位表示透明度,后面6位表示颜色, 可以转成rgba 8 | CollectionId uint `json:"collection_id" gorm:"default:0;not null;index"` 9 | Collection *AddressBookCollection `json:"collection,omitempty"` 10 | TimeModel 11 | } 12 | 13 | type TagList struct { 14 | Tags []*Tag `json:"list"` 15 | Pagination 16 | } 17 | -------------------------------------------------------------------------------- /model/user.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | type User struct { 4 | IdModel 5 | Username string `json:"username" gorm:"default:'';not null;uniqueIndex"` 6 | Email string `json:"email" gorm:"default:'';not null;index"` 7 | // Email string `json:"email" ` 8 | Password string `json:"-" gorm:"default:'';not null;"` 9 | Nickname string `json:"nickname" gorm:"default:'';not null;"` 10 | Avatar string `json:"avatar" gorm:"default:'';not null;"` 11 | GroupId uint `json:"group_id" gorm:"default:0;not null;index"` 12 | IsAdmin *bool `json:"is_admin" gorm:"default:0;not null;"` 13 | Status StatusCode `json:"status" gorm:"default:1;not null;"` 14 | TimeModel 15 | } 16 | 17 | // BeforeSave 钩子用于确保 email 字段有合理的默认值 18 | //func (u *User) BeforeSave(tx *gorm.DB) (err error) { 19 | // // 如果 email 为空,设置为默认值 20 | // if u.Email == "" { 21 | // u.Email = fmt.Sprintf("%s@example.com", u.Username) 22 | // } 23 | // return nil 24 | //} 25 | 26 | type UserList struct { 27 | Users []*User `json:"list,omitempty"` 28 | Pagination 29 | } 30 | 31 | var UserRouteNames = []string{ 32 | "MyTagList", "MyAddressBookList", "MyInfo", "MyAddressBookCollection", "MyPeer", "MyShareRecordList", "MyLoginLog", 33 | } 34 | var AdminRouteNames = []string{"*"} 35 | -------------------------------------------------------------------------------- /model/userThird.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "strings" 5 | ) 6 | 7 | type UserThird struct { 8 | IdModel 9 | UserId uint `json:"user_id" gorm:"not null;index"` 10 | OauthUser 11 | UnionId string `json:"union_id" gorm:"default:'';not null;"` 12 | // OauthType string `json:"oauth_type" gorm:"not null;"` 13 | ThirdType string `json:"third_type" gorm:"default:'';not null;"` //deprecated 14 | OauthType string `json:"oauth_type" gorm:"default:'';not null;"` 15 | Op string `json:"op" gorm:"default:'';not null;"` 16 | TimeModel 17 | } 18 | 19 | func (u *UserThird) FromOauthUser(userId uint, oauthUser *OauthUser, oauthType string, op string) { 20 | u.UserId = userId 21 | u.OauthUser = *oauthUser 22 | u.OauthType = oauthType 23 | u.Op = op 24 | // make sure email is lower case 25 | u.Email = strings.ToLower(u.Email) 26 | } 27 | -------------------------------------------------------------------------------- /model/userToken.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | type UserToken struct { 4 | IdModel 5 | UserId uint `json:"user_id" gorm:"default:0;not null;index"` 6 | DeviceUuid string `json:"device_uuid" gorm:"default:'';omitempty;"` 7 | DeviceId string `json:"device_id" gorm:"default:'';omitempty;"` 8 | Token string `json:"token" gorm:"default:'';not null;index"` 9 | ExpiredAt int64 `json:"expired_at" gorm:"default:0;not null;"` 10 | TimeModel 11 | } 12 | 13 | type UserTokenList struct { 14 | UserTokens []UserToken `json:"list"` 15 | Pagination 16 | } 17 | -------------------------------------------------------------------------------- /model/version.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | type Version struct { 4 | IdModel 5 | Version uint `json:"version" gorm:"default:0;not null;"` 6 | TimeModel 7 | } 8 | -------------------------------------------------------------------------------- /resources/public/upload/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lejianwen/rustdesk-api/7a5d141ce8039267b3ea0138950ae2b64d50f86e/resources/public/upload/.gitkeep -------------------------------------------------------------------------------- /resources/version: -------------------------------------------------------------------------------- 1 | v1.0.0 -------------------------------------------------------------------------------- /resources/web/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /resources/web/.last_build_id: -------------------------------------------------------------------------------- 1 | c72c6e6149056c379540743392607228 -------------------------------------------------------------------------------- /resources/web/assets/AssetManifest.json: -------------------------------------------------------------------------------- 1 | {"assets/android.png":["assets/android.png"],"assets/gestures.ttf":["assets/gestures.ttf"],"assets/insecure.png":["assets/insecure.png"],"assets/insecure_relay.png":["assets/insecure_relay.png"],"assets/linux.png":["assets/linux.png"],"assets/mac.png":["assets/mac.png"],"assets/secure.png":["assets/secure.png"],"assets/secure_relay.png":["assets/secure_relay.png"],"assets/win.png":["assets/win.png"],"packages/cupertino_icons/assets/CupertinoIcons.ttf":["packages/cupertino_icons/assets/CupertinoIcons.ttf"],"packages/wakelock_web/assets/no_sleep.js":["packages/wakelock_web/assets/no_sleep.js"]} -------------------------------------------------------------------------------- /resources/web/assets/FontManifest.json: -------------------------------------------------------------------------------- 1 | [{"family":"MaterialIcons","fonts":[{"asset":"fonts/MaterialIcons-Regular.otf"}]},{"family":"GestureIcons","fonts":[{"asset":"assets/gestures.ttf"}]},{"family":"packages/cupertino_icons/CupertinoIcons","fonts":[{"asset":"packages/cupertino_icons/assets/CupertinoIcons.ttf"}]}] -------------------------------------------------------------------------------- /resources/web/assets/assets/android.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lejianwen/rustdesk-api/7a5d141ce8039267b3ea0138950ae2b64d50f86e/resources/web/assets/assets/android.png -------------------------------------------------------------------------------- /resources/web/assets/assets/gestures.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lejianwen/rustdesk-api/7a5d141ce8039267b3ea0138950ae2b64d50f86e/resources/web/assets/assets/gestures.ttf -------------------------------------------------------------------------------- /resources/web/assets/assets/insecure.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lejianwen/rustdesk-api/7a5d141ce8039267b3ea0138950ae2b64d50f86e/resources/web/assets/assets/insecure.png -------------------------------------------------------------------------------- /resources/web/assets/assets/insecure_relay.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lejianwen/rustdesk-api/7a5d141ce8039267b3ea0138950ae2b64d50f86e/resources/web/assets/assets/insecure_relay.png -------------------------------------------------------------------------------- /resources/web/assets/assets/linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lejianwen/rustdesk-api/7a5d141ce8039267b3ea0138950ae2b64d50f86e/resources/web/assets/assets/linux.png -------------------------------------------------------------------------------- /resources/web/assets/assets/mac.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lejianwen/rustdesk-api/7a5d141ce8039267b3ea0138950ae2b64d50f86e/resources/web/assets/assets/mac.png -------------------------------------------------------------------------------- /resources/web/assets/assets/secure.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lejianwen/rustdesk-api/7a5d141ce8039267b3ea0138950ae2b64d50f86e/resources/web/assets/assets/secure.png -------------------------------------------------------------------------------- /resources/web/assets/assets/secure_relay.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lejianwen/rustdesk-api/7a5d141ce8039267b3ea0138950ae2b64d50f86e/resources/web/assets/assets/secure_relay.png -------------------------------------------------------------------------------- /resources/web/assets/assets/win.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lejianwen/rustdesk-api/7a5d141ce8039267b3ea0138950ae2b64d50f86e/resources/web/assets/assets/win.png -------------------------------------------------------------------------------- /resources/web/assets/fonts/MaterialIcons-Regular.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lejianwen/rustdesk-api/7a5d141ce8039267b3ea0138950ae2b64d50f86e/resources/web/assets/fonts/MaterialIcons-Regular.otf -------------------------------------------------------------------------------- /resources/web/assets/packages/cupertino_icons/assets/CupertinoIcons.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lejianwen/rustdesk-api/7a5d141ce8039267b3ea0138950ae2b64d50f86e/resources/web/assets/packages/cupertino_icons/assets/CupertinoIcons.ttf -------------------------------------------------------------------------------- /resources/web/favicon.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /resources/web/icons/Icon-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lejianwen/rustdesk-api/7a5d141ce8039267b3ea0138950ae2b64d50f86e/resources/web/icons/Icon-192.png -------------------------------------------------------------------------------- /resources/web/icons/Icon-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lejianwen/rustdesk-api/7a5d141ce8039267b3ea0138950ae2b64d50f86e/resources/web/icons/Icon-512.png -------------------------------------------------------------------------------- /resources/web/icons/Icon-maskable-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lejianwen/rustdesk-api/7a5d141ce8039267b3ea0138950ae2b64d50f86e/resources/web/icons/Icon-maskable-192.png -------------------------------------------------------------------------------- /resources/web/icons/Icon-maskable-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lejianwen/rustdesk-api/7a5d141ce8039267b3ea0138950ae2b64d50f86e/resources/web/icons/Icon-maskable-512.png -------------------------------------------------------------------------------- /resources/web/js/.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | -------------------------------------------------------------------------------- /resources/web/js/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | dist-ssr 4 | *.local 5 | *log 6 | ogvjs 7 | .vscode 8 | .yarn 9 | -------------------------------------------------------------------------------- /resources/web/js/.yarnrc.yml: -------------------------------------------------------------------------------- 1 | nodeLinker: node-modules 2 | -------------------------------------------------------------------------------- /resources/web/js/dist/index.css: -------------------------------------------------------------------------------- 1 | #app{font-family:Avenir,Helvetica,Arial,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;text-align:center;color:#2c3e50;margin-top:60px} 2 | -------------------------------------------------------------------------------- /resources/web/js/dist/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Vite App 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /resources/web/js/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Vite App 10 | 11 | 12 |
13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /resources/web/js/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "web_hbb", 3 | "version": "1.0.0", 4 | "scripts": { 5 | "dev": "vite", 6 | "build": "vite build", 7 | "preview": "vite preview" 8 | }, 9 | "devDependencies": { 10 | "typescript": "4.4.4", 11 | "vite": "2.8" 12 | }, 13 | "dependencies": { 14 | "fast-sha256": "^1.3.0", 15 | "libsodium": "^0.7.9", 16 | "libsodium-wrappers": "^0.7.9", 17 | "pcm-player": "^0.0.11", 18 | "ts-proto": "^1.141.1", 19 | "wasm-feature-detect": "^1.2.11", 20 | "zstddec": "^0.0.2" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /resources/web/js/src/codec.js: -------------------------------------------------------------------------------- 1 | // example: https://github.com/rgov/js-theora-decoder/blob/main/index.html 2 | // https://github.com/brion/ogv.js/releases, yarn add has no simd 3 | // dev: copy decoder files from node/ogv/dist/* to project dir 4 | // dist: .... to dist 5 | /* 6 | OGVDemuxerOggW: 'ogv-demuxer-ogg-wasm.js', 7 | OGVDemuxerWebMW: 'ogv-demuxer-webm-wasm.js', 8 | OGVDecoderAudioOpusW: 'ogv-decoder-audio-opus-wasm.js', 9 | OGVDecoderAudioVorbisW: 'ogv-decoder-audio-vorbis-wasm.js', 10 | OGVDecoderVideoTheoraW: 'ogv-decoder-video-theora-wasm.js', 11 | OGVDecoderVideoVP8W: 'ogv-decoder-video-vp8-wasm.js', 12 | OGVDecoderVideoVP8MTW: 'ogv-decoder-video-vp8-mt-wasm.js', 13 | OGVDecoderVideoVP9W: 'ogv-decoder-video-vp9-wasm.js', 14 | OGVDecoderVideoVP9SIMDW: 'ogv-decoder-video-vp9-simd-wasm.js', 15 | OGVDecoderVideoVP9MTW: 'ogv-decoder-video-vp9-mt-wasm.js', 16 | OGVDecoderVideoVP9SIMDMTW: 'ogv-decoder-video-vp9-simd-mt-wasm.js', 17 | OGVDecoderVideoAV1W: 'ogv-decoder-video-av1-wasm.js', 18 | OGVDecoderVideoAV1SIMDW: 'ogv-decoder-video-av1-simd-wasm.js', 19 | OGVDecoderVideoAV1MTW: 'ogv-decoder-video-av1-mt-wasm.js', 20 | OGVDecoderVideoAV1SIMDMTW: 'ogv-decoder-video-av1-simd-mt-wasm.js', 21 | */ 22 | import { simd } from "wasm-feature-detect"; 23 | 24 | export async function loadVp9(callback) { 25 | // Multithreading is used only if `options.threading` is true. 26 | // This requires browser support for the new `SharedArrayBuffer` and `Atomics` APIs, 27 | // currently available in Firefox and Chrome with experimental flags enabled. 28 | // 所有主流浏览器均默认于2018年1月5日禁用SharedArrayBuffer 29 | const isSIMD = await simd(); 30 | console.log('isSIMD: ' + isSIMD); 31 | window.OGVLoader.loadClass( 32 | isSIMD ? "OGVDecoderVideoVP9SIMDW" : "OGVDecoderVideoVP9W", 33 | (videoCodecClass) => { 34 | window.videoCodecClass = videoCodecClass; 35 | videoCodecClass({ videoFormat: {} }).then((decoder) => { 36 | decoder.init(() => { 37 | callback(decoder); 38 | }) 39 | }) 40 | }, 41 | { worker: true, threading: true } 42 | ); 43 | } -------------------------------------------------------------------------------- /resources/web/js/src/common.ts: -------------------------------------------------------------------------------- 1 | import * as zstd from "zstddec"; 2 | import { KeyEvent, controlKeyFromJSON, ControlKey } from "./message"; 3 | import { KEY_MAP, LANGS } from "./gen_js_from_hbb"; 4 | 5 | let decompressor: zstd.ZSTDDecoder; 6 | 7 | export async function initZstd() { 8 | const tmp = new zstd.ZSTDDecoder(); 9 | await tmp.init(); 10 | console.log("zstd ready"); 11 | decompressor = tmp; 12 | } 13 | 14 | export async function decompress(compressedArray: Uint8Array) { 15 | const MAX = 1024 * 1024 * 64; 16 | const MIN = 1024 * 1024; 17 | let n = 30 * compressedArray.length; 18 | if (n > MAX) { 19 | n = MAX; 20 | } 21 | if (n < MIN) { 22 | n = MIN; 23 | } 24 | try { 25 | if (!decompressor) { 26 | await initZstd(); 27 | } 28 | return decompressor.decode(compressedArray, n); 29 | } catch (e) { 30 | console.error("decompress failed: " + e); 31 | return undefined; 32 | } 33 | } 34 | 35 | const LANG = getLang(); 36 | 37 | export function translate(locale: string, text: string): string { 38 | const lang = LANG || locale.substring(locale.length - 2).toLowerCase(); 39 | let en = LANGS.en as any; 40 | let dict = (LANGS as any)[lang]; 41 | if (!dict) dict = en; 42 | let res = dict[text]; 43 | if (!res && lang != "en") res = en[text]; 44 | return res || text; 45 | } 46 | 47 | const zCode = "z".charCodeAt(0); 48 | const aCode = "a".charCodeAt(0); 49 | 50 | export function mapKey(name: string, isDesktop: Boolean) { 51 | const tmp = KEY_MAP[name] || name; 52 | if (tmp.length == 1) { 53 | const chr = tmp.charCodeAt(0); 54 | if (!isDesktop && (chr > zCode || chr < aCode)) 55 | return KeyEvent.fromPartial({ unicode: chr }); 56 | else return KeyEvent.fromPartial({ chr }); 57 | } 58 | const control_key = controlKeyFromJSON(tmp); 59 | if (control_key == ControlKey.UNRECOGNIZED) { 60 | console.error("Unknown control key " + tmp); 61 | } 62 | return KeyEvent.fromPartial({ control_key }); 63 | } 64 | 65 | export async function sleep(ms: number) { 66 | await new Promise((r) => setTimeout(r, ms)); 67 | } 68 | 69 | function getLang(): string { 70 | try { 71 | const queryString = window.location.search; 72 | const urlParams = new URLSearchParams(queryString); 73 | return urlParams.get("lang") || ""; 74 | } catch (e) { 75 | return ""; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /resources/web/js/src/main.ts: -------------------------------------------------------------------------------- 1 | import "./ljw"; 2 | import "./globals"; 3 | import "./ui"; 4 | -------------------------------------------------------------------------------- /resources/web/js/src/style.css: -------------------------------------------------------------------------------- 1 | #app { 2 | font-family: Avenir, Helvetica, Arial, sans-serif; 3 | -webkit-font-smoothing: antialiased; 4 | -moz-osx-font-smoothing: grayscale; 5 | text-align: center; 6 | color: #2c3e50; 7 | margin-top: 60px; 8 | } 9 | -------------------------------------------------------------------------------- /resources/web/js/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// -------------------------------------------------------------------------------- /resources/web/js/ts_proto.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import os 4 | 5 | path = os.path.abspath(os.path.join(os.getcwd(), '..', '..', '..', 'libs', 'hbb_common', 'protos')) 6 | 7 | if os.name == 'nt': 8 | cmd = r'protoc --ts_proto_opt=esModuleInterop=true --ts_proto_opt=snakeToCamel=false --plugin=protoc-gen-ts_proto=.\node_modules\.bin\protoc-gen-ts_proto.cmd -I "%s" --ts_proto_out=./src/ rendezvous.proto'%path 9 | print(cmd) 10 | os.system(cmd) 11 | cmd = r'protoc --ts_proto_opt=esModuleInterop=true --ts_proto_opt=snakeToCamel=false --plugin=protoc-gen-ts_proto=.\node_modules\.bin\protoc-gen-ts_proto.cmd -I "%s" --ts_proto_out=./src/ message.proto'%path 12 | print(cmd) 13 | os.system(cmd) 14 | else: 15 | cmd = r'protoc --ts_proto_opt=esModuleInterop=true --ts_proto_opt=snakeToCamel=false --plugin=./node_modules/.bin/protoc-gen-ts_proto -I "%s" --ts_proto_out=./src/ rendezvous.proto'%path 16 | print(cmd) 17 | os.system(cmd) 18 | cmd = r'protoc --ts_proto_opt=esModuleInterop=true --ts_proto_opt=snakeToCamel=false --plugin=./node_modules/.bin/protoc-gen-ts_proto -I "%s" --ts_proto_out=./src/ message.proto'%path 19 | print(cmd) 20 | os.system(cmd) 21 | -------------------------------------------------------------------------------- /resources/web/js/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "useDefineForClassFields": true, 5 | "module": "ESNext", 6 | "allowJs": true, 7 | "lib": [ 8 | "ESNext", 9 | "DOM" 10 | ], 11 | "moduleResolution": "Node", 12 | "strict": true, 13 | "sourceMap": true, 14 | "resolveJsonModule": true, 15 | "esModuleInterop": true, 16 | "noEmit": true, 17 | "noUnusedLocals": true, 18 | "noUnusedParameters": true, 19 | "noImplicitReturns": true 20 | }, 21 | "include": [ 22 | "./src" 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /resources/web/js/vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite'; 2 | 3 | export default defineConfig({ 4 | build: { 5 | manifest: false, 6 | rollupOptions: { 7 | output: { 8 | entryFileNames: `[name].js`, 9 | chunkFileNames: `[name].js`, 10 | assetFileNames: `[name].[ext]`, 11 | } 12 | } 13 | }, 14 | }) -------------------------------------------------------------------------------- /resources/web/libopus.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lejianwen/rustdesk-api/7a5d141ce8039267b3ea0138950ae2b64d50f86e/resources/web/libopus.wasm -------------------------------------------------------------------------------- /resources/web/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rustdesk", 3 | "short_name": "rustdesk", 4 | "start_url": ".", 5 | "display": "standalone", 6 | "background_color": "#0175C2", 7 | "theme_color": "#0175C2", 8 | "description": "Remote Desktop.", 9 | "orientation": "portrait-primary", 10 | "prefer_related_applications": false, 11 | "icons": [ 12 | { 13 | "src": "icons/Icon-192.png", 14 | "sizes": "192x192", 15 | "type": "image/png" 16 | }, 17 | { 18 | "src": "icons/Icon-512.png", 19 | "sizes": "512x512", 20 | "type": "image/png" 21 | }, 22 | { 23 | "src": "icons/Icon-maskable-192.png", 24 | "sizes": "192x192", 25 | "type": "image/png", 26 | "purpose": "maskable" 27 | }, 28 | { 29 | "src": "icons/Icon-maskable-512.png", 30 | "sizes": "512x512", 31 | "type": "image/png", 32 | "purpose": "maskable" 33 | } 34 | ] 35 | } 36 | -------------------------------------------------------------------------------- /resources/web/ogvjs-1.8.6/COPYING: -------------------------------------------------------------------------------- 1 | ogv.js wrapper and player code 2 | 3 | Copyright (c) 2013-2019 Brion Vibber and other contributors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /resources/web/ogvjs-1.8.6/COPYING-dav1d.txt: -------------------------------------------------------------------------------- 1 | Copyright © 2018-2019, VideoLAN and dav1d authors 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | 1. Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | 2. Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 15 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 16 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 18 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 19 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 20 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 21 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 23 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | -------------------------------------------------------------------------------- /resources/web/ogvjs-1.8.6/COPYING-ogg.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2002, Xiph.org Foundation 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions 5 | are met: 6 | 7 | - Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | 10 | - Redistributions in binary form must reproduce the above copyright 11 | notice, this list of conditions and the following disclaimer in the 12 | documentation and/or other materials provided with the distribution. 13 | 14 | - Neither the name of the Xiph.org Foundation nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 | ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION 22 | OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | -------------------------------------------------------------------------------- /resources/web/ogvjs-1.8.6/COPYING-opus.txt: -------------------------------------------------------------------------------- 1 | Copyright 2001-2011 Xiph.Org, Skype Limited, Octasic, 2 | Jean-Marc Valin, Timothy B. Terriberry, 3 | CSIRO, Gregory Maxwell, Mark Borgerding, 4 | Erik de Castro Lopo 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions 8 | are met: 9 | 10 | - Redistributions of source code must retain the above copyright 11 | notice, this list of conditions and the following disclaimer. 12 | 13 | - Redistributions in binary form must reproduce the above copyright 14 | notice, this list of conditions and the following disclaimer in the 15 | documentation and/or other materials provided with the distribution. 16 | 17 | - Neither the name of Internet Society, IETF or IETF Trust, nor the 18 | names of specific contributors, may be used to endorse or promote 19 | products derived from this software without specific prior written 20 | permission. 21 | 22 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 23 | ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 24 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 25 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER 26 | OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 27 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 28 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 29 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 30 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 31 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 32 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 33 | 34 | Opus is subject to the royalty-free patent licenses which are 35 | specified at: 36 | 37 | Xiph.Org Foundation: 38 | https://datatracker.ietf.org/ipr/1524/ 39 | 40 | Microsoft Corporation: 41 | https://datatracker.ietf.org/ipr/1914/ 42 | 43 | Broadcom Corporation: 44 | https://datatracker.ietf.org/ipr/1526/ 45 | -------------------------------------------------------------------------------- /resources/web/ogvjs-1.8.6/COPYING-theora.txt: -------------------------------------------------------------------------------- 1 | Copyright (C) 2002-2009 Xiph.org Foundation 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions 5 | are met: 6 | 7 | - Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | 10 | - Redistributions in binary form must reproduce the above copyright 11 | notice, this list of conditions and the following disclaimer in the 12 | documentation and/or other materials provided with the distribution. 13 | 14 | - Neither the name of the Xiph.org Foundation nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 | ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION 22 | OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | -------------------------------------------------------------------------------- /resources/web/ogvjs-1.8.6/COPYING-vorbis.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2002-2018 Xiph.org Foundation 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions 5 | are met: 6 | 7 | - Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | 10 | - Redistributions in binary form must reproduce the above copyright 11 | notice, this list of conditions and the following disclaimer in the 12 | documentation and/or other materials provided with the distribution. 13 | 14 | - Neither the name of the Xiph.org Foundation nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 | ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION 22 | OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | -------------------------------------------------------------------------------- /resources/web/ogvjs-1.8.6/LICENSE-nestegg.txt: -------------------------------------------------------------------------------- 1 | Copyright © 2010 Mozilla Foundation 2 | 3 | Permission to use, copy, modify, and distribute this software for any 4 | purpose with or without fee is hereby granted, provided that the above 5 | copyright notice and this permission notice appear in all copies. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | -------------------------------------------------------------------------------- /resources/web/ogvjs-1.8.6/LICENSE-vpx.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2010, The WebM Project authors. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright 11 | notice, this list of conditions and the following disclaimer in 12 | the documentation and/or other materials provided with the 13 | distribution. 14 | 15 | * Neither the name of Google, nor the WebM Project, nor the names 16 | of its contributors may be used to endorse or promote products 17 | derived from this software without specific prior written 18 | permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 22 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 23 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 24 | HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 25 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 26 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 27 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 28 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | 32 | -------------------------------------------------------------------------------- /resources/web/ogvjs-1.8.6/PATENTS-vpx.txt: -------------------------------------------------------------------------------- 1 | Additional IP Rights Grant (Patents) 2 | ------------------------------------ 3 | 4 | "These implementations" means the copyrightable works that implement the WebM 5 | codecs distributed by Google as part of the WebM Project. 6 | 7 | Google hereby grants to you a perpetual, worldwide, non-exclusive, no-charge, 8 | royalty-free, irrevocable (except as stated in this section) patent license to 9 | make, have made, use, offer to sell, sell, import, transfer, and otherwise 10 | run, modify and propagate the contents of these implementations of WebM, where 11 | such license applies only to those patent claims, both currently owned by 12 | Google and acquired in the future, licensable by Google that are necessarily 13 | infringed by these implementations of WebM. This grant does not include claims 14 | that would be infringed only as a consequence of further modification of these 15 | implementations. If you or your agent or exclusive licensee institute or order 16 | or agree to the institution of patent litigation or any other patent 17 | enforcement activity against any entity (including a cross-claim or 18 | counterclaim in a lawsuit) alleging that any of these implementations of WebM 19 | or any code incorporated within any of these implementations of WebM 20 | constitute direct or contributory patent infringement, or inducement of 21 | patent infringement, then any patent rights granted to you under this License 22 | for these implementations of WebM shall terminate as of the date such 23 | litigation is filed. 24 | -------------------------------------------------------------------------------- /resources/web/ogvjs-1.8.6/ogv-decoder-audio-opus-wasm.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lejianwen/rustdesk-api/7a5d141ce8039267b3ea0138950ae2b64d50f86e/resources/web/ogvjs-1.8.6/ogv-decoder-audio-opus-wasm.wasm -------------------------------------------------------------------------------- /resources/web/ogvjs-1.8.6/ogv-decoder-audio-vorbis-wasm.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lejianwen/rustdesk-api/7a5d141ce8039267b3ea0138950ae2b64d50f86e/resources/web/ogvjs-1.8.6/ogv-decoder-audio-vorbis-wasm.wasm -------------------------------------------------------------------------------- /resources/web/ogvjs-1.8.6/ogv-decoder-video-av1-mt-wasm.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lejianwen/rustdesk-api/7a5d141ce8039267b3ea0138950ae2b64d50f86e/resources/web/ogvjs-1.8.6/ogv-decoder-video-av1-mt-wasm.wasm -------------------------------------------------------------------------------- /resources/web/ogvjs-1.8.6/ogv-decoder-video-av1-simd-mt-wasm.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lejianwen/rustdesk-api/7a5d141ce8039267b3ea0138950ae2b64d50f86e/resources/web/ogvjs-1.8.6/ogv-decoder-video-av1-simd-mt-wasm.wasm -------------------------------------------------------------------------------- /resources/web/ogvjs-1.8.6/ogv-decoder-video-av1-simd-wasm.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lejianwen/rustdesk-api/7a5d141ce8039267b3ea0138950ae2b64d50f86e/resources/web/ogvjs-1.8.6/ogv-decoder-video-av1-simd-wasm.wasm -------------------------------------------------------------------------------- /resources/web/ogvjs-1.8.6/ogv-decoder-video-av1-wasm.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lejianwen/rustdesk-api/7a5d141ce8039267b3ea0138950ae2b64d50f86e/resources/web/ogvjs-1.8.6/ogv-decoder-video-av1-wasm.wasm -------------------------------------------------------------------------------- /resources/web/ogvjs-1.8.6/ogv-decoder-video-theora-wasm.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lejianwen/rustdesk-api/7a5d141ce8039267b3ea0138950ae2b64d50f86e/resources/web/ogvjs-1.8.6/ogv-decoder-video-theora-wasm.wasm -------------------------------------------------------------------------------- /resources/web/ogvjs-1.8.6/ogv-decoder-video-vp8-mt-wasm.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lejianwen/rustdesk-api/7a5d141ce8039267b3ea0138950ae2b64d50f86e/resources/web/ogvjs-1.8.6/ogv-decoder-video-vp8-mt-wasm.wasm -------------------------------------------------------------------------------- /resources/web/ogvjs-1.8.6/ogv-decoder-video-vp8-wasm.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lejianwen/rustdesk-api/7a5d141ce8039267b3ea0138950ae2b64d50f86e/resources/web/ogvjs-1.8.6/ogv-decoder-video-vp8-wasm.wasm -------------------------------------------------------------------------------- /resources/web/ogvjs-1.8.6/ogv-decoder-video-vp9-mt-wasm.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lejianwen/rustdesk-api/7a5d141ce8039267b3ea0138950ae2b64d50f86e/resources/web/ogvjs-1.8.6/ogv-decoder-video-vp9-mt-wasm.wasm -------------------------------------------------------------------------------- /resources/web/ogvjs-1.8.6/ogv-decoder-video-vp9-simd-mt-wasm.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lejianwen/rustdesk-api/7a5d141ce8039267b3ea0138950ae2b64d50f86e/resources/web/ogvjs-1.8.6/ogv-decoder-video-vp9-simd-mt-wasm.wasm -------------------------------------------------------------------------------- /resources/web/ogvjs-1.8.6/ogv-decoder-video-vp9-simd-wasm.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lejianwen/rustdesk-api/7a5d141ce8039267b3ea0138950ae2b64d50f86e/resources/web/ogvjs-1.8.6/ogv-decoder-video-vp9-simd-wasm.wasm -------------------------------------------------------------------------------- /resources/web/ogvjs-1.8.6/ogv-decoder-video-vp9-wasm.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lejianwen/rustdesk-api/7a5d141ce8039267b3ea0138950ae2b64d50f86e/resources/web/ogvjs-1.8.6/ogv-decoder-video-vp9-wasm.wasm -------------------------------------------------------------------------------- /resources/web/ogvjs-1.8.6/ogv-demuxer-ogg-wasm.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lejianwen/rustdesk-api/7a5d141ce8039267b3ea0138950ae2b64d50f86e/resources/web/ogvjs-1.8.6/ogv-demuxer-ogg-wasm.wasm -------------------------------------------------------------------------------- /resources/web/ogvjs-1.8.6/ogv-demuxer-webm-wasm.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lejianwen/rustdesk-api/7a5d141ce8039267b3ea0138950ae2b64d50f86e/resources/web/ogvjs-1.8.6/ogv-demuxer-webm-wasm.wasm -------------------------------------------------------------------------------- /resources/web/ogvjs-1.8.6/ogv-support.js: -------------------------------------------------------------------------------- 1 | (()=>{var e={575:e=>{e.exports=function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")},e.exports.__esModule=!0,e.exports.default=e.exports},913:e=>{function t(e,t){for(var o=0;o{e.exports=function(e){return e&&e.__esModule?e:{default:e}},e.exports.__esModule=!0,e.exports.default=e.exports},8:e=>{function t(o){return e.exports=t="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},e.exports.__esModule=!0,e.exports.default=e.exports,t(o)}e.exports=t,e.exports.__esModule=!0,e.exports.default=e.exports},523:(e,t,o)=>{"use strict";var r=o(318);Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var n=r(o(575)),u=r(o(913)),s=new(function(){function e(){(0,n.default)(this,e)}return(0,u.default)(e,[{key:"hasTypedArrays",value:function(){return!!window.Uint32Array}},{key:"hasWebAssembly",value:function(){return!!window.WebAssembly}},{key:"hasWebAudio",value:function(){return!(!window.AudioContext&&!window.webkitAudioContext)}},{key:"hasFlash",value:function(){return!1}},{key:"hasAudio",value:function(){return this.hasWebAudio()}},{key:"isBlacklisted",value:function(e){return!1}},{key:"isSlow",value:function(){return!1}},{key:"isTooSlow",value:function(){return!1}},{key:"supported",value:function(e){return"OGVDecoder"===e?this.hasWebAssembly():"OGVPlayer"===e&&this.supported("OGVDecoder")&&this.hasAudio()}}]),e}());t.default=s}},t={};function o(r){var n=t[r];if(void 0!==n)return n.exports;var u=t[r]={exports:{}};return e[r](u,u.exports,o),u.exports}(()=>{"use strict";var e=o(318),t=e(o(8)),r=e(o(523));"object"===("undefined"==typeof window?"undefined":(0,t.default)(window))&&(window.OGVCompat=r.default,window.OGVVersion="1.8.6-20220111172545-1f60d9d")})()})(); -------------------------------------------------------------------------------- /resources/web/ogvjs-1.8.6/ogv-version.js: -------------------------------------------------------------------------------- 1 | (()=>{var e={318:e=>{e.exports=function _interopRequireDefault(e){return e&&e.__esModule?e:{default:e}},e.exports.__esModule=!0,e.exports.default=e.exports},8:e=>{function _typeof(o){return e.exports=_typeof="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},e.exports.__esModule=!0,e.exports.default=e.exports,_typeof(o)}e.exports=_typeof,e.exports.__esModule=!0,e.exports.default=e.exports}},o={};function __webpack_require__(t){var r=o[t];if(void 0!==r)return r.exports;var p=o[t]={exports:{}};return e[t](p,p.exports,__webpack_require__),p.exports}(()=>{"use strict";var e=__webpack_require__(318)(__webpack_require__(8)),o="1.8.6-20220111172545-1f60d9d";"object"===("undefined"==typeof window?"undefined":(0,e.default)(window))&&(window.OGVVersion=o)})()})(); -------------------------------------------------------------------------------- /resources/web/start-server.bat: -------------------------------------------------------------------------------- 1 | flutter run --release -d web-server --web-port 5005 --web-hostname 0.0.0.0 2 | -------------------------------------------------------------------------------- /resources/web/version.json: -------------------------------------------------------------------------------- 1 | {"app_name":"flutter_hbb","version":"1.1.10-1","build_number":"28","package_name":"flutter_hbb"} -------------------------------------------------------------------------------- /resources/web/web_deps.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lejianwen/rustdesk-api/7a5d141ce8039267b3ea0138950ae2b64d50f86e/resources/web/web_deps.tar.gz -------------------------------------------------------------------------------- /resources/web/yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | -------------------------------------------------------------------------------- /resources/web/yuv.js: -------------------------------------------------------------------------------- 1 | var wasmExports; 2 | 3 | fetch('yuv.wasm').then(function (res) { return res.arrayBuffer(); }) 4 | .then(function (file) { return WebAssembly.instantiate(file); }) 5 | .then(function (wasm) { 6 | wasmExports = wasm.instance.exports; 7 | console.log('yuv ready'); 8 | }); 9 | 10 | var yPtr, yPtrLen, uPtr, uPtrLen, vPtr, vPtrLen, outPtr, outPtrLen; 11 | let testSpeed = [0, 0]; 12 | function I420ToARGB(yb) { 13 | if (!wasmExports) return; 14 | var tm0 = new Date().getTime(); 15 | var { malloc, free, memory } = wasmExports; 16 | var HEAPU8 = new Uint8Array(memory.buffer); 17 | let n = yb.y.bytes.length; 18 | if (yPtrLen != n) { 19 | if (yPtr) free(yPtr); 20 | yPtrLen = n; 21 | yPtr = malloc(n); 22 | } 23 | HEAPU8.set(yb.y.bytes, yPtr); 24 | n = yb.u.bytes.length; 25 | if (uPtrLen != n) { 26 | if (uPtr) free(uPtr); 27 | uPtrLen = n; 28 | uPtr = malloc(n); 29 | } 30 | HEAPU8.set(yb.u.bytes, uPtr); 31 | n = yb.v.bytes.length; 32 | if (vPtrLen != n) { 33 | if (vPtr) free(vPtr); 34 | vPtrLen = n; 35 | vPtr = malloc(n); 36 | } 37 | HEAPU8.set(yb.v.bytes, vPtr); 38 | var w = yb.format.displayWidth; 39 | var h = yb.format.displayHeight; 40 | n = w * h * 4; 41 | if (outPtrLen != n) { 42 | if (outPtr) free(outPtr); 43 | outPtrLen = n; 44 | outPtr = malloc(n); 45 | HEAPU8.fill(255, outPtr, outPtr + n); 46 | } 47 | // var res = wasmExports.I420ToARGB(yPtr, yb.y.stride, uPtr, yb.u.stride, vPtr, yb.v.stride, outPtr, w * 4, w, h); 48 | // var res = wasmExports.AVX_YUV_to_ARGB(outPtr, yPtr, yb.y.stride, uPtr, yb.u.stride, vPtr, yb.v.stride, w, h); 49 | var res = wasmExports.yuv420_rgb24_std(w, h, yPtr, uPtr, vPtr, yb.y.stride, yb.v.stride, outPtr, w * 4, 1); 50 | var out = HEAPU8.slice(outPtr, outPtr + n); 51 | testSpeed[1] += new Date().getTime() - tm0; 52 | testSpeed[0] += 1; 53 | if (testSpeed[0] > 30) { 54 | console.log('yuv: ' + parseInt('' + testSpeed[1] / testSpeed[0])); 55 | testSpeed = [0, 0]; 56 | } 57 | return out; 58 | } 59 | 60 | var currentFrame; 61 | self.addEventListener('message', (e) => { 62 | currentFrame = e.data; 63 | }); 64 | 65 | function run() { 66 | if (currentFrame) { 67 | self.postMessage(I420ToARGB(currentFrame)); 68 | currentFrame = undefined; 69 | } 70 | setTimeout(run, 1); 71 | } 72 | 73 | run(); -------------------------------------------------------------------------------- /resources/web/yuv.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lejianwen/rustdesk-api/7a5d141ce8039267b3ea0138950ae2b64d50f86e/resources/web/yuv.wasm -------------------------------------------------------------------------------- /resources/web2/assets/FontManifest.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "family": "MaterialIcons", 4 | "fonts": [ 5 | { 6 | "asset": "fonts/MaterialIcons-Regular.otf" 7 | } 8 | ] 9 | }, 10 | { 11 | "family": "GestureIcons", 12 | "fonts": [ 13 | { 14 | "asset": "assets/gestures.ttf" 15 | } 16 | ] 17 | }, 18 | { 19 | "family": "Tabbar", 20 | "fonts": [ 21 | { 22 | "asset": "assets/tabbar.ttf" 23 | } 24 | ] 25 | }, 26 | { 27 | "family": "PeerSearchbar", 28 | "fonts": [ 29 | { 30 | "asset": "assets/peer_searchbar.ttf" 31 | } 32 | ] 33 | }, 34 | { 35 | "family": "AddressBook", 36 | "fonts": [ 37 | { 38 | "asset": "assets/address_book.ttf" 39 | } 40 | ] 41 | }, 42 | { 43 | "family": "DeviceGroup", 44 | "fonts": [ 45 | { 46 | "asset": "assets/device_group.ttf" 47 | } 48 | ] 49 | }, 50 | { 51 | "family": "More", 52 | "fonts": [ 53 | { 54 | "asset": "assets/more.ttf" 55 | } 56 | ] 57 | } 58 | ] -------------------------------------------------------------------------------- /resources/web2/assets/assets/actions.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /resources/web2/assets/assets/actions_mobile.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /resources/web2/assets/assets/address_book.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lejianwen/rustdesk-api/7a5d141ce8039267b3ea0138950ae2b64d50f86e/resources/web2/assets/assets/address_book.ttf -------------------------------------------------------------------------------- /resources/web2/assets/assets/android.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /resources/web2/assets/assets/arrow.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /resources/web2/assets/assets/auth-apple.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /resources/web2/assets/assets/auth-auth0.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /resources/web2/assets/assets/auth-azure.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /resources/web2/assets/assets/auth-default.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /resources/web2/assets/assets/auth-facebook.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /resources/web2/assets/assets/auth-github.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /resources/web2/assets/assets/auth-gitlab.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /resources/web2/assets/assets/auth-google.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /resources/web2/assets/assets/auth-okta.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /resources/web2/assets/assets/call_end.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /resources/web2/assets/assets/call_wait.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /resources/web2/assets/assets/chat.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /resources/web2/assets/assets/chat2.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /resources/web2/assets/assets/checkbox-outline.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /resources/web2/assets/assets/chevron_up_chevron_down.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /resources/web2/assets/assets/close.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /resources/web2/assets/assets/device_group.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lejianwen/rustdesk-api/7a5d141ce8039267b3ea0138950ae2b64d50f86e/resources/web2/assets/assets/device_group.ttf -------------------------------------------------------------------------------- /resources/web2/assets/assets/display.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /resources/web2/assets/assets/dots.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /resources/web2/assets/assets/file.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /resources/web2/assets/assets/file_transfer.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /resources/web2/assets/assets/folder.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /resources/web2/assets/assets/folder_new.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /resources/web2/assets/assets/fullscreen.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /resources/web2/assets/assets/fullscreen_exit.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /resources/web2/assets/assets/gestures.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lejianwen/rustdesk-api/7a5d141ce8039267b3ea0138950ae2b64d50f86e/resources/web2/assets/assets/gestures.ttf -------------------------------------------------------------------------------- /resources/web2/assets/assets/home.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /resources/web2/assets/assets/icon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /resources/web2/assets/assets/insecure.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /resources/web2/assets/assets/insecure_relay.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /resources/web2/assets/assets/keyboard.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /resources/web2/assets/assets/linux.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /resources/web2/assets/assets/mac.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /resources/web2/assets/assets/message_24dp_5F6368.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /resources/web2/assets/assets/more.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lejianwen/rustdesk-api/7a5d141ce8039267b3ea0138950ae2b64d50f86e/resources/web2/assets/assets/more.ttf -------------------------------------------------------------------------------- /resources/web2/assets/assets/peer_searchbar.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lejianwen/rustdesk-api/7a5d141ce8039267b3ea0138950ae2b64d50f86e/resources/web2/assets/assets/peer_searchbar.ttf -------------------------------------------------------------------------------- /resources/web2/assets/assets/pinned.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /resources/web2/assets/assets/record_screen.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /resources/web2/assets/assets/refresh.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /resources/web2/assets/assets/scam.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lejianwen/rustdesk-api/7a5d141ce8039267b3ea0138950ae2b64d50f86e/resources/web2/assets/assets/scam.png -------------------------------------------------------------------------------- /resources/web2/assets/assets/screen.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /resources/web2/assets/assets/search.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /resources/web2/assets/assets/secure.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /resources/web2/assets/assets/secure_relay.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /resources/web2/assets/assets/tabbar.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lejianwen/rustdesk-api/7a5d141ce8039267b3ea0138950ae2b64d50f86e/resources/web2/assets/assets/tabbar.ttf -------------------------------------------------------------------------------- /resources/web2/assets/assets/transfer.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /resources/web2/assets/assets/trash.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /resources/web2/assets/assets/unpinned.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /resources/web2/assets/assets/voice_call.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /resources/web2/assets/assets/voice_call_waiting.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /resources/web2/assets/assets/win.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /resources/web2/assets/fonts/MaterialIcons-Regular.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lejianwen/rustdesk-api/7a5d141ce8039267b3ea0138950ae2b64d50f86e/resources/web2/assets/fonts/MaterialIcons-Regular.otf -------------------------------------------------------------------------------- /resources/web2/assets/packages/dash_chat_2/assets/placeholder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lejianwen/rustdesk-api/7a5d141ce8039267b3ea0138950ae2b64d50f86e/resources/web2/assets/packages/dash_chat_2/assets/placeholder.png -------------------------------------------------------------------------------- /resources/web2/assets/packages/dash_chat_2/assets/profile_placeholder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lejianwen/rustdesk-api/7a5d141ce8039267b3ea0138950ae2b64d50f86e/resources/web2/assets/packages/dash_chat_2/assets/profile_placeholder.png -------------------------------------------------------------------------------- /resources/web2/assets/packages/flex_color_picker/assets/opacity.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lejianwen/rustdesk-api/7a5d141ce8039267b3ea0138950ae2b64d50f86e/resources/web2/assets/packages/flex_color_picker/assets/opacity.png -------------------------------------------------------------------------------- /resources/web2/assets/packages/window_manager/images/ic_chrome_close.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lejianwen/rustdesk-api/7a5d141ce8039267b3ea0138950ae2b64d50f86e/resources/web2/assets/packages/window_manager/images/ic_chrome_close.png -------------------------------------------------------------------------------- /resources/web2/assets/packages/window_manager/images/ic_chrome_maximize.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lejianwen/rustdesk-api/7a5d141ce8039267b3ea0138950ae2b64d50f86e/resources/web2/assets/packages/window_manager/images/ic_chrome_maximize.png -------------------------------------------------------------------------------- /resources/web2/assets/packages/window_manager/images/ic_chrome_minimize.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lejianwen/rustdesk-api/7a5d141ce8039267b3ea0138950ae2b64d50f86e/resources/web2/assets/packages/window_manager/images/ic_chrome_minimize.png -------------------------------------------------------------------------------- /resources/web2/assets/packages/window_manager/images/ic_chrome_unmaximize.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lejianwen/rustdesk-api/7a5d141ce8039267b3ea0138950ae2b64d50f86e/resources/web2/assets/packages/window_manager/images/ic_chrome_unmaximize.png -------------------------------------------------------------------------------- /resources/web2/assets/shaders/ink_sparkle.frag: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lejianwen/rustdesk-api/7a5d141ce8039267b3ea0138950ae2b64d50f86e/resources/web2/assets/shaders/ink_sparkle.frag -------------------------------------------------------------------------------- /resources/web2/favicon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /resources/web2/ffmpeg-core.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lejianwen/rustdesk-api/7a5d141ce8039267b3ea0138950ae2b64d50f86e/resources/web2/ffmpeg-core.wasm -------------------------------------------------------------------------------- /resources/web2/icons/Icon-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lejianwen/rustdesk-api/7a5d141ce8039267b3ea0138950ae2b64d50f86e/resources/web2/icons/Icon-192.png -------------------------------------------------------------------------------- /resources/web2/icons/libopus.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lejianwen/rustdesk-api/7a5d141ce8039267b3ea0138950ae2b64d50f86e/resources/web2/icons/libopus.wasm -------------------------------------------------------------------------------- /resources/web2/libopus.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lejianwen/rustdesk-api/7a5d141ce8039267b3ea0138950ae2b64d50f86e/resources/web2/libopus.wasm -------------------------------------------------------------------------------- /resources/web2/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rustdesk", 3 | "short_name": "rustdesk", 4 | "start_url": ".", 5 | "display": "standalone", 6 | "background_color": "#0175C2", 7 | "theme_color": "#0175C2", 8 | "description": "Remote Desktop.", 9 | "orientation": "portrait-primary", 10 | "prefer_related_applications": false, 11 | "icons": [ 12 | { 13 | "src": "icons/Icon-192.png", 14 | "sizes": "192x192", 15 | "type": "image/png" 16 | }, 17 | { 18 | "src": "icons/Icon-512.png", 19 | "sizes": "512x512", 20 | "type": "image/png" 21 | }, 22 | { 23 | "src": "icons/Icon-maskable-192.png", 24 | "sizes": "192x192", 25 | "type": "image/png", 26 | "purpose": "maskable" 27 | }, 28 | { 29 | "src": "icons/Icon-maskable-512.png", 30 | "sizes": "512x512", 31 | "type": "image/png", 32 | "purpose": "maskable" 33 | } 34 | ] 35 | } -------------------------------------------------------------------------------- /runtime/cache/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lejianwen/rustdesk-api/7a5d141ce8039267b3ea0138950ae2b64d50f86e/runtime/cache/.gitkeep -------------------------------------------------------------------------------- /service/app.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "os" 5 | "sync" 6 | ) 7 | 8 | type AppService struct { 9 | } 10 | 11 | var version = "" 12 | 13 | var once = &sync.Once{} 14 | 15 | func (a *AppService) GetAppVersion() string { 16 | if version != "" { 17 | return version 18 | } 19 | once.Do(func() { 20 | v, err := os.ReadFile("resources/version") 21 | if err != nil { 22 | return 23 | } 24 | version = string(v) 25 | 26 | }) 27 | return version 28 | } 29 | -------------------------------------------------------------------------------- /service/app_test.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "sync" 5 | "testing" 6 | ) 7 | 8 | // TestGetAppVersion 9 | func TestGetAppVersion(t *testing.T) { 10 | s := &AppService{} 11 | v := s.GetAppVersion() 12 | // 打印结果 13 | t.Logf("App Version: %s", v) 14 | } 15 | 16 | func TestMultipleGetAppVersion(t *testing.T) { 17 | s := &AppService{} 18 | //并发测试 19 | // 使用 WaitGroup 等待所有 goroutine 完成 20 | wg := sync.WaitGroup{} 21 | wg.Add(10) // 启动 10 个 goroutine 22 | // 启动 10 个 goroutine 23 | for i := 0; i < 10; i++ { 24 | go func() { 25 | defer wg.Done() // 完成后减少计数 26 | v := s.GetAppVersion() 27 | // 打印结果 28 | t.Logf("App Version: %s", v) 29 | }() 30 | } 31 | // 等待所有 goroutine 完成 32 | wg.Wait() 33 | } 34 | -------------------------------------------------------------------------------- /service/group.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "github.com/lejianwen/rustdesk-api/v2/model" 5 | "gorm.io/gorm" 6 | ) 7 | 8 | type GroupService struct { 9 | } 10 | 11 | // InfoById 根据用户id取用户信息 12 | func (us *GroupService) InfoById(id uint) *model.Group { 13 | u := &model.Group{} 14 | DB.Where("id = ?", id).First(u) 15 | return u 16 | } 17 | 18 | func (us *GroupService) List(page, pageSize uint, where func(tx *gorm.DB)) (res *model.GroupList) { 19 | res = &model.GroupList{} 20 | res.Page = int64(page) 21 | res.PageSize = int64(pageSize) 22 | tx := DB.Model(&model.Group{}) 23 | if where != nil { 24 | where(tx) 25 | } 26 | tx.Count(&res.Total) 27 | tx.Scopes(Paginate(page, pageSize)) 28 | tx.Find(&res.Groups) 29 | return 30 | } 31 | 32 | // Create 创建 33 | func (us *GroupService) Create(u *model.Group) error { 34 | res := DB.Create(u).Error 35 | return res 36 | } 37 | func (us *GroupService) Delete(u *model.Group) error { 38 | return DB.Delete(u).Error 39 | } 40 | 41 | // Update 更新 42 | func (us *GroupService) Update(u *model.Group) error { 43 | return DB.Model(u).Updates(u).Error 44 | } 45 | 46 | // DeviceGroupInfoById 根据用户id取用户信息 47 | func (us *GroupService) DeviceGroupInfoById(id uint) *model.DeviceGroup { 48 | u := &model.DeviceGroup{} 49 | DB.Where("id = ?", id).First(u) 50 | return u 51 | } 52 | 53 | func (us *GroupService) DeviceGroupList(page, pageSize uint, where func(tx *gorm.DB)) (res *model.DeviceGroupList) { 54 | res = &model.DeviceGroupList{} 55 | res.Page = int64(page) 56 | res.PageSize = int64(pageSize) 57 | tx := DB.Model(&model.DeviceGroup{}) 58 | if where != nil { 59 | where(tx) 60 | } 61 | tx.Count(&res.Total) 62 | tx.Scopes(Paginate(page, pageSize)) 63 | tx.Find(&res.DeviceGroups) 64 | return 65 | } 66 | 67 | func (us *GroupService) DeviceGroupCreate(u *model.DeviceGroup) error { 68 | res := DB.Create(u).Error 69 | return res 70 | } 71 | func (us *GroupService) DeviceGroupDelete(u *model.DeviceGroup) error { 72 | return DB.Delete(u).Error 73 | } 74 | 75 | func (us *GroupService) DeviceGroupUpdate(u *model.DeviceGroup) error { 76 | return DB.Model(u).Updates(u).Error 77 | } 78 | -------------------------------------------------------------------------------- /service/loginLog.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "github.com/lejianwen/rustdesk-api/v2/model" 5 | "gorm.io/gorm" 6 | ) 7 | 8 | type LoginLogService struct { 9 | } 10 | 11 | // InfoById 根据用户id取用户信息 12 | func (us *LoginLogService) InfoById(id uint) *model.LoginLog { 13 | u := &model.LoginLog{} 14 | DB.Where("id = ?", id).First(u) 15 | return u 16 | } 17 | 18 | func (us *LoginLogService) List(page, pageSize uint, where func(tx *gorm.DB)) (res *model.LoginLogList) { 19 | res = &model.LoginLogList{} 20 | res.Page = int64(page) 21 | res.PageSize = int64(pageSize) 22 | tx := DB.Model(&model.LoginLog{}) 23 | if where != nil { 24 | where(tx) 25 | } 26 | tx.Count(&res.Total) 27 | tx.Scopes(Paginate(page, pageSize)) 28 | tx.Find(&res.LoginLogs) 29 | return 30 | } 31 | 32 | // Create 创建 33 | func (us *LoginLogService) Create(u *model.LoginLog) error { 34 | res := DB.Create(u).Error 35 | return res 36 | } 37 | func (us *LoginLogService) Delete(u *model.LoginLog) error { 38 | return DB.Delete(u).Error 39 | } 40 | 41 | // Update 更新 42 | func (us *LoginLogService) Update(u *model.LoginLog) error { 43 | return DB.Model(u).Updates(u).Error 44 | } 45 | 46 | func (us *LoginLogService) BatchDelete(ids []uint) error { 47 | return DB.Where("id in (?)", ids).Delete(&model.LoginLog{}).Error 48 | } 49 | 50 | func (us *LoginLogService) SoftDelete(l *model.LoginLog) error { 51 | l.IsDeleted = model.IsDeletedYes 52 | return us.Update(l) 53 | } 54 | 55 | func (us *LoginLogService) BatchSoftDelete(uid uint, ids []uint) error { 56 | return DB.Model(&model.LoginLog{}).Where("user_id = ? and id in (?)", uid, ids).Update("is_deleted", model.IsDeletedYes).Error 57 | } 58 | -------------------------------------------------------------------------------- /service/serverCmd.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "fmt" 5 | "github.com/lejianwen/rustdesk-api/v2/model" 6 | "net" 7 | "time" 8 | ) 9 | 10 | type ServerCmdService struct{} 11 | 12 | // List 13 | func (is *ServerCmdService) List(page, pageSize uint) (res *model.ServerCmdList) { 14 | res = &model.ServerCmdList{} 15 | res.Page = int64(page) 16 | res.PageSize = int64(pageSize) 17 | tx := DB.Model(&model.ServerCmd{}) 18 | tx.Count(&res.Total) 19 | tx.Scopes(Paginate(page, pageSize)) 20 | tx.Find(&res.ServerCmds) 21 | return 22 | } 23 | 24 | // Info 25 | func (is *ServerCmdService) Info(id uint) *model.ServerCmd { 26 | u := &model.ServerCmd{} 27 | DB.Where("id = ?", id).First(u) 28 | return u 29 | } 30 | 31 | // Delete 32 | func (is *ServerCmdService) Delete(u *model.ServerCmd) error { 33 | return DB.Delete(u).Error 34 | } 35 | 36 | // Create 37 | func (is *ServerCmdService) Create(u *model.ServerCmd) error { 38 | res := DB.Create(u).Error 39 | return res 40 | } 41 | 42 | // SendCmd 发送命令 43 | func (is *ServerCmdService) SendCmd(port int, cmd string, arg string) (string, error) { 44 | //组装命令 45 | cmd = cmd + " " + arg 46 | res, err := is.SendSocketCmd("v6", port, cmd) 47 | if err == nil { 48 | return res, nil 49 | } 50 | //v6连接失败,尝试v4 51 | res, err = is.SendSocketCmd("v4", port, cmd) 52 | if err == nil { 53 | return res, nil 54 | } 55 | return "", err 56 | } 57 | 58 | // SendSocketCmd 59 | func (is *ServerCmdService) SendSocketCmd(ty string, port int, cmd string) (string, error) { 60 | addr := "[::1]" 61 | tcp := "tcp6" 62 | if ty == "v4" { 63 | tcp = "tcp" 64 | addr = "127.0.0.1" 65 | } 66 | conn, err := net.Dial(tcp, fmt.Sprintf("%s:%v", addr, port)) 67 | if err != nil { 68 | Logger.Debugf("%s connect to id server failed: %v", ty, err) 69 | return "", err 70 | } 71 | defer conn.Close() 72 | //发送命令 73 | _, err = conn.Write([]byte(cmd)) 74 | if err != nil { 75 | Logger.Debugf("%s send cmd failed: %v", ty, err) 76 | return "", err 77 | } 78 | time.Sleep(100 * time.Millisecond) 79 | //读取返回 80 | buf := make([]byte, 1024) 81 | n, err := conn.Read(buf) 82 | if err != nil && err.Error() != "EOF" { 83 | Logger.Debugf("%s read response failed: %v", ty, err) 84 | return "", err 85 | } 86 | return string(buf[:n]), nil 87 | } 88 | 89 | func (is *ServerCmdService) Update(f *model.ServerCmd) error { 90 | return DB.Model(f).Updates(f).Error 91 | } 92 | -------------------------------------------------------------------------------- /service/service.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "github.com/lejianwen/rustdesk-api/v2/config" 5 | "github.com/lejianwen/rustdesk-api/v2/lib/jwt" 6 | "github.com/lejianwen/rustdesk-api/v2/lib/lock" 7 | "github.com/lejianwen/rustdesk-api/v2/model" 8 | log "github.com/sirupsen/logrus" 9 | "gorm.io/gorm" 10 | ) 11 | 12 | type Service struct { 13 | //AdminService *AdminService 14 | //AdminRoleService *AdminRoleService 15 | *UserService 16 | *AddressBookService 17 | *TagService 18 | *PeerService 19 | *GroupService 20 | *OauthService 21 | *LoginLogService 22 | *AuditService 23 | *ShareRecordService 24 | *ServerCmdService 25 | *LdapService 26 | *AppService 27 | } 28 | 29 | type Dependencies struct { 30 | Config *config.Config 31 | DB *gorm.DB 32 | Logger *log.Logger 33 | Jwt *jwt.Jwt 34 | Lock *lock.Locker 35 | } 36 | 37 | var Config *config.Config 38 | var DB *gorm.DB 39 | var Logger *log.Logger 40 | var Jwt *jwt.Jwt 41 | var Lock lock.Locker 42 | 43 | var AllService *Service 44 | 45 | func New(c *config.Config, g *gorm.DB, l *log.Logger, j *jwt.Jwt, lo lock.Locker) *Service { 46 | Config = c 47 | DB = g 48 | Logger = l 49 | Jwt = j 50 | Lock = lo 51 | AllService = new(Service) 52 | return AllService 53 | } 54 | 55 | func Paginate(page, pageSize uint) func(db *gorm.DB) *gorm.DB { 56 | return func(db *gorm.DB) *gorm.DB { 57 | if page == 0 { 58 | page = 1 59 | } 60 | if pageSize == 0 { 61 | pageSize = 10 62 | } 63 | offset := (page - 1) * pageSize 64 | return db.Offset(int(offset)).Limit(int(pageSize)) 65 | } 66 | } 67 | 68 | func CommonEnable() func(db *gorm.DB) *gorm.DB { 69 | return func(db *gorm.DB) *gorm.DB { 70 | return db.Where("status = ?", model.COMMON_STATUS_ENABLE) 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /service/shareRecord.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "github.com/lejianwen/rustdesk-api/v2/model" 5 | "gorm.io/gorm" 6 | ) 7 | 8 | type ShareRecordService struct { 9 | } 10 | 11 | // InfoById 根据用户id取用户信息 12 | func (srs *ShareRecordService) InfoById(id uint) *model.ShareRecord { 13 | u := &model.ShareRecord{} 14 | DB.Where("id = ?", id).First(u) 15 | return u 16 | } 17 | 18 | func (srs *ShareRecordService) List(page, pageSize uint, where func(tx *gorm.DB)) (res *model.ShareRecordList) { 19 | res = &model.ShareRecordList{} 20 | res.Page = int64(page) 21 | res.PageSize = int64(pageSize) 22 | tx := DB.Model(&model.ShareRecord{}) 23 | if where != nil { 24 | where(tx) 25 | } 26 | tx.Count(&res.Total) 27 | tx.Scopes(Paginate(page, pageSize)) 28 | tx.Find(&res.ShareRecords) 29 | return 30 | } 31 | 32 | // Create 创建 33 | func (srs *ShareRecordService) Create(u *model.ShareRecord) error { 34 | res := DB.Create(u).Error 35 | return res 36 | } 37 | func (srs *ShareRecordService) Delete(u *model.ShareRecord) error { 38 | return DB.Delete(u).Error 39 | } 40 | 41 | // Update 更新 42 | func (srs *ShareRecordService) Update(u *model.ShareRecord) error { 43 | return DB.Model(u).Updates(u).Error 44 | } 45 | 46 | func (srs *ShareRecordService) BatchDelete(ids []uint) error { 47 | return DB.Where("id in (?)", ids).Delete(&model.ShareRecord{}).Error 48 | } 49 | -------------------------------------------------------------------------------- /systemd/rustdesk-api.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Rustdesk api Server 3 | 4 | [Service] 5 | Type=simple 6 | LimitNOFILE=1000000 7 | ExecStart=/usr/bin/rustdesk-api 8 | WorkingDirectory=/var/lib/rustdesk-api/ 9 | User= 10 | Group= 11 | Restart=always 12 | StandardOutput=append:/var/log/rustdesk-api/rustdesk-api.log 13 | StandardError=append:/var/log/rustdesk-api/rustdesk-api.error 14 | # Restart service after 10 seconds if node service crashes 15 | RestartSec=10 16 | 17 | [Install] 18 | WantedBy=multi-user.target 19 | -------------------------------------------------------------------------------- /utils/captcha.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "github.com/mojocn/base64Captcha" 5 | "time" 6 | ) 7 | 8 | var capdString = base64Captcha.NewDriverString(50, 150, 0, 5, 4, "123456789abcdefghijklmnopqrstuvwxyz", nil, nil, nil) 9 | 10 | var capdMath = base64Captcha.NewDriverMath(50, 150, 3, 10, nil, nil, nil) 11 | 12 | type B64StringCaptchaProvider struct{} 13 | 14 | func (p B64StringCaptchaProvider) Generate() (string, string, string, error) { 15 | id, content, answer := capdString.GenerateIdQuestionAnswer() 16 | return id, content, answer, nil 17 | } 18 | 19 | func (p B64StringCaptchaProvider) Expiration() time.Duration { 20 | return 5 * time.Minute 21 | } 22 | func (p B64StringCaptchaProvider) Draw(content string) (string, error) { 23 | item, err := capdString.DrawCaptcha(content) 24 | if err != nil { 25 | return "", err 26 | } 27 | b64str := item.EncodeB64string() 28 | return b64str, nil 29 | } 30 | 31 | type B64MathCaptchaProvider struct{} 32 | 33 | func (p B64MathCaptchaProvider) Generate() (string, string, string, error) { 34 | id, content, answer := capdMath.GenerateIdQuestionAnswer() 35 | return id, content, answer, nil 36 | } 37 | 38 | func (p B64MathCaptchaProvider) Expiration() time.Duration { 39 | return 5 * time.Minute 40 | } 41 | func (p B64MathCaptchaProvider) Draw(content string) (string, error) { 42 | item, err := capdMath.DrawCaptcha(content) 43 | if err != nil { 44 | return "", err 45 | } 46 | b64str := item.EncodeB64string() 47 | return b64str, nil 48 | } 49 | --------------------------------------------------------------------------------