首頁  >  文章  >  後端開發  >  是時候離開了嗎?重建的時間到了!製作推特

是時候離開了嗎?重建的時間到了!製作推特

王林
王林原創
2024-08-12 22:34:351047瀏覽

對於厭倦了馬斯克和 Twitter 的用戶來說,新社群網路最關鍵的功能如下;

  • 導入 Twitter 的 archive.zip 檔案
  • 註冊盡可能簡單
  • 相似(如果不相同)的使用者功能

平台較不重要但絕對有用的功能;

  • 道德貨幣化與審核
  • 利用人工智慧來幫助辨識有問題的內容
  • 使用 Onfido 或 SMART 身分服務的藍色勾號

在這篇文章中,我們將專注於第一個功能。導入 Twitter 的 archive.zip 檔案。

該文件

Twitter 並沒有讓你的資料變得那麼容易取得。很高興他們允許您訪問它(從法律上講,他們必須這樣做)。格式太垃圾了

它實際上是一個迷你網路存檔,您的所有資料都保存在 JavaScript 檔案中。它更像是一個網絡應用程序,而不是方便的數據存儲。

當您開啟您的 archive.html 檔案時,您會看到類似這樣的內容;

Time to Leave? Time to Rebuild! Making Twitter

注意:我很早就決定使用 Next.js 建立網站,使用 Go 和 GraphQL 建立後端。

那麼,當您的資料不是結構化資料時該怎麼辦?

好吧,你解析一下。

建立基本的 Go 腳本

前往官方文件了解如何開始使用 Go,並設定您的專案目錄。

我們將一起破解這個過程。這似乎是吸引那些過度依賴 TwitterX 的人的最重要功能之一。

第一步是建立一個 main.go 檔案。在這個文件中,我們將繼續(哈哈)並做一些事情;

  • os.Args:這是一個保存命令列參數的切片。
  • os.Args[0] 是程式的名稱,os.Args[1] 是傳遞給程式的第一個參數。
  • 參數檢查:此函數檢查是否至少提供了一個參數。如果沒有,它會列印一條訊息,詢問路徑。
  • run 函數:該函數目前只是列印傳遞給它的路徑。
package main

import (
    "fmt"
    "os"
)

func run(path string) {
    fmt.Println("Path:", path)
}

func main() {
    if len(os.Args) < 2 {
        fmt.Println("Please provide a path as an argument.")
        return
    }

    path := os.Args[1]
    run(path)
}

在每一步,我們都會像這樣運行檔案;

go run main.go twitter.zip

如果您沒有 Twitter 檔案匯出,請建立簡單的 manifest.js 檔案並為其提供以下 JavaScript。

window.__THAR_CONFIG = {
  "userInfo" : {
    "accountId" : "1234567890",
    "userName" : "lukeocodes",
    "displayName" : "Luke ✨"
  },
};

將其壓縮到我們將在整個過程中使用的 twitter.zip 檔案中。

讀取 Zip 檔案

下一步是讀取 zip 檔案的內容。我們希望盡可能有效率地做到這一點,並減少在磁碟上提取資料的時間。

zip中有很多檔案也不需要解壓縮。

我們將編輯 main.go 檔案;

  • 開啟ZIP檔:zip.OpenReader()函數用來開啟路徑指定的ZIP檔。
  • 遍歷檔案:函數使用 r.File(它是 zip.File 的切片)循環遍歷 ZIP 檔案中的每個檔案。列印每個文件的名稱屬性。
package main

import (
    "archive/zip"
    "fmt"
    "log"
    "os"
)

func run(path string) {
    // Open the zip file
    r, err := zip.OpenReader(path)
    if err != nil {
        log.Fatal(err)
    }
    defer r.Close()

    // Iterate through the files in the zip archive
    fmt.Println("Files in the zip archive:")
    for _, f := range r.File {
        fmt.Println(f.Name)
    }
}

func main() {
    // Example usage
    if len(os.Args) < 2 {
        log.Fatal("Please provide the path to the zip file as an argument.")
    }

    path:= os.Args[1]
    run(path)
}

僅限JS!我們正在尋找結構化數據

這個存檔文件非常沒有幫助。我們只想檢查 /data 目錄中的 .js 檔案。

  • 開啟 ZIP 檔案:使用 zip.OpenReader() 開啟 ZIP 檔案。
  • 檢查 /data 目錄:程式循環存取 ZIP 檔案中的檔案。它使用 strings.HasPrefix(f.Name, "data/") 來檢查檔案是否位於 /data 目錄中。
  • 尋找 .js 檔案:程式也會使用 filepath.Ext(f.Name) 檢查檔案是否有 .js 副檔名。
  • 讀取和列印內容:如果在 /data 目錄中找到 .js 文件,程式將讀取並列印其內容。
package main

import (
    "archive/zip"
    "fmt"
    "io/ioutil"
    "log"
    "os"
    "path/filepath"
    "strings"
)

