├── .gitignore ├── Dockerfile ├── example.config.json ├── go.mod ├── .github └── workflows │ └── build.yml ├── README.md ├── LICENSE ├── .goreleaser.yaml ├── degen └── airdrop2.go ├── warpcast ├── auth.go ├── profile.go └── timeline.go └── main.go /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | dist/ 3 | config.json 4 | go.sum 5 | .idea/ -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM scratch 2 | COPY warpcast-tools /usr/bin/warpcast-tools 3 | ENTRYPOINT ["/usr/bin/warpcast-tools"] -------------------------------------------------------------------------------- /example.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "accounts": [], 3 | "delayFollow": 1000, 4 | "delayUnfollow": 800, 5 | "delayLike": 500, 6 | "delayComment": 700, 7 | "delayRecast": 800, 8 | "delayTimeline": 3000, 9 | "customCommentText": [], 10 | "ignoreUsers": [] 11 | } -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/tokinaa/warpcast-tools 2 | 3 | go 1.21.5 4 | 5 | require github.com/AlecAivazis/survey/v2 v2.3.7 6 | 7 | require ( 8 | github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect 9 | github.com/mattn/go-colorable v0.1.13 // indirect 10 | github.com/mattn/go-isatty v0.0.20 // indirect 11 | github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect 12 | golang.org/x/sys v0.17.0 // indirect 13 | golang.org/x/term v0.17.0 // indirect 14 | golang.org/x/text v0.14.0 // indirect 15 | ) 16 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | on: 4 | push: 5 | branches: 6 | - 'main' 7 | tags: 8 | - 'v*' 9 | pull_request: 10 | 11 | jobs: 12 | build: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v2 16 | with: 17 | fetch-depth: 0 18 | - uses: actions/setup-go@v2 19 | with: 20 | go-version: 1.21.5 21 | cache: true 22 | - run: go mod tidy 23 | - uses: docker/login-action@v1 24 | with: 25 | registry: ghcr.io 26 | username: ${{ github.repository_owner }} 27 | password: ${{ secrets.GH_PAT }} 28 | - uses: goreleaser/goreleaser-action@v5.0.0 29 | with: 30 | version: latest 31 | args: release --rm-dist 32 | env: 33 | GITHUB_TOKEN: ${{ secrets.GH_PAT }} 34 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Warpcast Tools 2 | This is a collection of tools for the Warpcast project. The tools are written in Go and are designed to be run from the command line. 3 | 4 | # Fiture 5 | - Account Manager 6 | - Follow Followers / Following Target 7 | - Unfollow Followers / Following Target 8 | - Auto Like, Comment, Recast Timeline (Home/All-Channels) 9 | 10 | # How to Install 11 | 1. Install Go 12 | 2. Clone this repository 13 | 3. Run `go build` in the root directory of the repository 14 | 4. Run the compiled binary 15 | 5. Done 16 | 17 | # How to Use 18 | 1. Run the compiled binary 19 | 2. Follow the instructions 20 | 3. Rename `example.config.json` to `config.json` 21 | 4. Arrange the configuration file according to your needs 22 | 3. Done 23 | 24 | # License 25 | This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details 26 | 27 | Thanks to [Goreleaser](https://goreleaser.com/) for the awesome release tool. -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Tokinaa 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.goreleaser.yaml: -------------------------------------------------------------------------------- 1 | # This is an example .goreleaser.yml file with some sensible defaults. 2 | # Make sure to check the documentation at https://goreleaser.com 3 | 4 | # The lines below are called `modelines`. See `:help modeline` 5 | # Feel free to remove those if you don't want/need to use them. 6 | # yaml-language-server: $schema=https://goreleaser.com/static/schema.json 7 | # vim: set ts=2 sw=2 tw=0 fo=cnqoj 8 | 9 | version: 1 10 | 11 | before: 12 | hooks: 13 | # You may remove this if you don't use go modules. 14 | - go mod tidy 15 | # you may remove this if you don't need go generate 16 | - go generate ./... 17 | 18 | nfpms: 19 | - maintainer: Tokina 20 | description: Warpcast Tools 21 | homepage: https://github.com/tokinaa/warpcast-tools 22 | license: MIT 23 | formats: 24 | - deb 25 | - rpm 26 | dockers: 27 | - image_templates: ["ghcr.io/tokinaa/warpcast-tools:{{ .Version }}"] 28 | dockerfile: Dockerfile 29 | build_flag_templates: 30 | - --label=org.opencontainers.image.title={{ .ProjectName }} 31 | - --label=org.opencontainers.image.description={{ .ProjectName }} 32 | - --label=org.opencontainers.image.url=https://github.com/tokinaa/warpcast-tools 33 | - --label=org.opencontainers.image.source=https://github.com/tokinaa/warpcast-tools 34 | - --label=org.opencontainers.image.version={{ .Version }} 35 | - --label=org.opencontainers.image.created={{ time "2006-01-02T15:04:05Z07:00" }} 36 | - --label=org.opencontainers.image.revision={{ .FullCommit }} 37 | - --label=org.opencontainers.image.licenses=MIT 38 | builds: 39 | - env: 40 | - CGO_ENABLED=0 41 | goos: 42 | - linux 43 | - windows 44 | - darwin 45 | 46 | archives: 47 | - format: tar.gz 48 | # this name template makes the OS and Arch compatible with the results of `uname`. 49 | name_template: >- 50 | {{ .ProjectName }}_ 51 | {{- title .Os }}_ 52 | {{- if eq .Arch "amd64" }}x86_64 53 | {{- else if eq .Arch "386" }}i386 54 | {{- else }}{{ .Arch }}{{ end }} 55 | {{- if .Arm }}v{{ .Arm }}{{ end }} 56 | # use zip for windows archives 57 | format_overrides: 58 | - goos: windows 59 | format: zip 60 | 61 | changelog: 62 | sort: asc 63 | filters: 64 | exclude: 65 | - "^doc`s:" 66 | - "^test:" 67 | -------------------------------------------------------------------------------- /degen/airdrop2.go: -------------------------------------------------------------------------------- 1 | package degen 2 | 3 | import ( 4 | "encoding/json" 5 | "net/http" 6 | "time" 7 | ) 8 | 9 | type GetPointsResponse struct { 10 | DisplayName string `json:"display_name"` 11 | Points string `json:"points"` 12 | } 13 | 14 | func GetPoints(address string) ([]GetPointsResponse, error) { 15 | url := "https://www.degen.tips/api/airdrop2/season2/points?address=" + address 16 | method := "GET" 17 | 18 | client := &http.Client{} 19 | req, err := http.NewRequest(method, url, nil) 20 | 21 | if err != nil { 22 | return nil, err 23 | } 24 | req.Header.Add("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36") 25 | req.Header.Add("Content-Type", "application/json; charset=utf-8") 26 | req.Header.Add("Referer", "https://warpcast.com/") 27 | 28 | res, err := client.Do(req) 29 | if err != nil { 30 | return nil, err 31 | } 32 | defer res.Body.Close() 33 | 34 | var response []GetPointsResponse 35 | err = json.NewDecoder(res.Body).Decode(&response) 36 | if err != nil { 37 | return nil, err 38 | } 39 | 40 | return response, nil 41 | } 42 | 43 | type GetTipAllowanceResponse struct { 44 | SnapshotDate time.Time `json:"snapshot_date"` 45 | UserRank string `json:"user_rank"` 46 | WalletAddress string `json:"wallet_address"` 47 | AvatarURL string `json:"avatar_url"` 48 | DisplayName string `json:"display_name"` 49 | TipAllowance string `json:"tip_allowance"` 50 | RemainingAllowance string `json:"remaining_allowance"` 51 | } 52 | 53 | func GetTipAllowance(address string) ([]GetTipAllowanceResponse, error) { 54 | url := "https://www.degen.tips/api/airdrop2/tip-allowance?address=" + address 55 | method := "GET" 56 | 57 | client := &http.Client{} 58 | req, err := http.NewRequest(method, url, nil) 59 | 60 | if err != nil { 61 | return nil, err 62 | } 63 | req.Header.Add("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36") 64 | req.Header.Add("Content-Type", "application/json; charset=utf-8") 65 | req.Header.Add("Referer", "https://warpcast.com/") 66 | 67 | res, err := client.Do(req) 68 | if err != nil { 69 | return nil, err 70 | } 71 | defer res.Body.Close() 72 | 73 | var response []GetTipAllowanceResponse 74 | err = json.NewDecoder(res.Body).Decode(&response) 75 | if err != nil { 76 | return nil, err 77 | } 78 | 79 | return response, nil 80 | } 81 | -------------------------------------------------------------------------------- /warpcast/auth.go: -------------------------------------------------------------------------------- 1 | package warpcast 2 | 3 | import ( 4 | "encoding/json" 5 | "io/ioutil" 6 | "net/http" 7 | ) 8 | 9 | type GetMyProfileResponse struct { 10 | Result struct { 11 | State struct { 12 | ID string `json:"id"` 13 | Email string `json:"email"` 14 | User struct { 15 | Fid int `json:"fid"` 16 | Username string `json:"username"` 17 | DisplayName string `json:"displayName"` 18 | Pfp struct { 19 | URL string `json:"url"` 20 | Verified bool `json:"verified"` 21 | } `json:"pfp"` 22 | Profile struct { 23 | Bio struct { 24 | Text string `json:"text"` 25 | Mentions []interface{} `json:"mentions"` 26 | ChannelMentions []interface{} `json:"channelMentions"` 27 | } `json:"bio"` 28 | Location struct { 29 | PlaceID string `json:"placeId"` 30 | Description string `json:"description"` 31 | } `json:"location"` 32 | } `json:"profile"` 33 | FollowerCount int `json:"followerCount"` 34 | FollowingCount int `json:"followingCount"` 35 | ActiveOnFcNetwork bool `json:"activeOnFcNetwork"` 36 | ViewerContext struct { 37 | Following bool `json:"following"` 38 | FollowedBy bool `json:"followedBy"` 39 | } `json:"viewerContext"` 40 | } `json:"user"` 41 | HasOnboarding bool `json:"hasOnboarding"` 42 | HasConfirmedEmail bool `json:"hasConfirmedEmail"` 43 | HandledConnectAddress bool `json:"handledConnectAddress"` 44 | CanRegisterUsername bool `json:"canRegisterUsername"` 45 | NeedsRegistrationPayment bool `json:"needsRegistrationPayment"` 46 | HasFid bool `json:"hasFid"` 47 | HasFname bool `json:"hasFname"` 48 | HasDelegatedSigner bool `json:"hasDelegatedSigner"` 49 | HasSetupProfile bool `json:"hasSetupProfile"` 50 | HasCompletedRegistration bool `json:"hasCompletedRegistration"` 51 | HasStorage bool `json:"hasStorage"` 52 | HandledPushNotificationsNudge bool `json:"handledPushNotificationsNudge"` 53 | HandledContactsNudge bool `json:"handledContactsNudge"` 54 | HandledInterestsNudge bool `json:"handledInterestsNudge"` 55 | HasValidPaidInvite bool `json:"hasValidPaidInvite"` 56 | HasPhone bool `json:"hasPhone"` 57 | NeedsPhone bool `json:"needsPhone"` 58 | SponsoredRegisterEligible bool `json:"sponsoredRegisterEligible"` 59 | } `json:"state"` 60 | } `json:"result"` 61 | } 62 | 63 | func GetMyProfile(accessToken string) (*GetMyProfileResponse, error) { 64 | url := "https://client.warpcast.com/v2/onboarding-state" 65 | method := "GET" 66 | 67 | client := &http.Client{} 68 | req, err := http.NewRequest(method, url, nil) 69 | 70 | if err != nil { 71 | return &GetMyProfileResponse{}, err 72 | } 73 | req.Header.Add("authority", "client.warpcast.com") 74 | req.Header.Add("accept", "*/*") 75 | req.Header.Add("accept-language", "id-ID,id;q=0.9,en-US;q=0.8,en;q=0.7") 76 | req.Header.Add("authorization", "Bearer "+accessToken) 77 | req.Header.Add("content-type", "application/json; charset=utf-8") 78 | req.Header.Add("origin", "https://warpcast.com") 79 | req.Header.Add("referer", "https://warpcast.com/") 80 | req.Header.Add("user-agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36") 81 | 82 | res, err := client.Do(req) 83 | if err != nil { 84 | return &GetMyProfileResponse{}, err 85 | } 86 | defer res.Body.Close() 87 | 88 | body, err := ioutil.ReadAll(res.Body) 89 | if err != nil { 90 | return &GetMyProfileResponse{}, err 91 | } 92 | var response GetMyProfileResponse 93 | err = json.Unmarshal(body, &response) 94 | if err != nil { 95 | return &GetMyProfileResponse{}, err 96 | } 97 | 98 | return &response, nil 99 | } 100 | -------------------------------------------------------------------------------- /warpcast/profile.go: -------------------------------------------------------------------------------- 1 | package warpcast 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "io/ioutil" 7 | "net/http" 8 | "strconv" 9 | "strings" 10 | ) 11 | 12 | type GetProfileResponse struct { 13 | Result struct { 14 | User struct { 15 | Fid int `json:"fid"` 16 | Username string `json:"username"` 17 | DisplayName string `json:"displayName"` 18 | Pfp struct { 19 | URL string `json:"url"` 20 | Verified bool `json:"verified"` 21 | } `json:"pfp"` 22 | Profile struct { 23 | Bio struct { 24 | Text string `json:"text"` 25 | Mentions []interface{} `json:"mentions"` 26 | ChannelMentions []interface{} `json:"channelMentions"` 27 | } `json:"bio"` 28 | Location struct { 29 | PlaceID string `json:"placeId"` 30 | Description string `json:"description"` 31 | } `json:"location"` 32 | } `json:"profile"` 33 | FollowerCount int `json:"followerCount"` 34 | FollowingCount int `json:"followingCount"` 35 | ActiveOnFcNetwork bool `json:"activeOnFcNetwork"` 36 | ViewerContext struct { 37 | Following bool `json:"following"` 38 | FollowedBy bool `json:"followedBy"` 39 | CanSendDirectCasts bool `json:"canSendDirectCasts"` 40 | HasUploadedInboxKeys bool `json:"hasUploadedInboxKeys"` 41 | } `json:"viewerContext"` 42 | } `json:"user"` 43 | InviterIsReferrer bool `json:"inviterIsReferrer"` 44 | CollectionsOwned []interface{} `json:"collectionsOwned"` 45 | Extras struct { 46 | Fid int `json:"fid"` 47 | CustodyAddress string `json:"custodyAddress"` 48 | } `json:"extras"` 49 | } `json:"result"` 50 | } 51 | 52 | func GetProfile(accessToken string, username string) (*GetProfileResponse, error) { 53 | url := "https://client.warpcast.com/v2/user-by-username?username=" + username 54 | method := "GET" 55 | 56 | client := &http.Client{} 57 | req, err := http.NewRequest(method, url, nil) 58 | 59 | if err != nil { 60 | return &GetProfileResponse{}, err 61 | } 62 | req.Header.Add("Authorization", "Bearer "+accessToken) 63 | req.Header.Add("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36") 64 | req.Header.Add("Content-Type", "application/json; charset=utf-8") 65 | req.Header.Add("Referer", "https://warpcast.com/") 66 | 67 | res, err := client.Do(req) 68 | if err != nil { 69 | return &GetProfileResponse{}, err 70 | } 71 | defer res.Body.Close() 72 | 73 | body, err := ioutil.ReadAll(res.Body) 74 | if err != nil { 75 | return &GetProfileResponse{}, err 76 | } 77 | 78 | var profile GetProfileResponse 79 | err = json.Unmarshal(body, &profile) 80 | if err != nil { 81 | return &GetProfileResponse{}, err 82 | } 83 | 84 | return &profile, nil 85 | } 86 | 87 | type GetFollowersResponse struct { 88 | Result struct { 89 | Users []struct { 90 | Fid int `json:"fid"` 91 | Username string `json:"username"` 92 | DisplayName string `json:"displayName"` 93 | Pfp struct { 94 | URL string `json:"url"` 95 | Verified bool `json:"verified"` 96 | } `json:"pfp"` 97 | Profile struct { 98 | Bio struct { 99 | Text string `json:"text"` 100 | Mentions []interface{} `json:"mentions"` 101 | ChannelMentions []interface{} `json:"channelMentions"` 102 | } `json:"bio"` 103 | Location struct { 104 | PlaceID string `json:"placeId"` 105 | Description string `json:"description"` 106 | } `json:"location"` 107 | } `json:"profile"` 108 | FollowerCount int `json:"followerCount"` 109 | FollowingCount int `json:"followingCount"` 110 | ActiveOnFcNetwork bool `json:"activeOnFcNetwork"` 111 | ViewerContext struct { 112 | Following bool `json:"following"` 113 | FollowedBy bool `json:"followedBy"` 114 | } `json:"viewerContext"` 115 | } `json:"users"` 116 | } `json:"result"` 117 | Next struct { 118 | Cursor string `json:"cursor"` 119 | } `json:"next"` 120 | } 121 | 122 | func GetProfileInformation(types string, accessToken string, fid string, cursor string) (*GetFollowersResponse, error) { 123 | url := "https://client.warpcast.com/v2/" + types + "?fid=" + fid + "&limit=200&cursor=" + cursor 124 | method := "GET" 125 | 126 | client := &http.Client{} 127 | req, err := http.NewRequest(method, url, nil) 128 | 129 | if err != nil { 130 | return &GetFollowersResponse{}, err 131 | } 132 | req.Header.Add("Authorization", "Bearer "+accessToken) 133 | req.Header.Add("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36") 134 | req.Header.Add("Content-Type", "application/json; charset=utf-8") 135 | req.Header.Add("Referer", "https://warpcast.com/") 136 | 137 | res, err := client.Do(req) 138 | if err != nil { 139 | return &GetFollowersResponse{}, err 140 | } 141 | defer res.Body.Close() 142 | 143 | body, err := ioutil.ReadAll(res.Body) 144 | if err != nil { 145 | return &GetFollowersResponse{}, err 146 | } 147 | 148 | var followers GetFollowersResponse 149 | err = json.Unmarshal(body, &followers) 150 | 151 | if err != nil { 152 | return &GetFollowersResponse{}, err 153 | } 154 | 155 | return &followers, nil 156 | } 157 | 158 | type FollowResponse struct { 159 | Result struct { 160 | Success bool `json:"success"` 161 | UserAppContext struct { 162 | CanAddLinks bool `json:"canAddLinks"` 163 | } `json:"userAppContext"` 164 | } `json:"result"` 165 | } 166 | 167 | func Follow(accessToken string, fid string) (*FollowResponse, error) { 168 | url := "https://client.warpcast.com/v2/follows" 169 | method := "PUT" 170 | 171 | payload := strings.NewReader(`{"targetFid":` + fid + `}`) 172 | 173 | client := &http.Client{} 174 | req, err := http.NewRequest(method, url, payload) 175 | 176 | if err != nil { 177 | return &FollowResponse{}, err 178 | } 179 | req.Header.Add("Authorization", "Bearer "+accessToken) 180 | req.Header.Add("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36") 181 | req.Header.Add("Content-Type", "application/json; charset=utf-8") 182 | req.Header.Add("Referer", "https://warpcast.com/") 183 | 184 | res, err := client.Do(req) 185 | if err != nil { 186 | return &FollowResponse{}, err 187 | } 188 | defer res.Body.Close() 189 | 190 | body, err := ioutil.ReadAll(res.Body) 191 | if err != nil { 192 | return &FollowResponse{}, err 193 | } 194 | 195 | statusCode := res.StatusCode 196 | if statusCode != 200 { 197 | return &FollowResponse{}, errors.New("error") 198 | } 199 | 200 | var follow FollowResponse 201 | err = json.Unmarshal(body, &follow) 202 | if err != nil { 203 | return &FollowResponse{}, err 204 | } 205 | 206 | return &follow, nil 207 | } 208 | 209 | func Unfollow(accessToken string, fid string) (*FollowResponse, error) { 210 | url := "https://client.warpcast.com/v2/follows" 211 | method := "DELETE" 212 | 213 | payload := strings.NewReader(`{"targetFid":` + fid + `}`) 214 | 215 | client := &http.Client{} 216 | req, err := http.NewRequest(method, url, payload) 217 | 218 | if err != nil { 219 | return &FollowResponse{}, err 220 | } 221 | req.Header.Add("Authorization", "Bearer "+accessToken) 222 | req.Header.Add("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36") 223 | req.Header.Add("Content-Type", "application/json; charset=utf-8") 224 | req.Header.Add("Referer", "https://warpcast.com/") 225 | 226 | res, err := client.Do(req) 227 | if err != nil { 228 | return &FollowResponse{}, err 229 | } 230 | defer res.Body.Close() 231 | 232 | body, err := ioutil.ReadAll(res.Body) 233 | if err != nil { 234 | return &FollowResponse{}, err 235 | } 236 | 237 | statusCode := res.StatusCode 238 | if statusCode != 200 { 239 | return &FollowResponse{}, errors.New("error | " + string(body) + " | " + strconv.Itoa(statusCode)) 240 | } 241 | 242 | var follow FollowResponse 243 | err = json.Unmarshal(body, &follow) 244 | if err != nil { 245 | return &FollowResponse{}, err 246 | } 247 | 248 | return &follow, nil 249 | } 250 | 251 | type GetAddressVerifiedResponse struct { 252 | Result struct { 253 | Verifications []struct { 254 | Fid int `json:"fid"` 255 | Address string `json:"address"` 256 | Timestamp int64 `json:"timestamp"` 257 | Version string `json:"version"` 258 | } `json:"verifications"` 259 | } `json:"result"` 260 | } 261 | 262 | func GetAddressVerified(accessToken string, fid string) (*GetAddressVerifiedResponse, error) { 263 | url := "https://client.warpcast.com/v2/verifications?fid=" + fid + "&limit=5" 264 | method := "GET" 265 | 266 | client := &http.Client{} 267 | req, err := http.NewRequest(method, url, nil) 268 | 269 | if err != nil { 270 | return &GetAddressVerifiedResponse{}, err 271 | } 272 | req.Header.Add("Authorization", "Bearer "+accessToken) 273 | req.Header.Add("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36") 274 | req.Header.Add("Content-Type", "application/json; charset=utf-8") 275 | req.Header.Add("Referer", "https://warpcast.com/") 276 | 277 | res, err := client.Do(req) 278 | if err != nil { 279 | return &GetAddressVerifiedResponse{}, err 280 | } 281 | defer res.Body.Close() 282 | 283 | body, err := ioutil.ReadAll(res.Body) 284 | if err != nil { 285 | return &GetAddressVerifiedResponse{}, err 286 | } 287 | 288 | var profile GetAddressVerifiedResponse 289 | err = json.Unmarshal(body, &profile) 290 | if err != nil { 291 | return &GetAddressVerifiedResponse{}, err 292 | } 293 | 294 | return &profile, nil 295 | } 296 | -------------------------------------------------------------------------------- /warpcast/timeline.go: -------------------------------------------------------------------------------- 1 | package warpcast 2 | 3 | import ( 4 | "encoding/json" 5 | "io/ioutil" 6 | "net/http" 7 | "strconv" 8 | "strings" 9 | ) 10 | 11 | type LikeResponse struct { 12 | Result struct { 13 | Like struct { 14 | Type string `json:"type"` 15 | Hash string `json:"hash"` 16 | Reactor struct { 17 | Fid int `json:"fid"` 18 | Username string `json:"username"` 19 | DisplayName string `json:"displayName"` 20 | Pfp struct { 21 | URL string `json:"url"` 22 | Verified bool `json:"verified"` 23 | } `json:"pfp"` 24 | Profile struct { 25 | Bio struct { 26 | Text string `json:"text"` 27 | Mentions []interface{} `json:"mentions"` 28 | ChannelMentions []interface{} `json:"channelMentions"` 29 | } `json:"bio"` 30 | Location struct { 31 | PlaceID string `json:"placeId"` 32 | Description string `json:"description"` 33 | } `json:"location"` 34 | } `json:"profile"` 35 | FollowerCount int `json:"followerCount"` 36 | FollowingCount int `json:"followingCount"` 37 | ActiveOnFcNetwork bool `json:"activeOnFcNetwork"` 38 | ViewerContext struct { 39 | Following bool `json:"following"` 40 | FollowedBy bool `json:"followedBy"` 41 | } `json:"viewerContext"` 42 | } `json:"reactor"` 43 | Timestamp int64 `json:"timestamp"` 44 | CastHash string `json:"castHash"` 45 | } `json:"like"` 46 | } `json:"result"` 47 | } 48 | 49 | func Like(accessToken string, castHash string) (*LikeResponse, error) { 50 | url := "https://client.warpcast.com/v2/cast-likes" 51 | method := "PUT" 52 | 53 | payload := strings.NewReader(`{"castHash":"` + castHash + `"}`) 54 | 55 | client := &http.Client{} 56 | req, err := http.NewRequest(method, url, payload) 57 | 58 | if err != nil { 59 | return &LikeResponse{}, err 60 | } 61 | req.Header.Add("authority", "client.warpcast.com") 62 | req.Header.Add("accept", "*/*") 63 | req.Header.Add("accept-language", "id-ID,id;q=0.9,en-US;q=0.8,en;q=0.7") 64 | req.Header.Add("authorization", "Bearer "+accessToken) 65 | req.Header.Add("content-type", "application/json; charset=utf-8") 66 | req.Header.Add("origin", "https://warpcast.com") 67 | req.Header.Add("referer", "https://warpcast.com/") 68 | req.Header.Add("user-agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36") 69 | 70 | res, err := client.Do(req) 71 | if err != nil { 72 | return &LikeResponse{}, err 73 | } 74 | defer res.Body.Close() 75 | 76 | body, err := ioutil.ReadAll(res.Body) 77 | if err != nil { 78 | return &LikeResponse{}, err 79 | } 80 | 81 | statusCode := res.StatusCode 82 | 83 | if statusCode != 200 { 84 | return &LikeResponse{}, err 85 | } 86 | 87 | var likeResponse LikeResponse 88 | err = json.Unmarshal(body, &likeResponse) 89 | if err != nil { 90 | return &LikeResponse{}, err 91 | } 92 | 93 | return &likeResponse, nil 94 | } 95 | 96 | type RecastResponse struct { 97 | Result struct { 98 | CastHash string `json:"castHash"` 99 | } `json:"result"` 100 | } 101 | 102 | func Recast(accessToken string, castHash string) (*RecastResponse, error) { 103 | url := "https://client.warpcast.com/v2/recasts" 104 | method := "PUT" 105 | 106 | payload := strings.NewReader(`{"castHash":"` + castHash + `"}`) 107 | 108 | client := &http.Client{} 109 | req, err := http.NewRequest(method, url, payload) 110 | 111 | if err != nil { 112 | return &RecastResponse{}, err 113 | } 114 | req.Header.Add("authority", "client.warpcast.com") 115 | req.Header.Add("accept", "*/*") 116 | req.Header.Add("accept-language", "id-ID,id;q=0.9,en-US;q=0.8,en;q=0.7") 117 | req.Header.Add("authorization", "Bearer "+accessToken) 118 | req.Header.Add("content-type", "application/json; charset=utf-8") 119 | req.Header.Add("origin", "https://warpcast.com") 120 | req.Header.Add("referer", "https://warpcast.com/") 121 | req.Header.Add("user-agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36") 122 | 123 | res, err := client.Do(req) 124 | if err != nil { 125 | return &RecastResponse{}, err 126 | } 127 | defer res.Body.Close() 128 | 129 | body, err := ioutil.ReadAll(res.Body) 130 | if err != nil { 131 | return &RecastResponse{}, err 132 | } 133 | 134 | statusCode := res.StatusCode 135 | 136 | if statusCode != 200 { 137 | return &RecastResponse{}, err 138 | } 139 | 140 | var recastResponse RecastResponse 141 | err = json.Unmarshal(body, &recastResponse) 142 | 143 | if err != nil { 144 | return &RecastResponse{}, err 145 | } 146 | 147 | return &recastResponse, nil 148 | } 149 | 150 | type CommentResponse struct { 151 | Result struct { 152 | Cast struct { 153 | Hash string `json:"hash"` 154 | ThreadHash string `json:"threadHash"` 155 | ParentHash string `json:"parentHash"` 156 | ParentAuthor struct { 157 | Fid int `json:"fid"` 158 | Username string `json:"username"` 159 | DisplayName string `json:"displayName"` 160 | Pfp struct { 161 | URL string `json:"url"` 162 | Verified bool `json:"verified"` 163 | } `json:"pfp"` 164 | Profile struct { 165 | Bio struct { 166 | Text string `json:"text"` 167 | Mentions []interface{} `json:"mentions"` 168 | ChannelMentions []interface{} `json:"channelMentions"` 169 | } `json:"bio"` 170 | Location struct { 171 | PlaceID string `json:"placeId"` 172 | Description string `json:"description"` 173 | } `json:"location"` 174 | } `json:"profile"` 175 | FollowerCount int `json:"followerCount"` 176 | FollowingCount int `json:"followingCount"` 177 | ActiveOnFcNetwork bool `json:"activeOnFcNetwork"` 178 | } `json:"parentAuthor"` 179 | Author struct { 180 | Fid int `json:"fid"` 181 | Username string `json:"username"` 182 | DisplayName string `json:"displayName"` 183 | Pfp struct { 184 | URL string `json:"url"` 185 | Verified bool `json:"verified"` 186 | } `json:"pfp"` 187 | Profile struct { 188 | Bio struct { 189 | Text string `json:"text"` 190 | Mentions []interface{} `json:"mentions"` 191 | ChannelMentions []interface{} `json:"channelMentions"` 192 | } `json:"bio"` 193 | Location struct { 194 | PlaceID string `json:"placeId"` 195 | Description string `json:"description"` 196 | } `json:"location"` 197 | } `json:"profile"` 198 | FollowerCount int `json:"followerCount"` 199 | FollowingCount int `json:"followingCount"` 200 | ActiveOnFcNetwork bool `json:"activeOnFcNetwork"` 201 | ViewerContext struct { 202 | Following bool `json:"following"` 203 | } `json:"viewerContext"` 204 | } `json:"author"` 205 | Text string `json:"text"` 206 | Timestamp int64 `json:"timestamp"` 207 | Embeds struct { 208 | Images []interface{} `json:"images"` 209 | Urls []interface{} `json:"urls"` 210 | Videos []interface{} `json:"videos"` 211 | Unknowns []interface{} `json:"unknowns"` 212 | } `json:"embeds"` 213 | Replies struct { 214 | Count int `json:"count"` 215 | } `json:"replies"` 216 | Reactions struct { 217 | Count int `json:"count"` 218 | } `json:"reactions"` 219 | Recasts struct { 220 | Count int `json:"count"` 221 | Recasters []interface{} `json:"recasters"` 222 | } `json:"recasts"` 223 | Watches struct { 224 | Count int `json:"count"` 225 | } `json:"watches"` 226 | Tags []struct { 227 | Type string `json:"type"` 228 | ID string `json:"id"` 229 | Name string `json:"name"` 230 | ImageURL string `json:"imageUrl"` 231 | } `json:"tags"` 232 | QuoteCount int `json:"quoteCount"` 233 | CombinedRecastCount int `json:"combinedRecastCount"` 234 | ViewerContext struct { 235 | Reacted bool `json:"reacted"` 236 | Recast bool `json:"recast"` 237 | Watched bool `json:"watched"` 238 | } `json:"viewerContext"` 239 | } `json:"cast"` 240 | } `json:"result"` 241 | } 242 | 243 | func Comment(accessToken string, castHash string, text string) (*CommentResponse, error) { 244 | url := "https://client.warpcast.com/v2/casts" 245 | method := "POST" 246 | 247 | payload := strings.NewReader(`{"text":"` + text + `","parent":{"hash":"` + castHash + `"},"embeds":[]}`) 248 | 249 | client := &http.Client{} 250 | req, err := http.NewRequest(method, url, payload) 251 | 252 | if err != nil { 253 | return &CommentResponse{}, err 254 | } 255 | req.Header.Add("authority", "client.warpcast.com") 256 | req.Header.Add("accept", "*/*") 257 | req.Header.Add("accept-language", "id-ID,id;q=0.9,en-US;q=0.8,en;q=0.7") 258 | req.Header.Add("authorization", "Bearer "+accessToken) 259 | req.Header.Add("content-type", "application/json; charset=utf-8") 260 | req.Header.Add("origin", "https://warpcast.com") 261 | req.Header.Add("referer", "https://warpcast.com/") 262 | req.Header.Add("user-agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36") 263 | 264 | res, err := client.Do(req) 265 | if err != nil { 266 | return &CommentResponse{}, err 267 | } 268 | defer res.Body.Close() 269 | 270 | body, err := ioutil.ReadAll(res.Body) 271 | if err != nil { 272 | return &CommentResponse{}, err 273 | } 274 | 275 | statusCode := res.StatusCode 276 | 277 | if statusCode != 201 { 278 | return &CommentResponse{}, err 279 | } 280 | 281 | var commentResponse CommentResponse 282 | err = json.Unmarshal(body, &commentResponse) 283 | if err != nil { 284 | return &CommentResponse{}, err 285 | } 286 | 287 | return &commentResponse, nil 288 | } 289 | 290 | type GetFeedsItemsResponse struct { 291 | Result struct { 292 | Items []struct { 293 | ID string `json:"id"` 294 | Timestamp int64 `json:"timestamp"` 295 | Cast struct { 296 | Hash string `json:"hash"` 297 | ThreadHash string `json:"threadHash"` 298 | Author struct { 299 | Fid int `json:"fid"` 300 | Username string `json:"username"` 301 | DisplayName string `json:"displayName"` 302 | Pfp struct { 303 | URL string `json:"url"` 304 | Verified bool `json:"verified"` 305 | } `json:"pfp"` 306 | Profile struct { 307 | Bio struct { 308 | Text string `json:"text"` 309 | Mentions []interface{} `json:"mentions"` 310 | ChannelMentions []interface{} `json:"channelMentions"` 311 | } `json:"bio"` 312 | Location struct { 313 | PlaceID string `json:"placeId"` 314 | Description string `json:"description"` 315 | } `json:"location"` 316 | } `json:"profile"` 317 | FollowerCount int `json:"followerCount"` 318 | FollowingCount int `json:"followingCount"` 319 | ActiveOnFcNetwork bool `json:"activeOnFcNetwork"` 320 | ViewerContext struct { 321 | Following bool `json:"following"` 322 | } `json:"viewerContext"` 323 | } `json:"author"` 324 | Text string `json:"text"` 325 | Timestamp int64 `json:"timestamp"` 326 | Mentions []interface{} `json:"mentions"` 327 | Attachments struct { 328 | } `json:"attachments"` 329 | Replies struct { 330 | Count int `json:"count"` 331 | } `json:"replies"` 332 | Reactions struct { 333 | Count int `json:"count"` 334 | } `json:"reactions"` 335 | Recasts struct { 336 | Count int `json:"count"` 337 | Recasters []interface{} `json:"recasters"` 338 | } `json:"recasts"` 339 | Watches struct { 340 | Count int `json:"count"` 341 | } `json:"watches"` 342 | Tags []struct { 343 | Type string `json:"type"` 344 | ID string `json:"id"` 345 | Name string `json:"name"` 346 | ImageURL string `json:"imageUrl"` 347 | } `json:"tags"` 348 | QuoteCount int `json:"quoteCount"` 349 | CombinedRecastCount int `json:"combinedRecastCount"` 350 | ViewerContext struct { 351 | Reacted bool `json:"reacted"` 352 | Recast bool `json:"recast"` 353 | Watched bool `json:"watched"` 354 | } `json:"viewerContext"` 355 | } `json:"cast"` 356 | OtherParticipants []interface{} `json:"otherParticipants"` 357 | } `json:"items"` 358 | LatestMainCastTimestamp int64 `json:"latestMainCastTimestamp"` 359 | FeedTopSeenAtTimestamp int64 `json:"feedTopSeenAtTimestamp"` 360 | } `json:"result"` 361 | } 362 | 363 | func GetFeedsItems(accessToken string, feedKey string, lastTimestamp int64, excludeHash []string) (*GetFeedsItemsResponse, error) { 364 | url := "https://client.warpcast.com/v2/feed-items" 365 | method := "POST" 366 | 367 | payload := &strings.Reader{} 368 | if lastTimestamp == 0 { 369 | payload = strings.NewReader(`{"feedKey":"` + feedKey + `","viewedCastHashes":"","updateState":true}`) 370 | } else if lastTimestamp != 0 && len(excludeHash) > 0 { 371 | excludeHashJSON, err := json.Marshal(excludeHash) 372 | if err != nil { 373 | return &GetFeedsItemsResponse{}, err 374 | } 375 | 376 | payload = strings.NewReader(`{"feedKey":"` + feedKey + `","viewedCastHashes":"","updateState":true, "olderThan":` + strconv.FormatInt(lastTimestamp, 10) + `, "excludeItemIdPrefixes": ` + string(excludeHashJSON) + `}`) 377 | } 378 | 379 | client := &http.Client{} 380 | req, err := http.NewRequest(method, url, payload) 381 | 382 | if err != nil { 383 | return &GetFeedsItemsResponse{}, err 384 | } 385 | req.Header.Add("authority", "client.warpcast.com") 386 | req.Header.Add("accept", "*/*") 387 | req.Header.Add("accept-language", "id-ID,id;q=0.9,en-US;q=0.8,en;q=0.7") 388 | req.Header.Add("authorization", "Bearer "+accessToken) 389 | req.Header.Add("content-type", "application/json; charset=utf-8") 390 | req.Header.Add("origin", "https://warpcast.com") 391 | req.Header.Add("referer", "https://warpcast.com/") 392 | req.Header.Add("user-agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36") 393 | 394 | res, err := client.Do(req) 395 | if err != nil { 396 | return &GetFeedsItemsResponse{}, err 397 | } 398 | defer res.Body.Close() 399 | 400 | body, err := ioutil.ReadAll(res.Body) 401 | if err != nil { 402 | return &GetFeedsItemsResponse{}, err 403 | } 404 | 405 | var getFeedsItems GetFeedsItemsResponse 406 | err = json.Unmarshal(body, &getFeedsItems) 407 | if err != nil { 408 | return &GetFeedsItemsResponse{}, err 409 | } 410 | 411 | return &getFeedsItems, nil 412 | } 413 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "github.com/AlecAivazis/survey/v2" 7 | "github.com/tokinaa/warpcast-tools/degen" 8 | "github.com/tokinaa/warpcast-tools/warpcast" 9 | "math/rand" 10 | "os" 11 | "strconv" 12 | "strings" 13 | "time" 14 | ) 15 | 16 | type ConfigStruct struct { 17 | Accounts []string `json:"accounts"` 18 | DelayFollow int `json:"delayFollow"` 19 | DelayUnfollow int `json:"delayUnfollow"` 20 | DelayLike int `json:"delayLike"` 21 | DelayComment int `json:"delayComment"` 22 | DelayRecast int `json:"delayRecast"` 23 | DelayTimeline int `json:"delayTimeline"` 24 | CustomCommentText []string `json:"customCommentText"` 25 | IgnoreUsers []string `json:"ignoreUsers"` 26 | } 27 | 28 | var ( 29 | myConfig = LoadConfig() 30 | ) 31 | 32 | func LoadConfig() ConfigStruct { 33 | // Load from config.json 34 | openFile, err := os.Open("config.json") 35 | if err != nil { 36 | return ConfigStruct{} 37 | } 38 | 39 | defer openFile.Close() 40 | 41 | var config ConfigStruct 42 | jsonParser := json.NewDecoder(openFile) 43 | jsonParser.Decode(&config) 44 | 45 | return config 46 | } 47 | 48 | func init() { 49 | if _, err := os.Stat("config.json"); os.IsNotExist(err) { 50 | file, _ := json.MarshalIndent(ConfigStruct{ 51 | Accounts: []string{}, 52 | DelayFollow: 1000, 53 | DelayUnfollow: 1000, 54 | DelayLike: 1000, 55 | DelayComment: 1000, 56 | DelayRecast: 1000, 57 | DelayTimeline: 1000, 58 | CustomCommentText: []string{}, 59 | IgnoreUsers: []string{}, 60 | }, "", " ") 61 | _ = os.WriteFile("config.json", file, 0644) 62 | } 63 | openLoadConfig := LoadConfig() 64 | myConfig = openLoadConfig 65 | } 66 | 67 | func checkingError(err error) { 68 | if err != nil { 69 | switch { 70 | case err.Error() == "interrupt": 71 | os.Exit(0) 72 | default: 73 | break 74 | } 75 | } 76 | } 77 | 78 | func showPressEnter() { 79 | fmt.Print("Press Enter to Back...") 80 | 81 | var input string 82 | fmt.Scanln(&input) 83 | } 84 | 85 | func multiAccountsManagement() { 86 | fmt.Print("\033[H\033[2J") 87 | fmt.Println("Multi Accounts Management") 88 | fmt.Println("Total Accounts :", len(myConfig.Accounts)) 89 | fmt.Println() 90 | fmt.Println("1. List Account") 91 | fmt.Println("2. Add Account") 92 | fmt.Println("3. Remove Accounts") 93 | fmt.Println("4. Back") 94 | fmt.Println() 95 | 96 | inputMenu := "" 97 | inputMenuError := survey.AskOne(&survey.Input{ 98 | Message: "Select Menu:", 99 | }, &inputMenu, survey.WithValidator(survey.Required)) 100 | 101 | checkingError(inputMenuError) 102 | 103 | switch inputMenu { 104 | case "1": 105 | fmt.Print("\033[H\033[2J") 106 | fmt.Println("List Account") 107 | fmt.Println() 108 | for i, account := range myConfig.Accounts { 109 | fmt.Println(i+1, account) 110 | } 111 | fmt.Println() 112 | 113 | showPressEnter() 114 | 115 | fmt.Print("\033[H\033[2J") 116 | multiAccountsManagement() 117 | break 118 | case "2": 119 | fmt.Print("\033[H\033[2J") 120 | fmt.Println("Add Account") 121 | fmt.Println() 122 | 123 | inputAccount := "" 124 | inputAccountError := survey.AskOne(&survey.Input{ 125 | Message: "Authorization Token:", 126 | }, &inputAccount, survey.WithValidator(survey.Required)) 127 | 128 | checkingError(inputAccountError) 129 | 130 | myConfig.Accounts = append(myConfig.Accounts, inputAccount) 131 | 132 | file, _ := json.MarshalIndent(myConfig, "", " ") 133 | _ = os.WriteFile("config.json", file, 0644) 134 | 135 | fmt.Println("Account Added") 136 | fmt.Println() 137 | 138 | showPressEnter() 139 | 140 | fmt.Print("\033[H\033[2J") 141 | multiAccountsManagement() 142 | break 143 | case "3": 144 | fmt.Print("\033[H\033[2J") 145 | fmt.Println("Remove Accounts") 146 | fmt.Println() 147 | 148 | for i, account := range myConfig.Accounts { 149 | fmt.Println(i+1, account) 150 | } 151 | 152 | fmt.Println() 153 | 154 | inputAccount := 0 155 | inputAccountError := survey.AskOne(&survey.Select{ 156 | Message: "Select Account:", 157 | Options: myConfig.Accounts, 158 | }, &inputAccount, survey.WithValidator(survey.Required)) 159 | 160 | checkingError(inputAccountError) 161 | 162 | myConfig.Accounts = append(myConfig.Accounts[:inputAccount], myConfig.Accounts[inputAccount+1:]...) 163 | 164 | file, _ := json.MarshalIndent(myConfig, "", " ") 165 | _ = os.WriteFile("config.json", file, 0644) 166 | 167 | fmt.Println("Account Removed") 168 | fmt.Println() 169 | 170 | showPressEnter() 171 | 172 | fmt.Print("\033[H\033[2J") 173 | multiAccountsManagement() 174 | break 175 | case "4": 176 | fmt.Print("\033[H\033[2J") 177 | main() 178 | break 179 | } 180 | } 181 | 182 | func autoTimeline() { 183 | fmt.Print("\033[H\033[2J") 184 | 185 | fmt.Println("Auto Like, Comment, and Recast Timeline") 186 | fmt.Println() 187 | 188 | inputSelectAccount := 0 189 | inputSelectAccountError := survey.AskOne(&survey.Select{ 190 | Message: "Select Account:", 191 | Options: myConfig.Accounts, 192 | }, &inputSelectAccount, survey.WithValidator(survey.Required)) 193 | 194 | checkingError(inputSelectAccountError) 195 | 196 | inputSelectMode := []string{} 197 | inputSelectModeError := survey.AskOne(&survey.MultiSelect{ 198 | Message: "Select Mode:", 199 | Options: []string{"Like", "Comments", "Recast"}, 200 | }, &inputSelectMode, survey.WithValidator(survey.Required)) 201 | 202 | checkingError(inputSelectModeError) 203 | 204 | inputChoiceTimeline := "" 205 | inputChoiceTimelineError := survey.AskOne(&survey.Select{ 206 | Message: "Select Timeline:", 207 | Options: []string{"Home", "All-Channels"}, 208 | }, &inputChoiceTimeline, survey.WithValidator(survey.Required)) 209 | 210 | checkingError(inputChoiceTimelineError) 211 | 212 | fmt.Println() 213 | fmt.Println("[PROFILE] Getting my address...") 214 | 215 | myProfile, err := warpcast.GetMyProfile(myConfig.Accounts[inputSelectAccount]) 216 | if err != nil { 217 | fmt.Printf("[PROFILE][GETTER] ERROR : %s\n", err) 218 | return 219 | } 220 | 221 | fidStr := strconv.Itoa(myProfile.Result.State.User.Fid) 222 | 223 | realAddress := "" 224 | myAddress, err := warpcast.GetAddressVerified(myConfig.Accounts[inputSelectAccount], fidStr) 225 | if err != nil { 226 | fmt.Printf("[ADDRESS] [GETTER] ERROR : %s\n", err) 227 | return 228 | } 229 | 230 | if len(myAddress.Result.Verifications) < 1 { 231 | realAddress = "No Address" 232 | } else { 233 | realAddress = myAddress.Result.Verifications[0].Address 234 | } 235 | 236 | fmt.Printf("[PROFILE] [@%s] FID : %s | Address : %s\n", myProfile.Result.State.User.Username, fidStr, realAddress) 237 | 238 | myPoints, err := degen.GetPoints(realAddress) 239 | if err != nil { 240 | fmt.Printf("[DEGEN] [POINTS] [GETTER] ERROR : %s\n", err) 241 | return 242 | } 243 | 244 | realPoints := 0 245 | if len(myPoints) != 0 { 246 | realPoints, _ = strconv.Atoi(myPoints[0].Points) 247 | } 248 | 249 | fmt.Printf("[DEGEN] [PROFILE] Points : %d ", realPoints) 250 | 251 | myAllowance, err := degen.GetTipAllowance(realAddress) 252 | if err != nil { 253 | fmt.Printf("[DEGEN] [ALLOWANCE] [GETTER] ERROR : %s\n", err) 254 | return 255 | } 256 | 257 | remainingAllowance := 0 258 | if len(myAllowance) != 0 { 259 | remainingAllowance, _ = strconv.Atoi(myAllowance[0].RemainingAllowance) 260 | } 261 | 262 | remainingTipAllowance := 0 263 | if len(myAllowance) != 0 { 264 | remainingTipAllowance, _ = strconv.Atoi(myAllowance[0].TipAllowance) 265 | } 266 | 267 | fmt.Printf("| Allowance : %d | Remaining Allowance : %d\n", remainingTipAllowance, remainingAllowance) 268 | 269 | fmt.Println() 270 | 271 | var excludeHash []string 272 | var lastTimestamp int64 = 0 273 | 274 | for { 275 | timeline, err := warpcast.GetFeedsItems(myConfig.Accounts[inputSelectAccount], inputChoiceTimeline, lastTimestamp, excludeHash) 276 | if err != nil { 277 | fmt.Printf("[TIMELINE][GETTER] ERROR : %s\n", err) 278 | break 279 | } 280 | 281 | if lastTimestamp == 0 { 282 | lastTimestamp = timeline.Result.LatestMainCastTimestamp 283 | } 284 | 285 | items := timeline.Result.Items 286 | 287 | if len(items) == 0 { 288 | delayTimeline := time.Duration(myConfig.DelayTimeline) * time.Millisecond 289 | time.Sleep(delayTimeline) 290 | continue 291 | } 292 | 293 | for _, item := range items { 294 | if !strings.Contains(strings.Join(excludeHash, ","), item.Cast.Hash[2:10]) { 295 | excludeHash = append(excludeHash, item.Cast.Hash[2:10]) 296 | } 297 | 298 | fmt.Printf("[TIMELINE] [https://warpcast.com/%s/%s] ", item.Cast.Author.Username, item.Cast.Hash) 299 | 300 | if strings.Contains(strings.Join(inputSelectMode, ","), "Like") { 301 | fmt.Printf("[LIKE]") 302 | 303 | if item.Cast.ViewerContext.Reacted { 304 | fmt.Printf(" ALREADY ") 305 | } else { 306 | _, err := warpcast.Like(myConfig.Accounts[inputSelectAccount], item.Cast.Hash) 307 | if err != nil { 308 | fmt.Printf(" ERROR : %s", err) 309 | } else { 310 | fmt.Printf(" SUCCESS") 311 | } 312 | fmt.Printf(" ") 313 | 314 | delayLike := time.Duration(myConfig.DelayLike) * time.Millisecond 315 | time.Sleep(delayLike) 316 | } 317 | } 318 | 319 | if strings.Contains(strings.Join(inputSelectMode, ","), "Comments") { 320 | fmt.Printf("[COMMENT]") 321 | 322 | commentText := "" 323 | if strings.Contains(item.Cast.Text, "$DEGEN") { 324 | randomThreeDigit := rand.Intn(remainingAllowance) 325 | commentText = fmt.Sprintf("%d $DEGEN", randomThreeDigit) 326 | } 327 | 328 | if commentText != "" { 329 | if remainingAllowance > 0 { 330 | _, err := warpcast.Comment(myConfig.Accounts[inputSelectAccount], item.Cast.Hash, commentText) 331 | if err != nil { 332 | fmt.Printf(" ERROR : %s", err) 333 | } else { 334 | fmt.Printf(" SUCCESS [%s]", commentText) 335 | } 336 | } else { 337 | fmt.Printf(" SKIP NO ALLOWANCE") 338 | } 339 | } else { 340 | fmt.Printf(" SKIP NO $DEGEN TEXT IN POST") 341 | } 342 | 343 | fmt.Printf(" ") 344 | } 345 | 346 | if strings.Contains(strings.Join(inputSelectMode, ","), "Recast") { 347 | fmt.Printf("[RECAST]") 348 | 349 | if item.Cast.ViewerContext.Recast { 350 | fmt.Printf(" ALREADY ") 351 | } else { 352 | _, err := warpcast.Recast(myConfig.Accounts[inputSelectAccount], item.Cast.Hash) 353 | if err != nil { 354 | fmt.Printf(" ERROR : %s", err) 355 | } else { 356 | fmt.Printf(" SUCCESS") 357 | } 358 | 359 | fmt.Printf(" ") 360 | 361 | delayRecast := time.Duration(myConfig.DelayRecast) * time.Millisecond 362 | time.Sleep(delayRecast) 363 | } 364 | } 365 | 366 | fmt.Printf("\n") 367 | } 368 | 369 | fmt.Println() 370 | fmt.Printf("\tWaiting for %ds to get new feeds...\n", myConfig.DelayTimeline/1000) 371 | fmt.Println() 372 | 373 | delayTimeline := time.Duration(myConfig.DelayTimeline) * time.Millisecond 374 | time.Sleep(delayTimeline) 375 | } 376 | } 377 | 378 | func followTarget() { 379 | fmt.Print("\033[H\033[2J") 380 | 381 | fmt.Println("Follow Following/Followers Target") 382 | fmt.Println() 383 | 384 | inputSelectAccount := 0 385 | inputSelectAccountError := survey.AskOne(&survey.Select{ 386 | Message: "Select Account:", 387 | Options: myConfig.Accounts, 388 | }, &inputSelectAccount, survey.WithValidator(survey.Required)) 389 | 390 | checkingError(inputSelectAccountError) 391 | 392 | inputTargetUsername := "" 393 | inputTargetUsernameError := survey.AskOne(&survey.Input{ 394 | Message: "Target Username:", 395 | }, &inputTargetUsername, survey.WithValidator(survey.Required)) 396 | 397 | checkingError(inputTargetUsernameError) 398 | 399 | inputChoiceMode := "" 400 | inputChoiceModeError := survey.AskOne(&survey.Select{ 401 | Message: "Select Mode:", 402 | Options: []string{"Following", "Followers"}, 403 | }, &inputChoiceMode, survey.WithValidator(survey.Required)) 404 | 405 | checkingError(inputChoiceModeError) 406 | 407 | fmt.Println() 408 | 409 | fmt.Printf("[%s] Getting Data of @%s...\n", inputChoiceMode, inputTargetUsername) 410 | 411 | profile, err := warpcast.GetProfile(myConfig.Accounts[inputSelectAccount], inputTargetUsername) 412 | if err != nil { 413 | fmt.Printf("[PROFILE][GETTER] ERROR : %s\n", err) 414 | return 415 | } 416 | 417 | fmt.Printf("[%s] [@%s] FID : %d | Followers : %d | Following : %d\n", inputChoiceMode, inputTargetUsername, profile.Result.User.Fid, profile.Result.User.FollowerCount, profile.Result.User.FollowingCount) 418 | fmt.Println() 419 | 420 | var cursor string = "" 421 | for { 422 | fidStr := strconv.Itoa(profile.Result.User.Fid) 423 | tryToGetFollowersOrFollowing, err := warpcast.GetProfileInformation(strings.ToLower(inputChoiceMode), myConfig.Accounts[inputSelectAccount], fidStr, cursor) 424 | if err != nil { 425 | fmt.Printf("[GET DATA][%s] FAILED TO GET DATA | ERROR : %s\n", inputChoiceMode, err) 426 | continue 427 | } 428 | 429 | for index, item := range tryToGetFollowersOrFollowing.Result.Users { 430 | fidTarget := strconv.Itoa(item.Fid) 431 | fmt.Printf("%d. [%s] [@%s] FID : %s", index, inputChoiceMode, item.Username, fidTarget) 432 | 433 | if item.ViewerContext.Following { 434 | fmt.Printf(" SKIP YOU ALREADY FOLLOW !\n") 435 | continue 436 | } 437 | 438 | _, err := warpcast.Follow(myConfig.Accounts[inputSelectAccount], fidTarget) 439 | if err != nil { 440 | fmt.Printf(" ERROR : %s\n", err) 441 | } else { 442 | fmt.Printf(" SUCCESS\n") 443 | } 444 | 445 | delayFollow := time.Duration(myConfig.DelayFollow) * time.Millisecond 446 | time.Sleep(delayFollow) 447 | } 448 | 449 | if tryToGetFollowersOrFollowing.Next.Cursor == "" { 450 | break 451 | } 452 | 453 | cursor = tryToGetFollowersOrFollowing.Next.Cursor 454 | 455 | fmt.Println() 456 | fmt.Printf("\tWaiting for %ds to get new feeds...\n", myConfig.DelayTimeline/1000) 457 | fmt.Println() 458 | 459 | delayTimeline := time.Duration(myConfig.DelayTimeline) * time.Millisecond 460 | time.Sleep(delayTimeline) 461 | } 462 | } 463 | 464 | func unfollowNotFB() { 465 | fmt.Print("\033[H\033[2J") 466 | 467 | fmt.Println("Unfollow Who Not Follow Back") 468 | fmt.Println() 469 | 470 | inputSelectAccount := 0 471 | inputSelectAccountError := survey.AskOne(&survey.Select{ 472 | Message: "Select Account:", 473 | Options: myConfig.Accounts, 474 | }, &inputSelectAccount, survey.WithValidator(survey.Required)) 475 | 476 | checkingError(inputSelectAccountError) 477 | 478 | fmt.Println() 479 | fmt.Printf("[PROFILE] Getting following data\n") 480 | 481 | profile, err := warpcast.GetMyProfile(myConfig.Accounts[inputSelectAccount]) 482 | if err != nil { 483 | fmt.Printf("[PROFILE][GETTER] ERROR : %s\n", err) 484 | return 485 | } 486 | 487 | fidStr := strconv.Itoa(profile.Result.State.User.Fid) 488 | 489 | fmt.Printf("[%s] [@%s] FID : %s | Followers : %d | Following : %d\n", "PROFILE", profile.Result.State.User.Username, fidStr, profile.Result.State.User.FollowerCount, profile.Result.State.User.FollowingCount) 490 | fmt.Println() 491 | 492 | var cursor string = "" 493 | for { 494 | tryToGetFollowing, err := warpcast.GetProfileInformation("following", myConfig.Accounts[inputSelectAccount], fidStr, cursor) 495 | if err != nil { 496 | fmt.Printf("[GET DATA][FOLLOWING] FAILED TO GET DATA | ERROR : %s\n", err) 497 | continue 498 | } 499 | for _, item := range tryToGetFollowing.Result.Users { 500 | fidTarget := strconv.Itoa(item.Fid) 501 | fmt.Printf("[UNFOLLOW] [@%s] FID : %s", item.Username, fidTarget) 502 | 503 | if item.ViewerContext.FollowedBy { 504 | fmt.Printf(" SKIP THEY FOLLOW YOU !\n") 505 | continue 506 | } 507 | 508 | // if item.Username in myConfig.IgnoreUsers 509 | if strings.Contains(strings.Join(myConfig.IgnoreUsers, ","), strings.ToLower(item.Username)) { 510 | fmt.Printf(" SKIP IGNORED USER\n") 511 | continue 512 | } 513 | 514 | _, err := warpcast.Unfollow(myConfig.Accounts[inputSelectAccount], fidTarget) 515 | if err != nil { 516 | fmt.Printf(" ERROR : %s\n", err.Error()) 517 | } else { 518 | fmt.Printf(" SUCCESS\n") 519 | } 520 | 521 | delayUnfollow := time.Duration(myConfig.DelayUnfollow) * time.Millisecond 522 | time.Sleep(delayUnfollow) 523 | } 524 | 525 | if tryToGetFollowing.Next.Cursor == "" { 526 | break 527 | } 528 | 529 | cursor = tryToGetFollowing.Next.Cursor 530 | } 531 | } 532 | 533 | func main() { 534 | fmt.Println("Warpcast Tools") 535 | fmt.Println("Author : @x0xdead / Wildaann") 536 | fmt.Println() 537 | fmt.Println("1. Multi Accounts Management") 538 | fmt.Println("2. Follow Target (Followers/Following)") 539 | fmt.Println("3. Auto Like, Comment, and Recast Timeline (Home/All-Channels)") 540 | fmt.Println("4. Unfollow Who Not Follow Back") 541 | fmt.Println() 542 | 543 | inputMenu := "" 544 | inputMenuError := survey.AskOne(&survey.Input{ 545 | Message: "Select Menu:", 546 | }, &inputMenu, survey.WithValidator(survey.Required)) 547 | 548 | checkingError(inputMenuError) 549 | 550 | switch inputMenu { 551 | case "1": 552 | multiAccountsManagement() 553 | break 554 | case "2": 555 | followTarget() 556 | break 557 | case "3": 558 | autoTimeline() 559 | break 560 | case "4": 561 | unfollowNotFB() 562 | break 563 | } 564 | } 565 | --------------------------------------------------------------------------------