Compare commits
9 Commits
65b0c519aa
...
bluesky
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f2f8a9c34a | ||
|
|
36ceae80fe | ||
|
|
5f5a54d68f | ||
|
|
e30e20b033 | ||
|
|
608044e8fb | ||
|
|
9efeedd1ff | ||
|
|
3512c3f32e | ||
|
|
e9f3b0e822 | ||
|
|
6b8fce7ddc |
16
.cursorrules
16
.cursorrules
@@ -1,14 +1,22 @@
|
||||
# How to use this codebase
|
||||
|
||||
- ALWAYS run `srb tc` after making changes to Ruby files to ensure the codebase is typechecked.
|
||||
- ALWAYS run `bin/rspec <path_to_spec_file>` after a spec file is modified.
|
||||
- Run `bin/tapioca dsl` after changing a model or concern.
|
||||
- Run `bin/tapioca gems` after changing the Gemfile.
|
||||
- Run `srb tc` after making changes to Ruby files to ensure the codebase is typechecked.
|
||||
- Run `bin/rspec <path_to_spec_file>` after a spec file is modified.
|
||||
- Run `tapioca dsl` if models or concerns are modified.
|
||||
- After modifying a file that has a corresponding spec file, run `bin/rspec <path_to_spec_file>` to run just that spec file.
|
||||
- Run `bin/rspec <path_to_spec_file>` to run tests for a single file.
|
||||
- There are no view-specific tests, so if a view changes then run the controller tests instead.
|
||||
- For instance, if you modify `app/models/domain/post.rb`, run `bin/rspec spec/models/domain/post_spec.rb`. If you modify `app/views/domain/users/index.html.erb`, run `bin/rspec spec/controllers/domain/users_controller_spec.rb`.
|
||||
- At the end of a series of changes, ALWAYS run `just test` to run the entire test suite.
|
||||
- At the end of a long series of changes, run `just test`.
|
||||
- If specs are failing, then fix the failures, and rerun with `bin/rspec <path_to_spec_file>`.
|
||||
|
||||
# Typescript Development
|
||||
|
||||
- React is the only frontend framework used.
|
||||
- Styling is done with Tailwind CSS and FontAwesome.
|
||||
- Put new typescript files in `app/javascript/bundles/Main/components/`
|
||||
|
||||
# === BACKLOG.MD GUIDELINES START ===
|
||||
|
||||
# Instructions for the usage of Backlog.md CLI Tool
|
||||
|
||||
@@ -24,7 +24,8 @@
|
||||
"esbenp.prettier-vscode",
|
||||
"ms-azuretools.vscode-docker",
|
||||
"1YiB.rust-bundle",
|
||||
"rust-lang.rust-analyzer"
|
||||
"rust-lang.rust-analyzer",
|
||||
"saoudrizwan.claude-dev"
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
4
Gemfile
4
Gemfile
@@ -187,3 +187,7 @@ gem "sorbet-runtime", SORBET_VERSION
|
||||
gem "tapioca", "0.16.6", require: false, group: %i[development test]
|
||||
gem "rspec-sorbet", group: [:test]
|
||||
gem "sorbet-struct-comparable"
|
||||
|
||||
gem "skyfall", "~> 0.6.0"
|
||||
|
||||
gem "didkit", "~> 0.2.3"
|
||||
|
||||
18
Gemfile.lock
18
Gemfile.lock
@@ -110,6 +110,7 @@ GEM
|
||||
public_suffix (>= 2.0.2, < 7.0)
|
||||
attr_json (2.5.0)
|
||||
activerecord (>= 6.0.0, < 8.1)
|
||||
base32 (0.3.4)
|
||||
base64 (0.3.0)
|
||||
bcrypt (3.1.20)
|
||||
benchmark (0.4.1)
|
||||
@@ -129,6 +130,7 @@ GEM
|
||||
rack-test (>= 0.6.3)
|
||||
regexp_parser (>= 1.5, < 3.0)
|
||||
xpath (~> 3.2)
|
||||
cbor (0.5.9.9)
|
||||
charlock_holmes (0.7.9)
|
||||
cloudflare-rails (6.2.0)
|
||||
actionpack (>= 7.1.0, < 8.1.0)
|
||||
@@ -165,6 +167,7 @@ GEM
|
||||
warden (~> 1.2.3)
|
||||
dhash-vips (0.2.3.0)
|
||||
ruby-vips (~> 2.0, != 2.1.1, != 2.1.0)
|
||||
didkit (0.2.3)
|
||||
diff-lcs (1.5.1)
|
||||
discard (1.4.0)
|
||||
activerecord (>= 4.2, < 9.0)
|
||||
@@ -202,6 +205,7 @@ GEM
|
||||
erubi (1.13.1)
|
||||
et-orbi (1.2.11)
|
||||
tzinfo
|
||||
eventmachine (1.2.7)
|
||||
execjs (2.10.0)
|
||||
factory_bot (6.5.0)
|
||||
activesupport (>= 5.0.0)
|
||||
@@ -219,6 +223,9 @@ GEM
|
||||
multipart-post (~> 2.0)
|
||||
faraday-net_http (3.4.1)
|
||||
net-http (>= 0.5.0)
|
||||
faye-websocket (0.12.0)
|
||||
eventmachine (>= 0.12.0)
|
||||
websocket-driver (>= 0.8.0)
|
||||
ffi (1.17.1-aarch64-linux-gnu)
|
||||
ffi (1.17.1-aarch64-linux-musl)
|
||||
ffi (1.17.1-arm64-darwin)
|
||||
@@ -609,6 +616,12 @@ GEM
|
||||
semantic_range (>= 2.3.0)
|
||||
shoulda-matchers (6.4.0)
|
||||
activesupport (>= 5.2.0)
|
||||
skyfall (0.6.0)
|
||||
base32 (~> 0.3, >= 0.3.4)
|
||||
base64 (~> 0.1)
|
||||
cbor (~> 0.5, >= 0.5.9.6)
|
||||
eventmachine (~> 1.2, >= 1.2.7)
|
||||
faye-websocket (~> 0.12)
|
||||
sorbet (0.5.12221)
|
||||
sorbet-static (= 0.5.12221)
|
||||
sorbet-runtime (0.5.12221)
|
||||
@@ -693,7 +706,8 @@ GEM
|
||||
selenium-webdriver (~> 4.0, < 4.11)
|
||||
webrick (1.9.1)
|
||||
websocket (1.2.11)
|
||||
websocket-driver (0.7.6)
|
||||
websocket-driver (0.8.0)
|
||||
base64
|
||||
websocket-extensions (>= 0.1.0)
|
||||
websocket-extensions (0.1.5)
|
||||
xpath (3.2.0)
|
||||
@@ -733,6 +747,7 @@ DEPENDENCIES
|
||||
debug (~> 1.11)
|
||||
devise (~> 4.9)
|
||||
dhash-vips
|
||||
didkit (~> 0.2.3)
|
||||
discard
|
||||
disco
|
||||
docx
|
||||
@@ -787,6 +802,7 @@ DEPENDENCIES
|
||||
selenium-webdriver
|
||||
shakapacker (~> 6.6)
|
||||
shoulda-matchers
|
||||
skyfall (~> 0.6.0)
|
||||
sorbet (= 0.5.12221)
|
||||
sorbet-runtime (= 0.5.12221)
|
||||
sorbet-struct-comparable
|
||||
|
||||
BIN
app/assets/images/domain-icons/bluesky.png
Normal file
BIN
app/assets/images/domain-icons/bluesky.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 7.6 KiB |
@@ -19,6 +19,8 @@ module Domain::DomainModelHelper
|
||||
"Inkbunny"
|
||||
when Domain::DomainType::Sofurry
|
||||
"Sofurry"
|
||||
when Domain::DomainType::Bluesky
|
||||
"Bluesky"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -33,6 +35,8 @@ module Domain::DomainModelHelper
|
||||
"IB"
|
||||
when Domain::DomainType::Sofurry
|
||||
"SF"
|
||||
when Domain::DomainType::Bluesky
|
||||
"BSKY"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -6,5 +6,6 @@ class Domain::DomainType < T::Enum
|
||||
E621 = new
|
||||
Inkbunny = new
|
||||
Sofurry = new
|
||||
Bluesky = new
|
||||
end
|
||||
end
|
||||
|
||||
@@ -48,6 +48,11 @@ module Domain::PostsHelper
|
||||
domain_icon_path: "domain-icons/sofurry.png",
|
||||
domain_icon_title: "SoFurry",
|
||||
),
|
||||
Domain::DomainType::Bluesky =>
|
||||
DomainData.new(
|
||||
domain_icon_path: "domain-icons/bluesky.png",
|
||||
domain_icon_title: "Bluesky",
|
||||
),
|
||||
},
|
||||
T::Hash[Domain::DomainType, DomainData],
|
||||
)
|
||||
@@ -189,6 +194,89 @@ module Domain::PostsHelper
|
||||
file.log_entry&.response_size&.then { |size| number_to_human_size(size) }
|
||||
end
|
||||
|
||||
sig do
|
||||
params(
|
||||
ok_files: T::Array[Domain::PostFile],
|
||||
initial_file_index: T.nilable(Integer),
|
||||
).returns(T::Hash[Symbol, T.untyped])
|
||||
end
|
||||
def props_for_post_files(ok_files, initial_file_index: nil)
|
||||
files_data =
|
||||
ok_files.map.with_index do |file, index|
|
||||
thumbnail_path = nil
|
||||
content_html = nil
|
||||
file_details_html = nil
|
||||
|
||||
if file.log_entry&.status_code == 200
|
||||
log_entry = file.log_entry
|
||||
|
||||
# Generate thumbnail path
|
||||
begin
|
||||
if log_entry && (response_sha256 = log_entry.response_sha256)
|
||||
thumbnail_path =
|
||||
blob_path(
|
||||
HexUtil.bin2hex(response_sha256),
|
||||
format: "jpg",
|
||||
thumb: "small",
|
||||
)
|
||||
end
|
||||
rescue StandardError
|
||||
# thumbnail_path remains nil
|
||||
end
|
||||
|
||||
# Generate content HTML
|
||||
begin
|
||||
content_html =
|
||||
ApplicationController.renderer.render(
|
||||
partial: "log_entries/content_container",
|
||||
locals: {
|
||||
log_entry: log_entry,
|
||||
},
|
||||
assigns: {
|
||||
current_user: nil,
|
||||
},
|
||||
)
|
||||
rescue StandardError
|
||||
# content_html remains nil
|
||||
end
|
||||
|
||||
# Generate file details HTML
|
||||
begin
|
||||
file_details_html =
|
||||
ApplicationController.renderer.render(
|
||||
partial: "log_entries/file_details_sky_section",
|
||||
locals: {
|
||||
log_entry: log_entry,
|
||||
},
|
||||
assigns: {
|
||||
current_user: nil,
|
||||
},
|
||||
)
|
||||
rescue StandardError
|
||||
# file_details_html remains nil
|
||||
end
|
||||
end
|
||||
|
||||
{
|
||||
id: file.id,
|
||||
thumbnailPath: thumbnail_path,
|
||||
hasContent: file.log_entry&.status_code == 200,
|
||||
index: index,
|
||||
contentHtml: content_html,
|
||||
fileDetailsHtml: file_details_html,
|
||||
}
|
||||
end
|
||||
|
||||
# Validate initial_file_index
|
||||
validated_initial_index = 0
|
||||
if initial_file_index && initial_file_index >= 0 &&
|
||||
initial_file_index < ok_files.count
|
||||
validated_initial_index = initial_file_index
|
||||
end
|
||||
|
||||
{ files: files_data, initialSelectedIndex: validated_initial_index }
|
||||
end
|
||||
|
||||
sig { params(url: String).returns(T.nilable(String)) }
|
||||
def icon_asset_for_url(url)
|
||||
domain = extract_domain(url)
|
||||
|
||||
@@ -102,6 +102,8 @@ module Domain::UsersHelper
|
||||
asset_path("domain-icons/inkbunny.png")
|
||||
when Domain::User::SofurryUser
|
||||
asset_path("domain-icons/sofurry.png")
|
||||
when Domain::User::BlueskyUser
|
||||
asset_path("domain-icons/bluesky.png")
|
||||
else
|
||||
Kernel.raise "Unknown user type: #{user.class}"
|
||||
end
|
||||
|
||||
32
app/javascript/bundles/Main/components/DisplayedFile.tsx
Normal file
32
app/javascript/bundles/Main/components/DisplayedFile.tsx
Normal file
@@ -0,0 +1,32 @@
|
||||
import * as React from 'react';
|
||||
import { FileData } from './PostFiles';
|
||||
import { FileDetails } from './FileDetails';
|
||||
|
||||
interface DisplayedFileProps {
|
||||
file: FileData;
|
||||
}
|
||||
|
||||
export const DisplayedFile: React.FC<DisplayedFileProps> = ({ file }) => {
|
||||
return (
|
||||
<>
|
||||
{/* File content display */}
|
||||
<div className="file-content-display mb-4">
|
||||
{file.contentHtml ? (
|
||||
<div dangerouslySetInnerHTML={{ __html: file.contentHtml }} />
|
||||
) : (
|
||||
<section className="flex grow justify-center text-slate-500">
|
||||
<div>
|
||||
<i className="fa-solid fa-file-arrow-down"></i>
|
||||
No file content available
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* File details */}
|
||||
{file.fileDetailsHtml && <FileDetails html={file.fileDetailsHtml} />}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default DisplayedFile;
|
||||
80
app/javascript/bundles/Main/components/FileCarousel.tsx
Normal file
80
app/javascript/bundles/Main/components/FileCarousel.tsx
Normal file
@@ -0,0 +1,80 @@
|
||||
import * as React from 'react';
|
||||
import { FileData } from './PostFiles';
|
||||
|
||||
interface FileCarouselProps {
|
||||
files: FileData[];
|
||||
totalFiles: number;
|
||||
selectedIndex: number;
|
||||
onFileSelect: (fileId: number, index: number) => void;
|
||||
}
|
||||
|
||||
export const FileCarousel: React.FC<FileCarouselProps> = ({
|
||||
files,
|
||||
totalFiles,
|
||||
selectedIndex,
|
||||
onFileSelect,
|
||||
}) => {
|
||||
const handleFileClick = (file: FileData) => {
|
||||
onFileSelect(file.id, file.index);
|
||||
};
|
||||
|
||||
// Only render if there are multiple files
|
||||
if (files.length <= 1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="mb-4">
|
||||
<div className="flex gap-2 overflow-x-auto" id="file-carousel">
|
||||
{files.map((file) => {
|
||||
const isSelected = file.index === selectedIndex;
|
||||
const buttonClasses = [
|
||||
'flex-shrink-0',
|
||||
'w-20',
|
||||
'h-20',
|
||||
'rounded-md',
|
||||
'border-2',
|
||||
'transition-all',
|
||||
'duration-200',
|
||||
'hover:border-blue-400',
|
||||
isSelected ? 'border-blue-500' : 'border-gray-300',
|
||||
];
|
||||
|
||||
if (file.thumbnailPath) {
|
||||
buttonClasses.push('overflow-hidden');
|
||||
} else {
|
||||
buttonClasses.push(
|
||||
'bg-gray-100',
|
||||
'flex',
|
||||
'items-center',
|
||||
'justify-center',
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<button
|
||||
key={file.id}
|
||||
className={buttonClasses.join(' ')}
|
||||
onClick={() => handleFileClick(file)}
|
||||
data-file-id={file.id}
|
||||
data-index={file.index}
|
||||
title={`File ${file.index + 1} of ${totalFiles}`}
|
||||
>
|
||||
{file.thumbnailPath ? (
|
||||
<img
|
||||
src={file.thumbnailPath}
|
||||
className="h-full w-full object-cover"
|
||||
alt={`File ${file.index + 1}`}
|
||||
/>
|
||||
) : (
|
||||
<i className="fa-solid fa-file text-gray-400"></i>
|
||||
)}
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default FileCarousel;
|
||||
16
app/javascript/bundles/Main/components/FileDetails.tsx
Normal file
16
app/javascript/bundles/Main/components/FileDetails.tsx
Normal file
@@ -0,0 +1,16 @@
|
||||
import * as React from 'react';
|
||||
|
||||
interface FileDetailsProps {
|
||||
html: string;
|
||||
}
|
||||
|
||||
export const FileDetails: React.FC<FileDetailsProps> = ({ html }) => {
|
||||
return (
|
||||
<div
|
||||
className="file-details-section"
|
||||
dangerouslySetInnerHTML={{ __html: html }}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default FileDetails;
|
||||
99
app/javascript/bundles/Main/components/PostFiles.tsx
Normal file
99
app/javascript/bundles/Main/components/PostFiles.tsx
Normal file
@@ -0,0 +1,99 @@
|
||||
import * as React from 'react';
|
||||
import { useState, useEffect, useCallback } from 'react';
|
||||
import { FileCarousel } from './FileCarousel';
|
||||
import { DisplayedFile } from './DisplayedFile';
|
||||
|
||||
export interface FileData {
|
||||
id: number;
|
||||
thumbnailPath?: string;
|
||||
hasContent: boolean;
|
||||
index: number;
|
||||
contentHtml?: string;
|
||||
fileDetailsHtml?: string;
|
||||
}
|
||||
|
||||
interface PostFilesProps {
|
||||
files: FileData[];
|
||||
initialSelectedIndex?: number;
|
||||
}
|
||||
|
||||
export const PostFiles: React.FC<PostFilesProps> = ({
|
||||
files,
|
||||
initialSelectedIndex = 0,
|
||||
}) => {
|
||||
const [selectedIndex, setSelectedIndex] = useState(initialSelectedIndex);
|
||||
|
||||
// Update URL parameter when selected file changes
|
||||
const updateUrlWithFileIndex = (index: number) => {
|
||||
if (typeof window === 'undefined' || files.length <= 1) return;
|
||||
|
||||
const url = new URL(window.location.href);
|
||||
url.searchParams.set('idx', index.toString());
|
||||
window.history.replaceState({}, '', url.toString());
|
||||
};
|
||||
|
||||
const handleFileSelect = (fileId: number, index: number) => {
|
||||
setSelectedIndex(index);
|
||||
updateUrlWithFileIndex(index);
|
||||
};
|
||||
|
||||
const navigateToNextFile = () => {
|
||||
if (files.length > 1) {
|
||||
const nextIndex = (selectedIndex + 1) % files.length;
|
||||
handleFileSelect(files[nextIndex].id, nextIndex);
|
||||
}
|
||||
};
|
||||
|
||||
const navigateToPreviousFile = () => {
|
||||
if (files.length > 1) {
|
||||
const prevIndex = (selectedIndex - 1 + files.length) % files.length;
|
||||
handleFileSelect(files[prevIndex].id, prevIndex);
|
||||
}
|
||||
};
|
||||
|
||||
// Add keyboard navigation
|
||||
useEffect(() => {
|
||||
const handleKeyDown = (event: KeyboardEvent) => {
|
||||
// Only handle arrow keys if we have multiple files
|
||||
if (files.length <= 1) return;
|
||||
|
||||
switch (event.key) {
|
||||
case 'ArrowLeft':
|
||||
event.preventDefault();
|
||||
navigateToPreviousFile();
|
||||
break;
|
||||
case 'ArrowRight':
|
||||
event.preventDefault();
|
||||
navigateToNextFile();
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
// Add event listener to document
|
||||
document.addEventListener('keydown', handleKeyDown);
|
||||
|
||||
// Cleanup event listener on unmount
|
||||
return () => {
|
||||
document.removeEventListener('keydown', handleKeyDown);
|
||||
};
|
||||
}, [selectedIndex, files.length]);
|
||||
|
||||
const selectedFile = files[selectedIndex];
|
||||
|
||||
return (
|
||||
<section id="file-display-section">
|
||||
{files.length > 1 && (
|
||||
<FileCarousel
|
||||
files={files}
|
||||
totalFiles={files.length}
|
||||
selectedIndex={selectedIndex}
|
||||
onFileSelect={handleFileSelect}
|
||||
/>
|
||||
)}
|
||||
|
||||
{selectedFile && <DisplayedFile file={selectedFile} />}
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export default PostFiles;
|
||||
@@ -4,6 +4,7 @@ import UserSearchBar from '../bundles/Main/components/UserSearchBar';
|
||||
import { PostHoverPreviewWrapper } from '../bundles/Main/components/PostHoverPreviewWrapper';
|
||||
import { UserHoverPreviewWrapper } from '../bundles/Main/components/UserHoverPreviewWrapper';
|
||||
import { TrackedObjectsChart } from '../bundles/Main/components/TrackedObjectsChart';
|
||||
import { PostFiles } from '../bundles/Main/components/PostFiles';
|
||||
import { initCollapsibleSections } from '../bundles/UI/collapsibleSections';
|
||||
import { IpAddressInput } from '../bundles/UI/components';
|
||||
import { StatsPage } from '../bundles/Main/components/StatsPage';
|
||||
@@ -16,6 +17,7 @@ ReactOnRails.register({
|
||||
PostHoverPreviewWrapper,
|
||||
UserHoverPreviewWrapper,
|
||||
TrackedObjectsChart,
|
||||
PostFiles,
|
||||
IpAddressInput,
|
||||
StatsPage,
|
||||
VisualSearchForm,
|
||||
|
||||
@@ -3,10 +3,12 @@ import ReactOnRails from 'react-on-rails';
|
||||
import UserSearchBar from '../bundles/Main/components/UserSearchBarServer';
|
||||
import { PostHoverPreviewWrapper } from '../bundles/Main/components/PostHoverPreviewWrapper';
|
||||
import { UserHoverPreviewWrapper } from '../bundles/Main/components/UserHoverPreviewWrapper';
|
||||
import { PostFiles } from '../bundles/Main/components/PostFiles';
|
||||
|
||||
// This is how react_on_rails can see the UserSearchBar in the browser.
|
||||
ReactOnRails.register({
|
||||
UserSearchBar,
|
||||
PostHoverPreviewWrapper,
|
||||
UserHoverPreviewWrapper,
|
||||
PostFiles,
|
||||
});
|
||||
|
||||
43
app/jobs/domain/bluesky/job/base.rb
Normal file
43
app/jobs/domain/bluesky/job/base.rb
Normal file
@@ -0,0 +1,43 @@
|
||||
# typed: strict
|
||||
class Domain::Bluesky::Job::Base < Scraper::JobBase
|
||||
abstract!
|
||||
discard_on ActiveJob::DeserializationError
|
||||
include HasBulkEnqueueJobs
|
||||
|
||||
sig { override.returns(Symbol) }
|
||||
def self.http_factory_method
|
||||
:get_generic_http_client
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
sig { returns(T.nilable(Domain::User::BlueskyUser)) }
|
||||
def user_from_args
|
||||
if (user = arguments[0][:user]).is_a?(Domain::User::BlueskyUser)
|
||||
user
|
||||
elsif (did = arguments[0][:did]).present?
|
||||
Domain::User::BlueskyUser.find_or_initialize_by(did: did)
|
||||
elsif (handle = arguments[0][:handle]).present?
|
||||
resolver = DIDKit::Resolver.new
|
||||
resolved =
|
||||
resolver.resolve_handle(handle) ||
|
||||
fatal_error("failed to resolve handle: #{handle}")
|
||||
Domain::User::BlueskyUser.find_or_initialize_by(
|
||||
did: resolved.did,
|
||||
) { |user| user.handle = handle }
|
||||
else
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
sig { returns(Domain::User::BlueskyUser) }
|
||||
def user_from_args!
|
||||
T.must(user_from_args)
|
||||
end
|
||||
|
||||
sig { params(user: Domain::User::BlueskyUser).returns(T::Boolean) }
|
||||
def buggy_user?(user)
|
||||
# Add any known problematic handles/DIDs here
|
||||
false
|
||||
end
|
||||
end
|
||||
310
app/jobs/domain/bluesky/job/scan_user_job.rb
Normal file
310
app/jobs/domain/bluesky/job/scan_user_job.rb
Normal file
@@ -0,0 +1,310 @@
|
||||
# typed: strict
|
||||
class Domain::Bluesky::Job::ScanUserJob < Domain::Bluesky::Job::Base
|
||||
self.default_priority = -30
|
||||
|
||||
sig { override.params(args: T::Hash[Symbol, T.untyped]).returns(T.untyped) }
|
||||
def perform(args)
|
||||
user = user_from_args!
|
||||
logger.push_tags(make_arg_tag(user))
|
||||
logger.info("Starting Bluesky user scan for #{user.handle}")
|
||||
|
||||
return if buggy_user?(user)
|
||||
|
||||
# Scan user profile/bio
|
||||
user = scan_user_profile(user) if force_scan? ||
|
||||
user.scanned_profile_at.nil? || due_for_profile_scan?(user)
|
||||
|
||||
# Scan user's historical posts
|
||||
if user.state_ok? &&
|
||||
(
|
||||
force_scan? || user.scanned_posts_at.nil? ||
|
||||
due_for_posts_scan?(user)
|
||||
)
|
||||
scan_user_posts(user)
|
||||
end
|
||||
|
||||
logger.info("Completed Bluesky user scan")
|
||||
ensure
|
||||
user.save! if user
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
sig do
|
||||
params(user: Domain::User::BlueskyUser).returns(Domain::User::BlueskyUser)
|
||||
end
|
||||
def scan_user_profile(user)
|
||||
logger.info("Scanning user profile for #{user.handle}")
|
||||
|
||||
# Use AT Protocol API to get user profile
|
||||
profile_url =
|
||||
"https://bsky.social/xrpc/com.atproto.repo.getRecord?repo=#{user.did}&collection=app.bsky.actor.profile&rkey=self"
|
||||
|
||||
response = http_client.get(profile_url)
|
||||
if response.status_code != 200
|
||||
logger.error("Failed to get user profile: #{response.status_code}")
|
||||
user.state_error!
|
||||
return user
|
||||
end
|
||||
|
||||
# Note: Store log entry reference if needed for debugging
|
||||
|
||||
begin
|
||||
profile_data = JSON.parse(response.body)
|
||||
|
||||
if profile_data["error"]
|
||||
logger.error("Profile API error: #{profile_data["error"]}")
|
||||
user.state_error!
|
||||
return user
|
||||
end
|
||||
|
||||
record = profile_data["value"]
|
||||
if record
|
||||
# Update user profile information
|
||||
user.description = record["description"]
|
||||
user.display_name = record["displayName"]
|
||||
user.profile_raw = record
|
||||
|
||||
# Process avatar if present
|
||||
if record["avatar"] && record["avatar"]["ref"]
|
||||
process_user_avatar(user, record["avatar"])
|
||||
end
|
||||
end
|
||||
|
||||
user.scanned_profile_at = Time.current
|
||||
user.state_ok! unless user.state_error?
|
||||
rescue JSON::ParserError => e
|
||||
logger.error("Failed to parse profile JSON: #{e.message}")
|
||||
user.state_error!
|
||||
end
|
||||
|
||||
user
|
||||
end
|
||||
|
||||
sig { params(user: Domain::User::BlueskyUser).void }
|
||||
def scan_user_posts(user)
|
||||
logger.info("Scanning historical posts for #{user.handle}")
|
||||
|
||||
# Use AT Protocol API to list user's posts
|
||||
posts_url =
|
||||
"https://bsky.social/xrpc/com.atproto.repo.listRecords?repo=#{user.did}&collection=app.bsky.feed.post&limit=100"
|
||||
|
||||
cursor = T.let(nil, T.nilable(String))
|
||||
posts_processed = 0
|
||||
posts_with_media = 0
|
||||
|
||||
loop do
|
||||
url = cursor ? "#{posts_url}&cursor=#{cursor}" : posts_url
|
||||
|
||||
response = http_client.get(url)
|
||||
if response.status_code != 200
|
||||
logger.error("Failed to get user posts: #{response.status_code}")
|
||||
break
|
||||
end
|
||||
|
||||
begin
|
||||
data = JSON.parse(response.body)
|
||||
|
||||
if data["error"]
|
||||
logger.error("Posts API error: #{data["error"]}")
|
||||
break
|
||||
end
|
||||
|
||||
records = data["records"] || []
|
||||
|
||||
records.each do |record_data|
|
||||
posts_processed += 1
|
||||
|
||||
record = record_data["value"]
|
||||
next unless record && record["embed"]
|
||||
|
||||
# Only process posts with media
|
||||
posts_with_media += 1
|
||||
user_did = user.did
|
||||
next unless user_did
|
||||
process_historical_post(user, record_data, record, user_did)
|
||||
end
|
||||
|
||||
cursor = data["cursor"]
|
||||
break if cursor.nil? || records.empty?
|
||||
|
||||
# Add small delay to avoid rate limiting
|
||||
sleep(0.1)
|
||||
rescue JSON::ParserError => e
|
||||
logger.error("Failed to parse posts JSON: #{e.message}")
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
user.scanned_posts_at = Time.current
|
||||
logger.info(
|
||||
"Processed #{posts_processed} posts, #{posts_with_media} with media",
|
||||
)
|
||||
end
|
||||
|
||||
sig do
|
||||
params(
|
||||
user: Domain::User::BlueskyUser,
|
||||
record_data: T::Hash[String, T.untyped],
|
||||
record: T::Hash[String, T.untyped],
|
||||
user_did: String,
|
||||
).void
|
||||
end
|
||||
def process_historical_post(user, record_data, record, user_did)
|
||||
uri = record_data["uri"]
|
||||
rkey = record_data["uri"].split("/").last
|
||||
|
||||
# Check if we already have this post
|
||||
existing_post = Domain::Post::BlueskyPost.find_by(at_uri: uri)
|
||||
return if existing_post
|
||||
|
||||
begin
|
||||
post =
|
||||
Domain::Post::BlueskyPost.create!(
|
||||
at_uri: uri,
|
||||
bluesky_rkey: rkey,
|
||||
text: record["text"] || "",
|
||||
bluesky_created_at: Time.parse(record["createdAt"]),
|
||||
post_raw: record,
|
||||
)
|
||||
|
||||
post.creator = user
|
||||
post.save!
|
||||
|
||||
# Process media if present
|
||||
process_post_media(post, record["embed"], user_did) if record["embed"]
|
||||
|
||||
logger.debug("Created historical post: #{post.bluesky_rkey}")
|
||||
rescue => e
|
||||
logger.error("Failed to create historical post #{rkey}: #{e.message}")
|
||||
end
|
||||
end
|
||||
|
||||
sig do
|
||||
params(
|
||||
post: Domain::Post::BlueskyPost,
|
||||
embed_data: T::Hash[String, T.untyped],
|
||||
did: String,
|
||||
).void
|
||||
end
|
||||
def process_post_media(post, embed_data, did)
|
||||
case embed_data["$type"]
|
||||
when "app.bsky.embed.images"
|
||||
process_post_images(post, embed_data["images"], did)
|
||||
when "app.bsky.embed.recordWithMedia"
|
||||
if embed_data["media"] &&
|
||||
embed_data["media"]["$type"] == "app.bsky.embed.images"
|
||||
process_post_images(post, embed_data["media"]["images"], did)
|
||||
end
|
||||
when "app.bsky.embed.external"
|
||||
process_external_embed(post, embed_data["external"], did)
|
||||
end
|
||||
end
|
||||
|
||||
sig do
|
||||
params(
|
||||
post: Domain::Post::BlueskyPost,
|
||||
images: T::Array[T::Hash[String, T.untyped]],
|
||||
did: String,
|
||||
).void
|
||||
end
|
||||
def process_post_images(post, images, did)
|
||||
files = []
|
||||
images.each_with_index do |image_data, index|
|
||||
blob_data = image_data["image"]
|
||||
next unless blob_data && blob_data["ref"]
|
||||
|
||||
post_file =
|
||||
post.files.build(
|
||||
type: "Domain::PostFile::BlueskyPostFile",
|
||||
file_order: index,
|
||||
url_str: construct_blob_url(did, blob_data["ref"]["$link"]),
|
||||
state: "pending",
|
||||
alt_text: image_data["alt"],
|
||||
blob_ref: blob_data["ref"]["$link"],
|
||||
)
|
||||
|
||||
# Store aspect ratio if present
|
||||
if image_data["aspectRatio"]
|
||||
post_file.aspect_ratio_width = image_data["aspectRatio"]["width"]
|
||||
post_file.aspect_ratio_height = image_data["aspectRatio"]["height"]
|
||||
end
|
||||
|
||||
post_file.save!
|
||||
Domain::StaticFileJob.perform_later({ post_file: })
|
||||
files << post_file
|
||||
end
|
||||
|
||||
logger.debug(
|
||||
"Created #{files.size} #{"file".pluralize(files.size)} for historical post: #{post.bluesky_rkey}",
|
||||
)
|
||||
end
|
||||
|
||||
sig do
|
||||
params(
|
||||
post: Domain::Post::BlueskyPost,
|
||||
external_data: T::Hash[String, T.untyped],
|
||||
did: String,
|
||||
).void
|
||||
end
|
||||
def process_external_embed(post, external_data, did)
|
||||
thumb_data = external_data["thumb"]
|
||||
return unless thumb_data && thumb_data["ref"]
|
||||
|
||||
post_file =
|
||||
post.files.build(
|
||||
type: "Domain::PostFile::BlueskyPostFile",
|
||||
file_order: 0,
|
||||
url_str: construct_blob_url(did, thumb_data["ref"]["$link"]),
|
||||
state: "pending",
|
||||
blob_ref: thumb_data["ref"]["$link"],
|
||||
)
|
||||
|
||||
post_file.save!
|
||||
Domain::StaticFileJob.perform_later({ post_file: })
|
||||
|
||||
logger.debug(
|
||||
"Created external thumbnail for historical post: #{post.bluesky_rkey}",
|
||||
)
|
||||
end
|
||||
|
||||
sig do
|
||||
params(
|
||||
user: Domain::User::BlueskyUser,
|
||||
avatar_data: T::Hash[String, T.untyped],
|
||||
).void
|
||||
end
|
||||
def process_user_avatar(user, avatar_data)
|
||||
return if user.avatar.present?
|
||||
return unless avatar_data["ref"]
|
||||
|
||||
user_did = user.did
|
||||
return unless user_did
|
||||
|
||||
user.create_avatar!(
|
||||
url_str: construct_blob_url(user_did, avatar_data["ref"]["$link"]),
|
||||
)
|
||||
|
||||
# Enqueue avatar download job if we had one
|
||||
logger.debug("Created avatar for user: #{user.handle}")
|
||||
end
|
||||
|
||||
sig { params(did: String, cid: String).returns(String) }
|
||||
def construct_blob_url(did, cid)
|
||||
"https://bsky.social/xrpc/com.atproto.sync.getBlob?did=#{did}&cid=#{cid}"
|
||||
end
|
||||
|
||||
sig { params(user: Domain::User::BlueskyUser).returns(T::Boolean) }
|
||||
def due_for_profile_scan?(user)
|
||||
scanned_at = user.scanned_profile_at
|
||||
return true if scanned_at.nil?
|
||||
scanned_at < 1.month.ago
|
||||
end
|
||||
|
||||
sig { params(user: Domain::User::BlueskyUser).returns(T::Boolean) }
|
||||
def due_for_posts_scan?(user)
|
||||
scanned_at = user.scanned_posts_at
|
||||
return true if scanned_at.nil?
|
||||
scanned_at < 1.week.ago
|
||||
end
|
||||
end
|
||||
@@ -2,7 +2,11 @@
|
||||
class Domain::StaticFileJob < Scraper::JobBase
|
||||
include Domain::StaticFileJobHelper
|
||||
queue_as :static_file
|
||||
abstract!
|
||||
|
||||
sig { override.returns(Symbol) }
|
||||
def self.http_factory_method
|
||||
:get_generic_http_client
|
||||
end
|
||||
|
||||
sig { override.params(args: T::Hash[Symbol, T.untyped]).returns(T.untyped) }
|
||||
def perform(args)
|
||||
|
||||
@@ -127,7 +127,7 @@ class Scraper::JobBase < ApplicationJob
|
||||
|
||||
sig { returns(Domain::PostFile) }
|
||||
def post_file_from_args!
|
||||
T.cast(arguments[0][:file], Domain::PostFile)
|
||||
T.cast(arguments[0][:file] || arguments[0][:post_file], Domain::PostFile)
|
||||
end
|
||||
|
||||
# The primary log entry for this job. Typically, this is the first request
|
||||
|
||||
@@ -42,7 +42,7 @@ class Scraper::ClientFactory
|
||||
end
|
||||
|
||||
def self.get_generic_http_client
|
||||
if Rails.env.test? || Rails.env.development?
|
||||
if Rails.env.test?
|
||||
@http_client_mock || raise("no http client mock set")
|
||||
else
|
||||
_http_client_impl(:generic, Scraper::GenericHttpClientConfig)
|
||||
|
||||
@@ -65,10 +65,12 @@ class Scraper::CurlHttpPerformer
|
||||
curl = get_curl
|
||||
start_at = Time.now
|
||||
|
||||
curl.url = request.uri.normalize.to_s
|
||||
# TODO - normalizing the URL breaks URLs with utf-8 characters
|
||||
# curl.url = request.uri.normalize.to_s
|
||||
curl.url = request.uri.to_s
|
||||
curl.follow_location = request.follow_redirects
|
||||
request.request_headers.each { |key, value| curl.headers[key.to_s] = value }
|
||||
curl.headers["User-Agent"] = "FurryArchiver/1.0 / dhelta"
|
||||
curl.headers["User-Agent"] = "FurryArchiver/1.0 / telegram: @DeltaNoises"
|
||||
case request.http_method
|
||||
when Method::Get
|
||||
curl.get
|
||||
|
||||
5
app/lib/tasks/bluesky.rb
Normal file
5
app/lib/tasks/bluesky.rb
Normal file
@@ -0,0 +1,5 @@
|
||||
# typed: true
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Tasks::Bluesky
|
||||
end
|
||||
266
app/lib/tasks/bluesky/monitor.rb
Normal file
266
app/lib/tasks/bluesky/monitor.rb
Normal file
@@ -0,0 +1,266 @@
|
||||
# typed: strict
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Tasks::Bluesky
|
||||
class Monitor
|
||||
include HasColorLogger
|
||||
extend T::Sig
|
||||
CURSOR_KEY = "task-bluesky-jetstream-cursor-1"
|
||||
|
||||
sig { params(pg_notify: T::Boolean).void }
|
||||
def initialize(pg_notify: true)
|
||||
@pg_notify = pg_notify
|
||||
@resolver = T.let(DIDKit::Resolver.new, DIDKit::Resolver)
|
||||
@dids = T.let(Concurrent::Set.new, Concurrent::Set)
|
||||
@dids.merge(Bluesky::MonitoredDid.pluck(:did))
|
||||
logger.info(
|
||||
"loaded #{@dids.size} #{"did".pluralize(@dids.size)} from database",
|
||||
)
|
||||
logger.info("dids: #{@dids.to_a.join(", ")}")
|
||||
|
||||
@bluesky_client =
|
||||
T.let(
|
||||
Skyfall::Jetstream.new(
|
||||
"jetstream2.us-east.bsky.network",
|
||||
{
|
||||
cursor: nil,
|
||||
# cursor: load_cursor,
|
||||
wanted_collections: %w[
|
||||
app.bsky.feed.post
|
||||
app.bsky.embed.images
|
||||
app.bsky.embed.recordWithMedia
|
||||
],
|
||||
},
|
||||
),
|
||||
Skyfall::Jetstream,
|
||||
)
|
||||
@bluesky_client.user_agent = "ReFurrer/1.0 (bsky:delta.refurrer.com)"
|
||||
end
|
||||
|
||||
sig { void }
|
||||
def run
|
||||
@bluesky_client.on_connecting { logger.info("connecting...") }
|
||||
@bluesky_client.on_connect { logger.info("connected") }
|
||||
@bluesky_client.on_disconnect { logger.info("disconnected") }
|
||||
@bluesky_client.on_reconnect do
|
||||
logger.info("connection lost, trying to reconnect...")
|
||||
end
|
||||
@bluesky_client.on_timeout do
|
||||
logger.info("connection stalled, triggering a reconnect...")
|
||||
end
|
||||
@bluesky_client.on_message do |msg|
|
||||
handle_message(msg)
|
||||
if msg.seq % 10_000 == 0
|
||||
logger.info("saving cursor: #{msg.seq.to_s.bold}")
|
||||
save_cursor(msg.seq)
|
||||
end
|
||||
end
|
||||
@bluesky_client.on_error { |e| logger.error("ERROR: #{e.to_s.red.bold}") }
|
||||
|
||||
# Start the thread to listen to postgres NOTIFYs to add to the @dids set
|
||||
pg_notify_thread =
|
||||
Thread.new { listen_to_postgres_notifies } if @pg_notify
|
||||
|
||||
@bluesky_client.connect
|
||||
rescue Interrupt
|
||||
logger.info("shutting down...")
|
||||
@bluesky_client.disconnect
|
||||
@bluesky_client.close
|
||||
pg_notify_thread&.raise(Interrupt)
|
||||
pg_notify_thread&.join
|
||||
logger.info("shutdown complete")
|
||||
end
|
||||
|
||||
sig { params(msg: Skyfall::Jetstream::Message).void }
|
||||
def handle_message(msg)
|
||||
case msg
|
||||
when Skyfall::Jetstream::CommitMessage
|
||||
handle_commit_message(msg)
|
||||
end
|
||||
end
|
||||
|
||||
sig { params(msg: Skyfall::Jetstream::CommitMessage).void }
|
||||
def handle_commit_message(msg)
|
||||
return unless msg.type == :commit
|
||||
return unless @dids.include?(msg.did)
|
||||
msg.operations.each do |op|
|
||||
next unless op.action == :create && op.type == :bsky_post
|
||||
embed_data =
|
||||
T.let(op.raw_record["embed"], T.nilable(T::Hash[String, T.untyped]))
|
||||
next unless embed_data
|
||||
|
||||
post =
|
||||
Domain::Post::BlueskyPost.find_or_create_by!(at_uri: op.uri) do |post|
|
||||
post.bluesky_rkey = op.rkey
|
||||
post.text = op.raw_record["text"]
|
||||
post.bluesky_created_at = msg.time.in_time_zone("UTC")
|
||||
post.creator = creator_for(msg)
|
||||
post.post_raw = op.raw_record
|
||||
end
|
||||
|
||||
process_media(post, embed_data, msg.did)
|
||||
|
||||
logger.info(
|
||||
"created bluesky post: `#{post.bluesky_rkey}` / `#{post.at_uri}`",
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
sig do
|
||||
params(msg: Skyfall::Jetstream::CommitMessage).returns(
|
||||
T.nilable(Domain::User::BlueskyUser),
|
||||
)
|
||||
end
|
||||
def creator_for(msg)
|
||||
did = msg.did
|
||||
Domain::User::BlueskyUser.find_or_create_by!(did:) do |creator|
|
||||
creator.handle = @resolver.get_validated_handle(did) || did
|
||||
logger.info(
|
||||
"created bluesky user: `#{creator.handle}` / `#{creator.did}`",
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
sig { returns(T.nilable(Integer)) }
|
||||
def load_cursor
|
||||
GlobalState.get(CURSOR_KEY)&.to_i
|
||||
end
|
||||
|
||||
sig { params(cursor: Integer).void }
|
||||
def save_cursor(cursor)
|
||||
GlobalState.set(CURSOR_KEY, cursor.to_s)
|
||||
end
|
||||
|
||||
sig { void }
|
||||
def listen_to_postgres_notifies
|
||||
logger.info("listening to postgres NOTIFYs")
|
||||
ActiveRecord::Base.connection_pool.with_connection do |conn|
|
||||
conn = T.cast(conn, ActiveRecord::ConnectionAdapters::PostgreSQLAdapter)
|
||||
conn.exec_query("LISTEN #{Bluesky::MonitoredDid::ADDED_NOTIFY_CHANNEL}")
|
||||
conn.exec_query(
|
||||
"LISTEN #{Bluesky::MonitoredDid::REMOVED_NOTIFY_CHANNEL}",
|
||||
)
|
||||
conn.raw_connection.wait_for_notify do |event, pid, payload|
|
||||
logger.info("NOTIFY: #{event} / pid: #{pid} / payload: #{payload}")
|
||||
case event
|
||||
when Bluesky::MonitoredDid::ADDED_NOTIFY_CHANNEL
|
||||
@dids.add(payload)
|
||||
when Bluesky::MonitoredDid::REMOVED_NOTIFY_CHANNEL
|
||||
@dids.delete(payload)
|
||||
end
|
||||
end
|
||||
rescue Interrupt
|
||||
logger.info("interrupt in notify thread...")
|
||||
ensure
|
||||
logger.info("unlistening to postgres NOTIFYs")
|
||||
conn.exec_query(
|
||||
"UNLISTEN #{Bluesky::MonitoredDid::ADDED_NOTIFY_CHANNEL}",
|
||||
)
|
||||
conn.exec_query(
|
||||
"UNLISTEN #{Bluesky::MonitoredDid::REMOVED_NOTIFY_CHANNEL}",
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
sig do
|
||||
params(
|
||||
post: Domain::Post::BlueskyPost,
|
||||
embed_data: T::Hash[String, T.untyped],
|
||||
did: String,
|
||||
).void
|
||||
end
|
||||
def process_media(post, embed_data, did)
|
||||
case embed_data["$type"]
|
||||
when "app.bsky.embed.images"
|
||||
process_images(post, embed_data["images"], did)
|
||||
when "app.bsky.embed.recordWithMedia"
|
||||
# Handle quote posts with media
|
||||
if embed_data["media"] &&
|
||||
embed_data["media"]["$type"] == "app.bsky.embed.images"
|
||||
process_images(post, embed_data["media"]["images"], did)
|
||||
end
|
||||
when "app.bsky.embed.external"
|
||||
# Handle external embeds (website cards) - could have thumbnail images
|
||||
process_external_embed(post, embed_data["external"], did)
|
||||
else
|
||||
logger.debug("unknown embed type: #{embed_data["$type"]}")
|
||||
end
|
||||
end
|
||||
|
||||
sig do
|
||||
params(
|
||||
post: Domain::Post::BlueskyPost,
|
||||
images: T::Array[T::Hash[String, T.untyped]],
|
||||
did: String,
|
||||
).void
|
||||
end
|
||||
def process_images(post, images, did)
|
||||
files = []
|
||||
images.each_with_index do |image_data, index|
|
||||
blob_data = image_data["image"]
|
||||
next unless blob_data && blob_data["ref"]
|
||||
|
||||
# Create PostFile record for the image
|
||||
post_file =
|
||||
post.files.build(
|
||||
type: "Domain::PostFile::BlueskyPostFile",
|
||||
file_order: index,
|
||||
url_str: construct_blob_url(did, blob_data["ref"]["$link"]),
|
||||
state: "pending",
|
||||
alt_text: image_data["alt"],
|
||||
blob_ref: blob_data["ref"]["$link"],
|
||||
)
|
||||
|
||||
# Store aspect ratio if present
|
||||
if image_data["aspectRatio"]
|
||||
post_file.aspect_ratio_width = image_data["aspectRatio"]["width"]
|
||||
post_file.aspect_ratio_height = image_data["aspectRatio"]["height"]
|
||||
end
|
||||
|
||||
post_file.save!
|
||||
Domain::StaticFileJob.perform_later({ post_file: })
|
||||
files << post_file
|
||||
end
|
||||
|
||||
logger.info(
|
||||
"created #{files.size} #{"file".pluralize(files.size)} for post: #{post.bluesky_rkey} / #{did}",
|
||||
)
|
||||
end
|
||||
|
||||
sig do
|
||||
params(
|
||||
post: Domain::Post::BlueskyPost,
|
||||
external_data: T::Hash[String, T.untyped],
|
||||
did: String,
|
||||
).void
|
||||
end
|
||||
def process_external_embed(post, external_data, did)
|
||||
# Handle thumbnail image from external embeds (website cards)
|
||||
thumb_data = external_data["thumb"]
|
||||
return unless thumb_data && thumb_data["ref"]
|
||||
|
||||
post_file =
|
||||
post.files.build(
|
||||
type: "Domain::PostFile::BlueskyPostFile",
|
||||
file_order: 0,
|
||||
url_str: construct_blob_url(did, thumb_data["ref"]["$link"]),
|
||||
state: "pending",
|
||||
)
|
||||
|
||||
# Store metadata
|
||||
post_file.alt_text = "Website preview thumbnail"
|
||||
post_file.blob_ref = thumb_data["ref"]["$link"]
|
||||
post_file.save!
|
||||
|
||||
logger.info("created bluesky external thumbnail: #{post_file.url_str}")
|
||||
end
|
||||
|
||||
sig { params(did: String, cid: String).returns(String) }
|
||||
def construct_blob_url(did, cid)
|
||||
# Construct the Bluesky blob URL using the AT Protocol getBlob endpoint
|
||||
"https://bsky.social/xrpc/com.atproto.sync.getBlob?did=#{did}&cid=#{cid}"
|
||||
end
|
||||
end
|
||||
end
|
||||
25
app/models/bluesky/monitored_did.rb
Normal file
25
app/models/bluesky/monitored_did.rb
Normal file
@@ -0,0 +1,25 @@
|
||||
# typed: strict
|
||||
class Bluesky::MonitoredDid < ReduxApplicationRecord
|
||||
self.table_name = "bluesky_monitored_dids"
|
||||
validates :did, presence: true, uniqueness: true
|
||||
|
||||
ADDED_NOTIFY_CHANNEL = "bluesky_did_added"
|
||||
REMOVED_NOTIFY_CHANNEL = "bluesky_did_removed"
|
||||
|
||||
after_create_commit :notify_monitor_added
|
||||
after_destroy_commit :notify_monitor_removed
|
||||
|
||||
sig { void }
|
||||
def notify_monitor_added
|
||||
self.class.connection.execute(
|
||||
"NOTIFY #{ADDED_NOTIFY_CHANNEL}, '#{self.did}'",
|
||||
)
|
||||
end
|
||||
|
||||
sig { void }
|
||||
def notify_monitor_removed
|
||||
self.class.connection.execute(
|
||||
"NOTIFY #{REMOVED_NOTIFY_CHANNEL}, '#{self.did}'",
|
||||
)
|
||||
end
|
||||
end
|
||||
102
app/models/domain/post/bluesky_post.rb
Normal file
102
app/models/domain/post/bluesky_post.rb
Normal file
@@ -0,0 +1,102 @@
|
||||
# typed: strict
|
||||
class Domain::Post::BlueskyPost < Domain::Post
|
||||
aux_table :bluesky, allow_redefining: :title
|
||||
|
||||
belongs_to :first_seen_entry, class_name: "::HttpLogEntry", optional: true
|
||||
|
||||
has_multiple_files! Domain::PostFile::BlueskyPostFile
|
||||
has_single_creator! Domain::User::BlueskyUser
|
||||
has_faving_users! Domain::User::BlueskyUser
|
||||
|
||||
after_initialize { self.state ||= "ok" if new_record? }
|
||||
|
||||
enum :state, { ok: "ok", removed: "removed" }, prefix: "state"
|
||||
|
||||
validates :state, presence: true
|
||||
validates :at_uri, presence: true, uniqueness: true
|
||||
validates :bluesky_rkey, presence: true
|
||||
validates :bluesky_created_at, presence: true
|
||||
|
||||
sig { override.returns([String, Symbol]) }
|
||||
def self.param_prefix_and_attribute
|
||||
["bsky", :bluesky_rkey]
|
||||
end
|
||||
|
||||
sig { override.returns(String) }
|
||||
def self.view_prefix
|
||||
"bsky"
|
||||
end
|
||||
|
||||
sig { override.returns(Symbol) }
|
||||
def self.post_order_attribute
|
||||
:bluesky_created_at
|
||||
end
|
||||
|
||||
sig { override.returns(Domain::DomainType) }
|
||||
def self.domain_type
|
||||
Domain::DomainType::Bluesky
|
||||
end
|
||||
|
||||
sig { override.returns(T.nilable(Domain::User)) }
|
||||
def primary_creator_for_view
|
||||
self.creator
|
||||
end
|
||||
|
||||
sig { override.returns(T.nilable(T.any(String, Integer))) }
|
||||
def domain_id_for_view
|
||||
self.bluesky_rkey
|
||||
end
|
||||
|
||||
sig { override.returns(T.nilable(Addressable::URI)) }
|
||||
def external_url_for_view
|
||||
handle = self.creator&.handle
|
||||
if bluesky_rkey.present? && handle.present?
|
||||
Addressable::URI.parse(
|
||||
"https://bsky.app/profile/#{handle}/post/#{bluesky_rkey}",
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
sig { override.returns(T.nilable(Domain::PostFile)) }
|
||||
def primary_file_for_view
|
||||
self.files.first
|
||||
end
|
||||
|
||||
sig { override.returns(T::Boolean) }
|
||||
def pending_scan?
|
||||
scanned_at.nil?
|
||||
end
|
||||
|
||||
sig { override.returns(T.nilable(String)) }
|
||||
def description_html_for_view
|
||||
text
|
||||
end
|
||||
|
||||
sig { override.returns(String) }
|
||||
def description_html_base_domain
|
||||
"bsky.app"
|
||||
end
|
||||
|
||||
sig { override.returns(T.nilable(String)) }
|
||||
def description_for_view
|
||||
self.text
|
||||
end
|
||||
|
||||
sig { override.returns(T.nilable(Integer)) }
|
||||
def num_favorites_for_view
|
||||
like_count
|
||||
end
|
||||
|
||||
sig { override.returns(T.nilable(String)) }
|
||||
def title
|
||||
# Use first 50 characters of text as title, or a fallback
|
||||
current_text = text
|
||||
current_text.present? ? current_text.truncate(50) : "Bluesky Post"
|
||||
end
|
||||
|
||||
sig { override.returns(T.nilable(T::Array[TagForView])) }
|
||||
def tags_for_view
|
||||
return nil unless hashtags.present?
|
||||
hashtags.map { |tag| TagForView.new(category: :general, value: tag) }
|
||||
end
|
||||
end
|
||||
7
app/models/domain/post_file/bluesky_post_file.rb
Normal file
7
app/models/domain/post_file/bluesky_post_file.rb
Normal file
@@ -0,0 +1,7 @@
|
||||
# typed: strict
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Domain::PostFile::BlueskyPostFile < Domain::PostFile
|
||||
aux_table :bluesky
|
||||
validates :file_order, presence: true, uniqueness: { scope: :post_id }
|
||||
end
|
||||
96
app/models/domain/user/bluesky_user.rb
Normal file
96
app/models/domain/user/bluesky_user.rb
Normal file
@@ -0,0 +1,96 @@
|
||||
# typed: strict
|
||||
class Domain::User::BlueskyUser < Domain::User
|
||||
aux_table :bluesky
|
||||
|
||||
due_timestamp :scanned_profile_at, 1.month
|
||||
due_timestamp :scanned_posts_at, 1.week
|
||||
|
||||
has_created_posts! Domain::Post::BlueskyPost
|
||||
has_faved_posts! Domain::Post::BlueskyPost
|
||||
|
||||
enum :state,
|
||||
{ ok: "ok", account_disabled: "account_disabled", error: "error" },
|
||||
prefix: :state
|
||||
|
||||
validates :handle, presence: true
|
||||
validates :did, presence: true
|
||||
validates :state, presence: true
|
||||
|
||||
after_initialize { self.state ||= "ok" if new_record? }
|
||||
after_commit :enqueue_initial_scan, on: :create
|
||||
|
||||
sig { override.returns([String, Symbol]) }
|
||||
def self.param_prefix_and_attribute
|
||||
["bsky", :handle]
|
||||
end
|
||||
|
||||
sig { override.returns(String) }
|
||||
def self.view_prefix
|
||||
"bsky"
|
||||
end
|
||||
|
||||
sig { override.returns(Domain::DomainType) }
|
||||
def self.domain_type
|
||||
Domain::DomainType::Bluesky
|
||||
end
|
||||
|
||||
sig { override.returns(String) }
|
||||
def description_html_base_domain
|
||||
"bsky.app"
|
||||
end
|
||||
|
||||
sig { override.returns(String) }
|
||||
def site_name_for_view
|
||||
"Bluesky"
|
||||
end
|
||||
|
||||
sig { override.returns(T.nilable(String)) }
|
||||
def description_html_for_view
|
||||
description
|
||||
end
|
||||
|
||||
sig { override.returns(T::Array[String]) }
|
||||
def names_for_search
|
||||
[display_name, handle].compact
|
||||
end
|
||||
|
||||
sig { override.returns(T.nilable(String)) }
|
||||
def external_url_for_view
|
||||
"https://bsky.app/profile/#{did}" if did.present?
|
||||
end
|
||||
|
||||
sig { override.returns(T.nilable(String)) }
|
||||
def name_for_view
|
||||
display_name || handle
|
||||
end
|
||||
|
||||
sig { override.returns(String) }
|
||||
def account_status_for_view
|
||||
"Active" # TODO: Implement proper status checking
|
||||
end
|
||||
|
||||
sig { returns(String) }
|
||||
def account_state_for_view
|
||||
case state
|
||||
when "ok"
|
||||
"Ok"
|
||||
when "account_disabled"
|
||||
"Disabled"
|
||||
when "error"
|
||||
"Error"
|
||||
else
|
||||
"Unknown"
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
sig { void }
|
||||
def enqueue_initial_scan
|
||||
# Only enqueue for valid users with proper DIDs and handles
|
||||
return unless state_ok? && did.present? && handle.present?
|
||||
|
||||
# Enqueue the scan job to run immediately
|
||||
Domain::Bluesky::Job::ScanUserJob.perform_later({ user: self })
|
||||
end
|
||||
end
|
||||
3
app/policies/domain/post/bluesky_post_policy.rb
Normal file
3
app/policies/domain/post/bluesky_post_policy.rb
Normal file
@@ -0,0 +1,3 @@
|
||||
# typed: strict
|
||||
class Domain::Post::BlueskyPostPolicy < Domain::PostPolicy
|
||||
end
|
||||
3
app/policies/domain/user/bluesky_user_policy.rb
Normal file
3
app/policies/domain/user/bluesky_user_policy.rb
Normal file
@@ -0,0 +1,3 @@
|
||||
# typed: strict
|
||||
class Domain::User::BlueskyUserPolicy < Domain::UserPolicy
|
||||
end
|
||||
67
app/views/domain/posts/default/_section_file.html.erb
Normal file
67
app/views/domain/posts/default/_section_file.html.erb
Normal file
@@ -0,0 +1,67 @@
|
||||
<% if policy(post).view_file? %>
|
||||
<% ok_files = post.files.state_ok.order(:created_at).to_a %>
|
||||
<% current_file = ok_files.first || post.primary_file_for_view %>
|
||||
<% if ok_files.any? && current_file&.log_entry&.status_code == 200 %>
|
||||
<!-- React PostFiles Component handles everything -->
|
||||
<%
|
||||
# Extract file index from URL parameter
|
||||
idx_param = params[:idx]
|
||||
initial_file_index = idx_param.present? ? idx_param.to_i : nil
|
||||
%>
|
||||
<%= react_component(
|
||||
"PostFiles",
|
||||
{
|
||||
prerender: true,
|
||||
props: props_for_post_files(ok_files, initial_file_index: initial_file_index),
|
||||
html_options: {
|
||||
id: "post-files-component"
|
||||
}
|
||||
}
|
||||
) %>
|
||||
<% elsif current_file.present? && (log_entry = current_file.log_entry) %>
|
||||
<!-- Fallback for error states -->
|
||||
<section id="file-display-section">
|
||||
<section class="flex grow justify-center text-slate-500">
|
||||
<div>
|
||||
<i class="fa-solid fa-exclamation-triangle"></i>
|
||||
File error
|
||||
<% if log_entry.status_code == 404 %>
|
||||
(404 not found)
|
||||
<% else %>
|
||||
(<%= log_entry.status_code %>)
|
||||
<% end %>
|
||||
</div>
|
||||
</section>
|
||||
</section>
|
||||
<% elsif current_file.present? && current_file.state_pending? %>
|
||||
<!-- Fallback for pending state -->
|
||||
<section id="file-display-section">
|
||||
<section class="flex grow justify-center text-slate-500">
|
||||
<div>
|
||||
<i class="fa-solid fa-file-arrow-down"></i>
|
||||
File pending download
|
||||
</div>
|
||||
</section>
|
||||
</section>
|
||||
<% else %>
|
||||
<!-- Fallback for no file -->
|
||||
<section id="file-display-section">
|
||||
<section class="flex grow justify-center overflow-clip">
|
||||
<div class="text-slate-500">
|
||||
<i class="fa-solid fa-file-arrow-down"></i>
|
||||
No file
|
||||
</div>
|
||||
</section>
|
||||
</section>
|
||||
<% end %>
|
||||
<% else %>
|
||||
<section class="sky-section">
|
||||
<%= link_to post.external_url_for_view.to_s,
|
||||
target: "_blank",
|
||||
rel: "noopener noreferrer",
|
||||
class: "section-header flex items-center gap-2 hover:text-slate-600" do %>
|
||||
<span>View Post on <%= domain_name_for_model(post) %></span>
|
||||
<i class="fa-solid fa-arrow-up-right-from-square"></i>
|
||||
<% end %>
|
||||
</section>
|
||||
<% end %>
|
||||
@@ -1,53 +0,0 @@
|
||||
<% if policy(post).view_file? %>
|
||||
<% file = post.primary_file_for_view %>
|
||||
<section>
|
||||
<% if file.present? && (log_entry = file.log_entry) %>
|
||||
<% if log_entry.status_code == 200 %>
|
||||
<%= render partial: "log_entries/content_container",
|
||||
locals: {
|
||||
log_entry: log_entry,
|
||||
} %>
|
||||
<% else %>
|
||||
<section class="flex grow justify-center text-slate-500">
|
||||
<div>
|
||||
<i class="fa-solid fa-exclamation-triangle"></i>
|
||||
File error
|
||||
<% if log_entry.status_code == 404 %>
|
||||
(404 not found)
|
||||
<% else %>
|
||||
(<%= log_entry.status_code %>)
|
||||
<% end %>
|
||||
</div>
|
||||
</section>
|
||||
<% end %>
|
||||
<% elsif file.present? && file.state_pending? %>
|
||||
<section class="flex grow justify-center text-slate-500">
|
||||
<div>
|
||||
<i class="fa-solid fa-file-arrow-down"></i>
|
||||
File pending download
|
||||
</div>
|
||||
</section>
|
||||
<% else %>
|
||||
<section class="flex grow justify-center overflow-clip">
|
||||
<div class="text-slate-500">
|
||||
<i class="fa-solid fa-file-arrow-down"></i>
|
||||
No file
|
||||
</div>
|
||||
</section>
|
||||
<% end %>
|
||||
</section>
|
||||
<%= render partial: "log_entries/file_details_sky_section",
|
||||
locals: {
|
||||
log_entry: post.primary_file_for_view&.log_entry,
|
||||
} %>
|
||||
<% else %>
|
||||
<section class="sky-section">
|
||||
<%= link_to post.external_url_for_view.to_s,
|
||||
target: "_blank",
|
||||
rel: "noopener noreferrer",
|
||||
class: "section-header flex items-center gap-2 hover:text-slate-600" do %>
|
||||
<span>View Post on <%= domain_name_for_model(post) %></span>
|
||||
<i class="fa-solid fa-arrow-up-right-from-square"></i>
|
||||
<% end %>
|
||||
</section>
|
||||
<% end %>
|
||||
@@ -29,7 +29,7 @@
|
||||
<%
|
||||
description = []
|
||||
description << "posted #{@post.posted_at.strftime("%B %d, %Y")}" if @post.respond_to?(:posted_at) && @post.posted_at.present?
|
||||
description << "by #{@post.primary_creator_for_view&.name || "Unknown"}"
|
||||
description << "by #{@post.primary_creator_for_view&.name_for_view || "Unknown"}"
|
||||
description << "@ #{domain_name_for_model(@post)}"
|
||||
%>
|
||||
<meta name="og:description" content="<%= description.join(" ") %>">
|
||||
@@ -46,7 +46,7 @@
|
||||
<div class="mx-auto mt-4 flex w-full max-w-2xl flex-col gap-4 pb-4">
|
||||
<%= render_for_model(@post, "section_post_title", as: :post) %>
|
||||
<%= render_for_model(@post, "section_post_groups", as: :post) %>
|
||||
<%= render_for_model(@post, "section_primary_file", as: :post) %>
|
||||
<%= render_for_model(@post, "section_file", as: :post) %>
|
||||
<%= render_for_model(@post, "section_description", as: :post) %>
|
||||
<%= render_for_model(@post, "section_tags", as: :post) %>
|
||||
<%= render_for_model(@post, "section_sources", as: :post) %>
|
||||
|
||||
@@ -31,12 +31,15 @@ class ActiveRecord::Migration
|
||||
end
|
||||
|
||||
sig do
|
||||
params(enum_name: Symbol, values: T.class_of(ReduxApplicationRecord)).void
|
||||
params(
|
||||
enum_name: Symbol,
|
||||
values: T.any(T.class_of(ReduxApplicationRecord), String),
|
||||
).void
|
||||
end
|
||||
def add_enum_value(enum_name, *values)
|
||||
up_only do
|
||||
values.each do |value|
|
||||
execute "ALTER TYPE #{enum_name} ADD VALUE IF NOT EXISTS '#{value.name}'"
|
||||
execute "ALTER TYPE #{enum_name} ADD VALUE IF NOT EXISTS '#{value.is_a?(String) ? value : value.name}'"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
# typed: strict
|
||||
# frozen_string_literal: true
|
||||
|
||||
class AddAuxTablesForDomainUsersBlueskyUsers < ActiveRecord::Migration[7.2]
|
||||
extend T::Sig
|
||||
|
||||
sig { void }
|
||||
def change
|
||||
add_enum_value :domain_user_type, "Domain::User::BlueskyUser"
|
||||
create_aux_table :domain_users, :bluesky do |t|
|
||||
t.string :state, null: false
|
||||
t.string :did, null: false, index: true # Decentralized identifier
|
||||
# handle is the "username"
|
||||
t.string :handle, null: false, index: true
|
||||
t.string :display_name
|
||||
# profile "bio"
|
||||
t.text :description
|
||||
|
||||
# Bluesky-specific fields
|
||||
t.integer :followers_count
|
||||
t.integer :following_count
|
||||
t.integer :posts_count
|
||||
|
||||
# avatar is tracked via Domain::UserAvatar, not here
|
||||
|
||||
# Scanning timestamps
|
||||
t.datetime :scanned_profile_at
|
||||
t.datetime :scanned_posts_at
|
||||
|
||||
# Raw data storage for debugging/analysis
|
||||
t.column :profile_raw, :jsonb, default: {}
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,43 @@
|
||||
# typed: strict
|
||||
# frozen_string_literal: true
|
||||
|
||||
class AddAuxTablesForDomainPostsBlueskyPosts < ActiveRecord::Migration[7.2]
|
||||
extend T::Sig
|
||||
|
||||
sig { void }
|
||||
def change
|
||||
add_enum_value :domain_post_type, "Domain::Post::BlueskyPost"
|
||||
create_aux_table :domain_posts, :bluesky do |t|
|
||||
t.string :state, null: false
|
||||
t.string :bluesky_rkey, null: false, index: true # Record key from AT URI
|
||||
t.string :at_uri # Full AT Protocol URI
|
||||
t.text :text # Post content
|
||||
|
||||
# Post metadata (posted_at is in main table)
|
||||
t.datetime :bluesky_created_at, index: true
|
||||
t.datetime :scanned_at
|
||||
t.string :language
|
||||
|
||||
# Engagement metrics
|
||||
t.integer :like_count
|
||||
t.integer :repost_count
|
||||
t.integer :reply_count
|
||||
t.integer :quote_count
|
||||
|
||||
# Content arrays
|
||||
t.column :hashtags, :jsonb, default: []
|
||||
t.column :mentions, :jsonb, default: []
|
||||
t.column :links, :jsonb, default: []
|
||||
|
||||
# References to other posts
|
||||
t.string :reply_to_uri # AT URI of post being replied to
|
||||
t.string :quote_uri # AT URI of post being quoted
|
||||
|
||||
# Raw data storage for debugging/analysis
|
||||
t.column :post_raw, :jsonb, default: {}
|
||||
|
||||
# Error tracking
|
||||
t.string :scan_error
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,14 @@
|
||||
# typed: strict
|
||||
# frozen_string_literal: true
|
||||
|
||||
class AddAuxTablesForDomainPostFilesBluesky < ActiveRecord::Migration[7.2]
|
||||
extend T::Sig
|
||||
|
||||
sig { void }
|
||||
def change
|
||||
add_enum_value :domain_post_file_type, "Domain::PostFile::BlueskyPostFile"
|
||||
create_aux_table :domain_post_files, :bluesky do |t|
|
||||
t.integer :file_order, null: false
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,8 @@
|
||||
class CreateBlueskyMonitoredDidsTable < ActiveRecord::Migration[7.2]
|
||||
def change
|
||||
create_table :bluesky_monitored_dids do |t|
|
||||
t.string :did, null: false, index: { unique: true }
|
||||
t.timestamps
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,17 @@
|
||||
# typed: strict
|
||||
# frozen_string_literal: true
|
||||
|
||||
class AddBlueskyMediaFieldsToPostFiles < ActiveRecord::Migration[7.2]
|
||||
extend T::Sig
|
||||
|
||||
sig { void }
|
||||
def change
|
||||
# Add media metadata fields to the Bluesky post files aux table
|
||||
change_table :domain_post_files_bluesky_aux do |t|
|
||||
t.text :alt_text # Alt text for accessibility
|
||||
t.integer :aspect_ratio_width # Image aspect ratio width
|
||||
t.integer :aspect_ratio_height # Image aspect ratio height
|
||||
t.string :blob_ref # Bluesky blob CID reference
|
||||
end
|
||||
end
|
||||
end
|
||||
303
db/structure.sql
303
db/structure.sql
@@ -100,7 +100,8 @@ COMMENT ON EXTENSION vector IS 'vector data type and ivfflat access method';
|
||||
|
||||
CREATE TYPE public.domain_post_file_type AS ENUM (
|
||||
'Domain::PostFile',
|
||||
'Domain::PostFile::InkbunnyPostFile'
|
||||
'Domain::PostFile::InkbunnyPostFile',
|
||||
'Domain::PostFile::BlueskyPostFile'
|
||||
);
|
||||
|
||||
|
||||
@@ -135,7 +136,8 @@ CREATE TYPE public.domain_post_type AS ENUM (
|
||||
'Domain::Post::E621Post',
|
||||
'Domain::Post::InkbunnyPost',
|
||||
'Domain::Post::SofurryPost',
|
||||
'Domain::Post::WeasylPost'
|
||||
'Domain::Post::WeasylPost',
|
||||
'Domain::Post::BlueskyPost'
|
||||
);
|
||||
|
||||
|
||||
@@ -158,7 +160,8 @@ CREATE TYPE public.domain_user_type AS ENUM (
|
||||
'Domain::User::E621User',
|
||||
'Domain::User::InkbunnyUser',
|
||||
'Domain::User::SofurryUser',
|
||||
'Domain::User::WeasylUser'
|
||||
'Domain::User::WeasylUser',
|
||||
'Domain::User::BlueskyUser'
|
||||
);
|
||||
|
||||
|
||||
@@ -1100,6 +1103,37 @@ CREATE TABLE public.blob_files_63 (
|
||||
);
|
||||
|
||||
|
||||
--
|
||||
-- Name: bluesky_monitored_dids; Type: TABLE; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
CREATE TABLE public.bluesky_monitored_dids (
|
||||
id bigint NOT NULL,
|
||||
did character varying NOT NULL,
|
||||
created_at timestamp(6) without time zone NOT NULL,
|
||||
updated_at timestamp(6) without time zone NOT NULL
|
||||
);
|
||||
|
||||
|
||||
--
|
||||
-- Name: bluesky_monitored_dids_id_seq; Type: SEQUENCE; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
CREATE SEQUENCE public.bluesky_monitored_dids_id_seq
|
||||
START WITH 1
|
||||
INCREMENT BY 1
|
||||
NO MINVALUE
|
||||
NO MAXVALUE
|
||||
CACHE 1;
|
||||
|
||||
|
||||
--
|
||||
-- Name: bluesky_monitored_dids_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
ALTER SEQUENCE public.bluesky_monitored_dids_id_seq OWNED BY public.bluesky_monitored_dids.id;
|
||||
|
||||
|
||||
--
|
||||
-- Name: domain_fa_fav_id_and_dates; Type: TABLE; Schema: public; Owner: -
|
||||
--
|
||||
@@ -1221,6 +1255,39 @@ CREATE TABLE public.domain_post_files (
|
||||
);
|
||||
|
||||
|
||||
--
|
||||
-- Name: domain_post_files_bluesky_aux; Type: TABLE; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
CREATE TABLE public.domain_post_files_bluesky_aux (
|
||||
base_table_id bigint NOT NULL,
|
||||
file_order integer NOT NULL,
|
||||
alt_text text,
|
||||
aspect_ratio_width integer,
|
||||
aspect_ratio_height integer,
|
||||
blob_ref character varying
|
||||
);
|
||||
|
||||
|
||||
--
|
||||
-- Name: domain_post_files_bluesky_aux_base_table_id_seq; Type: SEQUENCE; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
CREATE SEQUENCE public.domain_post_files_bluesky_aux_base_table_id_seq
|
||||
START WITH 1
|
||||
INCREMENT BY 1
|
||||
NO MINVALUE
|
||||
NO MAXVALUE
|
||||
CACHE 1;
|
||||
|
||||
|
||||
--
|
||||
-- Name: domain_post_files_bluesky_aux_base_table_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
ALTER SEQUENCE public.domain_post_files_bluesky_aux_base_table_id_seq OWNED BY public.domain_post_files_bluesky_aux.base_table_id;
|
||||
|
||||
|
||||
--
|
||||
-- Name: domain_post_files_id_seq; Type: SEQUENCE; Schema: public; Owner: -
|
||||
--
|
||||
@@ -1336,6 +1403,52 @@ CREATE TABLE public.domain_posts (
|
||||
);
|
||||
|
||||
|
||||
--
|
||||
-- Name: domain_posts_bluesky_aux; Type: TABLE; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
CREATE TABLE public.domain_posts_bluesky_aux (
|
||||
base_table_id bigint NOT NULL,
|
||||
state character varying NOT NULL,
|
||||
bluesky_rkey character varying NOT NULL,
|
||||
at_uri character varying,
|
||||
text text,
|
||||
bluesky_created_at timestamp(6) without time zone,
|
||||
scanned_at timestamp(6) without time zone,
|
||||
language character varying,
|
||||
like_count integer,
|
||||
repost_count integer,
|
||||
reply_count integer,
|
||||
quote_count integer,
|
||||
hashtags jsonb DEFAULT '[]'::jsonb,
|
||||
mentions jsonb DEFAULT '[]'::jsonb,
|
||||
links jsonb DEFAULT '[]'::jsonb,
|
||||
reply_to_uri character varying,
|
||||
quote_uri character varying,
|
||||
post_raw jsonb DEFAULT '{}'::jsonb,
|
||||
scan_error character varying
|
||||
);
|
||||
|
||||
|
||||
--
|
||||
-- Name: domain_posts_bluesky_aux_base_table_id_seq; Type: SEQUENCE; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
CREATE SEQUENCE public.domain_posts_bluesky_aux_base_table_id_seq
|
||||
START WITH 1
|
||||
INCREMENT BY 1
|
||||
NO MINVALUE
|
||||
NO MAXVALUE
|
||||
CACHE 1;
|
||||
|
||||
|
||||
--
|
||||
-- Name: domain_posts_bluesky_aux_base_table_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
ALTER SEQUENCE public.domain_posts_bluesky_aux_base_table_id_seq OWNED BY public.domain_posts_bluesky_aux.base_table_id;
|
||||
|
||||
|
||||
--
|
||||
-- Name: domain_posts_e621_aux; Type: TABLE; Schema: public; Owner: -
|
||||
--
|
||||
@@ -1827,6 +1940,45 @@ CREATE TABLE public.domain_users (
|
||||
);
|
||||
|
||||
|
||||
--
|
||||
-- Name: domain_users_bluesky_aux; Type: TABLE; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
CREATE TABLE public.domain_users_bluesky_aux (
|
||||
base_table_id bigint NOT NULL,
|
||||
state character varying NOT NULL,
|
||||
did character varying NOT NULL,
|
||||
handle character varying NOT NULL,
|
||||
display_name character varying,
|
||||
description text,
|
||||
followers_count integer,
|
||||
following_count integer,
|
||||
posts_count integer,
|
||||
scanned_profile_at timestamp(6) without time zone,
|
||||
scanned_posts_at timestamp(6) without time zone,
|
||||
profile_raw jsonb DEFAULT '{}'::jsonb
|
||||
);
|
||||
|
||||
|
||||
--
|
||||
-- Name: domain_users_bluesky_aux_base_table_id_seq; Type: SEQUENCE; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
CREATE SEQUENCE public.domain_users_bluesky_aux_base_table_id_seq
|
||||
START WITH 1
|
||||
INCREMENT BY 1
|
||||
NO MINVALUE
|
||||
NO MAXVALUE
|
||||
CACHE 1;
|
||||
|
||||
|
||||
--
|
||||
-- Name: domain_users_bluesky_aux_base_table_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
ALTER SEQUENCE public.domain_users_bluesky_aux_base_table_id_seq OWNED BY public.domain_users_bluesky_aux.base_table_id;
|
||||
|
||||
|
||||
--
|
||||
-- Name: domain_users_e621_aux; Type: TABLE; Schema: public; Owner: -
|
||||
--
|
||||
@@ -2907,6 +3059,13 @@ ALTER TABLE ONLY public.blob_files ATTACH PARTITION public.blob_files_62 FOR VAL
|
||||
ALTER TABLE ONLY public.blob_files ATTACH PARTITION public.blob_files_63 FOR VALUES WITH (modulus 64, remainder 63);
|
||||
|
||||
|
||||
--
|
||||
-- Name: bluesky_monitored_dids id; Type: DEFAULT; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.bluesky_monitored_dids ALTER COLUMN id SET DEFAULT nextval('public.bluesky_monitored_dids_id_seq'::regclass);
|
||||
|
||||
|
||||
--
|
||||
-- Name: domain_fa_fav_id_and_dates id; Type: DEFAULT; Schema: public; Owner: -
|
||||
--
|
||||
@@ -2935,6 +3094,13 @@ ALTER TABLE ONLY public.domain_post_file_thumbnails ALTER COLUMN id SET DEFAULT
|
||||
ALTER TABLE ONLY public.domain_post_files ALTER COLUMN id SET DEFAULT nextval('public.domain_post_files_id_seq'::regclass);
|
||||
|
||||
|
||||
--
|
||||
-- Name: domain_post_files_bluesky_aux base_table_id; Type: DEFAULT; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.domain_post_files_bluesky_aux ALTER COLUMN base_table_id SET DEFAULT nextval('public.domain_post_files_bluesky_aux_base_table_id_seq'::regclass);
|
||||
|
||||
|
||||
--
|
||||
-- Name: domain_post_files_inkbunny_aux base_table_id; Type: DEFAULT; Schema: public; Owner: -
|
||||
--
|
||||
@@ -2956,6 +3122,13 @@ ALTER TABLE ONLY public.domain_post_groups ALTER COLUMN id SET DEFAULT nextval('
|
||||
ALTER TABLE ONLY public.domain_posts ALTER COLUMN id SET DEFAULT nextval('public.domain_posts_id_seq'::regclass);
|
||||
|
||||
|
||||
--
|
||||
-- Name: domain_posts_bluesky_aux base_table_id; Type: DEFAULT; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.domain_posts_bluesky_aux ALTER COLUMN base_table_id SET DEFAULT nextval('public.domain_posts_bluesky_aux_base_table_id_seq'::regclass);
|
||||
|
||||
|
||||
--
|
||||
-- Name: domain_posts_e621_aux base_table_id; Type: DEFAULT; Schema: public; Owner: -
|
||||
--
|
||||
@@ -3026,6 +3199,13 @@ ALTER TABLE ONLY public.domain_user_search_names ALTER COLUMN id SET DEFAULT nex
|
||||
ALTER TABLE ONLY public.domain_users ALTER COLUMN id SET DEFAULT nextval('public.domain_users_id_seq'::regclass);
|
||||
|
||||
|
||||
--
|
||||
-- Name: domain_users_bluesky_aux base_table_id; Type: DEFAULT; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.domain_users_bluesky_aux ALTER COLUMN base_table_id SET DEFAULT nextval('public.domain_users_bluesky_aux_base_table_id_seq'::regclass);
|
||||
|
||||
|
||||
--
|
||||
-- Name: domain_users_e621_aux base_table_id; Type: DEFAULT; Schema: public; Owner: -
|
||||
--
|
||||
@@ -3125,6 +3305,14 @@ ALTER TABLE ONLY public.ar_internal_metadata
|
||||
ADD CONSTRAINT ar_internal_metadata_pkey PRIMARY KEY (key);
|
||||
|
||||
|
||||
--
|
||||
-- Name: bluesky_monitored_dids bluesky_monitored_dids_pkey; Type: CONSTRAINT; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.bluesky_monitored_dids
|
||||
ADD CONSTRAINT bluesky_monitored_dids_pkey PRIMARY KEY (id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: domain_fa_fav_id_and_dates domain_fa_fav_id_and_dates_pkey; Type: CONSTRAINT; Schema: public; Owner: -
|
||||
--
|
||||
@@ -3149,6 +3337,14 @@ ALTER TABLE ONLY public.domain_post_file_thumbnails
|
||||
ADD CONSTRAINT domain_post_file_thumbnails_pkey PRIMARY KEY (id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: domain_post_files_bluesky_aux domain_post_files_bluesky_aux_pkey; Type: CONSTRAINT; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.domain_post_files_bluesky_aux
|
||||
ADD CONSTRAINT domain_post_files_bluesky_aux_pkey PRIMARY KEY (base_table_id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: domain_post_files_inkbunny_aux domain_post_files_inkbunny_aux_pkey; Type: CONSTRAINT; Schema: public; Owner: -
|
||||
--
|
||||
@@ -3173,6 +3369,14 @@ ALTER TABLE ONLY public.domain_post_groups
|
||||
ADD CONSTRAINT domain_post_groups_pkey PRIMARY KEY (id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: domain_posts_bluesky_aux domain_posts_bluesky_aux_pkey; Type: CONSTRAINT; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.domain_posts_bluesky_aux
|
||||
ADD CONSTRAINT domain_posts_bluesky_aux_pkey PRIMARY KEY (base_table_id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: domain_posts_e621_aux domain_posts_e621_aux_pkey; Type: CONSTRAINT; Schema: public; Owner: -
|
||||
--
|
||||
@@ -3253,6 +3457,14 @@ ALTER TABLE ONLY public.domain_user_search_names
|
||||
ADD CONSTRAINT domain_user_search_names_pkey PRIMARY KEY (id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: domain_users_bluesky_aux domain_users_bluesky_aux_pkey; Type: CONSTRAINT; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.domain_users_bluesky_aux
|
||||
ADD CONSTRAINT domain_users_bluesky_aux_pkey PRIMARY KEY (base_table_id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: domain_users_e621_aux domain_users_e621_aux_pkey; Type: CONSTRAINT; Schema: public; Owner: -
|
||||
--
|
||||
@@ -4064,6 +4276,13 @@ CREATE UNIQUE INDEX index_blob_files_62_on_sha256 ON public.blob_files_62 USING
|
||||
CREATE UNIQUE INDEX index_blob_files_63_on_sha256 ON public.blob_files_63 USING btree (sha256);
|
||||
|
||||
|
||||
--
|
||||
-- Name: index_bluesky_monitored_dids_on_did; Type: INDEX; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
CREATE UNIQUE INDEX index_bluesky_monitored_dids_on_did ON public.bluesky_monitored_dids USING btree (did);
|
||||
|
||||
|
||||
--
|
||||
-- Name: index_domain_fa_fav_id_and_dates_on_fav_fa_id; Type: INDEX; Schema: public; Owner: -
|
||||
--
|
||||
@@ -4085,6 +4304,13 @@ CREATE INDEX index_domain_fa_fav_id_and_dates_on_user_id ON public.domain_fa_fav
|
||||
CREATE UNIQUE INDEX index_domain_fa_fav_id_and_dates_on_user_id_and_post_fa_id ON public.domain_fa_fav_id_and_dates USING btree (user_id, post_fa_id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: index_domain_post_files_bluesky_aux_on_base_table_id; Type: INDEX; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
CREATE INDEX index_domain_post_files_bluesky_aux_on_base_table_id ON public.domain_post_files_bluesky_aux USING btree (base_table_id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: index_domain_post_files_inkbunny_aux_on_base_table_id; Type: INDEX; Schema: public; Owner: -
|
||||
--
|
||||
@@ -4148,6 +4374,27 @@ CREATE INDEX index_domain_post_group_joins_on_type ON public.domain_post_group_j
|
||||
CREATE INDEX index_domain_post_groups_on_type ON public.domain_post_groups USING btree (type);
|
||||
|
||||
|
||||
--
|
||||
-- Name: index_domain_posts_bluesky_aux_on_base_table_id; Type: INDEX; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
CREATE INDEX index_domain_posts_bluesky_aux_on_base_table_id ON public.domain_posts_bluesky_aux USING btree (base_table_id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: index_domain_posts_bluesky_aux_on_bluesky_created_at; Type: INDEX; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
CREATE INDEX index_domain_posts_bluesky_aux_on_bluesky_created_at ON public.domain_posts_bluesky_aux USING btree (bluesky_created_at);
|
||||
|
||||
|
||||
--
|
||||
-- Name: index_domain_posts_bluesky_aux_on_bluesky_rkey; Type: INDEX; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
CREATE INDEX index_domain_posts_bluesky_aux_on_bluesky_rkey ON public.domain_posts_bluesky_aux USING btree (bluesky_rkey);
|
||||
|
||||
|
||||
--
|
||||
-- Name: index_domain_posts_e621_aux_on_base_table_id; Type: INDEX; Schema: public; Owner: -
|
||||
--
|
||||
@@ -4400,6 +4647,27 @@ CREATE UNIQUE INDEX index_domain_user_user_follows_on_from_id_and_to_id ON publi
|
||||
CREATE INDEX index_domain_user_user_follows_on_to_id_and_from_id ON public.domain_user_user_follows USING btree (to_id, from_id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: index_domain_users_bluesky_aux_on_base_table_id; Type: INDEX; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
CREATE INDEX index_domain_users_bluesky_aux_on_base_table_id ON public.domain_users_bluesky_aux USING btree (base_table_id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: index_domain_users_bluesky_aux_on_did; Type: INDEX; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
CREATE INDEX index_domain_users_bluesky_aux_on_did ON public.domain_users_bluesky_aux USING btree (did);
|
||||
|
||||
|
||||
--
|
||||
-- Name: index_domain_users_bluesky_aux_on_handle; Type: INDEX; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
CREATE INDEX index_domain_users_bluesky_aux_on_handle ON public.domain_users_bluesky_aux USING btree (handle);
|
||||
|
||||
|
||||
--
|
||||
-- Name: index_domain_users_e621_aux_on_base_table_id; Type: INDEX; Schema: public; Owner: -
|
||||
--
|
||||
@@ -5282,6 +5550,14 @@ ALTER TABLE ONLY public.domain_twitter_medias
|
||||
ADD CONSTRAINT fk_rails_278c1d09f0 FOREIGN KEY (tweet_id) REFERENCES public.domain_twitter_tweets(id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: domain_posts_bluesky_aux fk_rails_2a2f4bfba8; Type: FK CONSTRAINT; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.domain_posts_bluesky_aux
|
||||
ADD CONSTRAINT fk_rails_2a2f4bfba8 FOREIGN KEY (base_table_id) REFERENCES public.domain_posts(id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: domain_users_inkbunny_aux fk_rails_304ea0307f; Type: FK CONSTRAINT; Schema: public; Owner: -
|
||||
--
|
||||
@@ -5306,6 +5582,14 @@ ALTER TABLE ONLY public.http_log_entries
|
||||
ADD CONSTRAINT fk_rails_42f35e9da0 FOREIGN KEY (response_headers_id) REFERENCES public.http_log_entry_headers(id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: domain_post_files_bluesky_aux fk_rails_47e4648919; Type: FK CONSTRAINT; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.domain_post_files_bluesky_aux
|
||||
ADD CONSTRAINT fk_rails_47e4648919 FOREIGN KEY (base_table_id) REFERENCES public.domain_post_files(id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: domain_fa_fav_id_and_dates fk_rails_4ad7be007e; Type: FK CONSTRAINT; Schema: public; Owner: -
|
||||
--
|
||||
@@ -5338,6 +5622,14 @@ ALTER TABLE ONLY public.domain_twitter_medias
|
||||
ADD CONSTRAINT fk_rails_5fffa41fa6 FOREIGN KEY (file_id) REFERENCES public.http_log_entries(id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: domain_users_bluesky_aux fk_rails_673dd1243a; Type: FK CONSTRAINT; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.domain_users_bluesky_aux
|
||||
ADD CONSTRAINT fk_rails_673dd1243a FOREIGN KEY (base_table_id) REFERENCES public.domain_users(id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: domain_posts_e621_aux fk_rails_73ac068c64; Type: FK CONSTRAINT; Schema: public; Owner: -
|
||||
--
|
||||
@@ -5553,6 +5845,11 @@ ALTER TABLE ONLY public.domain_twitter_tweets
|
||||
SET search_path TO "$user", public;
|
||||
|
||||
INSERT INTO "schema_migrations" (version) VALUES
|
||||
('20250808004604'),
|
||||
('20250805200056'),
|
||||
('20250805191557'),
|
||||
('20250805070115'),
|
||||
('20250805070114'),
|
||||
('20250805045947'),
|
||||
('20250805044757'),
|
||||
('20250731035548'),
|
||||
|
||||
40
rake/bluesky.rake
Normal file
40
rake/bluesky.rake
Normal file
@@ -0,0 +1,40 @@
|
||||
# typed: true
|
||||
# frozen_string_literal: true
|
||||
T.bind(self, T.all(Rake::DSL, Object))
|
||||
|
||||
namespace :bluesky do
|
||||
desc "Start the Bluesky monitor"
|
||||
task monitor: :environment do
|
||||
Tasks::Bluesky::Monitor.new.run
|
||||
end
|
||||
|
||||
def resolve_did(handle)
|
||||
DIDKit::Resolver.new.resolve_handle(handle)&.did
|
||||
end
|
||||
|
||||
desc "Add a DID to the Bluesky monitor"
|
||||
task add: :environment do
|
||||
if (handle = ENV["handle"])
|
||||
did = resolve_did(handle)
|
||||
puts "resolved did: #{did}"
|
||||
else
|
||||
did = ENV["did"]
|
||||
end
|
||||
raise "did is required" if did.blank?
|
||||
|
||||
Bluesky::MonitoredDid.create!(did: did)
|
||||
end
|
||||
|
||||
desc "Remove a DID from the Bluesky monitor"
|
||||
task remove: :environment do
|
||||
if (handle = ENV["handle"])
|
||||
did = resolve_did(handle)
|
||||
puts "resolved did: #{did}"
|
||||
else
|
||||
did = ENV["did"]
|
||||
end
|
||||
raise "did is required" if did.blank?
|
||||
|
||||
Bluesky::MonitoredDid.find_by(did: did)&.destroy!
|
||||
end
|
||||
end
|
||||
44
rake/posts.rake
Normal file
44
rake/posts.rake
Normal file
@@ -0,0 +1,44 @@
|
||||
# typed: true
|
||||
# frozen_string_literal: true
|
||||
T.bind(self, T.all(Rake::DSL, Object))
|
||||
namespace :posts do
|
||||
desc "Find 404 post files with mismatched normalized URLs"
|
||||
task find_404_post_files_with_mismatched_normalized_urls: :environment do
|
||||
query =
|
||||
Domain::PostFile
|
||||
.where(state: "terminal_error")
|
||||
.where("url_str ~ '[^\\x00-\\x7F]'")
|
||||
.includes(:log_entry, post: :files)
|
||||
|
||||
query.find_each(batch_size: 32, order: :desc) do |post_file|
|
||||
le = post_file.log_entry
|
||||
next if le.nil?
|
||||
next if le.status_code != 404
|
||||
next if post_file.url_str.blank?
|
||||
uri = Addressable::URI.parse(post_file.url_str)
|
||||
next if uri.to_s == uri.normalize.to_s
|
||||
next if post_file.post&.files&.any? { |file| file.state_ok? }
|
||||
if post_file.post&.files&.any? { |file|
|
||||
file.url_str =~ /furarchiver.net/
|
||||
}
|
||||
next
|
||||
end
|
||||
puts "#{post_file.post.to_param} ::::: #{post_file.to_param} ::::: #{post_file.url_str}"
|
||||
end
|
||||
end
|
||||
|
||||
desc "Redownload 404 post files with mismatched normalized URLs"
|
||||
task redownload_404_post_files_with_mismatched_normalized_urls:
|
||||
:environment do
|
||||
File
|
||||
.readlines(ENV["file"] || raise("must provide file"))
|
||||
.map(&:strip)
|
||||
.each do |line|
|
||||
_, file_id, _ = line.split(" ::::: ")
|
||||
raise("no file id") if file_id.blank?
|
||||
file = Domain::PostFile.find(file_id)
|
||||
file.state_pending!
|
||||
Domain::Fa::Job::ScanFileJob.perform_now(file:)
|
||||
end
|
||||
end
|
||||
end
|
||||
1
sorbet/rbi/dsl/application_controller.rbi
generated
1
sorbet/rbi/dsl/application_controller.rbi
generated
@@ -46,6 +46,7 @@ class ApplicationController
|
||||
include ::FaUriHelper
|
||||
include ::GoodJobHelper
|
||||
include ::IpAddressHelper
|
||||
include ::TelegramBotLogsHelper
|
||||
include ::TimestampHelper
|
||||
include ::UiHelper
|
||||
include ::DeviseHelper
|
||||
|
||||
1268
sorbet/rbi/dsl/bluesky/monitored_did.rbi
generated
Normal file
1268
sorbet/rbi/dsl/bluesky/monitored_did.rbi
generated
Normal file
File diff suppressed because it is too large
Load Diff
1
sorbet/rbi/dsl/devise_controller.rbi
generated
1
sorbet/rbi/dsl/devise_controller.rbi
generated
@@ -43,6 +43,7 @@ class DeviseController
|
||||
include ::FaUriHelper
|
||||
include ::GoodJobHelper
|
||||
include ::IpAddressHelper
|
||||
include ::TelegramBotLogsHelper
|
||||
include ::TimestampHelper
|
||||
include ::UiHelper
|
||||
include ::DeviseHelper
|
||||
|
||||
16
sorbet/rbi/dsl/domain/bluesky/job/base.rbi
generated
Normal file
16
sorbet/rbi/dsl/domain/bluesky/job/base.rbi
generated
Normal file
@@ -0,0 +1,16 @@
|
||||
# typed: true
|
||||
|
||||
# DO NOT EDIT MANUALLY
|
||||
# This is an autogenerated file for dynamic methods in `Domain::Bluesky::Job::Base`.
|
||||
# Please instead update this file by running `bin/tapioca dsl Domain::Bluesky::Job::Base`.
|
||||
|
||||
|
||||
class Domain::Bluesky::Job::Base
|
||||
sig { returns(ColorLogger) }
|
||||
def logger; end
|
||||
|
||||
class << self
|
||||
sig { returns(ColorLogger) }
|
||||
def logger; end
|
||||
end
|
||||
end
|
||||
27
sorbet/rbi/dsl/domain/bluesky/job/scan_user_job.rbi
generated
Normal file
27
sorbet/rbi/dsl/domain/bluesky/job/scan_user_job.rbi
generated
Normal file
@@ -0,0 +1,27 @@
|
||||
# typed: true
|
||||
|
||||
# DO NOT EDIT MANUALLY
|
||||
# This is an autogenerated file for dynamic methods in `Domain::Bluesky::Job::ScanUserJob`.
|
||||
# Please instead update this file by running `bin/tapioca dsl Domain::Bluesky::Job::ScanUserJob`.
|
||||
|
||||
|
||||
class Domain::Bluesky::Job::ScanUserJob
|
||||
sig { returns(ColorLogger) }
|
||||
def logger; end
|
||||
|
||||
class << self
|
||||
sig { returns(ColorLogger) }
|
||||
def logger; end
|
||||
|
||||
sig do
|
||||
params(
|
||||
args: T::Hash[::Symbol, T.untyped],
|
||||
block: T.nilable(T.proc.params(job: Domain::Bluesky::Job::ScanUserJob).void)
|
||||
).returns(T.any(Domain::Bluesky::Job::ScanUserJob, FalseClass))
|
||||
end
|
||||
def perform_later(args, &block); end
|
||||
|
||||
sig { params(args: T::Hash[::Symbol, T.untyped]).returns(T.untyped) }
|
||||
def perform_now(args); end
|
||||
end
|
||||
end
|
||||
2709
sorbet/rbi/dsl/domain/post/bluesky_post.rbi
generated
Normal file
2709
sorbet/rbi/dsl/domain/post/bluesky_post.rbi
generated
Normal file
File diff suppressed because it is too large
Load Diff
2223
sorbet/rbi/dsl/domain/post_file/bluesky_post_file.rbi
generated
Normal file
2223
sorbet/rbi/dsl/domain/post_file/bluesky_post_file.rbi
generated
Normal file
File diff suppressed because it is too large
Load Diff
2592
sorbet/rbi/dsl/domain/user/bluesky_user.rbi
generated
Normal file
2592
sorbet/rbi/dsl/domain/user/bluesky_user.rbi
generated
Normal file
File diff suppressed because it is too large
Load Diff
1391
sorbet/rbi/dsl/domain_post_files_bluesky_aux.rbi
generated
Normal file
1391
sorbet/rbi/dsl/domain_post_files_bluesky_aux.rbi
generated
Normal file
File diff suppressed because it is too large
Load Diff
2199
sorbet/rbi/dsl/domain_posts_bluesky_aux.rbi
generated
Normal file
2199
sorbet/rbi/dsl/domain_posts_bluesky_aux.rbi
generated
Normal file
File diff suppressed because it is too large
Load Diff
1818
sorbet/rbi/dsl/domain_users_bluesky_aux.rbi
generated
Normal file
1818
sorbet/rbi/dsl/domain_users_bluesky_aux.rbi
generated
Normal file
File diff suppressed because it is too large
Load Diff
1
sorbet/rbi/dsl/rails/application_controller.rbi
generated
1
sorbet/rbi/dsl/rails/application_controller.rbi
generated
@@ -46,6 +46,7 @@ class Rails::ApplicationController
|
||||
include ::FaUriHelper
|
||||
include ::GoodJobHelper
|
||||
include ::IpAddressHelper
|
||||
include ::TelegramBotLogsHelper
|
||||
include ::TimestampHelper
|
||||
include ::UiHelper
|
||||
include ::DeviseHelper
|
||||
|
||||
@@ -46,6 +46,7 @@ class Rails::Conductor::BaseController
|
||||
include ::FaUriHelper
|
||||
include ::GoodJobHelper
|
||||
include ::IpAddressHelper
|
||||
include ::TelegramBotLogsHelper
|
||||
include ::TimestampHelper
|
||||
include ::UiHelper
|
||||
include ::DeviseHelper
|
||||
|
||||
1
sorbet/rbi/dsl/rails/health_controller.rbi
generated
1
sorbet/rbi/dsl/rails/health_controller.rbi
generated
@@ -46,6 +46,7 @@ class Rails::HealthController
|
||||
include ::FaUriHelper
|
||||
include ::GoodJobHelper
|
||||
include ::IpAddressHelper
|
||||
include ::TelegramBotLogsHelper
|
||||
include ::TimestampHelper
|
||||
include ::UiHelper
|
||||
include ::DeviseHelper
|
||||
|
||||
16
sorbet/rbi/dsl/tasks/bluesky/monitor.rbi
generated
Normal file
16
sorbet/rbi/dsl/tasks/bluesky/monitor.rbi
generated
Normal file
@@ -0,0 +1,16 @@
|
||||
# typed: true
|
||||
|
||||
# DO NOT EDIT MANUALLY
|
||||
# This is an autogenerated file for dynamic methods in `Tasks::Bluesky::Monitor`.
|
||||
# Please instead update this file by running `bin/tapioca dsl Tasks::Bluesky::Monitor`.
|
||||
|
||||
|
||||
class Tasks::Bluesky::Monitor
|
||||
sig { returns(ColorLogger) }
|
||||
def logger; end
|
||||
|
||||
class << self
|
||||
sig { returns(ColorLogger) }
|
||||
def logger; end
|
||||
end
|
||||
end
|
||||
18
sorbet/rbi/dsl/telegram_bot_log.rbi
generated
18
sorbet/rbi/dsl/telegram_bot_log.rbi
generated
@@ -407,10 +407,10 @@ class TelegramBotLog
|
||||
def status_invalid_image?; end
|
||||
|
||||
sig { void }
|
||||
def status_no_results!; end
|
||||
def status_processing!; end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def status_no_results?; end
|
||||
def status_processing?; end
|
||||
|
||||
sig { void }
|
||||
def status_success!; end
|
||||
@@ -534,7 +534,7 @@ class TelegramBotLog
|
||||
def not_status_invalid_image(*args, &blk); end
|
||||
|
||||
sig { params(args: T.untyped, blk: T.untyped).returns(PrivateAssociationRelation) }
|
||||
def not_status_no_results(*args, &blk); end
|
||||
def not_status_processing(*args, &blk); end
|
||||
|
||||
sig { params(args: T.untyped, blk: T.untyped).returns(PrivateAssociationRelation) }
|
||||
def not_status_success(*args, &blk); end
|
||||
@@ -606,6 +606,9 @@ class TelegramBotLog
|
||||
end
|
||||
def select(*args, &blk); end
|
||||
|
||||
sig { params(args: T.untyped, blk: T.untyped).returns(PrivateAssociationRelation) }
|
||||
def slow_requests(*args, &blk); end
|
||||
|
||||
sig { params(args: T.untyped, blk: T.untyped).returns(PrivateAssociationRelation) }
|
||||
def status_error(*args, &blk); end
|
||||
|
||||
@@ -613,7 +616,7 @@ class TelegramBotLog
|
||||
def status_invalid_image(*args, &blk); end
|
||||
|
||||
sig { params(args: T.untyped, blk: T.untyped).returns(PrivateAssociationRelation) }
|
||||
def status_no_results(*args, &blk); end
|
||||
def status_processing(*args, &blk); end
|
||||
|
||||
sig { params(args: T.untyped, blk: T.untyped).returns(PrivateAssociationRelation) }
|
||||
def status_success(*args, &blk); end
|
||||
@@ -1923,7 +1926,7 @@ class TelegramBotLog
|
||||
def not_status_invalid_image(*args, &blk); end
|
||||
|
||||
sig { params(args: T.untyped, blk: T.untyped).returns(PrivateRelation) }
|
||||
def not_status_no_results(*args, &blk); end
|
||||
def not_status_processing(*args, &blk); end
|
||||
|
||||
sig { params(args: T.untyped, blk: T.untyped).returns(PrivateRelation) }
|
||||
def not_status_success(*args, &blk); end
|
||||
@@ -1995,6 +1998,9 @@ class TelegramBotLog
|
||||
end
|
||||
def select(*args, &blk); end
|
||||
|
||||
sig { params(args: T.untyped, blk: T.untyped).returns(PrivateRelation) }
|
||||
def slow_requests(*args, &blk); end
|
||||
|
||||
sig { params(args: T.untyped, blk: T.untyped).returns(PrivateRelation) }
|
||||
def status_error(*args, &blk); end
|
||||
|
||||
@@ -2002,7 +2008,7 @@ class TelegramBotLog
|
||||
def status_invalid_image(*args, &blk); end
|
||||
|
||||
sig { params(args: T.untyped, blk: T.untyped).returns(PrivateRelation) }
|
||||
def status_no_results(*args, &blk); end
|
||||
def status_processing(*args, &blk); end
|
||||
|
||||
sig { params(args: T.untyped, blk: T.untyped).returns(PrivateRelation) }
|
||||
def status_success(*args, &blk); end
|
||||
|
||||
57
sorbet/rbi/gems/base32@0.3.4.rbi
generated
Normal file
57
sorbet/rbi/gems/base32@0.3.4.rbi
generated
Normal file
@@ -0,0 +1,57 @@
|
||||
# typed: true
|
||||
|
||||
# DO NOT EDIT MANUALLY
|
||||
# This is an autogenerated file for types exported from the `base32` gem.
|
||||
# Please instead update this file by running `bin/tapioca gem base32`.
|
||||
|
||||
|
||||
# Module for encoding and decoding in Base32 per RFC 3548
|
||||
#
|
||||
# source://base32//lib/base32.rb#4
|
||||
module Base32
|
||||
class << self
|
||||
# source://base32//lib/base32.rb#38
|
||||
def chunks(str, size); end
|
||||
|
||||
# source://base32//lib/base32.rb#52
|
||||
def decode(str); end
|
||||
|
||||
# source://base32//lib/base32.rb#48
|
||||
def encode(str); end
|
||||
|
||||
# source://base32//lib/base32.rb#56
|
||||
def random_base32(length = T.unsafe(nil), padding = T.unsafe(nil)); end
|
||||
|
||||
# Returns the value of attribute table.
|
||||
#
|
||||
# source://base32//lib/base32.rb#9
|
||||
def table; end
|
||||
|
||||
# @raise [ArgumentError]
|
||||
#
|
||||
# source://base32//lib/base32.rb#64
|
||||
def table=(table); end
|
||||
|
||||
# @return [Boolean]
|
||||
#
|
||||
# source://base32//lib/base32.rb#69
|
||||
def table_valid?(table); end
|
||||
end
|
||||
end
|
||||
|
||||
# source://base32//lib/base32.rb#12
|
||||
class Base32::Chunk
|
||||
# @return [Chunk] a new instance of Chunk
|
||||
#
|
||||
# source://base32//lib/base32.rb#13
|
||||
def initialize(bytes); end
|
||||
|
||||
# source://base32//lib/base32.rb#17
|
||||
def decode; end
|
||||
|
||||
# source://base32//lib/base32.rb#29
|
||||
def encode; end
|
||||
end
|
||||
|
||||
# source://base32//lib/base32.rb#5
|
||||
Base32::TABLE = T.let(T.unsafe(nil), String)
|
||||
123
sorbet/rbi/gems/cbor@0.5.9.9.rbi
generated
Normal file
123
sorbet/rbi/gems/cbor@0.5.9.9.rbi
generated
Normal file
@@ -0,0 +1,123 @@
|
||||
# typed: true
|
||||
|
||||
# DO NOT EDIT MANUALLY
|
||||
# This is an autogenerated file for types exported from the `cbor` gem.
|
||||
# Please instead update this file by running `bin/tapioca gem cbor`.
|
||||
|
||||
|
||||
# source://cbor//lib/cbor/version.rb#1
|
||||
module CBOR
|
||||
private
|
||||
|
||||
def decode(*_arg0); end
|
||||
def dump(*_arg0); end
|
||||
def encode(*_arg0); end
|
||||
def load(*_arg0); end
|
||||
def pack(*_arg0); end
|
||||
def unpack(*_arg0); end
|
||||
|
||||
class << self
|
||||
def decode(*_arg0); end
|
||||
def dump(*_arg0); end
|
||||
def encode(*_arg0); end
|
||||
def load(*_arg0); end
|
||||
def pack(*_arg0); end
|
||||
def unpack(*_arg0); end
|
||||
end
|
||||
end
|
||||
|
||||
class CBOR::Buffer
|
||||
def initialize(*_arg0); end
|
||||
|
||||
def <<(_arg0); end
|
||||
def clear; end
|
||||
def close; end
|
||||
def empty?; end
|
||||
def flush; end
|
||||
def io; end
|
||||
def read(*_arg0); end
|
||||
def read_all(*_arg0); end
|
||||
def size; end
|
||||
def skip(_arg0); end
|
||||
def skip_all(_arg0); end
|
||||
def to_a; end
|
||||
def to_s; end
|
||||
def to_str; end
|
||||
def write(_arg0); end
|
||||
def write_to(_arg0); end
|
||||
end
|
||||
|
||||
class CBOR::MalformedFormatError < ::CBOR::UnpackError; end
|
||||
|
||||
class CBOR::Packer
|
||||
def initialize(*_arg0); end
|
||||
|
||||
def buffer; end
|
||||
def clear; end
|
||||
def empty?; end
|
||||
def flush; end
|
||||
def pack(_arg0); end
|
||||
def size; end
|
||||
def to_a; end
|
||||
def to_s; end
|
||||
def to_str; end
|
||||
def write(_arg0); end
|
||||
def write_array_header(_arg0); end
|
||||
def write_map_header(_arg0); end
|
||||
def write_nil; end
|
||||
def write_to(_arg0); end
|
||||
end
|
||||
|
||||
class CBOR::Simple < ::Struct
|
||||
def to_cbor(*_arg0); end
|
||||
def value; end
|
||||
def value=(_); end
|
||||
|
||||
class << self
|
||||
def [](*_arg0); end
|
||||
def inspect; end
|
||||
def keyword_init?; end
|
||||
def members; end
|
||||
def new(*_arg0); end
|
||||
end
|
||||
end
|
||||
|
||||
class CBOR::StackError < ::CBOR::UnpackError; end
|
||||
|
||||
class CBOR::Tagged < ::Struct
|
||||
def tag; end
|
||||
def tag=(_); end
|
||||
def to_cbor(*_arg0); end
|
||||
def value; end
|
||||
def value=(_); end
|
||||
|
||||
class << self
|
||||
def [](*_arg0); end
|
||||
def inspect; end
|
||||
def keyword_init?; end
|
||||
def members; end
|
||||
def new(*_arg0); end
|
||||
end
|
||||
end
|
||||
|
||||
class CBOR::TypeError < ::StandardError; end
|
||||
class CBOR::UnpackError < ::StandardError; end
|
||||
|
||||
class CBOR::Unpacker
|
||||
def initialize(*_arg0); end
|
||||
|
||||
def buffer; end
|
||||
def each; end
|
||||
def feed(_arg0); end
|
||||
def feed_each(_arg0); end
|
||||
def read; end
|
||||
def read_array_header; end
|
||||
def read_map_header; end
|
||||
def reset; end
|
||||
def skip; end
|
||||
def skip_nil; end
|
||||
def unpack; end
|
||||
end
|
||||
|
||||
# source://cbor//lib/cbor/version.rb#2
|
||||
CBOR::VERSION = T.let(T.unsafe(nil), String)
|
||||
366
sorbet/rbi/gems/didkit@0.2.3.rbi
generated
Normal file
366
sorbet/rbi/gems/didkit@0.2.3.rbi
generated
Normal file
@@ -0,0 +1,366 @@
|
||||
# typed: true
|
||||
|
||||
# DO NOT EDIT MANUALLY
|
||||
# This is an autogenerated file for types exported from the `didkit` gem.
|
||||
# Please instead update this file by running `bin/tapioca gem didkit`.
|
||||
|
||||
|
||||
# source://didkit//lib/didkit.rb#13
|
||||
DID = DIDKit::DID
|
||||
|
||||
# source://didkit//lib/didkit/errors.rb#1
|
||||
module DIDKit; end
|
||||
|
||||
# source://didkit//lib/didkit/errors.rb#5
|
||||
class DIDKit::APIError < ::StandardError
|
||||
# @return [APIError] a new instance of APIError
|
||||
#
|
||||
# source://didkit//lib/didkit/errors.rb#8
|
||||
def initialize(response); end
|
||||
|
||||
# source://didkit//lib/didkit/errors.rb#17
|
||||
def body; end
|
||||
|
||||
# Returns the value of attribute response.
|
||||
#
|
||||
# source://didkit//lib/didkit/errors.rb#6
|
||||
def response; end
|
||||
|
||||
# source://didkit//lib/didkit/errors.rb#13
|
||||
def status; end
|
||||
end
|
||||
|
||||
# source://didkit//lib/didkit/at_handles.rb#2
|
||||
module DIDKit::AtHandles
|
||||
# @raise [FormatError]
|
||||
#
|
||||
# source://didkit//lib/didkit/at_handles.rb#6
|
||||
def parse_also_known_as(aka); end
|
||||
end
|
||||
|
||||
# source://didkit//lib/didkit/at_handles.rb#3
|
||||
class DIDKit::AtHandles::FormatError < ::StandardError; end
|
||||
|
||||
# source://didkit//lib/didkit/did.rb#6
|
||||
class DIDKit::DID
|
||||
include ::DIDKit::Requests
|
||||
|
||||
# @return [DID] a new instance of DID
|
||||
#
|
||||
# source://didkit//lib/didkit/did.rb#15
|
||||
def initialize(did, resolved_by = T.unsafe(nil)); end
|
||||
|
||||
# source://didkit//lib/didkit/did.rb#80
|
||||
def ==(other); end
|
||||
|
||||
# @return [Boolean]
|
||||
#
|
||||
# source://didkit//lib/didkit/did.rb#72
|
||||
def account_exists?; end
|
||||
|
||||
# Returns the value of attribute did.
|
||||
#
|
||||
# source://didkit//lib/didkit/did.rb#13
|
||||
def did; end
|
||||
|
||||
# source://didkit//lib/didkit/did.rb#40
|
||||
def get_audit_log; end
|
||||
|
||||
# source://didkit//lib/didkit/did.rb#32
|
||||
def get_document; end
|
||||
|
||||
# source://didkit//lib/didkit/did.rb#36
|
||||
def get_validated_handle; end
|
||||
|
||||
# @return [Boolean]
|
||||
#
|
||||
# source://didkit//lib/didkit/did.rb#52
|
||||
def is_known_by_relay?(relay, options = T.unsafe(nil)); end
|
||||
|
||||
# Returns the value of attribute resolved_by.
|
||||
#
|
||||
# source://didkit//lib/didkit/did.rb#13
|
||||
def resolved_by; end
|
||||
|
||||
# Returns the value of attribute did.
|
||||
#
|
||||
# source://didkit//lib/didkit/did.rb#13
|
||||
def to_s; end
|
||||
|
||||
# Returns the value of attribute type.
|
||||
#
|
||||
# source://didkit//lib/didkit/did.rb#13
|
||||
def type; end
|
||||
|
||||
# source://didkit//lib/didkit/did.rb#48
|
||||
def web_domain; end
|
||||
|
||||
class << self
|
||||
# source://didkit//lib/didkit/did.rb#9
|
||||
def resolve_handle(handle); end
|
||||
end
|
||||
end
|
||||
|
||||
# source://didkit//lib/didkit/errors.rb#2
|
||||
class DIDKit::DIDError < ::StandardError; end
|
||||
|
||||
# source://didkit//lib/didkit/document.rb#7
|
||||
class DIDKit::Document
|
||||
include ::DIDKit::AtHandles
|
||||
include ::DIDKit::Services
|
||||
|
||||
# @raise [FormatError]
|
||||
# @return [Document] a new instance of Document
|
||||
#
|
||||
# source://didkit//lib/didkit/document.rb#16
|
||||
def initialize(did, json); end
|
||||
|
||||
# Returns the value of attribute did.
|
||||
#
|
||||
# source://didkit//lib/didkit/document.rb#14
|
||||
def did; end
|
||||
|
||||
# source://didkit//lib/didkit/document.rb#40
|
||||
def get_validated_handle; end
|
||||
|
||||
# Returns the value of attribute handles.
|
||||
#
|
||||
# source://didkit//lib/didkit/document.rb#14
|
||||
def handles; end
|
||||
|
||||
# Returns the value of attribute json.
|
||||
#
|
||||
# source://didkit//lib/didkit/document.rb#14
|
||||
def json; end
|
||||
|
||||
# Returns the value of attribute services.
|
||||
#
|
||||
# source://didkit//lib/didkit/document.rb#14
|
||||
def services; end
|
||||
end
|
||||
|
||||
# source://didkit//lib/didkit/document.rb#8
|
||||
class DIDKit::Document::FormatError < ::StandardError; end
|
||||
|
||||
# source://didkit//lib/didkit/plc_importer.rb#8
|
||||
class DIDKit::PLCImporter
|
||||
# @return [PLCImporter] a new instance of PLCImporter
|
||||
#
|
||||
# source://didkit//lib/didkit/plc_importer.rb#14
|
||||
def initialize(since: T.unsafe(nil)); end
|
||||
|
||||
# @return [Boolean]
|
||||
#
|
||||
# source://didkit//lib/didkit/plc_importer.rb#83
|
||||
def eof?; end
|
||||
|
||||
# Returns the value of attribute error_handler.
|
||||
#
|
||||
# source://didkit//lib/didkit/plc_importer.rb#12
|
||||
def error_handler; end
|
||||
|
||||
# Sets the attribute error_handler
|
||||
#
|
||||
# @param value the value to set the attribute error_handler to.
|
||||
#
|
||||
# source://didkit//lib/didkit/plc_importer.rb#12
|
||||
def error_handler=(_arg0); end
|
||||
|
||||
# source://didkit//lib/didkit/plc_importer.rb#75
|
||||
def fetch(&block); end
|
||||
|
||||
# source://didkit//lib/didkit/plc_importer.rb#49
|
||||
def fetch_audit_log(did); end
|
||||
|
||||
# source://didkit//lib/didkit/plc_importer.rb#54
|
||||
def fetch_page; end
|
||||
|
||||
# source://didkit//lib/didkit/plc_importer.rb#41
|
||||
def get_export(args = T.unsafe(nil)); end
|
||||
|
||||
# Returns the value of attribute ignore_errors.
|
||||
#
|
||||
# source://didkit//lib/didkit/plc_importer.rb#12
|
||||
def ignore_errors; end
|
||||
|
||||
# Sets the attribute ignore_errors
|
||||
#
|
||||
# @param value the value to set the attribute ignore_errors to.
|
||||
#
|
||||
# source://didkit//lib/didkit/plc_importer.rb#31
|
||||
def ignore_errors=(val); end
|
||||
|
||||
# Returns the value of attribute last_date.
|
||||
#
|
||||
# source://didkit//lib/didkit/plc_importer.rb#12
|
||||
def last_date; end
|
||||
|
||||
# Sets the attribute last_date
|
||||
#
|
||||
# @param value the value to set the attribute last_date to.
|
||||
#
|
||||
# source://didkit//lib/didkit/plc_importer.rb#12
|
||||
def last_date=(_arg0); end
|
||||
|
||||
# source://didkit//lib/didkit/plc_importer.rb#27
|
||||
def plc_service; end
|
||||
end
|
||||
|
||||
# source://didkit//lib/didkit/plc_importer.rb#10
|
||||
DIDKit::PLCImporter::MAX_PAGE = T.let(T.unsafe(nil), Integer)
|
||||
|
||||
# source://didkit//lib/didkit/plc_importer.rb#9
|
||||
DIDKit::PLCImporter::PLC_SERVICE = T.let(T.unsafe(nil), String)
|
||||
|
||||
# source://didkit//lib/didkit/plc_operation.rb#8
|
||||
class DIDKit::PLCOperation
|
||||
include ::DIDKit::AtHandles
|
||||
include ::DIDKit::Services
|
||||
|
||||
# @raise [FormatError]
|
||||
# @return [PLCOperation] a new instance of PLCOperation
|
||||
#
|
||||
# source://didkit//lib/didkit/plc_operation.rb#17
|
||||
def initialize(json); end
|
||||
|
||||
# Returns the value of attribute created_at.
|
||||
#
|
||||
# source://didkit//lib/didkit/plc_operation.rb#15
|
||||
def created_at; end
|
||||
|
||||
# Returns the value of attribute did.
|
||||
#
|
||||
# source://didkit//lib/didkit/plc_operation.rb#15
|
||||
def did; end
|
||||
|
||||
# Returns the value of attribute handles.
|
||||
#
|
||||
# source://didkit//lib/didkit/plc_operation.rb#15
|
||||
def handles; end
|
||||
|
||||
# Returns the value of attribute json.
|
||||
#
|
||||
# source://didkit//lib/didkit/plc_operation.rb#15
|
||||
def json; end
|
||||
|
||||
# Returns the value of attribute services.
|
||||
#
|
||||
# source://didkit//lib/didkit/plc_operation.rb#15
|
||||
def services; end
|
||||
|
||||
# Returns the value of attribute type.
|
||||
#
|
||||
# source://didkit//lib/didkit/plc_operation.rb#15
|
||||
def type; end
|
||||
end
|
||||
|
||||
# source://didkit//lib/didkit/plc_operation.rb#9
|
||||
class DIDKit::PLCOperation::FormatError < ::StandardError; end
|
||||
|
||||
# source://didkit//lib/didkit/requests.rb#1
|
||||
module DIDKit::Requests
|
||||
# source://didkit//lib/didkit/requests.rb#2
|
||||
def get_response(url, options = T.unsafe(nil)); end
|
||||
end
|
||||
|
||||
# source://didkit//lib/didkit/resolver.rb#11
|
||||
class DIDKit::Resolver
|
||||
include ::DIDKit::Requests
|
||||
|
||||
# @return [Resolver] a new instance of Resolver
|
||||
#
|
||||
# source://didkit//lib/didkit/resolver.rb#19
|
||||
def initialize(options = T.unsafe(nil)); end
|
||||
|
||||
# source://didkit//lib/didkit/resolver.rb#97
|
||||
def get_validated_handle(did_or_doc); end
|
||||
|
||||
# Returns the value of attribute nameserver.
|
||||
#
|
||||
# source://didkit//lib/didkit/resolver.rb#17
|
||||
def nameserver; end
|
||||
|
||||
# Sets the attribute nameserver
|
||||
#
|
||||
# @param value the value to set the attribute nameserver to.
|
||||
#
|
||||
# source://didkit//lib/didkit/resolver.rb#17
|
||||
def nameserver=(_arg0); end
|
||||
|
||||
# source://didkit//lib/didkit/resolver.rb#70
|
||||
def parse_did_from_dns(txt); end
|
||||
|
||||
# source://didkit//lib/didkit/resolver.rb#74
|
||||
def parse_did_from_well_known(text); end
|
||||
|
||||
# source://didkit//lib/didkit/resolver.rb#103
|
||||
def pick_valid_handle(did, handles); end
|
||||
|
||||
# source://didkit//lib/didkit/resolver.rb#64
|
||||
def resolv_options; end
|
||||
|
||||
# source://didkit//lib/didkit/resolver.rb#79
|
||||
def resolve_did(did); end
|
||||
|
||||
# source://didkit//lib/didkit/resolver.rb#85
|
||||
def resolve_did_plc(did); end
|
||||
|
||||
# source://didkit//lib/didkit/resolver.rb#91
|
||||
def resolve_did_web(did); end
|
||||
|
||||
# source://didkit//lib/didkit/resolver.rb#23
|
||||
def resolve_handle(handle); end
|
||||
|
||||
# source://didkit//lib/didkit/resolver.rb#37
|
||||
def resolve_handle_by_dns(domain); end
|
||||
|
||||
# source://didkit//lib/didkit/resolver.rb#51
|
||||
def resolve_handle_by_well_known(domain); end
|
||||
end
|
||||
|
||||
# source://didkit//lib/didkit/resolver.rb#13
|
||||
DIDKit::Resolver::MAX_REDIRECTS = T.let(T.unsafe(nil), Integer)
|
||||
|
||||
# source://didkit//lib/didkit/resolver.rb#12
|
||||
DIDKit::Resolver::RESERVED_DOMAINS = T.let(T.unsafe(nil), Array)
|
||||
|
||||
# source://didkit//lib/didkit/service_record.rb#5
|
||||
class DIDKit::ServiceRecord
|
||||
# @return [ServiceRecord] a new instance of ServiceRecord
|
||||
#
|
||||
# source://didkit//lib/didkit/service_record.rb#11
|
||||
def initialize(key, type, endpoint); end
|
||||
|
||||
# Returns the value of attribute endpoint.
|
||||
#
|
||||
# source://didkit//lib/didkit/service_record.rb#9
|
||||
def endpoint; end
|
||||
|
||||
# Returns the value of attribute key.
|
||||
#
|
||||
# source://didkit//lib/didkit/service_record.rb#9
|
||||
def key; end
|
||||
|
||||
# Returns the value of attribute type.
|
||||
#
|
||||
# source://didkit//lib/didkit/service_record.rb#9
|
||||
def type; end
|
||||
end
|
||||
|
||||
# source://didkit//lib/didkit/service_record.rb#6
|
||||
class DIDKit::ServiceRecord::FormatError < ::StandardError; end
|
||||
|
||||
# source://didkit//lib/didkit/services.rb#2
|
||||
module DIDKit::Services
|
||||
# source://didkit//lib/didkit/services.rb#3
|
||||
def get_service(key, type); end
|
||||
|
||||
# source://didkit//lib/didkit/services.rb#11
|
||||
def labeler_endpoint; end
|
||||
|
||||
# source://didkit//lib/didkit/services.rb#7
|
||||
def pds_endpoint; end
|
||||
end
|
||||
|
||||
# source://didkit//lib/didkit/version.rb#4
|
||||
DIDKit::VERSION = T.let(T.unsafe(nil), String)
|
||||
5205
sorbet/rbi/gems/eventmachine@1.2.7.rbi
generated
Normal file
5205
sorbet/rbi/gems/eventmachine@1.2.7.rbi
generated
Normal file
File diff suppressed because it is too large
Load Diff
548
sorbet/rbi/gems/faye-websocket@0.12.0.rbi
generated
Normal file
548
sorbet/rbi/gems/faye-websocket@0.12.0.rbi
generated
Normal file
@@ -0,0 +1,548 @@
|
||||
# typed: true
|
||||
|
||||
# DO NOT EDIT MANUALLY
|
||||
# This is an autogenerated file for types exported from the `faye-websocket` gem.
|
||||
# Please instead update this file by running `bin/tapioca gem faye-websocket`.
|
||||
|
||||
|
||||
# source://faye-websocket//lib/faye/websocket.rb#13
|
||||
module Faye; end
|
||||
|
||||
# source://faye-websocket//lib/faye/eventsource.rb#4
|
||||
class Faye::EventSource
|
||||
include ::WebSocket::Driver::EventEmitter
|
||||
include ::Faye::WebSocket::API::EventTarget
|
||||
|
||||
# @return [EventSource] a new instance of EventSource
|
||||
#
|
||||
# source://faye-websocket//lib/faye/eventsource.rb#21
|
||||
def initialize(env, options = T.unsafe(nil)); end
|
||||
|
||||
# source://faye-websocket//lib/faye/eventsource.rb#100
|
||||
def close; end
|
||||
|
||||
# Returns the value of attribute env.
|
||||
#
|
||||
# source://faye-websocket//lib/faye/eventsource.rb#7
|
||||
def env; end
|
||||
|
||||
# source://faye-websocket//lib/faye/eventsource.rb#57
|
||||
def last_event_id; end
|
||||
|
||||
# source://faye-websocket//lib/faye/eventsource.rb#94
|
||||
def ping(message = T.unsafe(nil)); end
|
||||
|
||||
# source://faye-websocket//lib/faye/eventsource.rb#61
|
||||
def rack_response; end
|
||||
|
||||
# Returns the value of attribute ready_state.
|
||||
#
|
||||
# source://faye-websocket//lib/faye/eventsource.rb#7
|
||||
def ready_state; end
|
||||
|
||||
# source://faye-websocket//lib/faye/eventsource.rb#79
|
||||
def send(message, options = T.unsafe(nil)); end
|
||||
|
||||
# Returns the value of attribute url.
|
||||
#
|
||||
# source://faye-websocket//lib/faye/eventsource.rb#7
|
||||
def url; end
|
||||
|
||||
private
|
||||
|
||||
# source://faye-websocket//lib/faye/eventsource.rb#67
|
||||
def open; end
|
||||
|
||||
class << self
|
||||
# source://faye-websocket//lib/faye/eventsource.rb#17
|
||||
def determine_url(env); end
|
||||
|
||||
# @return [Boolean]
|
||||
#
|
||||
# source://faye-websocket//lib/faye/eventsource.rb#11
|
||||
def eventsource?(env); end
|
||||
end
|
||||
end
|
||||
|
||||
# source://faye-websocket//lib/faye/eventsource.rb#9
|
||||
Faye::EventSource::DEFAULT_RETRY = T.let(T.unsafe(nil), Integer)
|
||||
|
||||
# source://faye-websocket//lib/faye/eventsource.rb#112
|
||||
class Faye::EventSource::Stream < ::Faye::RackStream
|
||||
# source://faye-websocket//lib/faye/eventsource.rb#113
|
||||
def fail; end
|
||||
end
|
||||
|
||||
# source://faye-websocket//lib/faye/rack_stream.rb#2
|
||||
class Faye::RackStream
|
||||
include ::EventMachine::Deferrable
|
||||
|
||||
# @return [RackStream] a new instance of RackStream
|
||||
#
|
||||
# source://faye-websocket//lib/faye/rack_stream.rb#18
|
||||
def initialize(socket); end
|
||||
|
||||
# source://faye-websocket//lib/faye/rack_stream.rb#55
|
||||
def clean_rack_hijack; end
|
||||
|
||||
# source://faye-websocket//lib/faye/rack_stream.rb#61
|
||||
def close_connection; end
|
||||
|
||||
# source://faye-websocket//lib/faye/rack_stream.rb#66
|
||||
def close_connection_after_writing; end
|
||||
|
||||
# source://faye-websocket//lib/faye/rack_stream.rb#71
|
||||
def each(&callback); end
|
||||
|
||||
# source://faye-websocket//lib/faye/rack_stream.rb#75
|
||||
def fail; end
|
||||
|
||||
# source://faye-websocket//lib/faye/rack_stream.rb#30
|
||||
def hijack_rack_socket; end
|
||||
|
||||
# source://faye-websocket//lib/faye/rack_stream.rb#78
|
||||
def receive(data); end
|
||||
|
||||
# source://faye-websocket//lib/faye/rack_stream.rb#81
|
||||
def write(data); end
|
||||
end
|
||||
|
||||
# source://faye-websocket//lib/faye/rack_stream.rb#6
|
||||
module Faye::RackStream::Reader
|
||||
# source://faye-websocket//lib/faye/rack_stream.rb#9
|
||||
def receive_data(data); end
|
||||
|
||||
# Returns the value of attribute stream.
|
||||
#
|
||||
# source://faye-websocket//lib/faye/rack_stream.rb#7
|
||||
def stream; end
|
||||
|
||||
# Sets the attribute stream
|
||||
#
|
||||
# @param value the value to set the attribute stream to.
|
||||
#
|
||||
# source://faye-websocket//lib/faye/rack_stream.rb#7
|
||||
def stream=(_arg0); end
|
||||
|
||||
# source://faye-websocket//lib/faye/rack_stream.rb#13
|
||||
def unbind; end
|
||||
end
|
||||
|
||||
# source://faye-websocket//lib/faye/websocket.rb#17
|
||||
class Faye::WebSocket
|
||||
include ::WebSocket::Driver::EventEmitter
|
||||
include ::Faye::WebSocket::API::EventTarget
|
||||
include ::Faye::WebSocket::API
|
||||
|
||||
# @return [WebSocket] a new instance of WebSocket
|
||||
#
|
||||
# source://faye-websocket//lib/faye/websocket.rb#69
|
||||
def initialize(env, protocols = T.unsafe(nil), options = T.unsafe(nil)); end
|
||||
|
||||
# Returns the value of attribute env.
|
||||
#
|
||||
# source://faye-websocket//lib/faye/websocket.rb#66
|
||||
def env; end
|
||||
|
||||
# source://faye-websocket//lib/faye/websocket.rb#91
|
||||
def rack_response; end
|
||||
|
||||
# source://faye-websocket//lib/faye/websocket.rb#85
|
||||
def start_driver; end
|
||||
|
||||
class << self
|
||||
# source://faye-websocket//lib/faye/websocket.rb#31
|
||||
def determine_url(env, schemes = T.unsafe(nil)); end
|
||||
|
||||
# source://faye-websocket//lib/faye/websocket.rb#40
|
||||
def ensure_reactor_running; end
|
||||
|
||||
# source://faye-websocket//lib/faye/websocket.rb#45
|
||||
def load_adapter(backend); end
|
||||
|
||||
# @return [Boolean]
|
||||
#
|
||||
# source://faye-websocket//lib/faye/websocket.rb#52
|
||||
def secure_request?(env); end
|
||||
|
||||
# @return [Boolean]
|
||||
#
|
||||
# source://faye-websocket//lib/faye/websocket.rb#62
|
||||
def websocket?(env); end
|
||||
end
|
||||
end
|
||||
|
||||
# source://faye-websocket//lib/faye/websocket.rb#25
|
||||
Faye::WebSocket::ADAPTERS = T.let(T.unsafe(nil), Hash)
|
||||
|
||||
# source://faye-websocket//lib/faye/websocket/api/event_target.rb#1
|
||||
module Faye::WebSocket::API
|
||||
include ::WebSocket::Driver::EventEmitter
|
||||
include ::Faye::WebSocket::API::EventTarget
|
||||
extend ::Forwardable
|
||||
|
||||
# source://faye-websocket//lib/faye/websocket/api.rb#22
|
||||
def initialize(options = T.unsafe(nil)); end
|
||||
|
||||
# Returns the value of attribute buffered_amount.
|
||||
#
|
||||
# source://faye-websocket//lib/faye/websocket/api.rb#20
|
||||
def buffered_amount; end
|
||||
|
||||
# source://faye-websocket//lib/faye/websocket/api.rb#90
|
||||
def close(code = T.unsafe(nil), reason = T.unsafe(nil)); end
|
||||
|
||||
# source://faye-websocket//lib/faye/websocket/api.rb#85
|
||||
def ping(message = T.unsafe(nil), &callback); end
|
||||
|
||||
# source://faye-websocket//lib/faye/websocket/api.rb#109
|
||||
def protocol; end
|
||||
|
||||
# Returns the value of attribute ready_state.
|
||||
#
|
||||
# source://faye-websocket//lib/faye/websocket/api.rb#20
|
||||
def ready_state; end
|
||||
|
||||
# source://faye-websocket//lib/faye/websocket/api.rb#66
|
||||
def send(message); end
|
||||
|
||||
# Returns the value of attribute url.
|
||||
#
|
||||
# source://faye-websocket//lib/faye/websocket/api.rb#20
|
||||
def url; end
|
||||
|
||||
# source://forwardable/1.3.3/forwardable.rb#231
|
||||
def version(*args, **_arg1, &block); end
|
||||
|
||||
# source://faye-websocket//lib/faye/websocket/api.rb#62
|
||||
def write(data); end
|
||||
|
||||
private
|
||||
|
||||
# source://faye-websocket//lib/faye/websocket/api.rb#138
|
||||
def begin_close(reason, code, options = T.unsafe(nil)); end
|
||||
|
||||
# source://faye-websocket//lib/faye/websocket/api.rb#130
|
||||
def emit_error(message); end
|
||||
|
||||
# source://faye-websocket//lib/faye/websocket/api.rb#154
|
||||
def finalize_close; end
|
||||
|
||||
# source://faye-websocket//lib/faye/websocket/api.rb#115
|
||||
def open; end
|
||||
|
||||
# source://faye-websocket//lib/faye/websocket/api.rb#169
|
||||
def parse(data); end
|
||||
|
||||
# source://faye-websocket//lib/faye/websocket/api.rb#123
|
||||
def receive_message(data); end
|
||||
end
|
||||
|
||||
# source://faye-websocket//lib/faye/websocket/api.rb#11
|
||||
Faye::WebSocket::API::CLOSED = T.let(T.unsafe(nil), Integer)
|
||||
|
||||
# source://faye-websocket//lib/faye/websocket/api.rb#13
|
||||
Faye::WebSocket::API::CLOSE_TIMEOUT = T.let(T.unsafe(nil), Integer)
|
||||
|
||||
# source://faye-websocket//lib/faye/websocket/api.rb#10
|
||||
Faye::WebSocket::API::CLOSING = T.let(T.unsafe(nil), Integer)
|
||||
|
||||
# source://faye-websocket//lib/faye/websocket/api.rb#8
|
||||
Faye::WebSocket::API::CONNECTING = T.let(T.unsafe(nil), Integer)
|
||||
|
||||
# source://faye-websocket//lib/faye/websocket/api/event.rb#36
|
||||
class Faye::WebSocket::API::CloseEvent < ::Faye::WebSocket::API::Event
|
||||
# Returns the value of attribute code.
|
||||
#
|
||||
# source://faye-websocket//lib/faye/websocket/api/event.rb#37
|
||||
def code; end
|
||||
|
||||
# Returns the value of attribute reason.
|
||||
#
|
||||
# source://faye-websocket//lib/faye/websocket/api/event.rb#37
|
||||
def reason; end
|
||||
end
|
||||
|
||||
# source://faye-websocket//lib/faye/websocket/api/event.rb#40
|
||||
class Faye::WebSocket::API::ErrorEvent < ::Faye::WebSocket::API::Event
|
||||
# Returns the value of attribute message.
|
||||
#
|
||||
# source://faye-websocket//lib/faye/websocket/api/event.rb#41
|
||||
def message; end
|
||||
end
|
||||
|
||||
# source://faye-websocket//lib/faye/websocket/api/event.rb#3
|
||||
class Faye::WebSocket::API::Event
|
||||
# @return [Event] a new instance of Event
|
||||
#
|
||||
# source://faye-websocket//lib/faye/websocket/api/event.rb#11
|
||||
def initialize(event_type, options); end
|
||||
|
||||
# Returns the value of attribute bubbles.
|
||||
#
|
||||
# source://faye-websocket//lib/faye/websocket/api/event.rb#4
|
||||
def bubbles; end
|
||||
|
||||
# Returns the value of attribute cancelable.
|
||||
#
|
||||
# source://faye-websocket//lib/faye/websocket/api/event.rb#4
|
||||
def cancelable; end
|
||||
|
||||
# Returns the value of attribute current_target.
|
||||
#
|
||||
# source://faye-websocket//lib/faye/websocket/api/event.rb#5
|
||||
def current_target; end
|
||||
|
||||
# Sets the attribute current_target
|
||||
#
|
||||
# @param value the value to set the attribute current_target to.
|
||||
#
|
||||
# source://faye-websocket//lib/faye/websocket/api/event.rb#5
|
||||
def current_target=(_arg0); end
|
||||
|
||||
# Returns the value of attribute event_phase.
|
||||
#
|
||||
# source://faye-websocket//lib/faye/websocket/api/event.rb#5
|
||||
def event_phase; end
|
||||
|
||||
# Sets the attribute event_phase
|
||||
#
|
||||
# @param value the value to set the attribute event_phase to.
|
||||
#
|
||||
# source://faye-websocket//lib/faye/websocket/api/event.rb#5
|
||||
def event_phase=(_arg0); end
|
||||
|
||||
# source://faye-websocket//lib/faye/websocket/api/event.rb#16
|
||||
def init_event(event_type, can_bubble, cancelable); end
|
||||
|
||||
# source://faye-websocket//lib/faye/websocket/api/event.rb#25
|
||||
def prevent_default; end
|
||||
|
||||
# source://faye-websocket//lib/faye/websocket/api/event.rb#22
|
||||
def stop_propagation; end
|
||||
|
||||
# Returns the value of attribute target.
|
||||
#
|
||||
# source://faye-websocket//lib/faye/websocket/api/event.rb#5
|
||||
def target; end
|
||||
|
||||
# Sets the attribute target
|
||||
#
|
||||
# @param value the value to set the attribute target to.
|
||||
#
|
||||
# source://faye-websocket//lib/faye/websocket/api/event.rb#5
|
||||
def target=(_arg0); end
|
||||
|
||||
# Returns the value of attribute type.
|
||||
#
|
||||
# source://faye-websocket//lib/faye/websocket/api/event.rb#4
|
||||
def type; end
|
||||
|
||||
class << self
|
||||
# source://faye-websocket//lib/faye/websocket/api/event.rb#51
|
||||
def create(type, options = T.unsafe(nil)); end
|
||||
end
|
||||
end
|
||||
|
||||
# source://faye-websocket//lib/faye/websocket/api/event.rb#8
|
||||
Faye::WebSocket::API::Event::AT_TARGET = T.let(T.unsafe(nil), Integer)
|
||||
|
||||
# source://faye-websocket//lib/faye/websocket/api/event.rb#9
|
||||
Faye::WebSocket::API::Event::BUBBLING_PHASE = T.let(T.unsafe(nil), Integer)
|
||||
|
||||
# source://faye-websocket//lib/faye/websocket/api/event.rb#7
|
||||
Faye::WebSocket::API::Event::CAPTURING_PHASE = T.let(T.unsafe(nil), Integer)
|
||||
|
||||
# source://faye-websocket//lib/faye/websocket/api/event_target.rb#2
|
||||
module Faye::WebSocket::API::EventTarget
|
||||
include ::WebSocket::Driver::EventEmitter
|
||||
|
||||
# source://faye-websocket//lib/faye/websocket/api/event_target.rb#16
|
||||
def add_event_listener(event_type, listener, use_capture = T.unsafe(nil)); end
|
||||
|
||||
# source://faye-websocket//lib/faye/websocket/api/event_target.rb#20
|
||||
def add_listener(event_type, callable = T.unsafe(nil), &block); end
|
||||
|
||||
# source://faye-websocket//lib/faye/websocket/api/event_target.rb#32
|
||||
def dispatch_event(event); end
|
||||
|
||||
# source://faye-websocket//lib/faye/websocket/api/event_target.rb#8
|
||||
def onclose=(handler); end
|
||||
|
||||
# source://faye-websocket//lib/faye/websocket/api/event_target.rb#8
|
||||
def onerror=(handler); end
|
||||
|
||||
# source://faye-websocket//lib/faye/websocket/api/event_target.rb#8
|
||||
def onmessage=(handler); end
|
||||
|
||||
# source://faye-websocket//lib/faye/websocket/api/event_target.rb#8
|
||||
def onopen=(handler); end
|
||||
|
||||
# source://faye-websocket//lib/faye/websocket/api/event_target.rb#28
|
||||
def remove_event_listener(event_type, listener, use_capture = T.unsafe(nil)); end
|
||||
|
||||
private
|
||||
|
||||
# source://faye-websocket//lib/faye/websocket/api/event_target.rb#55
|
||||
def event_buffers; end
|
||||
|
||||
# source://faye-websocket//lib/faye/websocket/api/event_target.rb#49
|
||||
def flush(event_type, listener); end
|
||||
end
|
||||
|
||||
# source://faye-websocket//lib/faye/websocket/api/event.rb#32
|
||||
class Faye::WebSocket::API::MessageEvent < ::Faye::WebSocket::API::Event
|
||||
# Returns the value of attribute data.
|
||||
#
|
||||
# source://faye-websocket//lib/faye/websocket/api/event.rb#33
|
||||
def data; end
|
||||
end
|
||||
|
||||
# source://faye-websocket//lib/faye/websocket/api.rb#9
|
||||
Faye::WebSocket::API::OPEN = T.let(T.unsafe(nil), Integer)
|
||||
|
||||
# source://faye-websocket//lib/faye/websocket/api/event.rb#29
|
||||
class Faye::WebSocket::API::OpenEvent < ::Faye::WebSocket::API::Event; end
|
||||
|
||||
# source://faye-websocket//lib/faye/websocket/api/event.rb#44
|
||||
Faye::WebSocket::API::TYPES = T.let(T.unsafe(nil), Hash)
|
||||
|
||||
# source://faye-websocket//lib/faye/websocket/adapter.rb#4
|
||||
module Faye::WebSocket::Adapter
|
||||
# @return [Boolean]
|
||||
#
|
||||
# source://faye-websocket//lib/faye/websocket/adapter.rb#10
|
||||
def eventsource?; end
|
||||
|
||||
# @return [Boolean]
|
||||
#
|
||||
# source://faye-websocket//lib/faye/websocket/adapter.rb#15
|
||||
def socket_connection?; end
|
||||
|
||||
# @return [Boolean]
|
||||
#
|
||||
# source://faye-websocket//lib/faye/websocket/adapter.rb#5
|
||||
def websocket?; end
|
||||
end
|
||||
|
||||
# source://faye-websocket//lib/faye/websocket/client.rb#6
|
||||
class Faye::WebSocket::Client
|
||||
include ::WebSocket::Driver::EventEmitter
|
||||
include ::Faye::WebSocket::API::EventTarget
|
||||
include ::Faye::WebSocket::API
|
||||
extend ::Forwardable
|
||||
|
||||
# @return [Client] a new instance of Client
|
||||
#
|
||||
# source://faye-websocket//lib/faye/websocket/client.rb#15
|
||||
def initialize(url, protocols = T.unsafe(nil), options = T.unsafe(nil)); end
|
||||
|
||||
# source://forwardable/1.3.3/forwardable.rb#231
|
||||
def headers(*args, **_arg1, &block); end
|
||||
|
||||
# source://forwardable/1.3.3/forwardable.rb#231
|
||||
def status(*args, **_arg1, &block); end
|
||||
|
||||
private
|
||||
|
||||
# source://faye-websocket//lib/faye/websocket/client.rb#42
|
||||
def configure_proxy(proxy); end
|
||||
|
||||
# source://faye-websocket//lib/faye/websocket/client.rb#67
|
||||
def on_connect(stream); end
|
||||
|
||||
# source://faye-websocket//lib/faye/websocket/client.rb#75
|
||||
def on_network_error(error); end
|
||||
|
||||
# source://faye-websocket//lib/faye/websocket/client.rb#86
|
||||
def ssl_handshake_completed; end
|
||||
|
||||
# source://faye-websocket//lib/faye/websocket/client.rb#80
|
||||
def ssl_verify_peer(cert); end
|
||||
|
||||
# source://faye-websocket//lib/faye/websocket/client.rb#59
|
||||
def start_tls(uri, options); end
|
||||
end
|
||||
|
||||
# source://faye-websocket//lib/faye/websocket/client.rb#92
|
||||
module Faye::WebSocket::Client::Connection
|
||||
# source://faye-websocket//lib/faye/websocket/client.rb#95
|
||||
def connection_completed; end
|
||||
|
||||
# Returns the value of attribute parent.
|
||||
#
|
||||
# source://faye-websocket//lib/faye/websocket/client.rb#93
|
||||
def parent; end
|
||||
|
||||
# Sets the attribute parent
|
||||
#
|
||||
# @param value the value to set the attribute parent to.
|
||||
#
|
||||
# source://faye-websocket//lib/faye/websocket/client.rb#93
|
||||
def parent=(_arg0); end
|
||||
|
||||
# source://faye-websocket//lib/faye/websocket/client.rb#107
|
||||
def receive_data(data); end
|
||||
|
||||
# source://faye-websocket//lib/faye/websocket/client.rb#103
|
||||
def ssl_handshake_completed; end
|
||||
|
||||
# source://faye-websocket//lib/faye/websocket/client.rb#99
|
||||
def ssl_verify_peer(cert); end
|
||||
|
||||
# source://faye-websocket//lib/faye/websocket/client.rb#111
|
||||
def unbind(error = T.unsafe(nil)); end
|
||||
|
||||
# source://faye-websocket//lib/faye/websocket/client.rb#116
|
||||
def write(data); end
|
||||
end
|
||||
|
||||
# source://faye-websocket//lib/faye/websocket/client.rb#10
|
||||
Faye::WebSocket::Client::DEFAULT_PORTS = T.let(T.unsafe(nil), Hash)
|
||||
|
||||
# source://faye-websocket//lib/faye/websocket/client.rb#11
|
||||
Faye::WebSocket::Client::SECURE_PROTOCOLS = T.let(T.unsafe(nil), Array)
|
||||
|
||||
# source://faye-websocket//lib/faye/websocket/ssl_verifier.rb#26
|
||||
class Faye::WebSocket::SSLError < ::OpenSSL::SSL::SSLError; end
|
||||
|
||||
# source://faye-websocket//lib/faye/websocket/ssl_verifier.rb#28
|
||||
class Faye::WebSocket::SslVerifier
|
||||
# @return [SslVerifier] a new instance of SslVerifier
|
||||
#
|
||||
# source://faye-websocket//lib/faye/websocket/ssl_verifier.rb#29
|
||||
def initialize(hostname, ssl_opts); end
|
||||
|
||||
# source://faye-websocket//lib/faye/websocket/ssl_verifier.rb#56
|
||||
def ssl_handshake_completed; end
|
||||
|
||||
# source://faye-websocket//lib/faye/websocket/ssl_verifier.rb#41
|
||||
def ssl_verify_peer(cert_text); end
|
||||
|
||||
private
|
||||
|
||||
# @return [Boolean]
|
||||
#
|
||||
# source://faye-websocket//lib/faye/websocket/ssl_verifier.rb#86
|
||||
def identity_verified?; end
|
||||
|
||||
# source://faye-websocket//lib/faye/websocket/ssl_verifier.rb#74
|
||||
def parse_cert(cert_text); end
|
||||
|
||||
# @return [Boolean]
|
||||
#
|
||||
# source://faye-websocket//lib/faye/websocket/ssl_verifier.rb#70
|
||||
def should_verify?; end
|
||||
|
||||
# source://faye-websocket//lib/faye/websocket/ssl_verifier.rb#80
|
||||
def store_cert(certificate); end
|
||||
end
|
||||
|
||||
# source://faye-websocket//lib/faye/websocket.rb#96
|
||||
class Faye::WebSocket::Stream < ::Faye::RackStream
|
||||
# source://faye-websocket//lib/faye/websocket.rb#97
|
||||
def fail; end
|
||||
|
||||
# source://faye-websocket//lib/faye/websocket.rb#101
|
||||
def receive(data); end
|
||||
end
|
||||
829
sorbet/rbi/gems/skyfall@0.6.0.rbi
generated
Normal file
829
sorbet/rbi/gems/skyfall@0.6.0.rbi
generated
Normal file
@@ -0,0 +1,829 @@
|
||||
# typed: true
|
||||
|
||||
# DO NOT EDIT MANUALLY
|
||||
# This is an autogenerated file for types exported from the `skyfall` gem.
|
||||
# Please instead update this file by running `bin/tapioca gem skyfall`.
|
||||
|
||||
|
||||
# CIDs in DAG-CBOR: https://ipld.io/specs/codecs/dag-cbor/spec/
|
||||
# CIDs in JSON: https://ipld.io/specs/codecs/dag-json/spec/
|
||||
# multibase: https://github.com/multiformats/multibase
|
||||
#
|
||||
# source://skyfall//lib/skyfall/version.rb#3
|
||||
module Skyfall; end
|
||||
|
||||
# source://skyfall//lib/skyfall/cid.rb#10
|
||||
class Skyfall::CID
|
||||
# @return [CID] a new instance of CID
|
||||
#
|
||||
# source://skyfall//lib/skyfall/cid.rb#27
|
||||
def initialize(data); end
|
||||
|
||||
# source://skyfall//lib/skyfall/cid.rb#39
|
||||
def ==(other); end
|
||||
|
||||
# Returns the value of attribute data.
|
||||
#
|
||||
# source://skyfall//lib/skyfall/cid.rb#11
|
||||
def data; end
|
||||
|
||||
# source://skyfall//lib/skyfall/cid.rb#35
|
||||
def inspect; end
|
||||
|
||||
# source://skyfall//lib/skyfall/cid.rb#31
|
||||
def to_s; end
|
||||
|
||||
class << self
|
||||
# @raise [DecodeError]
|
||||
#
|
||||
# source://skyfall//lib/skyfall/cid.rb#13
|
||||
def from_cbor_tag(tag); end
|
||||
|
||||
# @raise [DecodeError]
|
||||
#
|
||||
# source://skyfall//lib/skyfall/cid.rb#19
|
||||
def from_json(string); end
|
||||
end
|
||||
end
|
||||
|
||||
# source://skyfall//lib/skyfall/car_archive.rb#26
|
||||
class Skyfall::CarArchive
|
||||
# @return [CarArchive] a new instance of CarArchive
|
||||
#
|
||||
# source://skyfall//lib/skyfall/car_archive.rb#31
|
||||
def initialize(data); end
|
||||
|
||||
# Returns the value of attribute roots.
|
||||
#
|
||||
# source://skyfall//lib/skyfall/car_archive.rb#29
|
||||
def roots; end
|
||||
|
||||
# source://skyfall//lib/skyfall/car_archive.rb#39
|
||||
def section_with_cid(cid); end
|
||||
|
||||
# Returns the value of attribute sections.
|
||||
#
|
||||
# source://skyfall//lib/skyfall/car_archive.rb#29
|
||||
def sections; end
|
||||
|
||||
private
|
||||
|
||||
# @raise [DecodeError]
|
||||
#
|
||||
# source://skyfall//lib/skyfall/car_archive.rb#80
|
||||
def read_header(buffer); end
|
||||
|
||||
# @raise [DecodeError]
|
||||
#
|
||||
# source://skyfall//lib/skyfall/car_archive.rb#91
|
||||
def read_section(buffer); end
|
||||
|
||||
class << self
|
||||
# source://skyfall//lib/skyfall/car_archive.rb#44
|
||||
def convert_data(object); end
|
||||
|
||||
# source://skyfall//lib/skyfall/car_archive.rb#74
|
||||
def make_bytes(data); end
|
||||
|
||||
# source://skyfall//lib/skyfall/car_archive.rb#70
|
||||
def make_cid_link(cid); end
|
||||
end
|
||||
end
|
||||
|
||||
# source://skyfall//lib/skyfall/car_archive.rb#13
|
||||
class Skyfall::CarSection
|
||||
# @return [CarSection] a new instance of CarSection
|
||||
#
|
||||
# source://skyfall//lib/skyfall/car_archive.rb#16
|
||||
def initialize(cid, body_data); end
|
||||
|
||||
# source://skyfall//lib/skyfall/car_archive.rb#21
|
||||
def body; end
|
||||
|
||||
# Returns the value of attribute cid.
|
||||
#
|
||||
# source://skyfall//lib/skyfall/car_archive.rb#14
|
||||
def cid; end
|
||||
end
|
||||
|
||||
# source://skyfall//lib/skyfall/collection.rb#2
|
||||
module Skyfall::Collection
|
||||
class << self
|
||||
# source://skyfall//lib/skyfall/collection.rb#46
|
||||
def from_short_code(code); end
|
||||
|
||||
# source://skyfall//lib/skyfall/collection.rb#42
|
||||
def short_code(collection); end
|
||||
end
|
||||
end
|
||||
|
||||
# source://skyfall//lib/skyfall/collection.rb#4
|
||||
Skyfall::Collection::BSKY_ACTOR_STATUS = T.let(T.unsafe(nil), String)
|
||||
|
||||
# source://skyfall//lib/skyfall/collection.rb#11
|
||||
Skyfall::Collection::BSKY_BLOCK = T.let(T.unsafe(nil), String)
|
||||
|
||||
# source://skyfall//lib/skyfall/collection.rb#20
|
||||
Skyfall::Collection::BSKY_CHAT_DECLARATION = T.let(T.unsafe(nil), String)
|
||||
|
||||
# source://skyfall//lib/skyfall/collection.rb#5
|
||||
Skyfall::Collection::BSKY_FEED = T.let(T.unsafe(nil), String)
|
||||
|
||||
# source://skyfall//lib/skyfall/collection.rb#12
|
||||
Skyfall::Collection::BSKY_FOLLOW = T.let(T.unsafe(nil), String)
|
||||
|
||||
# source://skyfall//lib/skyfall/collection.rb#18
|
||||
Skyfall::Collection::BSKY_LABELER = T.let(T.unsafe(nil), String)
|
||||
|
||||
# source://skyfall//lib/skyfall/collection.rb#6
|
||||
Skyfall::Collection::BSKY_LIKE = T.let(T.unsafe(nil), String)
|
||||
|
||||
# source://skyfall//lib/skyfall/collection.rb#13
|
||||
Skyfall::Collection::BSKY_LIST = T.let(T.unsafe(nil), String)
|
||||
|
||||
# source://skyfall//lib/skyfall/collection.rb#14
|
||||
Skyfall::Collection::BSKY_LISTBLOCK = T.let(T.unsafe(nil), String)
|
||||
|
||||
# source://skyfall//lib/skyfall/collection.rb#15
|
||||
Skyfall::Collection::BSKY_LISTITEM = T.let(T.unsafe(nil), String)
|
||||
|
||||
# source://skyfall//lib/skyfall/collection.rb#7
|
||||
Skyfall::Collection::BSKY_POST = T.let(T.unsafe(nil), String)
|
||||
|
||||
# source://skyfall//lib/skyfall/collection.rb#8
|
||||
Skyfall::Collection::BSKY_POSTGATE = T.let(T.unsafe(nil), String)
|
||||
|
||||
# source://skyfall//lib/skyfall/collection.rb#3
|
||||
Skyfall::Collection::BSKY_PROFILE = T.let(T.unsafe(nil), String)
|
||||
|
||||
# source://skyfall//lib/skyfall/collection.rb#9
|
||||
Skyfall::Collection::BSKY_REPOST = T.let(T.unsafe(nil), String)
|
||||
|
||||
# source://skyfall//lib/skyfall/collection.rb#16
|
||||
Skyfall::Collection::BSKY_STARTERPACK = T.let(T.unsafe(nil), String)
|
||||
|
||||
# source://skyfall//lib/skyfall/collection.rb#10
|
||||
Skyfall::Collection::BSKY_THREADGATE = T.let(T.unsafe(nil), String)
|
||||
|
||||
# source://skyfall//lib/skyfall/collection.rb#17
|
||||
Skyfall::Collection::BSKY_VERIFICATION = T.let(T.unsafe(nil), String)
|
||||
|
||||
# source://skyfall//lib/skyfall/collection.rb#22
|
||||
Skyfall::Collection::SHORT_CODES = T.let(T.unsafe(nil), Hash)
|
||||
|
||||
# source://skyfall//lib/skyfall/errors.rb#2
|
||||
class Skyfall::DecodeError < ::StandardError; end
|
||||
|
||||
# source://skyfall//lib/skyfall/extensions.rb#5
|
||||
module Skyfall::Extensions; end
|
||||
|
||||
# source://skyfall//lib/skyfall/firehose.rb#5
|
||||
class Skyfall::Firehose < ::Skyfall::Stream
|
||||
# @return [Firehose] a new instance of Firehose
|
||||
#
|
||||
# source://skyfall//lib/skyfall/firehose.rb#16
|
||||
def initialize(server, endpoint, cursor = T.unsafe(nil)); end
|
||||
|
||||
# Returns the value of attribute cursor.
|
||||
#
|
||||
# source://skyfall//lib/skyfall/firehose.rb#14
|
||||
def cursor; end
|
||||
|
||||
# Sets the attribute cursor
|
||||
#
|
||||
# @param value the value to set the attribute cursor to.
|
||||
#
|
||||
# source://skyfall//lib/skyfall/firehose.rb#14
|
||||
def cursor=(_arg0); end
|
||||
|
||||
# source://skyfall//lib/skyfall/firehose.rb#29
|
||||
def handle_message(msg); end
|
||||
|
||||
private
|
||||
|
||||
# source://skyfall//lib/skyfall/firehose.rb#45
|
||||
def build_websocket_url; end
|
||||
|
||||
# source://skyfall//lib/skyfall/firehose.rb#49
|
||||
def check_cursor(cursor); end
|
||||
|
||||
# source://skyfall//lib/skyfall/firehose.rb#59
|
||||
def check_endpoint(endpoint); end
|
||||
end
|
||||
|
||||
# source://skyfall//lib/skyfall/firehose/account_message.rb#4
|
||||
class Skyfall::Firehose::AccountMessage < ::Skyfall::Firehose::Message
|
||||
# source://skyfall//lib/skyfall/firehose/account_message.rb#5
|
||||
def active?; end
|
||||
|
||||
# source://skyfall//lib/skyfall/firehose/account_message.rb#9
|
||||
def status; end
|
||||
end
|
||||
|
||||
# source://skyfall//lib/skyfall/firehose/commit_message.rb#7
|
||||
class Skyfall::Firehose::CommitMessage < ::Skyfall::Firehose::Message
|
||||
# source://skyfall//lib/skyfall/firehose/commit_message.rb#17
|
||||
def blocks; end
|
||||
|
||||
# source://skyfall//lib/skyfall/firehose/commit_message.rb#8
|
||||
def commit; end
|
||||
|
||||
# source://skyfall//lib/skyfall/firehose/commit_message.rb#21
|
||||
def operations; end
|
||||
|
||||
# source://skyfall//lib/skyfall/firehose/commit_message.rb#12
|
||||
def prev; end
|
||||
|
||||
# source://skyfall//lib/skyfall/firehose/commit_message.rb#25
|
||||
def raw_record_for_operation(op); end
|
||||
end
|
||||
|
||||
# source://skyfall//lib/skyfall/firehose/handle_message.rb#9
|
||||
class Skyfall::Firehose::HandleMessage < ::Skyfall::Firehose::Message
|
||||
# source://skyfall//lib/skyfall/firehose/handle_message.rb#10
|
||||
def handle; end
|
||||
end
|
||||
|
||||
# source://skyfall//lib/skyfall/firehose/identity_message.rb#4
|
||||
class Skyfall::Firehose::IdentityMessage < ::Skyfall::Firehose::Message
|
||||
# source://skyfall//lib/skyfall/firehose/identity_message.rb#5
|
||||
def handle; end
|
||||
end
|
||||
|
||||
# source://skyfall//lib/skyfall/firehose/info_message.rb#4
|
||||
class Skyfall::Firehose::InfoMessage < ::Skyfall::Firehose::Message
|
||||
# source://skyfall//lib/skyfall/firehose/info_message.rb#9
|
||||
def initialize(type_object, data_object); end
|
||||
|
||||
# source://skyfall//lib/skyfall/firehose/info_message.rb#20
|
||||
def inspectable_variables; end
|
||||
|
||||
# source://skyfall//lib/skyfall/firehose/info_message.rb#5
|
||||
def message; end
|
||||
|
||||
# source://skyfall//lib/skyfall/firehose/info_message.rb#5
|
||||
def name; end
|
||||
|
||||
# source://skyfall//lib/skyfall/firehose/info_message.rb#16
|
||||
def to_s; end
|
||||
end
|
||||
|
||||
# source://skyfall//lib/skyfall/firehose/info_message.rb#7
|
||||
Skyfall::Firehose::InfoMessage::OUTDATED_CURSOR = T.let(T.unsafe(nil), String)
|
||||
|
||||
# source://skyfall//lib/skyfall/firehose/labels_message.rb#5
|
||||
class Skyfall::Firehose::LabelsMessage
|
||||
# source://skyfall//lib/skyfall/firehose/labels_message.rb#11
|
||||
def initialize(type_object, data_object); end
|
||||
|
||||
# source://skyfall//lib/skyfall/firehose/labels_message.rb#8
|
||||
def data_object; end
|
||||
|
||||
# source://skyfall//lib/skyfall/firehose/labels_message.rb#19
|
||||
def labels; end
|
||||
|
||||
# source://skyfall//lib/skyfall/firehose/labels_message.rb#9
|
||||
def seq; end
|
||||
|
||||
# source://skyfall//lib/skyfall/firehose/labels_message.rb#9
|
||||
def type; end
|
||||
|
||||
# source://skyfall//lib/skyfall/firehose/labels_message.rb#8
|
||||
def type_object; end
|
||||
end
|
||||
|
||||
# source://skyfall//lib/skyfall/firehose/message.rb#9
|
||||
class Skyfall::Firehose::Message
|
||||
# source://skyfall//lib/skyfall/firehose/message.rb#48
|
||||
def initialize(type_object, data_object); end
|
||||
|
||||
# source://skyfall//lib/skyfall/firehose/message.rb#26
|
||||
def data_object; end
|
||||
|
||||
# source://skyfall//lib/skyfall/firehose/message.rb#22
|
||||
def did; end
|
||||
|
||||
# source://skyfall//lib/skyfall/firehose/message.rb#73
|
||||
def inspect; end
|
||||
|
||||
# source://skyfall//lib/skyfall/firehose/message.rb#69
|
||||
def inspectable_variables; end
|
||||
|
||||
# source://skyfall//lib/skyfall/firehose/message.rb#57
|
||||
def operations; end
|
||||
|
||||
# source://skyfall//lib/skyfall/firehose/message.rb#22
|
||||
def repo; end
|
||||
|
||||
# source://skyfall//lib/skyfall/firehose/message.rb#22
|
||||
def seq; end
|
||||
|
||||
# source://skyfall//lib/skyfall/firehose/message.rb#65
|
||||
def time; end
|
||||
|
||||
# source://skyfall//lib/skyfall/firehose/message.rb#22
|
||||
def type; end
|
||||
|
||||
# source://skyfall//lib/skyfall/firehose/message.rb#26
|
||||
def type_object; end
|
||||
|
||||
# source://skyfall//lib/skyfall/firehose/message.rb#61
|
||||
def unknown?; end
|
||||
|
||||
class << self
|
||||
# source://skyfall//lib/skyfall/firehose/message.rb#80
|
||||
def decode_cbor_objects(data); end
|
||||
|
||||
# source://skyfall//lib/skyfall/firehose/message.rb#28
|
||||
def new(data); end
|
||||
end
|
||||
end
|
||||
|
||||
# source://skyfall//lib/skyfall/firehose.rb#9
|
||||
Skyfall::Firehose::NAMED_ENDPOINTS = T.let(T.unsafe(nil), Hash)
|
||||
|
||||
# source://skyfall//lib/skyfall/firehose/operation.rb#5
|
||||
class Skyfall::Firehose::Operation
|
||||
# source://skyfall//lib/skyfall/firehose/operation.rb#6
|
||||
def initialize(message, json); end
|
||||
|
||||
# source://skyfall//lib/skyfall/firehose/operation.rb#21
|
||||
def action; end
|
||||
|
||||
# source://skyfall//lib/skyfall/firehose/operation.rb#37
|
||||
def cid; end
|
||||
|
||||
# source://skyfall//lib/skyfall/firehose/operation.rb#25
|
||||
def collection; end
|
||||
|
||||
# source://skyfall//lib/skyfall/firehose/operation.rb#11
|
||||
def did; end
|
||||
|
||||
# source://skyfall//lib/skyfall/firehose/operation.rb#53
|
||||
def inspect; end
|
||||
|
||||
# source://skyfall//lib/skyfall/firehose/operation.rb#49
|
||||
def inspectable_variables; end
|
||||
|
||||
# source://skyfall//lib/skyfall/firehose/operation.rb#17
|
||||
def path; end
|
||||
|
||||
# source://skyfall//lib/skyfall/firehose/operation.rb#41
|
||||
def raw_record; end
|
||||
|
||||
# source://skyfall//lib/skyfall/firehose/operation.rb#11
|
||||
def repo; end
|
||||
|
||||
# source://skyfall//lib/skyfall/firehose/operation.rb#29
|
||||
def rkey; end
|
||||
|
||||
# source://skyfall//lib/skyfall/firehose/operation.rb#45
|
||||
def type; end
|
||||
|
||||
# source://skyfall//lib/skyfall/firehose/operation.rb#33
|
||||
def uri; end
|
||||
end
|
||||
|
||||
# source://skyfall//lib/skyfall/firehose.rb#7
|
||||
Skyfall::Firehose::SUBSCRIBE_LABELS = T.let(T.unsafe(nil), String)
|
||||
|
||||
# source://skyfall//lib/skyfall/firehose.rb#6
|
||||
Skyfall::Firehose::SUBSCRIBE_REPOS = T.let(T.unsafe(nil), String)
|
||||
|
||||
# source://skyfall//lib/skyfall/firehose/sync_message.rb#4
|
||||
class Skyfall::Firehose::SyncMessage < ::Skyfall::Firehose::Message
|
||||
# source://skyfall//lib/skyfall/firehose/sync_message.rb#5
|
||||
def rev; end
|
||||
end
|
||||
|
||||
# source://skyfall//lib/skyfall/firehose/tombstone_message.rb#9
|
||||
class Skyfall::Firehose::TombstoneMessage < ::Skyfall::Firehose::Message; end
|
||||
|
||||
# source://skyfall//lib/skyfall/firehose/unknown_message.rb#4
|
||||
class Skyfall::Firehose::UnknownMessage < ::Skyfall::Firehose::Message; end
|
||||
|
||||
# source://skyfall//lib/skyfall/jetstream.rb#8
|
||||
class Skyfall::Jetstream < ::Skyfall::Stream
|
||||
# @return [Jetstream] a new instance of Jetstream
|
||||
#
|
||||
# source://skyfall//lib/skyfall/jetstream.rb#11
|
||||
def initialize(server, params = T.unsafe(nil)); end
|
||||
|
||||
# Returns the value of attribute cursor.
|
||||
#
|
||||
# source://skyfall//lib/skyfall/jetstream.rb#9
|
||||
def cursor; end
|
||||
|
||||
# Sets the attribute cursor
|
||||
#
|
||||
# @param value the value to set the attribute cursor to.
|
||||
#
|
||||
# source://skyfall//lib/skyfall/jetstream.rb#9
|
||||
def cursor=(_arg0); end
|
||||
|
||||
# source://skyfall//lib/skyfall/jetstream.rb#25
|
||||
def handle_message(msg); end
|
||||
|
||||
private
|
||||
|
||||
# source://skyfall//lib/skyfall/jetstream.rb#40
|
||||
def build_websocket_url; end
|
||||
|
||||
# source://skyfall//lib/skyfall/jetstream.rb#110
|
||||
def check_cursor(cursor); end
|
||||
|
||||
# source://skyfall//lib/skyfall/jetstream.rb#69
|
||||
def check_option(k, v); end
|
||||
|
||||
# @raise [ArgumentError]
|
||||
#
|
||||
# source://skyfall//lib/skyfall/jetstream.rb#47
|
||||
def check_params(params); end
|
||||
|
||||
# source://skyfall//lib/skyfall/jetstream.rb#84
|
||||
def check_wanted_collections(list); end
|
||||
|
||||
# source://skyfall//lib/skyfall/jetstream.rb#99
|
||||
def check_wanted_dids(list); end
|
||||
end
|
||||
|
||||
# source://skyfall//lib/skyfall/jetstream/account_message.rb#5
|
||||
class Skyfall::Jetstream::AccountMessage < ::Skyfall::Jetstream::Message
|
||||
# source://skyfall//lib/skyfall/jetstream/account_message.rb#6
|
||||
def initialize(json); end
|
||||
|
||||
# source://skyfall//lib/skyfall/jetstream/account_message.rb#11
|
||||
def active?; end
|
||||
|
||||
# source://skyfall//lib/skyfall/jetstream/account_message.rb#15
|
||||
def status; end
|
||||
end
|
||||
|
||||
# source://skyfall//lib/skyfall/jetstream/commit_message.rb#6
|
||||
class Skyfall::Jetstream::CommitMessage < ::Skyfall::Jetstream::Message
|
||||
# source://skyfall//lib/skyfall/jetstream/commit_message.rb#7
|
||||
def initialize(json); end
|
||||
|
||||
# source://skyfall//lib/skyfall/jetstream/commit_message.rb#12
|
||||
def operations; end
|
||||
end
|
||||
|
||||
# source://skyfall//lib/skyfall/jetstream/identity_message.rb#5
|
||||
class Skyfall::Jetstream::IdentityMessage < ::Skyfall::Jetstream::Message
|
||||
# source://skyfall//lib/skyfall/jetstream/identity_message.rb#6
|
||||
def initialize(json); end
|
||||
|
||||
# source://skyfall//lib/skyfall/jetstream/identity_message.rb#11
|
||||
def handle; end
|
||||
end
|
||||
|
||||
# source://skyfall//lib/skyfall/jetstream/message.rb#7
|
||||
class Skyfall::Jetstream::Message
|
||||
# source://skyfall//lib/skyfall/jetstream/message.rb#35
|
||||
def initialize(json); end
|
||||
|
||||
# source://skyfall//lib/skyfall/jetstream/message.rb#13
|
||||
def did; end
|
||||
|
||||
# source://skyfall//lib/skyfall/jetstream/message.rb#18
|
||||
def json; end
|
||||
|
||||
# source://skyfall//lib/skyfall/jetstream/message.rb#46
|
||||
def operations; end
|
||||
|
||||
# source://skyfall//lib/skyfall/jetstream/message.rb#13
|
||||
def repo; end
|
||||
|
||||
# source://skyfall//lib/skyfall/jetstream/message.rb#13
|
||||
def seq; end
|
||||
|
||||
# source://skyfall//lib/skyfall/jetstream/message.rb#50
|
||||
def time; end
|
||||
|
||||
# source://skyfall//lib/skyfall/jetstream/message.rb#13
|
||||
def time_us; end
|
||||
|
||||
# source://skyfall//lib/skyfall/jetstream/message.rb#13
|
||||
def type; end
|
||||
|
||||
# source://skyfall//lib/skyfall/jetstream/message.rb#42
|
||||
def unknown?; end
|
||||
|
||||
class << self
|
||||
# source://skyfall//lib/skyfall/jetstream/message.rb#20
|
||||
def new(data); end
|
||||
end
|
||||
end
|
||||
|
||||
# source://skyfall//lib/skyfall/jetstream/operation.rb#5
|
||||
class Skyfall::Jetstream::Operation
|
||||
# source://skyfall//lib/skyfall/jetstream/operation.rb#6
|
||||
def initialize(message, json); end
|
||||
|
||||
# source://skyfall//lib/skyfall/jetstream/operation.rb#21
|
||||
def action; end
|
||||
|
||||
# source://skyfall//lib/skyfall/jetstream/operation.rb#37
|
||||
def cid; end
|
||||
|
||||
# source://skyfall//lib/skyfall/jetstream/operation.rb#25
|
||||
def collection; end
|
||||
|
||||
# source://skyfall//lib/skyfall/jetstream/operation.rb#11
|
||||
def did; end
|
||||
|
||||
# source://skyfall//lib/skyfall/jetstream/operation.rb#53
|
||||
def inspect; end
|
||||
|
||||
# source://skyfall//lib/skyfall/jetstream/operation.rb#49
|
||||
def inspectable_variables; end
|
||||
|
||||
# source://skyfall//lib/skyfall/jetstream/operation.rb#17
|
||||
def path; end
|
||||
|
||||
# source://skyfall//lib/skyfall/jetstream/operation.rb#41
|
||||
def raw_record; end
|
||||
|
||||
# source://skyfall//lib/skyfall/jetstream/operation.rb#11
|
||||
def repo; end
|
||||
|
||||
# source://skyfall//lib/skyfall/jetstream/operation.rb#29
|
||||
def rkey; end
|
||||
|
||||
# source://skyfall//lib/skyfall/jetstream/operation.rb#45
|
||||
def type; end
|
||||
|
||||
# source://skyfall//lib/skyfall/jetstream/operation.rb#33
|
||||
def uri; end
|
||||
end
|
||||
|
||||
# source://skyfall//lib/skyfall/jetstream/unknown_message.rb#4
|
||||
class Skyfall::Jetstream::UnknownMessage < ::Skyfall::Jetstream::Message; end
|
||||
|
||||
# source://skyfall//lib/skyfall/label.rb#5
|
||||
class Skyfall::Label
|
||||
# @raise [DecodeError]
|
||||
# @return [Label] a new instance of Label
|
||||
#
|
||||
# source://skyfall//lib/skyfall/label.rb#8
|
||||
def initialize(data); end
|
||||
|
||||
# source://skyfall//lib/skyfall/label.rb#27
|
||||
def authority; end
|
||||
|
||||
# source://skyfall//lib/skyfall/label.rb#35
|
||||
def cid; end
|
||||
|
||||
# source://skyfall//lib/skyfall/label.rb#47
|
||||
def created_at; end
|
||||
|
||||
# source://skyfall//lib/skyfall/label.rb#47
|
||||
def cts; end
|
||||
|
||||
# Returns the value of attribute data.
|
||||
#
|
||||
# source://skyfall//lib/skyfall/label.rb#6
|
||||
def data; end
|
||||
|
||||
# source://skyfall//lib/skyfall/label.rb#51
|
||||
def exp; end
|
||||
|
||||
# source://skyfall//lib/skyfall/label.rb#51
|
||||
def expires_at; end
|
||||
|
||||
# @return [Boolean]
|
||||
#
|
||||
# source://skyfall//lib/skyfall/label.rb#43
|
||||
def neg; end
|
||||
|
||||
# @return [Boolean]
|
||||
#
|
||||
# source://skyfall//lib/skyfall/label.rb#43
|
||||
def negation?; end
|
||||
|
||||
# source://skyfall//lib/skyfall/label.rb#27
|
||||
def src; end
|
||||
|
||||
# source://skyfall//lib/skyfall/label.rb#31
|
||||
def subject; end
|
||||
|
||||
# source://skyfall//lib/skyfall/label.rb#31
|
||||
def uri; end
|
||||
|
||||
# source://skyfall//lib/skyfall/label.rb#39
|
||||
def val; end
|
||||
|
||||
# source://skyfall//lib/skyfall/label.rb#39
|
||||
def value; end
|
||||
|
||||
# source://skyfall//lib/skyfall/label.rb#23
|
||||
def ver; end
|
||||
|
||||
# source://skyfall//lib/skyfall/label.rb#23
|
||||
def version; end
|
||||
end
|
||||
|
||||
# source://skyfall//lib/skyfall/stream.rb#8
|
||||
class Skyfall::Stream
|
||||
# @return [Stream] a new instance of Stream
|
||||
#
|
||||
# source://skyfall//lib/skyfall/stream.rb#15
|
||||
def initialize(service); end
|
||||
|
||||
# Returns the value of attribute auto_reconnect.
|
||||
#
|
||||
# source://skyfall//lib/skyfall/stream.rb#12
|
||||
def auto_reconnect; end
|
||||
|
||||
# Sets the attribute auto_reconnect
|
||||
#
|
||||
# @param value the value to set the attribute auto_reconnect to.
|
||||
#
|
||||
# source://skyfall//lib/skyfall/stream.rb#12
|
||||
def auto_reconnect=(_arg0); end
|
||||
|
||||
# Returns the value of attribute check_heartbeat.
|
||||
#
|
||||
# source://skyfall//lib/skyfall/stream.rb#13
|
||||
def check_heartbeat; end
|
||||
|
||||
# Sets the attribute check_heartbeat
|
||||
#
|
||||
# @param value the value to set the attribute check_heartbeat to.
|
||||
#
|
||||
# source://skyfall//lib/skyfall/stream.rb#116
|
||||
def check_heartbeat=(value); end
|
||||
|
||||
# source://skyfall//lib/skyfall/stream.rb#98
|
||||
def close; end
|
||||
|
||||
# source://skyfall//lib/skyfall/stream.rb#30
|
||||
def connect; end
|
||||
|
||||
# source://skyfall//lib/skyfall/stream.rb#108
|
||||
def default_user_agent; end
|
||||
|
||||
# source://skyfall//lib/skyfall/stream.rb#98
|
||||
def disconnect; end
|
||||
|
||||
# source://skyfall//lib/skyfall/stream.rb#86
|
||||
def handle_message(msg); end
|
||||
|
||||
# Returns the value of attribute heartbeat_interval.
|
||||
#
|
||||
# source://skyfall//lib/skyfall/stream.rb#13
|
||||
def heartbeat_interval; end
|
||||
|
||||
# Sets the attribute heartbeat_interval
|
||||
#
|
||||
# @param value the value to set the attribute heartbeat_interval to.
|
||||
#
|
||||
# source://skyfall//lib/skyfall/stream.rb#13
|
||||
def heartbeat_interval=(_arg0); end
|
||||
|
||||
# Returns the value of attribute heartbeat_timeout.
|
||||
#
|
||||
# source://skyfall//lib/skyfall/stream.rb#13
|
||||
def heartbeat_timeout; end
|
||||
|
||||
# Sets the attribute heartbeat_timeout
|
||||
#
|
||||
# @param value the value to set the attribute heartbeat_timeout to.
|
||||
#
|
||||
# source://skyfall//lib/skyfall/stream.rb#13
|
||||
def heartbeat_timeout=(_arg0); end
|
||||
|
||||
# source://skyfall//lib/skyfall/stream.rb#160
|
||||
def inspect; end
|
||||
|
||||
# source://skyfall//lib/skyfall/stream.rb#156
|
||||
def inspectable_variables; end
|
||||
|
||||
# Returns the value of attribute last_update.
|
||||
#
|
||||
# source://skyfall//lib/skyfall/stream.rb#12
|
||||
def last_update; end
|
||||
|
||||
# Sets the attribute last_update
|
||||
#
|
||||
# @param value the value to set the attribute last_update to.
|
||||
#
|
||||
# source://skyfall//lib/skyfall/stream.rb#12
|
||||
def last_update=(_arg0); end
|
||||
|
||||
# source://skyfall//lib/skyfall/stream.rb#147
|
||||
def on_connect(&block); end
|
||||
|
||||
# source://skyfall//lib/skyfall/stream.rb#151
|
||||
def on_connect=(block); end
|
||||
|
||||
# source://skyfall//lib/skyfall/stream.rb#147
|
||||
def on_connecting(&block); end
|
||||
|
||||
# source://skyfall//lib/skyfall/stream.rb#151
|
||||
def on_connecting=(block); end
|
||||
|
||||
# source://skyfall//lib/skyfall/stream.rb#147
|
||||
def on_disconnect(&block); end
|
||||
|
||||
# source://skyfall//lib/skyfall/stream.rb#151
|
||||
def on_disconnect=(block); end
|
||||
|
||||
# source://skyfall//lib/skyfall/stream.rb#147
|
||||
def on_error(&block); end
|
||||
|
||||
# source://skyfall//lib/skyfall/stream.rb#151
|
||||
def on_error=(block); end
|
||||
|
||||
# source://skyfall//lib/skyfall/stream.rb#147
|
||||
def on_message(&block); end
|
||||
|
||||
# source://skyfall//lib/skyfall/stream.rb#151
|
||||
def on_message=(block); end
|
||||
|
||||
# source://skyfall//lib/skyfall/stream.rb#147
|
||||
def on_raw_message(&block); end
|
||||
|
||||
# source://skyfall//lib/skyfall/stream.rb#151
|
||||
def on_raw_message=(block); end
|
||||
|
||||
# source://skyfall//lib/skyfall/stream.rb#147
|
||||
def on_reconnect(&block); end
|
||||
|
||||
# source://skyfall//lib/skyfall/stream.rb#151
|
||||
def on_reconnect=(block); end
|
||||
|
||||
# source://skyfall//lib/skyfall/stream.rb#147
|
||||
def on_timeout(&block); end
|
||||
|
||||
# source://skyfall//lib/skyfall/stream.rb#151
|
||||
def on_timeout=(block); end
|
||||
|
||||
# source://skyfall//lib/skyfall/stream.rb#91
|
||||
def reconnect; end
|
||||
|
||||
# source://skyfall//lib/skyfall/stream.rb#126
|
||||
def start_heartbeat_timer; end
|
||||
|
||||
# source://skyfall//lib/skyfall/stream.rb#141
|
||||
def stop_heartbeat_timer; end
|
||||
|
||||
# Returns the value of attribute user_agent.
|
||||
#
|
||||
# source://skyfall//lib/skyfall/stream.rb#12
|
||||
def user_agent; end
|
||||
|
||||
# Sets the attribute user_agent
|
||||
#
|
||||
# @param value the value to set the attribute user_agent to.
|
||||
#
|
||||
# source://skyfall//lib/skyfall/stream.rb#12
|
||||
def user_agent=(_arg0); end
|
||||
|
||||
# source://skyfall//lib/skyfall/stream.rb#112
|
||||
def version_string; end
|
||||
|
||||
private
|
||||
|
||||
# source://skyfall//lib/skyfall/stream.rb#184
|
||||
def build_root_url(service); end
|
||||
|
||||
# source://skyfall//lib/skyfall/stream.rb#176
|
||||
def build_websocket_client(url); end
|
||||
|
||||
# source://skyfall//lib/skyfall/stream.rb#180
|
||||
def build_websocket_url; end
|
||||
|
||||
# source://skyfall//lib/skyfall/stream.rb#168
|
||||
def reconnect_delay; end
|
||||
end
|
||||
|
||||
# source://skyfall//lib/skyfall/stream.rb#9
|
||||
Skyfall::Stream::EVENTS = T.let(T.unsafe(nil), Array)
|
||||
|
||||
# source://skyfall//lib/skyfall/stream.rb#10
|
||||
Skyfall::Stream::MAX_RECONNECT_INTERVAL = T.let(T.unsafe(nil), Integer)
|
||||
|
||||
# source://skyfall//lib/skyfall/errors.rb#8
|
||||
class Skyfall::SubscriptionError < ::StandardError
|
||||
# @return [SubscriptionError] a new instance of SubscriptionError
|
||||
#
|
||||
# source://skyfall//lib/skyfall/errors.rb#11
|
||||
def initialize(error_type, error_message = T.unsafe(nil)); end
|
||||
|
||||
# Returns the value of attribute error_message.
|
||||
#
|
||||
# source://skyfall//lib/skyfall/errors.rb#9
|
||||
def error_message; end
|
||||
|
||||
# Returns the value of attribute error_type.
|
||||
#
|
||||
# source://skyfall//lib/skyfall/errors.rb#9
|
||||
def error_type; end
|
||||
end
|
||||
|
||||
# source://skyfall//lib/skyfall/errors.rb#5
|
||||
class Skyfall::UnsupportedError < ::StandardError; end
|
||||
|
||||
# source://skyfall//lib/skyfall/version.rb#4
|
||||
Skyfall::VERSION = T.let(T.unsafe(nil), String)
|
||||
@@ -31,19 +31,19 @@ class WebSocket::Driver
|
||||
# source://websocket-driver//lib/websocket/driver.rb#72
|
||||
def initialize(socket, options = T.unsafe(nil)); end
|
||||
|
||||
# source://websocket-driver//lib/websocket/driver.rb#90
|
||||
# source://websocket-driver//lib/websocket/driver.rb#92
|
||||
def add_extension(extension); end
|
||||
|
||||
# source://websocket-driver//lib/websocket/driver.rb#123
|
||||
# source://websocket-driver//lib/websocket/driver.rb#125
|
||||
def binary(message); end
|
||||
|
||||
# source://websocket-driver//lib/websocket/driver.rb#135
|
||||
# source://websocket-driver//lib/websocket/driver.rb#137
|
||||
def close(reason = T.unsafe(nil), code = T.unsafe(nil)); end
|
||||
|
||||
# source://websocket-driver//lib/websocket/driver.rb#127
|
||||
# source://websocket-driver//lib/websocket/driver.rb#129
|
||||
def ping(*args); end
|
||||
|
||||
# source://websocket-driver//lib/websocket/driver.rb#131
|
||||
# source://websocket-driver//lib/websocket/driver.rb#133
|
||||
def pong(*args); end
|
||||
|
||||
# Returns the value of attribute protocol.
|
||||
@@ -56,54 +56,54 @@ class WebSocket::Driver
|
||||
# source://websocket-driver//lib/websocket/driver.rb#70
|
||||
def ready_state; end
|
||||
|
||||
# source://websocket-driver//lib/websocket/driver.rb#94
|
||||
# source://websocket-driver//lib/websocket/driver.rb#96
|
||||
def set_header(name, value); end
|
||||
|
||||
# source://websocket-driver//lib/websocket/driver.rb#100
|
||||
# source://websocket-driver//lib/websocket/driver.rb#102
|
||||
def start; end
|
||||
|
||||
# source://websocket-driver//lib/websocket/driver.rb#85
|
||||
# source://websocket-driver//lib/websocket/driver.rb#87
|
||||
def state; end
|
||||
|
||||
# source://websocket-driver//lib/websocket/driver.rb#118
|
||||
# source://websocket-driver//lib/websocket/driver.rb#120
|
||||
def text(message); end
|
||||
|
||||
private
|
||||
|
||||
# source://websocket-driver//lib/websocket/driver.rb#156
|
||||
# source://websocket-driver//lib/websocket/driver.rb#158
|
||||
def fail(type, message); end
|
||||
|
||||
# source://websocket-driver//lib/websocket/driver.rb#144
|
||||
# source://websocket-driver//lib/websocket/driver.rb#146
|
||||
def fail_handshake(error); end
|
||||
|
||||
# source://websocket-driver//lib/websocket/driver.rb#162
|
||||
# source://websocket-driver//lib/websocket/driver.rb#164
|
||||
def open; end
|
||||
|
||||
# source://websocket-driver//lib/websocket/driver.rb#169
|
||||
# source://websocket-driver//lib/websocket/driver.rb#171
|
||||
def queue(message); end
|
||||
|
||||
class << self
|
||||
# source://websocket-driver//lib/websocket/driver.rb#174
|
||||
# source://websocket-driver//lib/websocket/driver.rb#176
|
||||
def client(socket, options = T.unsafe(nil)); end
|
||||
|
||||
# source://websocket-driver//lib/websocket/driver.rb#198
|
||||
# source://websocket-driver//lib/websocket/driver.rb#200
|
||||
def encode(data, encoding = T.unsafe(nil)); end
|
||||
|
||||
# source://websocket-driver//lib/websocket/driver.rb#213
|
||||
# source://websocket-driver//lib/websocket/driver.rb#216
|
||||
def host_header(uri); end
|
||||
|
||||
# source://websocket-driver//lib/websocket/driver.rb#182
|
||||
# source://websocket-driver//lib/websocket/driver.rb#184
|
||||
def rack(socket, options = T.unsafe(nil)); end
|
||||
|
||||
# source://websocket-driver//lib/websocket/driver.rb#178
|
||||
# source://websocket-driver//lib/websocket/driver.rb#180
|
||||
def server(socket, options = T.unsafe(nil)); end
|
||||
|
||||
# source://websocket-driver//lib/websocket/driver.rb#221
|
||||
# source://websocket-driver//lib/websocket/driver.rb#224
|
||||
def validate_options(options, valid_keys); end
|
||||
|
||||
# @return [Boolean]
|
||||
#
|
||||
# source://websocket-driver//lib/websocket/driver.rb#229
|
||||
# source://websocket-driver//lib/websocket/driver.rb#238
|
||||
def websocket?(env); end
|
||||
end
|
||||
end
|
||||
@@ -363,34 +363,34 @@ class WebSocket::Driver::Hybi < ::WebSocket::Driver
|
||||
|
||||
private
|
||||
|
||||
# source://websocket-driver//lib/websocket/driver/hybi.rb#336
|
||||
# source://websocket-driver//lib/websocket/driver/hybi.rb#338
|
||||
def check_frame_length; end
|
||||
|
||||
# source://websocket-driver//lib/websocket/driver/hybi.rb#347
|
||||
# source://websocket-driver//lib/websocket/driver/hybi.rb#349
|
||||
def emit_frame(buffer); end
|
||||
|
||||
# source://websocket-driver//lib/websocket/driver/hybi.rb#395
|
||||
# source://websocket-driver//lib/websocket/driver/hybi.rb#397
|
||||
def emit_message; end
|
||||
|
||||
# source://websocket-driver//lib/websocket/driver/hybi.rb#270
|
||||
# source://websocket-driver//lib/websocket/driver/hybi.rb#272
|
||||
def fail(type, message); end
|
||||
|
||||
# source://websocket-driver//lib/websocket/driver/hybi.rb#232
|
||||
# source://websocket-driver//lib/websocket/driver/hybi.rb#234
|
||||
def handshake_response; end
|
||||
|
||||
# source://websocket-driver//lib/websocket/driver/hybi.rb#325
|
||||
# source://websocket-driver//lib/websocket/driver/hybi.rb#327
|
||||
def parse_extended_length(buffer); end
|
||||
|
||||
# source://websocket-driver//lib/websocket/driver/hybi.rb#308
|
||||
# source://websocket-driver//lib/websocket/driver/hybi.rb#310
|
||||
def parse_length(octet); end
|
||||
|
||||
# source://websocket-driver//lib/websocket/driver/hybi.rb#275
|
||||
# source://websocket-driver//lib/websocket/driver/hybi.rb#277
|
||||
def parse_opcode(octet); end
|
||||
|
||||
# source://websocket-driver//lib/websocket/driver/hybi.rb#196
|
||||
# source://websocket-driver//lib/websocket/driver/hybi.rb#198
|
||||
def send_frame(frame); end
|
||||
|
||||
# source://websocket-driver//lib/websocket/driver/hybi.rb#258
|
||||
# source://websocket-driver//lib/websocket/driver/hybi.rb#260
|
||||
def shutdown(code, reason, error = T.unsafe(nil)); end
|
||||
|
||||
class << self
|
||||
13
sorbet/rbi/shims/didkit.rbi
Normal file
13
sorbet/rbi/shims/didkit.rbi
Normal file
@@ -0,0 +1,13 @@
|
||||
# typed: strict
|
||||
|
||||
class DIDKit::Resolver
|
||||
sig { params(handle: String).returns(T.nilable(DIDKit::DID)) }
|
||||
def resolve_handle(handle)
|
||||
end
|
||||
end
|
||||
|
||||
class DIDKit::DID
|
||||
sig { returns(String) }
|
||||
def did
|
||||
end
|
||||
end
|
||||
65
sorbet/rbi/shims/skyfall.rbi
Normal file
65
sorbet/rbi/shims/skyfall.rbi
Normal file
@@ -0,0 +1,65 @@
|
||||
# typed: strict
|
||||
|
||||
class Skyfall::Jetstream
|
||||
sig do
|
||||
params(
|
||||
block: T.proc.params(message: ::Skyfall::Jetstream::Message).void,
|
||||
).void
|
||||
end
|
||||
def on_message(&block)
|
||||
end
|
||||
end
|
||||
|
||||
class Skyfall::Jetstream::Message
|
||||
sig { returns(Integer) }
|
||||
def seq
|
||||
end
|
||||
|
||||
sig { returns(Symbol) }
|
||||
def type
|
||||
end
|
||||
|
||||
sig { returns(Time) }
|
||||
def time
|
||||
end
|
||||
end
|
||||
|
||||
class Skyfall::Jetstream::CommitMessage
|
||||
sig { returns(T::Array[Skyfall::Jetstream::Operation]) }
|
||||
def operations
|
||||
end
|
||||
end
|
||||
|
||||
class Skyfall::Jetstream::Operation
|
||||
sig { returns(Symbol) }
|
||||
def action
|
||||
end
|
||||
|
||||
sig { returns(Symbol) }
|
||||
def type
|
||||
end
|
||||
|
||||
sig { returns(String) }
|
||||
def repo
|
||||
end
|
||||
|
||||
sig { returns(String) }
|
||||
def collection
|
||||
end
|
||||
|
||||
sig { returns(String) }
|
||||
def rkey
|
||||
end
|
||||
|
||||
sig { returns(String) }
|
||||
def path
|
||||
end
|
||||
|
||||
sig { returns(String) }
|
||||
def uri
|
||||
end
|
||||
|
||||
sig { returns(Skyfall::CID) }
|
||||
def cid
|
||||
end
|
||||
end
|
||||
@@ -2,8 +2,6 @@
|
||||
require "rails_helper"
|
||||
|
||||
RSpec.describe Domain::PostsController, type: :controller do
|
||||
render_views
|
||||
|
||||
# Create a real user with admin role
|
||||
let(:user) { create(:user, :admin) }
|
||||
|
||||
@@ -75,6 +73,11 @@ RSpec.describe Domain::PostsController, type: :controller do
|
||||
it_behaves_like "a post"
|
||||
end
|
||||
|
||||
context "with a bluesky post" do
|
||||
let(:post) { create(:domain_post_bluesky_post) }
|
||||
it_behaves_like "a post"
|
||||
end
|
||||
|
||||
context "when post file is pending download" do
|
||||
let(:post) do
|
||||
create(
|
||||
|
||||
15
spec/factories/domain/post/bluesky_post.rb
Normal file
15
spec/factories/domain/post/bluesky_post.rb
Normal file
@@ -0,0 +1,15 @@
|
||||
# typed: false
|
||||
FactoryBot.define do
|
||||
factory :domain_post_bluesky_post, class: "Domain::Post::BlueskyPost" do
|
||||
association :creator, factory: :domain_user_bluesky_user
|
||||
sequence(:bluesky_rkey) { |n| "rkey#{n}" }
|
||||
sequence(:at_uri) do |n|
|
||||
"at://did:plc:#{n.to_s.rjust(10, "0")}/app.bsky.feed.post/rkey#{n}"
|
||||
end
|
||||
bluesky_created_at { Time.now }
|
||||
state { "ok" }
|
||||
text { "Hello from Bluesky" }
|
||||
hashtags { %w[test bluesky] }
|
||||
like_count { 0 }
|
||||
end
|
||||
end
|
||||
8
spec/factories/domain/user/bluesky_user.rb
Normal file
8
spec/factories/domain/user/bluesky_user.rb
Normal file
@@ -0,0 +1,8 @@
|
||||
# typed: false
|
||||
FactoryBot.define do
|
||||
factory :domain_user_bluesky_user, class: "Domain::User::BlueskyUser" do
|
||||
sequence(:handle) { |n| "user#{n}.bsky.social" }
|
||||
sequence(:did) { |n| "did:plc:#{n.to_s.rjust(10, "0")}" }
|
||||
state { "ok" }
|
||||
end
|
||||
end
|
||||
206
spec/jobs/domain/bluesky/job/scan_user_job_spec.rb
Normal file
206
spec/jobs/domain/bluesky/job/scan_user_job_spec.rb
Normal file
@@ -0,0 +1,206 @@
|
||||
# typed: false
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "rails_helper"
|
||||
|
||||
RSpec.describe Domain::Bluesky::Job::ScanUserJob do
|
||||
include PerformJobHelpers
|
||||
|
||||
let(:http_client_mock) { instance_double("::Scraper::HttpClient") }
|
||||
let(:user) do
|
||||
create(
|
||||
:domain_user_bluesky_user,
|
||||
did: "did:plc:test123",
|
||||
handle: "testuser.bsky.social",
|
||||
scanned_profile_at: nil,
|
||||
scanned_posts_at: nil,
|
||||
)
|
||||
end
|
||||
|
||||
before { Scraper::ClientFactory.http_client_mock = http_client_mock }
|
||||
|
||||
describe "#perform" do
|
||||
context "when user profile scanning is due" do
|
||||
let(:profile_response_body) do
|
||||
{
|
||||
"uri" => "at://#{user.did}/app.bsky.actor.profile/self",
|
||||
"cid" => "bafyreiabc123",
|
||||
"value" => {
|
||||
"displayName" => "Test User",
|
||||
"description" => "A test user profile",
|
||||
"avatar" => {
|
||||
"ref" => {
|
||||
"$link" => "bafkreiavatar123",
|
||||
},
|
||||
"mimeType" => "image/jpeg",
|
||||
"size" => 50_000,
|
||||
},
|
||||
},
|
||||
}.to_json
|
||||
end
|
||||
|
||||
let(:posts_response_body) do
|
||||
{
|
||||
"records" => [
|
||||
{
|
||||
"uri" => "at://#{user.did}/app.bsky.feed.post/post1",
|
||||
"cid" => "bafyreiapost123",
|
||||
"value" => {
|
||||
"text" => "Hello world with image!",
|
||||
"createdAt" => "2025-01-08T12:00:00.000Z",
|
||||
"embed" => {
|
||||
"$type" => "app.bsky.embed.images",
|
||||
"images" => [
|
||||
{
|
||||
"alt" => "Test image",
|
||||
"aspectRatio" => {
|
||||
"width" => 1920,
|
||||
"height" => 1080,
|
||||
},
|
||||
"image" => {
|
||||
"$type" => "blob",
|
||||
"ref" => {
|
||||
"$link" => "bafkreiimage123",
|
||||
},
|
||||
"mimeType" => "image/jpeg",
|
||||
"size" => 256_000,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"uri" => "at://#{user.did}/app.bsky.feed.post/post2",
|
||||
"cid" => "bafyreiapost456",
|
||||
"value" => {
|
||||
"text" => "Just a text post",
|
||||
"createdAt" => "2025-01-08T11:00:00.000Z",
|
||||
},
|
||||
},
|
||||
],
|
||||
"cursor" => nil,
|
||||
}.to_json
|
||||
end
|
||||
|
||||
before do
|
||||
# Mock profile API call
|
||||
expect(http_client_mock).to receive(:get).with(
|
||||
"https://bsky.social/xrpc/com.atproto.repo.getRecord?repo=#{user.did}&collection=app.bsky.actor.profile&rkey=self",
|
||||
anything,
|
||||
).and_return(
|
||||
double(
|
||||
status_code: 200,
|
||||
body: profile_response_body,
|
||||
log_entry: double,
|
||||
),
|
||||
)
|
||||
|
||||
# Mock posts API call
|
||||
expect(http_client_mock).to receive(:get).with(
|
||||
"https://bsky.social/xrpc/com.atproto.repo.listRecords?repo=#{user.did}&collection=app.bsky.feed.post&limit=100",
|
||||
anything,
|
||||
).and_return(
|
||||
double(
|
||||
status_code: 200,
|
||||
body: posts_response_body,
|
||||
log_entry: double,
|
||||
),
|
||||
)
|
||||
|
||||
# Mock static file job enqueueing - allow it but don't require it
|
||||
allow(Domain::StaticFileJob).to receive(:perform_later)
|
||||
end
|
||||
|
||||
it "scans user profile and updates user data" do
|
||||
perform_now({ user: user })
|
||||
|
||||
user.reload
|
||||
expect(user.display_name).to eq("Test User")
|
||||
expect(user.description).to eq("A test user profile")
|
||||
expect(user.scanned_profile_at).to be_present
|
||||
expect(user.scanned_posts_at).to be_present
|
||||
expect(user.state).to eq("ok")
|
||||
end
|
||||
|
||||
it "creates avatar for user" do
|
||||
expect { perform_now({ user: user }) }.to change {
|
||||
user.reload.avatar.present?
|
||||
}.from(false).to(true)
|
||||
|
||||
avatar = user.reload.avatar
|
||||
expect(avatar.url_str).to eq(
|
||||
"https://bsky.social/xrpc/com.atproto.sync.getBlob?did=#{user.did}&cid=bafkreiavatar123",
|
||||
)
|
||||
end
|
||||
|
||||
it "creates posts with media and associated files" do
|
||||
expect { perform_now({ user: user }) }.to change(
|
||||
Domain::Post::BlueskyPost,
|
||||
:count,
|
||||
).by(1).and change(Domain::PostFile::BlueskyPostFile, :count).by(1)
|
||||
|
||||
post = Domain::Post::BlueskyPost.last
|
||||
expect(post.text).to eq("Hello world with image!")
|
||||
expect(post.creator).to eq(user)
|
||||
expect(post.bluesky_rkey).to eq("post1")
|
||||
|
||||
file = post.files.first
|
||||
expect(file.alt_text).to eq("Test image")
|
||||
expect(file.blob_ref).to eq("bafkreiimage123")
|
||||
expect(file.aspect_ratio_width).to eq(1920)
|
||||
expect(file.aspect_ratio_height).to eq(1080)
|
||||
expect(file.url_str).to eq(
|
||||
"https://bsky.social/xrpc/com.atproto.sync.getBlob?did=#{user.did}&cid=bafkreiimage123",
|
||||
)
|
||||
end
|
||||
|
||||
it "does not create posts without media" do
|
||||
perform_now({ user: user })
|
||||
|
||||
# Should only create 1 post (the one with media), not the text-only post
|
||||
expect(Domain::Post::BlueskyPost.count).to eq(1)
|
||||
expect(Domain::Post::BlueskyPost.first.text).to eq(
|
||||
"Hello world with image!",
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context "when user already scanned recently" do
|
||||
before do
|
||||
user.update!(scanned_profile_at: 1.day.ago, scanned_posts_at: 1.day.ago)
|
||||
end
|
||||
|
||||
it "skips scanning if not due" do
|
||||
expect(http_client_mock).not_to receive(:get)
|
||||
|
||||
perform_now({ user: user })
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "user creation callback" do
|
||||
it "enqueues scan job when user is created" do
|
||||
expect(Domain::Bluesky::Job::ScanUserJob).to receive(:perform_later).with(
|
||||
{ user: instance_of(Domain::User::BlueskyUser) },
|
||||
)
|
||||
|
||||
create(
|
||||
:domain_user_bluesky_user,
|
||||
did: "did:plc:newuser123",
|
||||
handle: "newuser.bsky.social",
|
||||
)
|
||||
end
|
||||
|
||||
it "does not enqueue scan job for users in error state" do
|
||||
expect(Domain::Bluesky::Job::ScanUserJob).not_to receive(:perform_later)
|
||||
|
||||
create(
|
||||
:domain_user_bluesky_user,
|
||||
did: "did:plc:erroruser123",
|
||||
handle: "erroruser.bsky.social",
|
||||
state: "error",
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
462
spec/lib/tasks/bluesky/monitor_spec.rb
Normal file
462
spec/lib/tasks/bluesky/monitor_spec.rb
Normal file
@@ -0,0 +1,462 @@
|
||||
# typed: false
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "rails_helper"
|
||||
require_relative "../../../../app/lib/tasks/bluesky/monitor"
|
||||
|
||||
RSpec.describe Tasks::Bluesky::Monitor do
|
||||
subject(:monitor) { described_class.new(pg_notify: false) }
|
||||
|
||||
let(:test_did) { "did:plc:test123456789" }
|
||||
let(:base_time) { Time.parse("2025-01-08 12:00:00 UTC") }
|
||||
|
||||
before do
|
||||
# Add the test DID to the monitored set
|
||||
monitor.instance_variable_get(:@dids).add(test_did)
|
||||
|
||||
# Create a Bluesky user for the test DID
|
||||
create(
|
||||
:domain_user_bluesky_user,
|
||||
did: test_did,
|
||||
handle: "testuser.bsky.social",
|
||||
)
|
||||
end
|
||||
|
||||
# Helper method to create real CommitMessage objects
|
||||
def create_commit_message(did:, time:, rkey:, record:)
|
||||
message_json = {
|
||||
"did" => did,
|
||||
"time_us" => (time.to_f * 1_000_000).to_i,
|
||||
"kind" => "commit",
|
||||
"commit" => {
|
||||
"rev" => "#{rkey}rev",
|
||||
"operation" => "create",
|
||||
"collection" => "app.bsky.feed.post",
|
||||
"rkey" => rkey,
|
||||
"record" => record,
|
||||
"cid" => "bafyreih#{rkey}",
|
||||
},
|
||||
}
|
||||
|
||||
Skyfall::Jetstream::Message.new(message_json.to_json)
|
||||
end
|
||||
|
||||
describe "#handle_message" do
|
||||
context "when message is a commit with a post containing media" do
|
||||
context "with image embeds" do
|
||||
let(:commit_message) do
|
||||
create_commit_message(
|
||||
did: test_did,
|
||||
time: base_time,
|
||||
rkey: "test123",
|
||||
record: {
|
||||
"text" => "Check out this image!",
|
||||
"embed" => {
|
||||
"$type" => "app.bsky.embed.images",
|
||||
"images" => [
|
||||
{
|
||||
"alt" => "A beautiful sunset",
|
||||
"aspectRatio" => {
|
||||
"height" => 1080,
|
||||
"width" => 1920,
|
||||
},
|
||||
"image" => {
|
||||
"$type" => "blob",
|
||||
"ref" => {
|
||||
"$link" => "bafkreiabc123",
|
||||
},
|
||||
"mimeType" => "image/jpeg",
|
||||
"size" => 256_000,
|
||||
},
|
||||
},
|
||||
{
|
||||
"alt" => "",
|
||||
"aspectRatio" => {
|
||||
"height" => 800,
|
||||
"width" => 600,
|
||||
},
|
||||
"image" => {
|
||||
"$type" => "blob",
|
||||
"ref" => {
|
||||
"$link" => "bafkreidef456",
|
||||
},
|
||||
"mimeType" => "image/png",
|
||||
"size" => 128_000,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
)
|
||||
end
|
||||
|
||||
it "creates a post with associated media files" do
|
||||
expect { monitor.handle_message(commit_message) }.to change(
|
||||
Domain::Post::BlueskyPost,
|
||||
:count,
|
||||
).by(1).and change(Domain::PostFile::BlueskyPostFile, :count).by(2)
|
||||
|
||||
post = Domain::Post::BlueskyPost.last
|
||||
expect(post.at_uri).to eq(
|
||||
"at://#{test_did}/app.bsky.feed.post/test123",
|
||||
)
|
||||
expect(post.text).to eq("Check out this image!")
|
||||
expect(post.bluesky_rkey).to eq("test123")
|
||||
expect(post.bluesky_created_at).to eq(base_time)
|
||||
|
||||
files = post.files.order(:file_order)
|
||||
expect(files.count).to eq(2)
|
||||
|
||||
# First image
|
||||
first_file = files.first
|
||||
expect(first_file.alt_text).to eq("A beautiful sunset")
|
||||
expect(first_file.blob_ref).to eq("bafkreiabc123")
|
||||
expect(first_file.aspect_ratio_width).to eq(1920)
|
||||
expect(first_file.aspect_ratio_height).to eq(1080)
|
||||
expect(first_file.url_str).to eq(
|
||||
"https://bsky.social/xrpc/com.atproto.sync.getBlob?did=#{test_did}&cid=bafkreiabc123",
|
||||
)
|
||||
expect(first_file.file_order).to eq(0)
|
||||
expect(first_file.state).to eq("pending")
|
||||
|
||||
# Second image
|
||||
second_file = files.second
|
||||
expect(second_file.alt_text).to eq("")
|
||||
expect(second_file.blob_ref).to eq("bafkreidef456")
|
||||
expect(second_file.aspect_ratio_width).to eq(600)
|
||||
expect(second_file.aspect_ratio_height).to eq(800)
|
||||
expect(second_file.url_str).to eq(
|
||||
"https://bsky.social/xrpc/com.atproto.sync.getBlob?did=#{test_did}&cid=bafkreidef456",
|
||||
)
|
||||
expect(second_file.file_order).to eq(1)
|
||||
expect(second_file.state).to eq("pending")
|
||||
end
|
||||
|
||||
it "enqueues download jobs for the media files" do
|
||||
expect(Domain::StaticFileJob).to receive(:perform_later).twice
|
||||
|
||||
monitor.handle_message(commit_message)
|
||||
end
|
||||
end
|
||||
|
||||
context "with recordWithMedia embed (quote post with media)" do
|
||||
let(:commit_message) do
|
||||
create_commit_message(
|
||||
did: test_did,
|
||||
time: base_time,
|
||||
rkey: "quote123",
|
||||
record: {
|
||||
"text" => "Quote tweet with media",
|
||||
"embed" => {
|
||||
"$type" => "app.bsky.embed.recordWithMedia",
|
||||
"record" => {
|
||||
"$type" => "app.bsky.embed.record",
|
||||
"record" => {
|
||||
"uri" => "at://other.user/app.bsky.feed.post/abc123",
|
||||
"cid" => "bafyreianotherpost",
|
||||
},
|
||||
},
|
||||
"media" => {
|
||||
"$type" => "app.bsky.embed.images",
|
||||
"images" => [
|
||||
{
|
||||
"alt" => "Quote post image",
|
||||
"aspectRatio" => {
|
||||
"height" => 720,
|
||||
"width" => 1280,
|
||||
},
|
||||
"image" => {
|
||||
"$type" => "blob",
|
||||
"ref" => {
|
||||
"$link" => "bafkreiquote123",
|
||||
},
|
||||
"mimeType" => "image/webp",
|
||||
"size" => 64_000,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
)
|
||||
end
|
||||
|
||||
it "creates a post with media from the quote post" do
|
||||
expect { monitor.handle_message(commit_message) }.to change(
|
||||
Domain::Post::BlueskyPost,
|
||||
:count,
|
||||
).by(1).and change(Domain::PostFile::BlueskyPostFile, :count).by(1)
|
||||
|
||||
post = Domain::Post::BlueskyPost.last
|
||||
file = post.files.first
|
||||
|
||||
expect(file.alt_text).to eq("Quote post image")
|
||||
expect(file.blob_ref).to eq("bafkreiquote123")
|
||||
expect(file.aspect_ratio_width).to eq(1280)
|
||||
expect(file.aspect_ratio_height).to eq(720)
|
||||
end
|
||||
end
|
||||
|
||||
context "with external embed (website card with thumbnail)" do
|
||||
let(:commit_message) do
|
||||
create_commit_message(
|
||||
did: test_did,
|
||||
time: base_time,
|
||||
rkey: "external123",
|
||||
record: {
|
||||
"text" => "Check out this website",
|
||||
"embed" => {
|
||||
"$type" => "app.bsky.embed.external",
|
||||
"external" => {
|
||||
"uri" => "https://example.com",
|
||||
"title" => "Example Website",
|
||||
"description" => "A great website",
|
||||
"thumb" => {
|
||||
"$type" => "blob",
|
||||
"ref" => {
|
||||
"$link" => "bafkreithumb123",
|
||||
},
|
||||
"mimeType" => "image/jpeg",
|
||||
"size" => 32_000,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
)
|
||||
end
|
||||
|
||||
it "creates a post with thumbnail file" do
|
||||
expect { monitor.handle_message(commit_message) }.to change(
|
||||
Domain::Post::BlueskyPost,
|
||||
:count,
|
||||
).by(1).and change(Domain::PostFile::BlueskyPostFile, :count).by(1)
|
||||
|
||||
post = Domain::Post::BlueskyPost.last
|
||||
file = post.files.first
|
||||
|
||||
expect(file.blob_ref).to eq("bafkreithumb123")
|
||||
expect(file.file_order).to eq(0)
|
||||
expect(file.url_str).to eq(
|
||||
"https://bsky.social/xrpc/com.atproto.sync.getBlob?did=#{test_did}&cid=bafkreithumb123",
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "when message is a commit with a post without media" do
|
||||
let(:commit_message) do
|
||||
instance_double(
|
||||
Skyfall::Jetstream::CommitMessage,
|
||||
type: :commit,
|
||||
did: test_did,
|
||||
time: base_time,
|
||||
operations: [operation],
|
||||
)
|
||||
end
|
||||
|
||||
let(:operation) do
|
||||
instance_double(
|
||||
Skyfall::Jetstream::Operation,
|
||||
action: :create,
|
||||
type: :bsky_post,
|
||||
uri: "at://#{test_did}/app.bsky.feed.post/textonly",
|
||||
rkey: "textonly",
|
||||
raw_record: {
|
||||
"text" => "Just a text post",
|
||||
},
|
||||
)
|
||||
end
|
||||
|
||||
it "does not create a post" do
|
||||
expect { monitor.handle_message(commit_message) }.not_to change(
|
||||
Domain::Post::BlueskyPost,
|
||||
:count,
|
||||
)
|
||||
end
|
||||
|
||||
it "does not create any media files" do
|
||||
expect { monitor.handle_message(commit_message) }.not_to change(
|
||||
Domain::PostFile::BlueskyPostFile,
|
||||
:count,
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context "when message is from a DID not in the monitored set" do
|
||||
let(:unmonitored_did) { "did:plc:unmonitored123" }
|
||||
let(:commit_message) do
|
||||
instance_double(
|
||||
Skyfall::Jetstream::CommitMessage,
|
||||
type: :commit,
|
||||
did: unmonitored_did,
|
||||
time: base_time,
|
||||
operations: [operation],
|
||||
)
|
||||
end
|
||||
|
||||
let(:operation) do
|
||||
instance_double(
|
||||
Skyfall::Jetstream::Operation,
|
||||
action: :create,
|
||||
type: :bsky_post,
|
||||
uri: "at://#{unmonitored_did}/app.bsky.feed.post/test123",
|
||||
rkey: "test123",
|
||||
raw_record: {
|
||||
"text" => "Post with media from unmonitored user",
|
||||
"embed" => {
|
||||
"$type" => "app.bsky.embed.images",
|
||||
"images" => [
|
||||
{
|
||||
"alt" => "Should be ignored",
|
||||
"image" => {
|
||||
"$type" => "blob",
|
||||
"ref" => {
|
||||
"$link" => "bafkreiignored",
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
)
|
||||
end
|
||||
|
||||
it "does not create a post" do
|
||||
expect { monitor.handle_message(commit_message) }.not_to change(
|
||||
Domain::Post::BlueskyPost,
|
||||
:count,
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context "when message is not a commit" do
|
||||
let(:non_commit_message) do
|
||||
instance_double(Skyfall::Jetstream::Message, type: :account)
|
||||
end
|
||||
|
||||
it "does not create a post" do
|
||||
expect { monitor.handle_message(non_commit_message) }.not_to change(
|
||||
Domain::Post::BlueskyPost,
|
||||
:count,
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context "when operation is not a create action" do
|
||||
let(:commit_message) do
|
||||
instance_double(
|
||||
Skyfall::Jetstream::CommitMessage,
|
||||
type: :commit,
|
||||
did: test_did,
|
||||
time: base_time,
|
||||
operations: [operation],
|
||||
)
|
||||
end
|
||||
|
||||
let(:operation) do
|
||||
instance_double(
|
||||
Skyfall::Jetstream::Operation,
|
||||
action: :delete,
|
||||
type: :bsky_post,
|
||||
uri: "at://#{test_did}/app.bsky.feed.post/deleted",
|
||||
rkey: "deleted",
|
||||
raw_record: {
|
||||
},
|
||||
)
|
||||
end
|
||||
|
||||
it "does not create a post" do
|
||||
expect { monitor.handle_message(commit_message) }.not_to change(
|
||||
Domain::Post::BlueskyPost,
|
||||
:count,
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context "when operation is not a bsky_post type" do
|
||||
let(:commit_message) do
|
||||
instance_double(
|
||||
Skyfall::Jetstream::CommitMessage,
|
||||
type: :commit,
|
||||
did: test_did,
|
||||
time: base_time,
|
||||
operations: [operation],
|
||||
)
|
||||
end
|
||||
|
||||
let(:operation) do
|
||||
instance_double(
|
||||
Skyfall::Jetstream::Operation,
|
||||
action: :create,
|
||||
type: :bsky_like,
|
||||
uri: "at://#{test_did}/app.bsky.feed.like/like123",
|
||||
rkey: "like123",
|
||||
raw_record: {
|
||||
},
|
||||
)
|
||||
end
|
||||
|
||||
it "does not create a post" do
|
||||
expect { monitor.handle_message(commit_message) }.not_to change(
|
||||
Domain::Post::BlueskyPost,
|
||||
:count,
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "edge cases" do
|
||||
context "with malformed embed data" do
|
||||
let(:commit_message) do
|
||||
create_commit_message(
|
||||
did: test_did,
|
||||
time: base_time,
|
||||
rkey: "malformed123",
|
||||
record: {
|
||||
"text" => "Post with malformed embed",
|
||||
"embed" => {
|
||||
"$type" => "app.bsky.embed.images",
|
||||
"images" => [
|
||||
{
|
||||
"alt" => "Missing image data",
|
||||
# Missing "image" field
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
)
|
||||
end
|
||||
|
||||
it "creates the post but skips malformed media" do
|
||||
expect { monitor.handle_message(commit_message) }.to change(
|
||||
Domain::Post::BlueskyPost,
|
||||
:count,
|
||||
).by(1).and not_change(Domain::PostFile::BlueskyPostFile, :count)
|
||||
end
|
||||
end
|
||||
|
||||
context "with unknown embed type" do
|
||||
let(:commit_message) do
|
||||
create_commit_message(
|
||||
did: test_did,
|
||||
time: base_time,
|
||||
rkey: "unknown123",
|
||||
record: {
|
||||
"text" => "Post with unknown embed type",
|
||||
"embed" => {
|
||||
"$type" => "app.bsky.embed.unknown",
|
||||
"data" => "some data",
|
||||
},
|
||||
},
|
||||
)
|
||||
end
|
||||
|
||||
it "creates the post but does not process the unknown embed" do
|
||||
expect { monitor.handle_message(commit_message) }.to change(
|
||||
Domain::Post::BlueskyPost,
|
||||
:count,
|
||||
).by(1).and not_change(Domain::PostFile::BlueskyPostFile, :count)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user