Skip to content

Commit

Permalink
version 1.0
Browse files Browse the repository at this point in the history
  • Loading branch information
b3nguang committed Jul 2, 2024
1 parent f832b9e commit 200a1da
Show file tree
Hide file tree
Showing 21 changed files with 1,632 additions and 0 deletions.
Binary file added .DS_Store
Binary file not shown.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,4 @@
# Go workspace file
go.work
go.work.sum
cookies.json
46 changes: 46 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,48 @@
# DingTalkLiveDownload
DingTalkLiveDownload——钉钉直播回放下载器

这个工具旨在帮助用户方便地下载钉钉上的回放视频,便于离线观看和保存重要会议或课程内容。

## 特性

- 快速下载钉钉回放视频
- 用户友好的命令行界面
- 支持批量下载

## 前提条件

在使用本工具前,请确保你的系统中已安装以下软件:

- Go
- ffmpeg
- Google Chrome

## 安装

首先,克隆本项目到本地:

```bash
git clone https://github.com/b3nguang/DingTalkLiveDownload.git
cd DingTalkLiveDownload
./build.sh
```

## 使用方式

<img src="assets/image-20240702154446402.png" alt="image-20240702154446402" style="zoom:33%;" />

获取钉钉分享回放链接-复制链接-打开命令行窗口-执行以下命令

```bash
./DingTalkLiveDownload_darwin_arm64 -u "https://n.dingtalk.com/dingding/live-room/index.html?roomId=xxx&liveUuid=xxx"
```

## 免责声明

本工具仅供学习和研究目的,请勿用于非法用途。用户因使用本工具产生的一切后果,本项目开发者不承担任何责任。

## 参考

https://github.com/NAXG/GoDingtalk/

本工具代码是参考GoDingtalk重构的,参考了fscan的项目结构。
Binary file added assets/image-20240702154446402.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
16 changes: 16 additions & 0 deletions build.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#!/bin/bash

# 项目名称
PROJECT_NAME="DingTalkLiveDownload"



# 编译 Windows 版本
echo "Building for Windows..."
GOOS=windows GOARCH=amd64 go build -ldflags "-w -s" -o ${PROJECT_NAME}_windows_amd64.exe

# 编译 macOS 版本 (针对 ARM 架构)
echo "Building for macOS (ARM)..."
GOOS=darwin GOARCH=arm64 go build -ldflags "-w -s" -o ${PROJECT_NAME}_darwin_arm64

echo "Build completed."
26 changes: 26 additions & 0 deletions common/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package common

var author = "b3nguang"

var (
Host = "lv.dingtalk.com"
UserAgent = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36"
Dnt = "1"
SecChUa = `"Chromium";v="122", "Not(A:Brand";v="24", "Google Chrome";v="122"`
SecChUaMobile = "?0"
SecChUaPlatform = "macOS"
UpgradeInsecureRequests = "1"
Accept = "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7"
SecFetchSite = "none"
SecFetchMode = "navigate"
SecFetchUser = "?1"
SecFetchDest = "document"
AcceptLanguage = "zh-CN,zh;q=0.9"
)

type Info struct {
URL string
UrlFile string
}

var URLs = []string{}
18 changes: 18 additions & 0 deletions common/flag.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package common

import (
"flag"
)

func Banner() {
banner := `DingTalkLiveDownLoad author: ` + author + `
`
print(banner)
}
func Flag(Info *Info) {
Banner()
flag.StringVar(&Info.URL, "u", "", "需要下载的回放URL")
flag.StringVar(&Info.UrlFile, "f", "", "包含需要下载的回放URL的文件路径")
flag.Parse()
}
65 changes: 65 additions & 0 deletions common/parse.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package common

import (
"bufio"
"flag"
"fmt"
"os"
"strings"
)

func Readfile(filename string) ([]string, error) {
file, err := os.Open(filename)
if err != nil {
fmt.Printf("Open %s error, %v\n", filename, err)
os.Exit(0)
}
defer file.Close()
var content []string
scanner := bufio.NewScanner(file)
scanner.Split(bufio.ScanLines)
for scanner.Scan() {
text := strings.TrimSpace(scanner.Text())
if text != "" {
content = append(content, scanner.Text())
}
}
return content, nil
}