func readFile(file *zip.File) {
    // Open the file inside the zip
    rc, err := file.Open()
    if err != nil {
        log.Fatal(err)
    }
    defer rc.Close()

    // Read the contents of the file
    contents, err := ioutil.ReadAll(rc) // deprecated? :/ 
    if err != nil {
        log.Fatal(err)
    }

    // Print the contents
    fmt.Printf("Contents of %s:\n", file.Name)
    fmt.Println(string(contents))
}

func run(path string) {
    // Open the zip file
    r, err := zip.OpenReader(path)
    if err != nil {
        log.Fatal(err)
    }
    defer r.Close()

    // Iterate through the files in the zip archive
    fmt.Println("JavaScript files in the zip archive:")
    for _, f := range r.File {
        // Use filepath.Ext to check the file extension
        if strings.HasPrefix(f.Name, "data/") && strings.ToLower(filepath.Ext(f.Name)) == ".js" {
            readFile(f)
            return // Exit after processing the first .js file so we don't end up printing a gazillion lines when testing
        }
    }
}

func main() {
    // Example usage
    if len(os.Args) < 2 {
        log.Fatal("Please provide the path to the zip file as an argument.")
    }

    path:= os.Args[1]
    run(path)
}

解析JS!我們想要這些數據

我們找到了結構化資料。現在我們需要解析它。好消息是,已經有在 Go 中使用 JavaScript 的現有套件。我們將使用 goja。

如果您正在閱讀本節,熟悉 Goja,並且已經看過該文件的輸出,您可能會發現我們將來會出現錯誤。

安裝goja:

go get github.com/dop251/goja

現在我們將編輯 main.go 檔案以執行以下操作;

  • 用goja解析:goja.New()函數建立一個新的JavaScript執行時,vm.RunString(processedContents)在該執行階段執行處理後的JavaScript程式碼。
  • 處理解析錯誤
package main

import (
    "archive/zip"
    "fmt"
    "io/ioutil"
    "log"
    "os"
    "path/filepath"
    "strings"
)

func readFile(file *zip.File) {
    // Open the file inside the zip
    rc, err := file.Open()
    if err != nil {
        log.Fatal(err)
    }
    defer rc.Close()

    // Read the contents of the file
    contents, err := ioutil.ReadAll(rc) // deprecated? :/ 
    if err != nil {
        log.Fatal(err)
    }

    // Parse the JavaScript file using goja
    vm := goja.New()
    _, err = vm.RunString(contents)
    if err != nil {
        log.Fatalf("Error parsing JS file: %v", err)
    }

    fmt.Printf("Parsed JavaScript file: %s\n", file.Name)
}

func run(path string) {
    // Open the zip file
    r, err := zip.OpenReader(path)
    if err != nil {
        log.Fatal(err)
    }
    defer r.Close()

    // Iterate through the files in the zip archive
    fmt.Println("JavaScript files in the zip archive:")
    for _, f := range r.File {
        // Use filepath.Ext to check the file extension
        if strings.HasPrefix(f.Name, "data/") && strings.ToLower(filepath.Ext(f.Name)) == ".js" {
            readFile(f)
            return // Exit after processing the first .js file so we don't end up printing a gazillion lines when testing
        }
    }
}

func main() {
    // Example usage
    if len(os.Args) < 2 {
        log.Fatal("Please provide the path to the zip file as an argument.")
    }

    path:= os.Args[1]
    run(path)
}

驚喜。視窗未定義可能是熟悉的錯誤。基本上 goja 運行 EMCA 運行時。視窗是瀏覽器上下文,遺憾的是不可用。

實際解析 JS

此時我遇到了一些問題。包括無法返回數據,因為它是頂級 JS 文件。

長話短說,我們需要在將檔案載入到執行階段之前修改它們的內容。

