+-----------------------------------------+
| Your current life progress is |
|-----------------------------------------+
| Days : 8628 |
| Weeks : 1232 |
| Months: 283 |
| Age : 23 years and 7 months |
+-----------------------------------------+
这周发生了啥?
telegram-message-sync-bot
获得附件下载地址
https://api.telegram.org/file/bot<BOT_TOKEN>/<FilePath>
参考
- https://core.telegram.org/bots/api#getfile
- https://stackoverflow.com/questions/31096358/how-do-i-download-a-file-or-photo-that-was-sent-to-my-telegram-bot
b.Token 访问作用域
downloadURL := fmt.Sprintf("https://api.telegram.org/file/bot%s/%s", b.Token, file.FilePath)
报错
[Bot] 2025/03/18 21:13:47 main.go:59: 下载文件失败: %v parse "https://api.telegram.org/file/bot%!s(func() string=0x810960)/photos/file_8.jpg": invalid URL escape "%!s"
表达式 | 作用 | 使用场景 |
---|---|---|
b.Token | 直接访问结构体字段 | 数据简单且无需额外处理 |
b.Token() | 调用方法获取数据 | 需要处理、验证或转换数据 |
需要更改为
downloadURL := fmt.Sprintf("https://api.telegram.org/file/bot%s/%s", b.Token(), file.FilePath)
防止文件重名
实际执行仍然有同名覆盖的问题
[Bot] 2025/03/18 21:38:44 main.go:45: 下载URL: https://api.telegram.org/file/bot5883330296:AAFCI0jtgH1iHU1zDaL2p3Hj5Z6fkqaRWaU/photos/file_8.jpg
文件下载成功: /home/bgzo/workspaces/telegram-message-sync/archives/assets/tingtalk/20250318_213844.jpg
[Bot] 2025/03/18 21:38:44 main.go:193: 下载文件成功 []
[Bot] 2025/03/18 21:38:45 main.go:45: 下载URL: https://api.telegram.org/file/bot5883330296:AAFCI0jtgH1iHU1zDaL2p3Hj5Z6fkqaRWaU/photos/file_9.jpg
文件下载成功: /home/bgzo/workspaces/telegram-message-sync/archives/assets/tingtalk/20250318_213845.jpg
[Bot] 2025/03/18 21:38:45 main.go:193: 下载文件成功 [assets/tingtalk/20250318_213844.jpg]
[Bot] 2025/03/18 21:38:45 main.go:45: 下载URL: https://api.telegram.org/file/bot5883330296:AAFCI0jtgH1iHU1zDaL2p3Hj5Z6fkqaRWaU/photos/file_10.jpg
文件下载成功: /home/bgzo/workspaces/telegram-message-sync/archives/assets/tingtalk/20250318_213845.jpg
[Bot] 2025/03/18 21:38:46 main.go:193: 下载文件成功 [assets/tingtalk/20250318_213844.jpg assets/tingtalk/20250318_213845.jpg]
[Bot] 2025/03/18 21:38:46 main.go:45: 下载URL: https://api.telegram.org/file/bot5883330296:AAFCI0jtgH1iHU1zDaL2p3Hj5Z6fkqaRWaU/photos/file_11.jpg
文件下载成功: /home/bgzo/workspaces/telegram-message-sync/archives/assets/tingtalk/20250318_213846.jpg
获取纳秒级的时间戳
timestamp := time.Now().Format("20060102_150405") + fmt.Sprintf("_%d", time.Now().UnixNano()%1e6)
savePath := filepath.Join(assetsDir, fmt.Sprintf("%s%s", timestamp, ext))
UUID
import "github.com/google/uuid"
filename := fmt.Sprintf("%s%s", uuid.New().String(), ext)
savePath := filepath.Join(assetsDir, filename)
判断文件是否存在,自动递增文件名
counter := 1
originalSavePath := savePath
for {
if _, err := os.Stat(savePath); os.IsNotExist(err) {
break
}
savePath = fmt.Sprintf("%s_%d%s", originalSavePath[:len(originalSavePath)-len(ext)], counter, ext)
counter++
}
update.Message.Photo 一次性会返回多个图片,如何获得原图?
在 Telegram Bot API 中,update.Message.Photo 是一个 PhotoSize 数组,按分辨率从低到高排序。
- 缩略图(thumbnail)
- 小尺寸图
- 中等尺寸图
- 原图(Full-size)
直接取最后一张就行
photoSizes := update.Message.Photo
if len(photoSizes) > 0 {
highestResolutionPhoto := photoSizes[len(photoSizes)-1]
fmt.Printf("File ID: %s, Width: %d, Height: %d\n", highestResolutionPhoto.FileID, highestResolutionPhoto.Width, highestResolutionPhoto.Height)
}
文件无法预览
我的目录如下:
~/workspaces/telegram-message-sync > tree
。
├── archives
│ ├── assets
│ │ ├── tingtalk
│ │ │ ├── 20250318_214347_800313.jpg
│ │ │ ├── 20250318_214347_821576.jpg
│ │ │ ├── 20250318_214348_617869.jpg
│ │ │ └── 20250318_214350_82498.jpg
│ ├── tingtalk.md
为什么我在 tingtalk.md 写

用 IDEA 无法预览?
消息去重
目前采用最浪费性能的方式,直接搜索文件是否存在消息 link,如果存在就是重复消息
package main
import (
"bufio"
"fmt"
"os"
"strings"
)
// searchInFile 读取文件并搜索目标字符串
func searchInFile(filePath, searchString string) bool {
// 尝试打开文件
file, err := os.Open(filePath)
if err != nil {
if os.IsNotExist(err) {
fmt.Println("文件不存在")
return false
}
fmt.Printf("打开文件失败: %v\n", err)
return false
}
defer file.Close()
// 使用 bufio 逐行读取文件
scanner := bufio.NewScanner(file)
for scanner.Scan() {
if strings.Contains(scanner.Text(), searchString) {
fmt.Println("匹配成功")
return true
}
}
// 检查读取文件时的错误
if err := scanner.Err(); err != nil {
fmt.Printf("读取文件时出错: %v\n", err)
return false
}
fmt.Println("未找到匹配内容")
return false
}
func main() {
filePath := "example.txt" // 替换为你的文件路径
searchString := "目标字符串" // 替换为你要查找的字符串
result := searchInFile(filePath, searchString)
fmt.Printf("结果代码: %d\n", result)
}
自定义模板: text/template
模板内容
你好,{{.Name}}!
今天是 {{.Date}}。
欢迎使用 {{.AppName}}。
package main
import (
"fmt"
"os"
"text/template"
"time"
)
func main() {
// 读取模板文件
tmplData, err := os.ReadFile("template.txt")
if err != nil {
fmt.Printf("读取模板失败: %v\n", err)
return
}
// 定义模板变量
data := map[string]interface{}{
"Name": "张三",
"Date": time.Now().Format("2006-01-02"),
"AppName": "Go模板引擎",
}
// 创建并解析模板
tmpl, err := template.New("example").Parse(string(tmplData))
if err != nil {
fmt.Printf("解析模板失败: %v\n", err)
return
}
// 使用 bytes.Buffer 捕获渲染结果
var buf bytes.Buffer
err = tmpl.Execute(&buf, data)
if err != nil {
fmt.Printf("渲染模板失败: %v\n", err)
return
}
// 将结果赋值给变量
renderedString := buf.String()
fmt.Println("渲染结果:")
fmt.Println(renderedString)
}
https://pkg.go.dev/text/template
如何理解
tmpl.Execute(&buf, data) 会将结果写入 buf
- bytes.Buffer
- bytes.Buffer 实现了 io.Writer 接口,它是一个内存中的缓冲区。
- &buf 传递的是 bytes.Buffer 的指针,意味着数据会直接写入 buf,而不需要额外的返回
- tmpl.Execute()
- tmpl.Execute() 会遍历模板中的占位符 {{.Name}},用 data 中的 Name 替换它。
- 渲染的结果直接写入 buf,类似于 fmt.Fprint() 的用法。
- buf.String()
- 最后通过 buf.String() 将内存中的结果转换为字符串。
// Writer is the interface that wraps the basic Write method.
//
// Write writes len(p) bytes from p to the underlying data stream.
// It returns the number of bytes written from p (0 <= n <= len(p))
// and any error encountered that caused the write to stop early.
// Write must return a non-nil error if it returns n < len(p).
// Write must not modify the slice data, even temporarily.
//
// Implementations must not retain p.
type Writer interface {
Write(p []byte) (n int, err error)
}
这个接口的 Buffer 实现签名为
// Write appends the contents of p to the buffer, growing the buffer as
// needed. The return value n is the length of p; err is always nil. If the
// buffer becomes too large, Write will panic with [ErrTooLarge].
func (b *Buffer) Write(p []byte) (n int, err error) {
b.lastRead = opInvalid
m, ok := b.tryGrowByReslice(len(p))
if !ok {
m = b.grow(len(p))
}
return copy(b.buf[m:], p), nil
}
要求是传递一个 buffer 的 值接收者,然后传递副本给调用对象,故需要传递一个指针/引用。
传引用的好处
- 灵活:如果想把结果直接写入文件或网络流,还需要额外的步骤将字符串写入。
- 内存占用小:如果模板渲染的结果非常大,使用返回值会导致大量内存分配。
- 接口复用性:使用 io.Writer 的方式,可以直接将结果写入 os.Stdout、HTTP 响应或者文件中,无需额外处理。
所以问题变成了,为什么一定要使用
保留两个时间
已经实现,故不再赘述
golang 分包
myapp/
├── cmd/ // 程序入口
│ ├── main.go
├── config/ // 配置管理
│ ├── config.go
├── internal/ // 业务逻辑和核心代码
│ ├── entity/ // 数据模型 (Entity)
│ │ ├── user.go
│ ├── repository/ // 数据访问层 (Repository)
│ │ ├── user_repository.go
│ ├── service/ // 业务逻辑层 (Service)
│ │ ├── user_service.go
│ ├── handler/ // 控制器层 (Handler)
│ │ ├── user_handler.go
│ ├── router/ // 路由 (Router)
│ │ ├── router.go
├── pkg/ // 公共工具、库
│ ├── logger/
│ │ ├── logger.go
└── go.mod
https://go.dev/doc/modules/layout
生成随机数—— 时间戳
人类一思考,上帝就发笑
我对比了我高中,大学和最近一两年写的日记后发现,自从我开始工作之后,我就越来越少写些新的东西了,
内耗的都是自己罢了
会对前女友的微信视奸,会频繁的内耗,即使删除微信,也甚至会病态地记忆助对方的手机号从而重复这一行为。
希望以后每天睡个好觉,希望每天能都能看看微信读书,遇到有想法的句子,直接发到 Telegram Channel,而不是微信个签。
对女性的猜测是不靠谱的,从来没有准过
我同样会感受到很冒犯
这周有断舍离吗?
把手机的抖音卸载了,装在了 Mac 上
这周有什么多快好省的东西吗?
部落冲突月卡 ¥27
用 AppStore 9 折充值