func Parse(Info *Info) {
//var UrlList []string

if Info.URL == "" && Info.UrlFile == "" {
fmt.Println("未提供 URL 或 URL 文件路径,退出...")
flag.Usage()
os.Exit(0)
}

if Info.URL != "" {
urls := strings.Split(Info.URL, ",")
TmpUrls := make(map[string]struct{})
for _, url := range urls {
if _, ok := TmpUrls[url]; !ok {
TmpUrls[url] = struct{}{}
if url != "" {
URLs = append(URLs, url)
}
}
}
}
if Info.UrlFile != "" {
urls, err := Readfile(Info.UrlFile)
if err == nil {
TmpUrls := make(map[string]struct{})
for _, url := range urls {
if _, ok := TmpUrls[url]; !ok {
TmpUrls[url] = struct{}{}
if url != "" {
URLs = append(URLs, url)
}
}
}
}
}
}
73 changes: 73 additions & 0 deletions download_plugin/chrome.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package download_plugin

import (
"context"
"encoding/json"
"fmt"
"github.com/chromedp/cdproto/network"
"github.com/chromedp/chromedp"
"os"
"strings"
"time"
)

func startChrome() {
fmt.Println("正在启动Chrome获取Cookies...")
opts := append(
// select all the elements after the third element
chromedp.DefaultExecAllocatorOptions[3:],
chromedp.NoFirstRun,
chromedp.NoDefaultBrowserCheck,
)

var siteCookies []*network.Cookie
parentCtx, cancel := chromedp.NewExecAllocator(context.Background(), opts...)
defer cancel()

ctx, cancel := chromedp.NewContext(parentCtx)
defer cancel()

// 设置超时时间,确保扫码后能够及时跳转
ctx, cancel = context.WithTimeout(ctx, 20*time.Minute)
defer cancel()

// 访问钉钉登录页面
H5url := "https://h5.dingtalk.com"
Lurl := "https://login.dingtalk.com/oauth2/challenge.htm?client_id=dingavo6at488jbofmjs&response_type=code&scope=openid&redirect_uri=https%3A%2F%2Flv.dingtalk.com%2Fsso%2Flogin%3Fcontinue%3Dhttps%253A%252F%252Fh5.dingtalk.com%252Fgroup-live-share%252Findex.htm%253Ftype%253D2%2523%252F"
chromedp.Run(ctx,
network.Enable(), // 启用网络事件
chromedp.Navigate(Lurl),
chromedp.WaitVisible(`body`, chromedp.ByQuery),
chromedp.ActionFunc(func(ctx context.Context) error {
var currentURL string
for {
if err := chromedp.Evaluate(`window.location.href`, &currentURL).Do(ctx); err != nil {
return err
}

if strings.Contains(currentURL, H5url) {
break
}
time.Sleep(2 * time.Second)
}
return nil
}),
chromedp.ActionFunc(func(ctx context.Context) error {
// 到达此处,说明已经跳转到了指定的URL
siteCookies, _ = network.GetCookies().Do(ctx)
//for _, cookie := range siteCookies {
// fmt.Printf("Cookie: %s=%s\n", cookie.Name, cookie.Value)
//}
fmt.Println("成功获取Cookie...")
return nil
}),
)

// 保存cookies到文件
cookies := make(map[string]string)
for _, cookie := range siteCookies {
cookies[cookie.Name] = cookie.Value
}
jsonCookies, _ := json.Marshal(cookies)
os.WriteFile("cookies.json", jsonCookies, 0644)
}
18 changes: 18 additions & 0 deletions download_plugin/ffmpeg.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package download_plugin

import (
"fmt"
"os"
"os/exec"
)

func ffmpeg(ts string) {
fmt.Println("正在转换ts为mp4...")
cmd := exec.Command("ffmpeg", "-i", "video/"+ts+".ts", "-c:v", "copy", "-c:a", "copy", "-f", "mp4", "-y", ts+".mp4")
err := cmd.Run()
if err != nil {
return
}
fmt.Println(ts + ".mp4 转换完成\n")
os.Remove("video/" + ts + ".ts")
}
70 changes: 70 additions & 0 deletions download_plugin/info.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package download_plugin

