mirror of
https://github.com/sky-systems/sky_phone.git
synced 2026-08-29 01:08:59 +00:00
ENH - open exact EasyShare content targets
This commit is contained in:
@@ -183,4 +183,12 @@ describe('FlipTok verification updates', () => {
|
||||
expect(store.feed).toEqual([])
|
||||
expect(store.activities).toEqual([])
|
||||
})
|
||||
|
||||
it('loads an exact shared video by id', async () => {
|
||||
vi.mocked(nuiCall).mockResolvedValue({ success: true, data: { ...video } })
|
||||
const store = useFlipTokStore()
|
||||
|
||||
expect(await store.loadVideo(video.id)).toEqual(video)
|
||||
expect(nuiCall).toHaveBeenCalledWith('fliptok:video', { id: video.id })
|
||||
})
|
||||
})
|
||||
|
||||
@@ -114,6 +114,10 @@ export const useFlipTokStore = defineStore('fliptok', {
|
||||
this.feed = response.data.items
|
||||
return true
|
||||
},
|
||||
async loadVideo(id: string): Promise<FlipTokVideo | null> {
|
||||
const response = await nuiCall<FlipTokVideo>('fliptok:video', { id })
|
||||
return response.success && response.data ? response.data : null
|
||||
},
|
||||
async discover(search: string): Promise<FlipTokVideo[]> {
|
||||
const response = await nuiCall<FlipTokVideo[]>('fliptok:discover', {
|
||||
search,
|
||||
|
||||
@@ -213,4 +213,12 @@ describe('Picstagram store', () => {
|
||||
expect(store.feed).toEqual([])
|
||||
expect(store.stories).toEqual([])
|
||||
})
|
||||
|
||||
it('loads an exact shared post by id', async () => {
|
||||
vi.mocked(nuiCall).mockResolvedValue({ success: true, data: { ...post } })
|
||||
const store = usePicstagramStore()
|
||||
|
||||
expect(await store.loadPost(post.id)).toEqual(post)
|
||||
expect(nuiCall).toHaveBeenCalledWith('picstagram:post', { id: post.id })
|
||||
})
|
||||
})
|
||||
|
||||
@@ -127,6 +127,10 @@ export const usePicstagramStore = defineStore('picstagram', {
|
||||
this.feedCursor = response.data.nextCursor
|
||||
return true
|
||||
},
|
||||
async loadPost(id: string): Promise<PicstagramPost | null> {
|
||||
const response = await nuiCall<PicstagramPost>('picstagram:post', { id })
|
||||
return response.success && response.data ? response.data : null
|
||||
},
|
||||
async loadExplore(append = false): Promise<boolean> {
|
||||
const response = await nuiCall<PicstagramPage>('picstagram:explore', {
|
||||
cursor: append ? this.exploreCursor : undefined,
|
||||
|
||||
@@ -49,4 +49,26 @@ describe('EasyShare deep links', () => {
|
||||
).query.listingId,
|
||||
).toBe('listing-1')
|
||||
})
|
||||
|
||||
it.each([
|
||||
['picstagram', 'post', 'pic-post-1', '/apps/picstagram'],
|
||||
['picstagram', 'profile', 'pic-profile-1', '/apps/picstagram'],
|
||||
['feather', 'post', 'feather-post-1', '/apps/feather'],
|
||||
['fliptok', 'post', 'fliptok-1', '/apps/fliptok'],
|
||||
['local-pages', 'post', 'pages-post-1', '/apps/local-pages'],
|
||||
])('preserves the exact %s %s target', (appId, kind, id, path) => {
|
||||
expect(
|
||||
easyShareRoute(
|
||||
payload({
|
||||
appId: appId as EasySharePayload['appId'],
|
||||
id,
|
||||
kind: kind as EasySharePayload['kind'],
|
||||
link: `skyphone://${appId}/${kind}/${id}`,
|
||||
}),
|
||||
),
|
||||
).toMatchObject({
|
||||
path,
|
||||
query: { easyShareId: id, easyShareKind: kind },
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -709,6 +709,17 @@ onMounted(async () => {
|
||||
if (route.query.compose === '1') screen.value = 'composer'
|
||||
await feather.bootstrap()
|
||||
if (feather.onboarded) {
|
||||
const easyShareId = String(route.query.easyShareId ?? '')
|
||||
if (easyShareId && route.query.easyShareKind === 'profile') {
|
||||
const profileId = Number(easyShareId)
|
||||
if (Number.isInteger(profileId) && profileId > 0)
|
||||
await openProfile(profileId)
|
||||
} else if (easyShareId && route.query.easyShareKind === 'post') {
|
||||
if (await feather.loadThread(easyShareId)) {
|
||||
threadReplyTarget.value = feather.thread?.post ?? null
|
||||
screen.value = 'thread'
|
||||
}
|
||||
}
|
||||
await feather.loadActivities()
|
||||
if (tab.value === 'profile' && feather.profile)
|
||||
await feather.loadProfile(feather.profile.id)
|
||||
|
||||
@@ -50,7 +50,7 @@ import {
|
||||
kToggle,
|
||||
} from 'konsta/vue'
|
||||
import { computed, nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
|
||||
import flipTokIcon from '@/assets/img/app-icons/fliptok.webp'
|
||||
import { useFlipTokStore } from '@/stores/fliptok'
|
||||
@@ -71,6 +71,7 @@ type AuthMode = 'login' | 'register'
|
||||
const phone = usePhoneStore()
|
||||
const store = useFlipTokStore()
|
||||
const messageMedia = useMessageMediaStore()
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const authMode = ref<AuthMode>('login')
|
||||
const authHandle = ref('')
|
||||
@@ -776,6 +777,18 @@ onMounted(async () => {
|
||||
composeOpen.value = true
|
||||
}
|
||||
await store.bootstrap()
|
||||
const easyShareId = String(route.query.easyShareId ?? '')
|
||||
if (easyShareId && route.query.easyShareKind === 'profile') {
|
||||
const profileId = Number(easyShareId)
|
||||
if (Number.isInteger(profileId) && profileId > 0)
|
||||
await openProfile(profileId)
|
||||
} else if (easyShareId && route.query.easyShareKind === 'post') {
|
||||
const video = await store.loadVideo(easyShareId)
|
||||
if (video) {
|
||||
store.feed = [video]
|
||||
tab.value = 'feed'
|
||||
}
|
||||
}
|
||||
await nextTick()
|
||||
observeVideos()
|
||||
})
|
||||
|
||||
@@ -279,7 +279,7 @@ function sharePost(): void {
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
onMounted(async () => {
|
||||
const selection = messageMedia.consumeMany<MediaContext>('local-pages:compose')
|
||||
if (selection) {
|
||||
if (selection.context) {
|
||||
@@ -294,7 +294,16 @@ onMounted(() => {
|
||||
}
|
||||
}
|
||||
if (route.query.compose === '1') screen.value = 'compose'
|
||||
void loadFeed()
|
||||
await loadFeed()
|
||||
const easyShareId = String(route.query.easyShareId ?? '')
|
||||
if (easyShareId && route.query.easyShareKind === 'post') {
|
||||
const response = await pages.get(easyShareId)
|
||||
if (response.success && response.data) {
|
||||
selected.value = response.data
|
||||
galleryIndex.value = 0
|
||||
screen.value = 'detail'
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
@@ -55,7 +55,7 @@ import {
|
||||
kToggle,
|
||||
} from 'konsta/vue'
|
||||
import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
|
||||
import picstagramIcon from '@/assets/img/app-icons/picstagram.webp'
|
||||
import { useMessageMediaStore } from '@/stores/messageMedia'
|
||||
@@ -80,6 +80,7 @@ type ProfileSection = 'photos' | 'saved'
|
||||
const phone = usePhoneStore()
|
||||
const store = usePicstagramStore()
|
||||
const mediaPicker = useMessageMediaStore()
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
|
||||
const tab = ref<Tab>('home')
|
||||
@@ -629,6 +630,16 @@ watch(profileSection, (section) => {
|
||||
|
||||
onMounted(async () => {
|
||||
await store.bootstrap()
|
||||
const easyShareId = String(route.query.easyShareId ?? '')
|
||||
if (easyShareId && route.query.easyShareKind === 'profile') {
|
||||
await openProfile(easyShareId)
|
||||
} else if (easyShareId && route.query.easyShareKind === 'post') {
|
||||
const post = await store.loadPost(easyShareId)
|
||||
if (post) {
|
||||
tab.value = 'home'
|
||||
selectedPost.value = post
|
||||
}
|
||||
}
|
||||
const composeSelection = mediaPicker.consumeMany<{
|
||||
caption?: string
|
||||
commentsEnabled?: boolean
|
||||
|
||||
@@ -5387,6 +5387,15 @@ app.post('/api/:endpoint', (request, response) => {
|
||||
})
|
||||
return
|
||||
}
|
||||
if (endpoint === 'picstagram:post') {
|
||||
const post = picstagramPosts.find((item) => item.id === request.body.id)
|
||||
response.json(
|
||||
post
|
||||
? { success: true, data: post }
|
||||
: { success: false, error: 'post_not_found' },
|
||||
)
|
||||
return
|
||||
}
|
||||
if (endpoint === 'picstagram:explore') {
|
||||
response.json({
|
||||
success: true,
|
||||
@@ -5768,6 +5777,15 @@ app.post('/api/:endpoint', (request, response) => {
|
||||
response.json({ success: true })
|
||||
return
|
||||
}
|
||||
if (endpoint === 'fliptok:video') {
|
||||
const video = flipTokVideos.find((item) => item.id === request.body.id)
|
||||
response.json(
|
||||
video
|
||||
? { success: true, data: video }
|
||||
: { success: false, error: 'video_not_found' },
|
||||
)
|
||||
return
|
||||
}
|
||||
if (endpoint === 'fliptok:follow') {
|
||||
flipTokVideos
|
||||
.filter((video) => video.profile_id === request.body.profileId)
|
||||
|
||||
@@ -69,6 +69,7 @@ local server_callbacks = {
|
||||
"fliptok:logout",
|
||||
"fliptok:bootstrap",
|
||||
"fliptok:feed",
|
||||
"fliptok:video",
|
||||
"fliptok:discover",
|
||||
"fliptok:publish",
|
||||
"fliptok:react",
|
||||
@@ -91,6 +92,7 @@ local server_callbacks = {
|
||||
"picstagram:logout",
|
||||
"picstagram:bootstrap",
|
||||
"picstagram:feed",
|
||||
"picstagram:post",
|
||||
"picstagram:explore",
|
||||
"picstagram:search",
|
||||
"picstagram:saved",
|
||||
|
||||
@@ -303,6 +303,32 @@ end)
|
||||
|
||||
Bridge.Callbacks.Register("sky_phone:fliptok:feed", feed)
|
||||
|
||||
Bridge.Callbacks.Register("sky_phone:fliptok:video", function(source, data)
|
||||
local profile, error_response = require_profile(source)
|
||||
if not profile then return error_response end
|
||||
if not SkyPhone.AllowOperation(source, "fliptok:video", 60, 60) then
|
||||
return { success = false, error = "rate_limited" }
|
||||
end
|
||||
local video_id = type(data) == "table" and data.id or nil
|
||||
if type(video_id) ~= "string" or #video_id > 64 then
|
||||
return { success = false, error = "video_not_found" }
|
||||
end
|
||||
local rows = list_videos(
|
||||
profile.id,
|
||||
[[v.`id` = ? AND (v.`visibility` = 'public' OR v.`profile_id` = ? OR
|
||||
(v.`visibility` = 'followers' AND EXISTS(
|
||||
SELECT 1 FROM `sky_phone_fliptok_follows` follow
|
||||
WHERE follow.`follower_id` = ? AND follow.`following_id` = v.`profile_id`)))]],
|
||||
{ video_id, profile.id, profile.id, profile.id, profile.id },
|
||||
1,
|
||||
0,
|
||||
"v.`created_at` DESC"
|
||||
)
|
||||
return rows[1]
|
||||
and { success = true, data = rows[1] }
|
||||
or { success = false, error = "video_not_found" }
|
||||
end)
|
||||
|
||||
Bridge.Callbacks.Register("sky_phone:fliptok:discover", function(source, data)
|
||||
local profile, error_response = require_profile(source)
|
||||
if not profile then return error_response end
|
||||
|
||||
@@ -466,6 +466,35 @@ Bridge.Callbacks.Register("sky_phone:picstagram:feed", function(source, data)
|
||||
return result and { success = true, data = result } or { success = false, error = list_error }
|
||||
end)
|
||||
|
||||
Bridge.Callbacks.Register("sky_phone:picstagram:post", function(source, data)
|
||||
local profile, error_response = profile_for_session(source)
|
||||
if not profile then
|
||||
return error_response
|
||||
end
|
||||
if not SkyPhone.AllowOperation(source, "picstagram:post", 60, 60) then
|
||||
return { success = false, error = "rate_limited" }
|
||||
end
|
||||
local post_id = type(data) == "table" and data.id or nil
|
||||
if not valid_id(post_id) then
|
||||
return { success = false, error = "post_not_found" }
|
||||
end
|
||||
local result, list_error = list_posts(
|
||||
profile.id,
|
||||
[[post.`id` = ? AND (profile.`private` = 0 OR post.`profile_id` = ? OR EXISTS(
|
||||
SELECT 1 FROM `sky_phone_picstagram_follows` follow
|
||||
WHERE follow.`follower_id` = ? AND follow.`following_id` = post.`profile_id`
|
||||
AND follow.`status` = 'accepted'))]],
|
||||
{ post_id, profile.id, profile.id },
|
||||
nil
|
||||
)
|
||||
if not result then
|
||||
return { success = false, error = list_error }
|
||||
end
|
||||
return result.items[1]
|
||||
and { success = true, data = result.items[1] }
|
||||
or { success = false, error = "post_not_found" }
|
||||
end)
|
||||
|
||||
Bridge.Callbacks.Register("sky_phone:picstagram:explore", function(source, data)
|
||||
local profile, error_response = profile_for_session(source)
|
||||
if not profile then
|
||||
|
||||
Reference in New Issue
Block a user