skypost 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 72251c65823ba0eb09ed0383726432a97ee1cb1359fef32441158e4c7c090c89
4
+ data.tar.gz: bd91c3aa9a407183191f6e6203a3279c7b76c4d4518fbd9355c6f08e3361d9d4
5
+ SHA512:
6
+ metadata.gz: aebd23ca3736be8ce1b4e80b084e28f5b3032557d747e0fd07186dd508775708f78339f0eff58789936559d3f0d78d96028e94509aa4c983e07bf5a1d498ea8e
7
+ data.tar.gz: fc2d2eb5d9bdf1a69b54c864f95aefd00b64f3d08cb56c8980e7848ad51447300624f3bb0fd8d3191559aabd39aaac6b845acdd6dfada4ddbff2e967e1381296
data/README.md ADDED
@@ -0,0 +1,61 @@
1
+ # Skypost
2
+
3
+ A Ruby gem for posting messages to Bluesky social network.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ ```ruby
10
+ gem 'skypost'
11
+ ```
12
+
13
+ And then execute:
14
+
15
+ $ bundle install
16
+
17
+ Or install it yourself as:
18
+
19
+ $ gem install skypost
20
+
21
+ ## Usage
22
+
23
+ ```ruby
24
+ require 'skypost'
25
+
26
+ # Initialize the client with your Bluesky credentials
27
+ # You can use either your custom domain or .bsky.social handle
28
+ client = Skypost::Client.new("username.com", "your-app-password")
29
+ # OR
30
+ client = Skypost::Client.new("username.bsky.social", "your-app-password")
31
+
32
+ # Post a message
33
+ client.post("Hello from Skypost!")
34
+
35
+ # Post a message with a clickable link
36
+ client.post('Check out this cool website: <a href="https://example.com">Example</a>!')
37
+ ```
38
+
39
+ ### App Password
40
+ To get your app password, go to your Bluesky account settings at [bsky.app/settings/app-passwords](https://bsky.app/settings/app-passwords) and create a new app password. Never use your main account password for API access.
41
+
42
+ ### Handle Format
43
+ You can use either:
44
+ 1. Your custom domain (if you have one), e.g., `username.com`
45
+ 2. Your Bluesky handle in the format `username.bsky.social`
46
+
47
+ To find your handle, look at your profile URL in Bluesky. For example:
48
+ - If your profile is at `https://bsky.app/profile/username.com`, use `username.com`
49
+ - If your profile is at `https://bsky.app/profile/username.bsky.social`, use `username.bsky.social`
50
+
51
+ ## Development
52
+
53
+ After checking out the repo, run `bundle install` to install dependencies.
54
+
55
+ ## Contributing
56
+
57
+ Bug reports and pull requests are welcome on GitHub.
58
+
59
+ ## License
60
+
61
+ The gem is available as open source under the terms of the MIT License.
@@ -0,0 +1,116 @@
1
+ require 'faraday'
2
+ require 'json'
3
+
4
+ module Skypost
5
+ class Client
6
+ BASE_URL = "https://bsky.social"
7
+
8
+ class AuthenticationError < StandardError; end
9
+ class ValidationError < StandardError; end
10
+
11
+ def initialize(identifier = nil, password = nil)
12
+ @identifier = identifier
13
+ @password = password
14
+ @session = nil
15
+ validate_identifier if identifier
16
+ end
17
+
18
+ def authenticate
19
+ raise AuthenticationError, "Identifier and password are required" if @identifier.nil? || @password.nil?
20
+
21
+ response = connection.post("/xrpc/com.atproto.server.createSession") do |req|
22
+ req.headers["Content-Type"] = "application/json"
23
+ req.body = JSON.generate({
24
+ identifier: @identifier,
25
+ password: @password
26
+ })
27
+ end
28
+
29
+ @session = JSON.parse(response.body)
30
+ @session
31
+ rescue Faraday::ResourceNotFound => e
32
+ raise AuthenticationError, "Authentication failed: Invalid credentials or incorrect handle format. Your handle should be either your custom domain (e.g., 'username.com') or your Bluesky handle (e.g., 'username.bsky.social')"
33
+ rescue Faraday::Error => e
34
+ raise AuthenticationError, "Failed to authenticate: #{e.message}"
35
+ end
36
+
37
+ def post(text)
38
+ ensure_authenticated
39
+
40
+ current_time = Time.now.utc.strftime("%Y-%m-%dT%H:%M:%S.%3NZ")
41
+ facets = extract_links(text)
42
+
43
+ request_body = {
44
+ repo: @session["did"],
45
+ collection: "app.bsky.feed.post",
46
+ record: {
47
+ text: text.gsub(/<a href="[^"]*">|<\/a>/, ''), # Remove HTML tags but keep link text
48
+ facets: facets,
49
+ createdAt: current_time,
50
+ "$type": "app.bsky.feed.post"
51
+ }
52
+ }
53
+
54
+ response = connection.post("/xrpc/com.atproto.repo.createRecord") do |req|
55
+ req.headers["Content-Type"] = "application/json"
56
+ req.headers["Authorization"] = "Bearer #{@session["accessJwt"]}"
57
+ req.body = JSON.generate(request_body)
58
+ end
59
+
60
+ JSON.parse(response.body)
61
+ rescue Faraday::ResourceNotFound => e
62
+ raise "Failed to post: The API endpoint returned 404. Please check if you're authenticated and using the correct API endpoint."
63
+ rescue Faraday::Error => e
64
+ raise "Failed to post: #{e.message}"
65
+ end
66
+
67
+ private
68
+
69
+ def validate_identifier
70
+ unless @identifier.include?(".")
71
+ raise ValidationError, "Invalid handle format. Handle must be either a custom domain (e.g., 'username.com') or a Bluesky handle (e.g., 'username.bsky.social')"
72
+ end
73
+ end
74
+
75
+ def ensure_authenticated
76
+ authenticate if @session.nil?
77
+ end
78
+
79
+ def connection
80
+ @connection ||= Faraday.new(url: BASE_URL) do |f|
81
+ f.request :json
82
+ f.response :raise_error
83
+ end
84
+ end
85
+
86
+ def extract_links(text)
87
+ facets = []
88
+ link_pattern = /<a href="([^"]*)">(.*?)<\/a>/
89
+
90
+ # First, find all matches to calculate correct byte positions
91
+ matches = text.to_enum(:scan, link_pattern).map { Regexp.last_match }
92
+ plain_text = text.gsub(/<a href="[^"]*">|<\/a>/, '') # Text with HTML removed
93
+
94
+ matches.each do |match|
95
+ url = match[1]
96
+ link_text = match[2]
97
+
98
+ # Find the link text in the plain text to get correct byte positions
99
+ if link_position = plain_text.index(link_text)
100
+ facets << {
101
+ index: {
102
+ byteStart: link_position,
103
+ byteEnd: link_position + link_text.bytesize
104
+ },
105
+ features: [{
106
+ "$type": "app.bsky.richtext.facet#link",
107
+ uri: url
108
+ }]
109
+ }
110
+ end
111
+ end
112
+
113
+ facets
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,3 @@
1
+ module Skypost
2
+ VERSION = "0.0.1"
3
+ end
data/lib/skypost.rb ADDED
@@ -0,0 +1,8 @@
1
+ require "faraday"
2
+ require "json"
3
+ require_relative "skypost/client"
4
+ require_relative "skypost/version"
5
+
6
+ module Skypost
7
+ class Error < StandardError; end
8
+ end
metadata ADDED
@@ -0,0 +1,118 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: skypost
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Jeroen Roosenboom
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2024-11-26 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: faraday
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '2.7'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '2.7'
27
+ - !ruby/object:Gem::Dependency
28
+ name: json
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '2.6'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '2.6'
41
+ - !ruby/object:Gem::Dependency
42
+ name: bundler
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '2.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '2.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rake
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '13.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '13.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rspec
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '3.0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '3.0'
83
+ description: A simple Ruby gem that allows posting messages to the Bluesky social
84
+ network
85
+ email:
86
+ - hi@jro7.com
87
+ executables: []
88
+ extensions: []
89
+ extra_rdoc_files: []
90
+ files:
91
+ - README.md
92
+ - lib/skypost.rb
93
+ - lib/skypost/client.rb
94
+ - lib/skypost/version.rb
95
+ homepage: https://github.com/jro7/skypost
96
+ licenses:
97
+ - MIT
98
+ metadata: {}
99
+ post_install_message:
100
+ rdoc_options: []
101
+ require_paths:
102
+ - lib
103
+ required_ruby_version: !ruby/object:Gem::Requirement
104
+ requirements:
105
+ - - ">="
106
+ - !ruby/object:Gem::Version
107
+ version: 2.6.0
108
+ required_rubygems_version: !ruby/object:Gem::Requirement
109
+ requirements:
110
+ - - ">="
111
+ - !ruby/object:Gem::Version
112
+ version: '0'
113
+ requirements: []
114
+ rubygems_version: 3.5.23
115
+ signing_key:
116
+ specification_version: 4
117
+ summary: A Ruby gem for posting to Bluesky
118
+ test_files: []