import (
"DingTalkLiveDownload/common"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"os"
"strings"
)

func getLiveRoomPublicInfo(roomId, liveUuid string, Thread int) {
// 构造URL
urlStr := "https://lv.dingtalk.com/getOpenLiveInfo?roomId=" + roomId + "&liveUuid=" + liveUuid
urlObj, _ := url.Parse(urlStr)

// 创建请求
req, _ := http.NewRequest("GET", urlObj.String(), nil)

// 读取Cookies.json文件
jsonCookies, _ := os.ReadFile("cookies.json")
var cookies map[string]string
_ = json.Unmarshal(jsonCookies, &cookies)

// 添加Cookies到请求
var cookieStr strings.Builder
for name, value := range cookies {
cookieStr.WriteString(fmt.Sprintf("%s=%s; ", name, value))
}
cookieHeader := cookieStr.String()
CookiepcSession := cookies["LV_PC_SESSION"]
// 设置请求头
req.Header.Set("Host", common.Host)
req.Header.Set("Cookie", cookieHeader)
req.Header.Set("Cookie", "PC_SESSION="+CookiepcSession)
req.Header.Set("Sec-Ch-Ua", common.SecChUa)
req.Header.Set("Sec-Ch-Ua-Mobile", common.SecChUaMobile)
req.Header.Set("Sec-Ch-Ua-Platform", common.SecChUaPlatform)
req.Header.Set("Dnt", common.Dnt)
req.Header.Set("Upgrade-Insecure-Requests", common.UpgradeInsecureRequests)
req.Header.Set("User-Agent", common.UserAgent)
req.Header.Set("Accept", common.Accept)
req.Header.Set("Sec-Fetch-Site", common.SecFetchSite)
req.Header.Set("Sec-Fetch-Mode", common.SecFetchMode)
req.Header.Set("Sec-Fetch-User", common.SecFetchUser)
req.Header.Set("Sec-Fetch-Dest", common.SecFetchDest)
req.Header.Set("Accept-Language", common.AcceptLanguage)

// 发送请求
client := &http.Client{}
resp, _ := client.Do(req)

// 关闭响应
defer resp.Body.Close()

// 读取响应内容
body, _ := io.ReadAll(resp.Body)

var result map[string]interface{}
json.Unmarshal(body, &result)

title := result["openLiveDetailModel"].(map[string]interface{})["title"].(string)
playbackUrl := result["openLiveDetailModel"].(map[string]interface{})["playbackUrl"].(string)

fmt.Println("标题:", title)
fmt.Println("请求网址:", playbackUrl)
M3u8Down(title, playbackUrl, Thread)
}
14 changes: 14 additions & 0 deletions download_plugin/m3u8down.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package download_plugin

import "DingTalkLiveDownload/m3u8_plugin"

func M3u8Down(title, playbackUrl string, Thread int) {
m3u8 := m3u8_plugin.NewDownloader()
m3u8.SetUrl(playbackUrl)
m3u8.SetMovieName(title)
m3u8.SetNumOfThread(Thread)
m3u8.SetIfShowTheBar(true)
if m3u8.DefaultDownload() {
ffmpeg(title)
}
}
27 changes: 27 additions & 0 deletions download_plugin/process.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package download_plugin

import (
"fmt"
"net/url"
)

func process(target_url string) {
// 解析 URL
parsedURL, err := url.Parse(target_url)
if err != nil {
fmt.Println("解析 URL 时出错:", err)
return
}
// 提取查询参数中的 roomId 和 liveUuid
queryParams := parsedURL.Query()
roomId := queryParams.Get("roomId")
liveUuid := queryParams.Get("liveUuid")
if roomId == "" || liveUuid == "" {
fmt.Println("URL 中缺少 roomId 或 liveUuid 参数,退出...")
return
}

// 调用函数
// 假设你有一个处理这些信息的函数
getLiveRoomPublicInfo(roomId, liveUuid, 10)
}
Loading

0 comments on commit 200a1da

Please sign in to comment.