我們來修改main.go檔;

  • reConfig: A regex that matches any assignment of the form window.someVariable = { and replaces it with var data = {.
  • reArray: A regex that matches any assignment of the form window.someObject.someArray = [ and replaces it with var data = [
  • Extracting data: Running the script, we use vm.Get("data") to retrieve the value of the data variable from the JavaScript context.
package main

import (
    "archive/zip"
    "fmt"
    "io/ioutil"
    "log"
    "os"
    "path/filepath"
    "regexp"
    "strings"

    "github.com/dop251/goja"
)

func readFile(file *zip.File) {
    // Open the file inside the zip
    rc, err := file.Open()
    if err != nil {
        log.Fatal(err)
    }
    defer rc.Close()

    // Read the contents of the file
    contents, err := ioutil.ReadAll(rc)
    if err != nil {
        log.Fatal(err)
    }

    // Regular expressions to replace specific patterns
    reConfig := regexp.MustCompile(`window\.\w+\s*=\s*{`)
    reArray := regexp.MustCompile(`window\.\w+\.\w+\.\w+\s*=\s*\[`)

    // Replace patterns in the content
    processedContents := reConfig.ReplaceAllStringFunc(string(contents), func(s string) string {
        return "var data = {"
    })
    processedContents = reArray.ReplaceAllStringFunc(processedContents, func(s string) string {
        return "var data = ["
    })

    // Parse the JavaScript file using goja
    vm := goja.New()
    _, err = vm.RunString(processedContents)
    if err != nil {
        log.Fatalf("Error parsing JS file: %v", err)
    }

    // Retrieve the value of the 'data' variable from the JavaScript context
    value := vm.Get("data")
    if value == nil {
        log.Fatalf("No data variable found in the JS file")
    }

    // Output the parsed data
    fmt.Printf("Processed JavaScript file: %s\n", file.Name)
    fmt.Printf("Data extracted: %v\n", value.Export())
}

func run(path string) {
    // Open the zip file
    r, err := zip.OpenReader(path)
    if err != nil {
        log.Fatal(err)
    }
    defer r.Close()

    // Iterate through the files in the zip archive
    for _, f := range r.File {
        // Check if the file is in the /data directory and has a .js extension
        if strings.HasPrefix(f.Name, "data/") && strings.ToLower(filepath.Ext(f.Name)) == ".js" {
            readFile(f)
            return // Exit after processing the first .js file so we don't end up printing a gazillion lines when testing
        }
    }
}

func main() {
    // Example usage
    if len(os.Args) < 2 {
        log.Fatal("Please provide the path to the zip file as an argument.")
    }

    path:= os.Args[1]
    run(path)
}

Hurrah. Assuming I didn't muck up the copypaste into this post, you should now see a rather ugly print of the struct data from Go.

JSON would be nice

Edit the main.go file to marshall the JSON output.

  • Use value.Export() to get the data from the struct
  • Use json.MarshallIndent() for pretty printed JSON (use json.Marshall if you want to minify the output).
package main

import (
    "archive/zip"
    "encoding/json"
    "fmt"
    "io/ioutil"
    "log"
    "os"
    "path/filepath"
    "regexp"
    "strings"

    "github.com/dop251/goja"
)

func readFile(file *zip.File) {
    // Open the file inside the zip
    rc, err := file.Open()
    if err != nil {
        log.Fatal(err)
    }
    defer rc.Close()

    // Read the contents of the file
    contents, err := ioutil.ReadAll(rc) // deprecated :/
    if err != nil {
        log.Fatal(err)
    }

    // Regular expressions to replace specific patterns
    reConfig := regexp.MustCompile(`window\.\w+\s*=\s*{`)
    reArray := regexp.MustCompile(`window\.\w+\.\w+\.\w+\s*=\s*\[`)

    // Replace patterns in the content
    processedContents := reConfig.ReplaceAllStringFunc(string(contents), func(s string) string {
        return "var data = {"
    })
    processedContents = reArray.ReplaceAllStringFunc(processedContents, func(s string) string {
        return "var data = ["
    })

    // Parse the JavaScript file using goja
    vm := goja.New()
    _, err = vm.RunString(processedContents)
    if err != nil {
        log.Fatalf("Error parsing JS file: %v", err)
    }

    // Retrieve the value of the 'data' variable from the JavaScript context
    value := vm.Get("data")
    if value == nil {
        log.Fatalf("No data variable found in the JS file")
    }

    // Convert the data to a Go-native type
    data := value.Export()

    // Marshal the Go-native type to JSON
    jsonData, err := json.MarshalIndent(data, "", "  ")
    if err != nil {
        log.Fatalf("Error marshalling data to JSON: %v", err)
    }

    // Output the JSON data
    fmt.Println(string(jsonData))
}

func run(zipFilePath string) {
    // Open the zip file
    r, err := zip.OpenReader(zipFilePath)
    if err != nil {
        log.Fatal(err)
    }
    defer r.Close()

    // Iterate through the files in the zip archive
    for _, f := range r.File {
        // Check if the file is in the /data directory and has a .js extension
        if strings.HasPrefix(f.Name, "data/") && strings.ToLower(filepath.Ext(f.Name)) == ".js" {
            readFile(f)
            return // Exit after processing the first .js file
        }
    }
}

func main() {
    // Example usage
    if len(os.Args) < 2 {
        log.Fatal("Please provide the path to the zip file as an argument.")
    }

    zipFilePath := os.Args[1]
    run(zipFilePath)
}

That's it!

go run main.go twitter.zip
}
  "userInfo": {
    "accountId": "1234567890",
    "displayName": "Luke ✨",
    "userName": "lukeocodes"
  }
}

Open source

I'll be open sourcing a lot of this work so that others who want to parse the data from the archive, can store it how they like.

以上是是時候離開了嗎?重建的時間到了!製作推特的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn