<< Prev | 2025 | Next >>

+-----------------------------------------+
|       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>

参考

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 写

![](assets/tingtalk/20250318_214347_800313.jpg) 

用 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

  1. bytes.Buffer
    1. bytes.Buffer 实现了 io.Writer 接口,它是一个内存中的缓冲区。
    2. &buf 传递的是 bytes.Buffer 的指针,意味着数据会直接写入 buf,而不需要额外的返回
  2. tmpl.Execute()
    1. tmpl.Execute() 会遍历模板中的占位符 {{.Name}},用 data 中的 Name 替换它。
    2. 渲染的结果直接写入 buf,类似于 fmt.Fprint() 的用法。
  3. buf.String()
    1. 最后通过 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 的 值接收者,然后传递副本给调用对象,故需要传递一个指针/引用。

传引用的好处

  1. 灵活:如果想把结果直接写入文件或网络流,还需要额外的步骤将字符串写入。
  2. 内存占用小:如果模板渲染的结果非常大,使用返回值会导致大量内存分配。
  3. 接口复用性:使用 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 折充值

steam deck oled Tomtoc 保护硬壳 ¥88

遥感帽 ¥5

包含圈 ¥2

迪卡侬渔夫帽 ¥60

漱口水 ¥20