├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── -------.md │ └── bug--.md ├── dependabot.yml └── workflows │ ├── ci.yml │ ├── codeql-analysis.yml │ ├── gitee-sync.yml │ └── release.yaml ├── .gitignore ├── EXAMPLE.md ├── FAQ.md ├── FRAMEWORK.md ├── INSTALL.md ├── LICENSE ├── Makefile ├── README.md ├── TEMPLATE.md ├── UPDATE.md ├── bot.go ├── cmd ├── hook_other.go ├── hook_windows.go ├── main.go └── play.go ├── go.mod ├── go.sum ├── image_pool ├── local_pool │ └── pool.go ├── lolicon_pool │ ├── api.go │ ├── error.go │ ├── persist.go │ └── pool.go ├── model.go └── pool.go ├── internal ├── test │ └── test.go └── test_concern │ └── concern.go ├── logging └── log.go ├── lsp ├── acfun │ ├── acfun.go │ ├── acfun.pb.go │ ├── acfun.proto │ ├── acfun_test.go │ ├── apiChannelList.go │ ├── apiChannelList_test.go │ ├── concern.go │ ├── concern_test.go │ ├── config.go │ ├── config_test.go │ ├── init.go │ ├── keyset.go │ ├── keyset_test.go │ ├── livePage.go │ ├── livePage_test.go │ ├── model.go │ ├── stateManager.go │ └── stateManager_test.go ├── bilibili │ ├── README.md │ ├── XRelationStat.go │ ├── XSpaceAccInfo.go │ ├── XSpaceAccInfo_test.go │ ├── XWebInterfaceNav.go │ ├── XWebInterfaceNav_test.go │ ├── bilibili.go │ ├── bilibili.pb.go │ ├── bilibili.proto │ ├── bilibili_test.go │ ├── card_extension.go │ ├── card_extension_test.go │ ├── concern.go │ ├── concern_emit_fresher.go │ ├── concern_fresher.go │ ├── concern_test.go │ ├── config.go │ ├── config_test.go │ ├── dynamicHistory.go │ ├── dynamicHistory_test.go │ ├── dynamicNew.go │ ├── dynamicNew_test.go │ ├── feedList.go │ ├── feedList_test.go │ ├── getAttentionList.go │ ├── getAttentionList_test.go │ ├── init.go │ ├── keyset.go │ ├── keyset_test.go │ ├── login.go │ ├── login_test.go │ ├── model.go │ ├── model_test.go │ ├── relation.go │ ├── relation_test.go │ ├── spaceHistory.go │ ├── spaceHistory_test.go │ ├── stateManager.go │ └── stateManager_test.go ├── buntdb │ ├── buntdb.go │ ├── buntdb_test.go │ ├── errors.go │ ├── key.go │ ├── key_test.go │ ├── option.go │ ├── option_test.go │ ├── shortcut.go │ └── shortcut_test.go ├── cfg │ ├── config.go │ └── logger.go ├── command.go ├── commandRunetime_test.go ├── commandRuntime.go ├── command_test.go ├── concern │ ├── callback.go │ ├── callback_test.go │ ├── concern.go │ ├── concern_ext.go │ ├── config.go │ ├── config_at.go │ ├── config_filter.go │ ├── config_notify.go │ ├── config_test.go │ ├── errors.go │ ├── hook.go │ ├── hook_test.go │ ├── identity.go │ ├── identity_test.go │ ├── json.go │ ├── keyset.go │ ├── keyset_test.go │ ├── registry.go │ ├── registry_test.go │ ├── stateManager.go │ └── stateManager_test.go ├── concern_type │ ├── type.go │ └── type_test.go ├── cron.go ├── douyu │ ├── betard.go │ ├── concern.go │ ├── concern_test.go │ ├── config.go │ ├── config_test.go │ ├── douyu.go │ ├── douyu.pb.go │ ├── douyu.proto │ ├── douyu_test.go │ ├── errors.go │ ├── init.go │ ├── keyset.go │ ├── keyset_test.go │ ├── model.go │ ├── model_test.go │ ├── stateManager.go │ └── stateManager_test.go ├── groupCommand.go ├── huya │ ├── concern.go │ ├── concern_test.go │ ├── config.go │ ├── config_test.go │ ├── errors.go │ ├── huya.go │ ├── init.go │ ├── keyset.go │ ├── keyset_test.go │ ├── model.go │ ├── model_test.go │ ├── roomPage.go │ ├── stateManager.go │ └── stateManager_test.go ├── iCommand.go ├── iCommand_test.go ├── messageContext.go ├── migration_v1.go ├── migration_v1_test.go ├── migrations.go ├── mmsg │ ├── at.go │ ├── ctx.go │ ├── cut.go │ ├── image.go │ ├── image_test.go │ ├── logger.go │ ├── poke.go │ ├── target.go │ ├── target_test.go │ ├── type.go │ ├── typed.go │ ├── typed_test.go │ ├── writer.go │ └── writer_test.go ├── module.go ├── notify.go ├── notify_test.go ├── parser │ ├── parser.go │ └── parser_test.go ├── permission │ ├── errors.go │ ├── errors_test.go │ ├── keyset.go │ ├── keyset_test.go │ ├── permission.go │ ├── permission_test.go │ ├── stateManager.go │ └── stateManager_test.go ├── privateCommand.go ├── stateManager.go ├── stateManager_test.go ├── status.go ├── status_test.go ├── template │ ├── README.md │ ├── default │ │ ├── .keep │ │ ├── command.group.checkin.tmpl │ │ ├── command.group.help.tmpl │ │ ├── command.group.lsp.tmpl │ │ ├── command.private.help.tmpl │ │ ├── command.private.ping.tmpl │ │ ├── notify.group.acfun.live.tmpl │ │ ├── notify.group.bilibili.live.tmpl │ │ ├── notify.group.douyu.live.tmpl │ │ ├── notify.group.huya.live.tmpl │ │ ├── trigger.group.member_in.tmpl │ │ ├── trigger.group.member_out.tmpl │ │ ├── trigger.group.poke.tmpl │ │ ├── trigger.private.group_invited.tmpl │ │ ├── trigger.private.new_friend_added.tmpl │ │ └── trigger.private.poke.tmpl │ ├── error.go │ ├── exec.go │ ├── funcs.go │ ├── funcs_cast_ext.go │ ├── funcs_cmp_ext.go │ ├── funcs_date_ext.go │ ├── funcs_default_ext.go │ ├── funcs_dict_ext.go │ ├── funcs_ext.go │ ├── funcs_hash_ext.go │ ├── funcs_json_ext.go │ ├── funcs_list_ext.go │ ├── funcs_request_ext.go │ ├── funcs_strings_ext.go │ ├── helper.go │ ├── loader.go │ ├── logger.go │ ├── option.go │ ├── sort.go │ ├── template.go │ └── template_test.go ├── twitcasting │ ├── concern.go │ ├── config.go │ ├── live.go │ └── notify.go ├── version.go ├── version │ ├── errors.go │ ├── helper.go │ ├── migration.go │ ├── migration_test.go │ ├── version.go │ └── version_test.go ├── version_test.go ├── weibo │ ├── concern.go │ ├── concern_test.go │ ├── containerGetIndex.go │ ├── containerGetIndex_test.go │ ├── freshCookie.go │ ├── freshCookie_test.go │ ├── init.go │ ├── keyset.go │ ├── keyset_test.go │ ├── model.go │ ├── model_test.go │ ├── stateManager.go │ ├── stateManager_test.go │ ├── weibo.go │ ├── weibo.pb.go │ └── weibo.proto └── youtube │ ├── concern.go │ ├── concern_test.go │ ├── config.go │ ├── config_test.go │ ├── fetchInfo.go │ ├── fetchInfo_test.go │ ├── init.go │ ├── keyset.go │ ├── keyset_test.go │ ├── model.go │ ├── model_test.go │ ├── stateManager.go │ ├── stateManager_test.go │ ├── youtube.go │ ├── youtube.pb.go │ └── youtube.proto ├── msg-marker └── marker.go ├── proxy_pool ├── local_proxy_pool │ ├── local.go │ └── local_test.go ├── proxyPool.go └── py │ └── pyPool.go ├── requests ├── fake_useragent.go ├── fake_useragent_0.2.0.json ├── fake_useragent_test.go ├── json.go └── requests.go ├── utils ├── blockCache │ ├── blockCache.go │ ├── blockCache_test.go │ ├── fnv.go │ └── fnv_test.go ├── emitQueue.go ├── emitQueue_test.go ├── expirable │ ├── expirable.go │ └── expirable_test.go ├── hackedBot.go ├── hackedBot_test.go ├── helper.go ├── helper_test.go ├── image.go ├── image_test.go ├── logger.go ├── miraigo.go ├── miraigo_test.go └── msgstringer │ ├── logger.go │ ├── stringer.go │ └── stringer_test.go └── warn ├── warn_other.go └── warn_windows.go /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: ['https://afdian.net/@sora233'] 13 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/-------.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 希望添加新功能 3 | about: Suggest an idea for this project 4 | title: "[FEATURE]" 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | **描述一下您的想法** 11 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug--.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug反馈 3 | about: 向DDBOT反馈一个BUG,帮助DDBOT变得更好 4 | title: "[BUG]" 5 | labels: bug, question 6 | assignees: '' 7 | 8 | --- 9 | 10 | **详细描述** 11 | 在这里描述一下BUG发生时的情况,以及您做了什么操作导致BUG发生。 12 | 13 | **正常情况应该是怎样的** 14 | 在这里描述一下如果没有BUG,正常情况应该是怎样的。 15 | 16 | **截图** 17 | 如果有截图,可以放一下,请注意保护个人隐私。 18 | 19 | **如果是私有部署** 20 | 操作系统版本: 21 | BOT版本: 22 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: gomod 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | time: "21:00" 8 | open-pull-requests-limit: 10 9 | reviewers: 10 | - Sora233 11 | ignore: 12 | - dependency-name: github.com/aliyun/alibaba-cloud-sdk-go 13 | versions: 14 | - 1.61.885 15 | - 1.61.888 16 | - 1.61.902 17 | - 1.61.905 18 | - 1.61.913 19 | - 1.61.946 20 | - 1.61.950 21 | - 1.61.957 22 | - 1.61.962 23 | - 1.61.968 24 | - 1.61.971 25 | - 1.61.976 26 | - 1.61.977 27 | - 1.61.979 28 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: "CI" 2 | on: 3 | push: 4 | branches: 5 | - '**' 6 | pull_request: 7 | branches: 8 | - '**' 9 | jobs: 10 | # run unittest 11 | test: 12 | runs-on: ubuntu-latest 13 | strategy: 14 | fail-fast: false 15 | matrix: 16 | go: [ '1.20' ] 17 | 18 | steps: 19 | - uses: actions/setup-go@v3 20 | with: 21 | go-version: ${{ matrix.go }} 22 | - uses: actions/checkout@v3 23 | - run: go test -v -coverprofile=profile.cov ./... 24 | - name: go vet 25 | run: | 26 | go vet ./... 27 | - name: Send coverage 28 | uses: shogo82148/actions-goveralls@v1 29 | with: 30 | path-to-profile: profile.cov 31 | flag-name: Go-${{ matrix.go }} 32 | parallel: true 33 | 34 | # build binary 35 | build: 36 | needs: test 37 | name: Build binary CI 38 | runs-on: ubuntu-latest 39 | strategy: 40 | matrix: 41 | goos: [ linux, windows, darwin ] 42 | goarch: [ "386", amd64, arm, arm64 ] 43 | exclude: 44 | - goos: darwin 45 | goarch: arm 46 | - goos: darwin 47 | goarch: "386" 48 | fail-fast: true 49 | steps: 50 | - uses: actions/checkout@v3 51 | - name: Setup Go environment 52 | uses: actions/setup-go@v3 53 | with: 54 | go-version: '1.20' 55 | - name: Set env 56 | run: | 57 | echo BUILD_TIME=$(date --rfc-3339=seconds) >> ${GITHUB_ENV} 58 | - name: Build binary file 59 | env: 60 | GOOS: ${{ matrix.goos }} 61 | GOARCH: ${{ matrix.goarch }} 62 | run: | 63 | export BINARY_NAME="DDBOT" 64 | if [ $GOOS = "windows" ]; then export BINARY_NAME="$BINARY_NAME.exe"; fi 65 | go build -o "$BINARY_NAME" -ldflags '-w -s -X "github.com/Sora233/DDBOT/lsp.BuildTime=${{ env.BUILD_TIME }}" -X github.com/Sora233/DDBOT/lsp.CommitId=${{ github.sha }} -X github.com/Sora233/DDBOT/lsp.Tags=${{ env.Tags }}' github.com/Sora233/DDBOT/cmd 66 | - name: Upload artifact 67 | uses: actions/upload-artifact@v2 68 | if: ${{ !github.head_ref }} 69 | with: 70 | name: ${{ matrix.goos }}_${{ matrix.goarch }} 71 | path: | 72 | ./DDBOT 73 | ./DDBOT.exe 74 | 75 | 76 | # notifies that all test jobs are finished. 77 | finish: 78 | needs: test 79 | runs-on: ubuntu-latest 80 | steps: 81 | - uses: shogo82148/actions-goveralls@v1 82 | with: 83 | parallel-finished: true -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ master ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ master ] 20 | schedule: 21 | - cron: '20 18 * * 3' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | permissions: 28 | actions: read 29 | contents: read 30 | security-events: write 31 | 32 | strategy: 33 | fail-fast: false 34 | matrix: 35 | language: [ 'go' ] 36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] 37 | # Learn more: 38 | # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed 39 | 40 | steps: 41 | - name: Checkout repository 42 | uses: actions/checkout@v2 43 | 44 | # Initializes the CodeQL tools for scanning. 45 | - name: Initialize CodeQL 46 | uses: github/codeql-action/init@v2 47 | with: 48 | languages: ${{ matrix.language }} 49 | # If you wish to specify custom queries, you can do so here or in a config file. 50 | # By default, queries listed here will override any specified in a config file. 51 | # Prefix the list here with "+" to use these queries and those in the config file. 52 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 53 | 54 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 55 | # If this step fails, then you should remove it and run the build manually (see below) 56 | - name: Autobuild 57 | uses: github/codeql-action/autobuild@v2 58 | 59 | # ℹ️ Command-line programs to run using the OS shell. 60 | # 📚 https://git.io/JvXDl 61 | 62 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 63 | # and modify them (or add more) to build your code if your project 64 | # uses a compiled language 65 | 66 | #- run: | 67 | # make bootstrap 68 | # make release 69 | 70 | - name: Perform CodeQL Analysis 71 | uses: github/codeql-action/analyze@v2 72 | -------------------------------------------------------------------------------- /.github/workflows/gitee-sync.yml: -------------------------------------------------------------------------------- 1 | name: Mirror GitHub Auto Queried Repos to Gitee 2 | on: 3 | push: 4 | branches: 5 | - 'develop' 6 | - 'master' 7 | jobs: 8 | run: 9 | name: Sync-GitHub-to-Gitee 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Mirror the Github repos to Gitee. 13 | uses: Yikun/hub-mirror-action@master 14 | with: 15 | src: github/Sora233 16 | dst: gitee/sora233 17 | dst_key: ${{ secrets.GITEE_PRIVATE_KEY }} 18 | dst_token: ${{ secrets.GITEE_TOKEN }} 19 | static_list: "DDBOT" 20 | force_update: true -------------------------------------------------------------------------------- /.github/workflows/release.yaml: -------------------------------------------------------------------------------- 1 | name: release 2 | 3 | on: 4 | release: 5 | types: [ created ] 6 | 7 | jobs: 8 | releases-matrix: 9 | name: Release Go Binary 10 | runs-on: ubuntu-latest 11 | strategy: 12 | matrix: 13 | goos: [ linux, windows, darwin ] 14 | goarch: [ "386", amd64, arm, arm64 ] 15 | exclude: 16 | - goos: darwin 17 | goarch: arm 18 | - goos: darwin 19 | goarch: "386" 20 | steps: 21 | - uses: actions/checkout@v3 22 | - name: Set env 23 | run: | 24 | echo BUILD_TIME=$(date --rfc-3339=seconds) >> ${GITHUB_ENV} 25 | echo Tags=${GITHUB_REF##*/} >> ${GITHUB_ENV} 26 | - uses: wangyoucao577/go-release-action@v1.22 27 | with: 28 | github_token: ${{ secrets.GITHUB_TOKEN }} 29 | goos: ${{ matrix.goos }} 30 | goarch: ${{ matrix.goarch }} 31 | goversion: "https://go.dev/dl/go1.20.2.linux-amd64.tar.gz" 32 | ldflags: -w -s -X "github.com/Sora233/DDBOT/lsp.BuildTime=${{ env.BUILD_TIME }}" -X github.com/Sora233/DDBOT/lsp.CommitId=${{ github.sha }} -X github.com/Sora233/DDBOT/lsp.Tags=${{ env.Tags }} 33 | project_path: "./cmd" -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.idea 2 | /logs 3 | /miraigo-logs 4 | application.yaml 5 | device.json 6 | Sora233-MiraiGo 7 | .lsp.db 8 | .lsp.db.lock 9 | DDBOT 10 | /.coverage.out 11 | /coverage.html 12 | /qrcode.png 13 | /qq-logs 14 | /session.token 15 | /dist/ 16 | /template 17 | /version*.json -------------------------------------------------------------------------------- /FAQ.md: -------------------------------------------------------------------------------- 1 | ## DDBOT常见问题问答 2 | 3 | ### Q:为什么我设置了@全体成员却没效果? 4 | 5 | - 我使用的是官方bot: 6 | 7 | 首先@全体成员无法在官方demo bot上正常使用,如果您使用的是官方bot,该问题是已知的,请使用@特定用户作为替代。 8 | (这是因为一个账号一天只能@全体成员十次,该次数在所有群内共享,因为官方bot用户众多,很容易把次数用尽,用尽后即无法再@全体成员) 9 | 10 | - 我使用的是私人部署的bot: 11 | 12 | 请检查是否把bot设为qq群管理员,检查`/config at_all`配置是否设置为`on`,如果确认配置无问题,请把该问题提交给开发者。 13 | 14 | ### Q:为什么我在群里发指令它没反应? 15 | 16 | - 我使用的是官方bot: 17 | 18 | 请确认该命令是正确的,以及该命令没有被管理员禁用,最后确认bot处于正常运行的状态(可以使用/help测试有没有反应),如果确认后仍然没有解决,请把该问题提交给开发者。 19 | 20 | - 我使用的是私人部署的bot: 21 | 22 | 请检查后台有无反应,如果后台有发送消息失败的日志但是qq没有收到,大概率是qq帐号处于风控帐号,需要人工登陆帐号正常使用一段时间以解除风控(注意不要再继续使用BOT了,否则会导致被封号)。 23 | 24 | 如果后台没有消息发送失败的日志,但是qq也没有收到,该情况小概率触发,一般交替使用别的命令数次,以及等待一段时间可以解决。 25 | 26 | 如果后台无法接受消息,请用手机登陆bot帐号,在设备管理页面把`mirai`名字的设备全部删除,等待一段时间重新登陆。 27 | 28 | ### Q:为什么我无法订阅b站动态/直播? 29 | 30 | - 我使用的是官方bot: 31 | 32 | 请把该问题提交给开发者。 33 | 34 | - 我使用的是私人部署的bot: 35 | 36 | 请检查b站配置完整,用文本编辑`application.yaml`文件,需要配置`bilibili`一栏的`account`和`password`。 37 | 38 | 常见情况: 39 | 40 | - 写错了帐号/密码 41 | - 填写了错误的格式 42 | 43 | 一个格式示范: 44 | 45 | 英文引号后需要一个英文空格,帐号和密码用英文引号括起来 46 | 47 | ```yaml 48 | bilibili: 49 | account: "account" 50 | password: "password" 51 | ``` 52 | 53 | - 如果上面确认无误,那您的b站帐号不支持自动登陆,此时只能手动设置cookie,如果不知道如何查看cookie,请百度搜索`如何查看cookie` 54 | 55 | **cookie内容可能较长,请确认复制完整** 56 | 57 | 请按照下面格式配置,注意把`account`和`password`删除 58 | 59 | ```yaml 60 | # b站登陆后的cookie字段,从cookie中找到这两个填进去 61 | # 警告: 62 | # SESSDATA和bili_jct等价于您的帐号凭证 63 | # 请绝对不要透露给他人,更不能上传至Github等公开平台 64 | # 否则将导致您的帐号被盗 65 | bilibili: 66 | SESSDATA: "xxxxxxxxxxxx" 67 | bili_jct: "xxxxxxxxxxxx" 68 | ``` 69 | 70 | **获取cookie后,不可点击b站的退出登陆,否则cookie将失效,您可以使用浏览器的清除历史记录功能,或者在浏览器隐私窗口内登陆获取** 71 | 72 | **注意:刚注册的号有时候关注时会提示“账户异常”,此为B站帐号的原因,无法在DDBOT解决** 73 | 74 | ### Q:为什么我订阅了动态/直播没有推送? 75 | 76 | - 我使用的是官方bot: 77 | 78 | 检查是否使用`/config filter`设置了动态过滤。 79 | 80 | - 我使用的是私人部署的bot 81 | 82 | 检查bot日志是否有该条`notify`,以及是否有消息发送失败。 83 | 84 | ### Q:为什么我私人部署的bot修改了配置后运行时闪退? 85 | 86 | 因为配置格式填写的不对,一些填写时注意事项: 87 | 88 | - 填写时请把井号及后面的内容删除 89 | - 所有引号、冒号均需要使用英文(半角)格式 90 | - 冒号后需要加一个英文空格 91 | 92 | ### Q:为什么我私人部署的bot会出现发送群消息失败? 93 | 94 | 如果您是刚开始使用BOT,大概是因为qq账号被风控导致,建议使用bot挂机3-7天(期间请不要使用bot的功能,否则可能导致封号),即可解除风控。 95 | 96 | 如果仍出现问题,请删除`device.json`文件,重复上面的步骤。 97 | 98 | ### Q:我下载的DDBOT程序应该如何运行? 99 | 100 | 检查您是否下载了正确的程序,windows程序应该有.exe后缀。 101 | 102 | - 根据系统选择windows / linux / darwin 103 | - 根据架构选择 32位->386 / 64位->amd64 / arm->arm 104 | 105 | 例如您的系统是windows 11/10/7/server,64位则选择windows-amd64下载,32位则选择windows-386下载。 106 | 107 | ### Q:我的数据库文件损坏如何处理? 108 | 109 | **操作前请备份.lsp.db文件** 110 | 111 | 尝试进行下面的操作: 112 | 113 | - 用文本编辑器打开.lsp.db文件,删掉最后一行,然后测试能否正常启动。 114 | 115 | 连续重试上面步骤数十次,如果仍然无法解决,请到交流群内寻求帮助。 -------------------------------------------------------------------------------- /FRAMEWORK.md: -------------------------------------------------------------------------------- 1 | # 为DDBOT编写插件 2 | 3 | *阅读这个内容需要一定的开发能力。* 4 | 5 | 文档地址: 6 | [![GoDoc](http://img.shields.io/badge/go-documentation-blue.svg?style=flat-square)](https://pkg.go.dev/github.com/Sora233/DDBOT) 7 | 8 | 为DDBOT编写插件的基本步骤为: 9 | 10 | - 通过`concern.StateManager`,实现`concern.Concern`接口 11 | - 在`init()`函数中进行注册 12 | - 在`main`中引入刚刚编写的包 13 | 14 | DDBOT提供了一个插件脚手架,可以快速创建一个插件模版:[DDBOT-template](https://github.com/Sora233/DDBOT-template) 15 | 16 | 在编写插件前,请先阅读一个示例插件:[DDBOT-example](https://github.com/Sora233/DDBOT-example) 17 | 18 | 示例插件展示了为DDBOT编写插件的方法。 19 | 20 | ```golang 21 | // 引入刚刚编写的插件 22 | import ( 23 | _ "github.com/Sora233/DDBOT-example/concern" 24 | ) 25 | ``` 26 | 27 | 这个例子为网站`example`新增了一个类型`example`,可以使用对应的`watch`命令进行订阅: 28 | 29 | ``` 30 | /watch -s example -t example ID 31 | ``` 32 | 33 | 使用unwatch进行取消订阅: 34 | 35 | ``` 36 | /unwatch -s example -t example ID 37 | ``` 38 | 39 | 使用list进行查看订阅: 40 | 41 | ``` 42 | /list 43 | ``` 44 | 45 | ## 一些开箱即用的组件 46 | 47 | **示例代码为了简洁,均忽略错误处理,在实际编写代码的过程中,请注意错误处理。** 48 | 49 | 在编写插件的过程中,第一步通常是创建新的`StateManager`,下面的示例均基于这个结构: 50 | 51 | ```golang 52 | type StateManager struct { 53 | *concern.StateManager 54 | } 55 | var s *StateManager = ... // 初始化 56 | ``` 57 | 58 | ### 持久化 59 | 60 | 在编写插件的过程中,总会遇到想要存储一些数据的需求,DDBOT默认提供了一个key-value数据库可以使用,支持串行事务,key ttl,并且已经包含在`StateManager`中: 61 | 62 | ```golang 63 | s.SetInt64("myInt64", 123456) // 即可往数据库中设置kv对 "myInt64" - 123456 64 | v, _ := s.GetInt64("myInt64") // 获取刚刚写入的值 65 | // v == 123456 66 | 67 | // 还有 s.SetJson() s.Set() 等更多方法 68 | ``` 69 | 70 | 还有更多方法请参考`buntdb/shortcut.go` 71 | 72 | 73 | ### 轮询器 74 | 75 | 当订阅网站暂时没有发现比较高效的爬虫方式时,我们只能选择不断地依次访问每一个订阅目标的特定页面来获取目标是否有新信息,这种方式叫做轮询。 76 | 77 | DDBOT内置了一个轮询器来满足这种方式: 78 | 79 | ```golang 80 | // 首先需要启动 EmitQueue 轮询器 81 | s.UseEmitQueue() 82 | // 然后可以使用轮询器提供的helper创建一个 FreshFunc 83 | s.UseFreshFunc(s.EmitQueueFresher(func(p concern_type.Type, id interface{}) ([]concern.Event, error) { 84 | // id 是此时轮到的目标信息,此时可以刷新这个目标 85 | // p是id所有订阅过的Type的集合 86 | })) 87 | ``` 88 | 89 | ### 更多文档正在施工中。 -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | BUILD_TIME := $(shell date --rfc-3339=seconds) 2 | COMMIT_ID := $(shell git rev-parse HEAD) 3 | 4 | LDFLAGS = -X "github.com/Sora233/DDBOT/lsp.BuildTime='"$(BUILD_TIME)"'" -X "github.com/Sora233/DDBOT/lsp.CommitId='"$(COMMIT_ID)"'" 5 | 6 | SRC := $(shell find . -type f -name '*.go') lsp/template/default/* 7 | PROTO := $(shell find . -type f -name '*.proto') 8 | COV := .coverage.out 9 | TARGET := DDBOT 10 | 11 | $(COV): $(SRC) 12 | go test ./... -coverprofile=$(COV) 13 | 14 | 15 | $(TARGET): $(SRC) go.mod go.sum 16 | go build -ldflags '$(LDFLAGS)' -o $(TARGET) github.com/Sora233/DDBOT/cmd 17 | 18 | build: $(TARGET) 19 | 20 | proto: $(PROTO) 21 | protoc --go_out=. $(PROTO) 22 | 23 | test: $(COV) 24 | 25 | coverage: $(COV) 26 | go tool cover -func=$(COV) | grep -v 'pb.go' 27 | 28 | report: $(COV) 29 | go tool cover -html=$(COV) 30 | 31 | clean: 32 | - rm -rf $(TARGET) $(COV) 33 | -------------------------------------------------------------------------------- /cmd/hook_other.go: -------------------------------------------------------------------------------- 1 | //go:build !windows 2 | // +build !windows 3 | 4 | package main 5 | 6 | func exitHook(func()) error { 7 | return nil 8 | } 9 | -------------------------------------------------------------------------------- /cmd/hook_windows.go: -------------------------------------------------------------------------------- 1 | //go:build windows 2 | // +build windows 3 | 4 | package main 5 | 6 | import ( 7 | "syscall" 8 | "time" 9 | ) 10 | 11 | const ( 12 | CTRL_C_EVENT = uint32(0) 13 | CTRL_BREAK_EVENT = uint32(1) 14 | CTRL_CLOSE_EVENT = uint32(2) 15 | CTRL_LOGOFF_EVENT = uint32(5) 16 | CTRL_SHUTDOWN_EVENT = uint32(6) 17 | ) 18 | 19 | var ( 20 | kernel32 = syscall.NewLazyDLL("kernel32.dll") 21 | procSetConsoleCtrlHandler = kernel32.NewProc("SetConsoleCtrlHandler") 22 | ) 23 | 24 | func exitHook(f func()) error { 25 | n, _, err := procSetConsoleCtrlHandler.Call( 26 | syscall.NewCallback(func(controlType uint32) uint { 27 | f() 28 | time.Sleep(time.Second * 1) 29 | switch controlType { 30 | case CTRL_CLOSE_EVENT: 31 | return 1 32 | default: 33 | return 0 34 | } 35 | }), 36 | 1) 37 | 38 | if n == 0 { 39 | return err 40 | } 41 | return nil 42 | } 43 | -------------------------------------------------------------------------------- /cmd/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/Sora233/DDBOT" 6 | _ "github.com/Sora233/DDBOT/logging" 7 | "github.com/Sora233/DDBOT/lsp" 8 | _ "github.com/Sora233/DDBOT/lsp/acfun" 9 | "github.com/Sora233/DDBOT/lsp/bilibili" 10 | localdb "github.com/Sora233/DDBOT/lsp/buntdb" 11 | _ "github.com/Sora233/DDBOT/lsp/douyu" 12 | _ "github.com/Sora233/DDBOT/lsp/huya" 13 | "github.com/Sora233/DDBOT/lsp/permission" 14 | _ "github.com/Sora233/DDBOT/lsp/weibo" 15 | _ "github.com/Sora233/DDBOT/lsp/youtube" 16 | _ "github.com/Sora233/DDBOT/msg-marker" 17 | "github.com/Sora233/DDBOT/warn" 18 | "github.com/Sora233/MiraiGo-Template/config" 19 | "github.com/alecthomas/kong" 20 | "net/http" 21 | _ "net/http/pprof" 22 | "os" 23 | "runtime" 24 | ) 25 | 26 | func main() { 27 | var cli struct { 28 | Play bool `optional:"" help:"运行play函数,适用于测试和开发"` 29 | Debug bool `optional:"" help:"启动debug模式"` 30 | SetAdmin int64 `optional:"" xor:"c" help:"设置admin权限"` 31 | Version bool `optional:"" xor:"c" short:"v" help:"打印版本信息"` 32 | SyncBilibili bool `optional:"" xor:"c" help:"同步b站帐号的关注,适用于更换或迁移b站帐号的时候"` 33 | } 34 | kong.Parse(&cli) 35 | 36 | if cli.Version { 37 | fmt.Printf("Tags: %v\n", lsp.Tags) 38 | fmt.Printf("COMMIT_ID: %v\n", lsp.CommitId) 39 | fmt.Printf("BUILD_TIME: %v\n", lsp.BuildTime) 40 | os.Exit(0) 41 | } 42 | 43 | if err := localdb.InitBuntDB(""); err != nil { 44 | if err == localdb.ErrLockNotHold { 45 | warn.Warn("tryLock数据库失败:您可能重复启动了这个BOT!\n如果您确认没有重复启动,请删除.lsp.db.lock文件并重新运行。") 46 | } else { 47 | warn.Warn("无法正常初始化数据库!请检查.lsp.db文件权限是否正确,如无问题则为数据库文件损坏,请阅读文档获得帮助。") 48 | } 49 | return 50 | } 51 | 52 | if runtime.GOOS == "windows" { 53 | if err := exitHook(func() { 54 | localdb.Close() 55 | }); err != nil { 56 | localdb.Close() 57 | warn.Warn("无法正常初始化Windows环境!") 58 | return 59 | } 60 | } else { 61 | defer localdb.Close() 62 | } 63 | 64 | if cli.SetAdmin != 0 { 65 | sm := permission.NewStateManager() 66 | err := sm.GrantRole(cli.SetAdmin, permission.Admin) 67 | if err != nil { 68 | fmt.Printf("设置Admin权限失败 %v\n", err) 69 | } 70 | return 71 | } 72 | 73 | if cli.SyncBilibili { 74 | config.Init() 75 | c := bilibili.NewConcern(nil) 76 | c.StateManager.FreshIndex() 77 | bilibili.Init() 78 | c.SyncSub() 79 | return 80 | } 81 | 82 | fmt.Println("DDBOT唯一指定交流群:755612788") 83 | 84 | if cli.Debug { 85 | lsp.Debug = true 86 | go http.ListenAndServe("localhost:6060", nil) 87 | } 88 | 89 | if cli.Play { 90 | play() 91 | return 92 | } 93 | 94 | DDBOT.SetUpLog() 95 | 96 | DDBOT.Run() 97 | } 98 | -------------------------------------------------------------------------------- /cmd/play.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | func play() { 4 | } 5 | -------------------------------------------------------------------------------- /image_pool/local_pool/pool.go: -------------------------------------------------------------------------------- 1 | package local_pool 2 | 3 | import ( 4 | "errors" 5 | "github.com/Sora233/DDBOT/image_pool" 6 | localutils "github.com/Sora233/DDBOT/utils" 7 | "github.com/Sora233/MiraiGo-Template/utils" 8 | "io/ioutil" 9 | "math/rand" 10 | "os" 11 | "sync" 12 | "time" 13 | ) 14 | 15 | var logger = utils.GetModuleLogger("local_pool") 16 | 17 | type LocalPool struct { 18 | freshMutex *sync.Mutex 19 | 20 | imageDir string 21 | imageList []string 22 | } 23 | 24 | type Image struct { 25 | Path string 26 | } 27 | 28 | func (i *Image) Content() ([]byte, error) { 29 | return ioutil.ReadFile(i.Path) 30 | } 31 | 32 | func (pool *LocalPool) Get(opts ...image_pool.OptionFunc) ([]image_pool.Image, error) { 33 | if pool == nil { 34 | return nil, errors.New("pool status down") 35 | } 36 | pool.freshMutex.Lock() 37 | defer pool.freshMutex.Unlock() 38 | 39 | if len(pool.imageList) == 0 { 40 | return nil, errors.New("no image") 41 | } 42 | 43 | var ( 44 | result []image_pool.Image 45 | option = make(image_pool.Option) 46 | num = 1 47 | ) 48 | 49 | for _, opt := range opts { 50 | opt(option) 51 | } 52 | 53 | for k, v := range option { 54 | switch k { 55 | case "num": 56 | _v, ok := v.(int) 57 | if ok { 58 | num = _v 59 | } 60 | } 61 | } 62 | 63 | for i := 0; i < num; i++ { 64 | result = append(result, &Image{ 65 | Path: pool.imageList[rand.Intn(len(pool.imageList))], 66 | }) 67 | } 68 | 69 | return result, nil 70 | } 71 | 72 | func (pool *LocalPool) RefreshImage() error { 73 | pool.freshMutex.Lock() 74 | defer pool.freshMutex.Unlock() 75 | files, err := localutils.FilePathWalkDir(pool.imageDir) 76 | if err != nil { 77 | return err 78 | } else { 79 | pool.imageList = files 80 | } 81 | return nil 82 | } 83 | 84 | func NewLocalPool(path string) (*LocalPool, error) { 85 | if i, err := os.Stat(path); err != nil || i == nil { 86 | return nil, errors.New("invalid path") 87 | } 88 | pool := &LocalPool{ 89 | imageDir: path, 90 | freshMutex: new(sync.Mutex), 91 | imageList: make([]string, 0), 92 | } 93 | err := pool.RefreshImage() 94 | if err != nil { 95 | return nil, err 96 | } 97 | go func() { 98 | timer := time.NewTimer(time.Minute) 99 | for { 100 | select { 101 | case <-timer.C: 102 | err := pool.RefreshImage() 103 | if err != nil { 104 | logger.Errorf("local pool refresh failed %v", err) 105 | } 106 | timer.Reset(time.Minute) 107 | } 108 | } 109 | }() 110 | return pool, nil 111 | } 112 | -------------------------------------------------------------------------------- /image_pool/lolicon_pool/api.go: -------------------------------------------------------------------------------- 1 | package lolicon_pool 2 | 3 | import ( 4 | "github.com/Sora233/DDBOT/proxy_pool" 5 | "github.com/Sora233/DDBOT/requests" 6 | "github.com/Sora233/DDBOT/utils" 7 | "github.com/Sora233/MiraiGo-Template/config" 8 | "time" 9 | ) 10 | 11 | const Host = "https://api.lolicon.app/setu" 12 | 13 | type R18Type int 14 | 15 | const ( 16 | R18Off R18Type = iota 17 | R18On 18 | //R18Mix 19 | ) 20 | 21 | func (r R18Type) String() string { 22 | switch r { 23 | case R18Off: 24 | return "R18Off" 25 | case R18On: 26 | return "R18On" 27 | //case R18Mix: 28 | // return "R18Mix" 29 | default: 30 | return "Unknown" 31 | } 32 | } 33 | 34 | type Request struct { 35 | Apikey string `json:"apikey"` 36 | R18 int `json:"r18"` 37 | Keyword string `json:"keyword"` 38 | Num int `json:"num"` 39 | Proxy string `json:"proxy"` 40 | Size1200 bool `json:"size1200"` 41 | } 42 | 43 | type Setu struct { 44 | Pid int `json:"pid"` 45 | P int `json:"p"` 46 | Uid int `json:"uid"` 47 | Title string `json:"title"` 48 | Author string `json:"author"` 49 | Url string `json:"url"` 50 | R18 bool `json:"r18"` 51 | Width int `json:"width"` 52 | Height int `json:"height"` 53 | Tags []string `json:"tags"` 54 | } 55 | 56 | func (s *Setu) Content() ([]byte, error) { 57 | return utils.ImageGet(s.Url, requests.HeaderOption("referer", "https://www.pixiv.net"), requests.ProxyOption(proxy_pool.PreferOversea)) 58 | } 59 | 60 | type Response struct { 61 | Code int `json:"code"` 62 | Msg string `json:"msg"` 63 | Quota int `json:"quota"` 64 | QuotaMinTTL int `json:"quota_min_ttl"` 65 | Count int `json:"count"` 66 | Data []*Setu `json:"data"` 67 | } 68 | 69 | func LoliconAppSetu(apikey string, R18 R18Type, keyword string, num int) (*Response, error) { 70 | params, err := utils.ToParams(&Request{ 71 | Apikey: apikey, 72 | R18: int(R18), 73 | Keyword: keyword, 74 | Num: num, 75 | Proxy: config.GlobalConfig.GetString("loliconPool.proxy"), 76 | Size1200: true, 77 | }) 78 | if err != nil { 79 | return nil, err 80 | } 81 | apiResp := new(Response) 82 | err = requests.Get(Host, params, apiResp, 83 | requests.RetryOption(3), 84 | requests.TimeoutOption(time.Second*15), 85 | requests.ProxyOption(proxy_pool.PreferOversea), 86 | ) 87 | if err != nil { 88 | return nil, err 89 | } 90 | return apiResp, nil 91 | } 92 | -------------------------------------------------------------------------------- /image_pool/lolicon_pool/error.go: -------------------------------------------------------------------------------- 1 | package lolicon_pool 2 | 3 | import "fmt" 4 | 5 | var ( 6 | ErrNotFound = fmt.Errorf("没有符合条件的图片") 7 | ErrAPIKeyError = fmt.Errorf("APIKEY 不存在或被封禁") 8 | ErrQuotaExceed = fmt.Errorf("达到调用额度限制") 9 | ) 10 | -------------------------------------------------------------------------------- /image_pool/lolicon_pool/persist.go: -------------------------------------------------------------------------------- 1 | package lolicon_pool 2 | 3 | import ( 4 | localdb "github.com/Sora233/DDBOT/lsp/buntdb" 5 | jsoniter "github.com/json-iterator/go" 6 | "github.com/tidwall/buntdb" 7 | ) 8 | 9 | var json = jsoniter.ConfigCompatibleWithStandardLibrary 10 | 11 | func (pool *LoliconPool) load() { 12 | pool.cond.L.Lock() 13 | defer pool.cond.L.Unlock() 14 | 15 | logger.Debug("image pool load cache start") 16 | 17 | for k, v := range pool.cache { 18 | var img []*Setu 19 | err := localdb.RCoverTx(func(tx *buntdb.Tx) error { 20 | key := localdb.LoliconPoolStoreKey(k.String()) 21 | val, err := tx.Get(key) 22 | if err == buntdb.ErrNotFound { 23 | return nil 24 | } else if err != nil { 25 | return err 26 | } 27 | return json.Unmarshal([]byte(val), &img) 28 | }) 29 | if err != nil { 30 | logger.WithField("r18", k.String()). 31 | Errorf("image pool cache load failed %v", err) 32 | continue 33 | } 34 | for _, i := range img { 35 | v.PushBack(i) 36 | } 37 | logger.WithField("r18", k.String()). 38 | WithField("image_count", v.Len()). 39 | Debug("image cache loaded") 40 | } 41 | } 42 | 43 | func (pool *LoliconPool) store() { 44 | pool.cond.L.Lock() 45 | defer pool.cond.L.Unlock() 46 | 47 | if !pool.changed { 48 | return 49 | } 50 | 51 | defer func() { 52 | pool.changed = false 53 | }() 54 | 55 | logger.Debug("image pool store cache start") 56 | 57 | for k, v := range pool.cache { 58 | var img []*Setu 59 | root := v.Front() 60 | if root == nil { 61 | continue 62 | } 63 | for { 64 | img = append(img, root.Value.(*Setu)) 65 | if root == v.Back() { 66 | break 67 | } 68 | root = root.Next() 69 | if root == nil { 70 | break 71 | } 72 | } 73 | err := localdb.RWCoverTx(func(tx *buntdb.Tx) error { 74 | key := localdb.LoliconPoolStoreKey(k.String()) 75 | b, err := json.Marshal(img) 76 | if err != nil { 77 | return err 78 | } 79 | _, _, err = tx.Set(key, string(b), nil) 80 | return err 81 | }) 82 | if err != nil { 83 | logger.WithField("r18", k.String()).Errorf("image pool cache store failed %v", err) 84 | continue 85 | } 86 | logger.WithField("r18", k.String()). 87 | WithField("image_count", v.Len()). 88 | Debug("image pool cache stored") 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /image_pool/model.go: -------------------------------------------------------------------------------- 1 | package image_pool 2 | 3 | import "errors" 4 | 5 | var ErrNotInit = errors.New("not init") 6 | -------------------------------------------------------------------------------- /image_pool/pool.go: -------------------------------------------------------------------------------- 1 | package image_pool 2 | 3 | type Image interface { 4 | Content() ([]byte, error) 5 | } 6 | 7 | type Option map[string]interface{} 8 | 9 | type OptionFunc func(option Option) Option 10 | 11 | func NumOption(num int) OptionFunc { 12 | return func(option Option) Option { 13 | option["num"] = num 14 | return option 15 | } 16 | } 17 | 18 | type Pool interface { 19 | Get(...OptionFunc) ([]Image, error) 20 | } 21 | -------------------------------------------------------------------------------- /internal/test/test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "fmt" 5 | "github.com/Mrs4s/MiraiGo/client" 6 | "github.com/Mrs4s/MiraiGo/message" 7 | localdb "github.com/Sora233/DDBOT/lsp/buntdb" 8 | "github.com/Sora233/DDBOT/lsp/concern_type" 9 | "github.com/Sora233/MiraiGo-Template/bot" 10 | "github.com/stretchr/testify/assert" 11 | "testing" 12 | ) 13 | 14 | // 这个包只允许在单元测试中使用 15 | 16 | const ( 17 | ROOMID1 int64 = 1 18 | ROOMID2 int64 = 2 19 | UID1 int64 = 777 20 | UID2 int64 = 778 21 | UID3 int64 = 779 22 | DynamicID1 int64 = 1001 23 | DynamicID2 int64 = 1002 24 | MessageID1 int32 = 5001 25 | MessageID2 int32 = 5002 26 | G1 int64 = 123456 27 | G2 int64 = 654321 28 | TIMESTAMP1 int64 = 1624126814 29 | TIMESTAMP2 int64 = 1624126914 30 | 31 | NAME1 = "name1" 32 | NAME2 = "name2" 33 | 34 | CMD1 = "command1" 35 | CMD2 = "command2" 36 | 37 | BVID1 = "bvid1" 38 | BVID2 = "bvid2" 39 | 40 | ID1 = 2001 41 | ID2 = 2002 42 | 43 | VersionName = "testVersion" 44 | 45 | Site1 = "site1" 46 | Site2 = "site2" 47 | Site3 = "site3" 48 | 49 | Type1 = "type1" 50 | Type2 = "type2" 51 | Type3 = "type3" 52 | ) 53 | 54 | const ( 55 | BibiliLive concern_type.Type = "bilibiliLive" 56 | BilibiliNews concern_type.Type = "bilibiliNews" 57 | DouyuLive concern_type.Type = "douyuLive" 58 | YoutubeLive concern_type.Type = "youtubeLive" 59 | YoutubeVideo concern_type.Type = "youtubeVideo" 60 | HuyaLive concern_type.Type = "huyaLive" 61 | T1 concern_type.Type = "t1" 62 | T2 concern_type.Type = "t2" 63 | T3 concern_type.Type = "t3" 64 | ) 65 | 66 | var ( 67 | Sender1 = &message.Sender{ 68 | Uin: UID1, 69 | Nickname: NAME1, 70 | } 71 | 72 | Sender2 = &message.Sender{ 73 | Uin: UID2, 74 | Nickname: NAME2, 75 | } 76 | ) 77 | 78 | func InitBuntdb(t *testing.T) { 79 | assert.Nil(t, localdb.InitBuntDB(localdb.MEMORYDB)) 80 | } 81 | func CloseBuntdb(t *testing.T) { 82 | assert.Nil(t, localdb.Close()) 83 | } 84 | 85 | func FakeImage(size int) string { 86 | if size == 0 { 87 | size = 150 88 | } 89 | return fmt.Sprintf("https://via.placeholder.com/%v", size) 90 | } 91 | 92 | func InitMirai() { 93 | bot.Instance = &bot.Bot{ 94 | QQClient: client.NewClient(123456, "fake"), 95 | } 96 | } 97 | 98 | func CloseMirai() { 99 | bot.Instance = nil 100 | } 101 | -------------------------------------------------------------------------------- /lsp/acfun/acfun.go: -------------------------------------------------------------------------------- 1 | package acfun 2 | 3 | import ( 4 | jsoniter "github.com/json-iterator/go" 5 | "strconv" 6 | "strings" 7 | ) 8 | 9 | var json = jsoniter.ConfigCompatibleWithStandardLibrary 10 | 11 | const ( 12 | Site = "acfun" 13 | BaseHost = "https://live.acfun.cn" 14 | ) 15 | 16 | var BasePath = map[string]string{ 17 | PathApiChannelList: BaseHost, 18 | } 19 | 20 | func APath(path string) string { 21 | if strings.HasPrefix(path, "/") { 22 | return BasePath[path] + path 23 | } else { 24 | return BasePath[path] + "/" + path 25 | } 26 | } 27 | 28 | func LiveUrl(uid int64) string { 29 | return "https://live.acfun.cn/live/" + strconv.FormatInt(uid, 10) 30 | } 31 | -------------------------------------------------------------------------------- /lsp/acfun/acfun.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package acfun; 3 | 4 | option go_package = "lsp/acfun"; 5 | 6 | message ApiChannelListResponse { 7 | message ChannelListData { 8 | message LiveListItem { 9 | // bool disableDanmakuShow = 1; 10 | // string requestId = 2; 11 | // string groupId = 3; 12 | // int32 action = 4; 13 | // int32 href = 5; 14 | message User { 15 | int32 action = 1; 16 | string id = 2; 17 | string name = 3; 18 | int64 fanCountValue = 4; 19 | string fanCount = 5; 20 | string headUrl = 6; 21 | } 22 | User user = 6; 23 | repeated string coverUrls = 7; 24 | string title = 8; 25 | int64 createTime = 9; 26 | int64 authorId = 10; 27 | string liveId = 11; 28 | } 29 | int64 result = 1; 30 | string requestId = 2; 31 | repeated LiveListItem liveList = 3; 32 | int32 count = 4; 33 | string pcursor = 5; 34 | int32 totalCount = 6; 35 | } 36 | 37 | ChannelListData channelListData = 1; 38 | } 39 | 40 | message LivePageResponse { 41 | message LiveInfo { 42 | message User { 43 | string headUrl = 1; 44 | int32 fanCountValue = 2; 45 | string name = 3; 46 | } 47 | User user = 1; 48 | } 49 | LiveInfo liveInfo = 1; 50 | } 51 | -------------------------------------------------------------------------------- /lsp/acfun/acfun_test.go: -------------------------------------------------------------------------------- 1 | package acfun 2 | 3 | import ( 4 | "github.com/Sora233/DDBOT/internal/test" 5 | "github.com/stretchr/testify/assert" 6 | "testing" 7 | ) 8 | 9 | func TestAcfun(t *testing.T) { 10 | assert.NotEmpty(t, APath(PathApiChannelList)) 11 | assert.NotEmpty(t, APath("api/channel/list")) 12 | assert.NotEmpty(t, LiveUrl(test.UID1)) 13 | } 14 | -------------------------------------------------------------------------------- /lsp/acfun/apiChannelList.go: -------------------------------------------------------------------------------- 1 | package acfun 2 | 3 | import ( 4 | "github.com/Sora233/DDBOT/proxy_pool" 5 | "github.com/Sora233/DDBOT/requests" 6 | "github.com/Sora233/DDBOT/utils" 7 | "time" 8 | ) 9 | 10 | const ( 11 | PathApiChannelList = "/api/channel/list" 12 | ) 13 | 14 | type ApiChannelListRequest struct { 15 | Count int32 `json:"count"` 16 | PCursor string `json:"pcursor"` 17 | // and maybe filter ... 18 | } 19 | 20 | func ApiChannelList(count int32, pcursor string) (*ApiChannelListResponse, error) { 21 | st := time.Now() 22 | defer func() { 23 | ed := time.Now() 24 | logger.WithField("FuncName", utils.FuncName()).Tracef("cost %v", ed.Sub(st)) 25 | }() 26 | url := APath(PathApiChannelList) 27 | params, err := utils.ToParams(&ApiChannelListRequest{ 28 | Count: count, 29 | PCursor: pcursor, 30 | }) 31 | if err != nil { 32 | return nil, err 33 | } 34 | var opts []requests.Option 35 | opts = append(opts, 36 | requests.ProxyOption(proxy_pool.PreferNone), 37 | requests.AddUAOption(), 38 | requests.TimeoutOption(time.Second*10), 39 | ) 40 | apiChannelListResp := new(ApiChannelListResponse) 41 | err = requests.Get(url, params, &apiChannelListResp, opts...) 42 | if err != nil { 43 | return nil, err 44 | } 45 | return apiChannelListResp, nil 46 | } 47 | -------------------------------------------------------------------------------- /lsp/acfun/apiChannelList_test.go: -------------------------------------------------------------------------------- 1 | package acfun 2 | 3 | import ( 4 | localutils "github.com/Sora233/DDBOT/utils" 5 | "github.com/stretchr/testify/assert" 6 | "testing" 7 | "time" 8 | ) 9 | 10 | func TestApiChannelList(t *testing.T) { 11 | var resp *ApiChannelListResponse 12 | var err error 13 | localutils.Retry(5, time.Second, func() bool { 14 | resp, err = ApiChannelList(100, "") 15 | return err == nil 16 | }) 17 | assert.Nil(t, err) 18 | assert.NotNil(t, resp) 19 | } 20 | -------------------------------------------------------------------------------- /lsp/acfun/config.go: -------------------------------------------------------------------------------- 1 | package acfun 2 | 3 | import "github.com/Sora233/DDBOT/lsp/concern" 4 | 5 | type GroupConcernConfig struct { 6 | concern.IConfig 7 | } 8 | 9 | func NewGroupConcernConfig(g concern.IConfig) *GroupConcernConfig { 10 | return &GroupConcernConfig{g} 11 | } 12 | -------------------------------------------------------------------------------- /lsp/acfun/init.go: -------------------------------------------------------------------------------- 1 | package acfun 2 | 3 | import ( 4 | "github.com/Sora233/DDBOT/lsp/concern" 5 | ) 6 | 7 | func init() { 8 | concern.RegisterConcern(NewConcern(concern.GetNotifyChan())) 9 | } 10 | -------------------------------------------------------------------------------- /lsp/acfun/keyset.go: -------------------------------------------------------------------------------- 1 | package acfun 2 | 3 | import localdb "github.com/Sora233/DDBOT/lsp/buntdb" 4 | 5 | type extraKey struct{} 6 | 7 | func (e *extraKey) UserInfoKey(keys ...interface{}) string { 8 | return localdb.AcfunUserInfoKey(keys...) 9 | } 10 | 11 | func (e *extraKey) LiveInfoKey(keys ...interface{}) string { 12 | return localdb.AcfunLiveInfoKey(keys...) 13 | } 14 | 15 | func (e *extraKey) NotLiveKey(keys ...interface{}) string { 16 | return localdb.AcfunNotLiveKey(keys...) 17 | } 18 | 19 | func (e *extraKey) UidFirstTimestamp(keys ...interface{}) string { 20 | return localdb.AcfunUidFirstTimestampKey(keys...) 21 | } 22 | -------------------------------------------------------------------------------- /lsp/acfun/keyset_test.go: -------------------------------------------------------------------------------- 1 | package acfun 2 | 3 | import "testing" 4 | 5 | func TestExtraKeySet(t *testing.T) { 6 | var e extraKey 7 | e.LiveInfoKey() 8 | e.NotLiveKey() 9 | e.UserInfoKey() 10 | e.UidFirstTimestamp() 11 | } 12 | -------------------------------------------------------------------------------- /lsp/acfun/livePage.go: -------------------------------------------------------------------------------- 1 | package acfun 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "github.com/Sora233/DDBOT/proxy_pool" 7 | "github.com/Sora233/DDBOT/requests" 8 | "github.com/Sora233/DDBOT/utils" 9 | "regexp" 10 | "time" 11 | ) 12 | 13 | var livePageRegex = regexp.MustCompile("