-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
21 changed files
with
1,632 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -20,3 +20,4 @@ | |
# Go workspace file | ||
go.work | ||
go.work.sum | ||
cookies.json |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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的项目结构。 |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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." |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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{} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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`, ¤tURL).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) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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") | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} |
Oops, something went wrong.