Kuanza haraka Ukurasa huu umetafsiriwa kutoka Kiingereza. Ikiwa hizi mbili zinatofautiana, ukurasa wa Kiingereza ndio sahihi. Soma ukurasa wa Kiingereza
Ukurasa huu unaipeleka programu kutoka mwanzo kabisa hadi kadi kwenye akaunti yako mwenyewe. Sampuli zilizo hapa chini hufuata hatua zilezile katika kila lugha.
Unda mteja wa OAuth
Programu ni mteja wa OAuth mwenye manifest, kwa hivyo anza na mteja. Unda mmoja kwenye ukurasa wa wateja wa OAuth kwenye dashibodi na umpe uwezo wa API ambao programu yako itahitaji, kama vile kusoma mipango ya masomo. Kitambulisho chake cha mteja na siri yake huruhusu seva yako kuita API ya Lingara kama programu yako. Viweke katika mazingira ya seva yako.
Jenga na upakie manifest
Manifest huieleza Lingara kuhusu programu yako:
jina lake na maelezo yake, katika lugha kuu, pamoja na tafsiri za hiari;
render_url, anwani ya HTTPS ambayo Lingara hutuma maombi kwake;
nafasi ambazo kadi yake inaweza kuonekana;
muktadha inaoomba kuona, ambao mwanafunzi huukubali wakati wa kusakinisha (tazama Muktadha na ridhaa );
uwezo wa API inaohitaji, ambao mteja wake wa OAuth lazima awe nao tayari;
kama inaweza kutoa dokezo kwa mkufunzi (tazama Dokezo kwa mkufunzi ).
Kifurushi hujenga manifest kama JSON na kuikagua dhidi ya sheria zinazotumika wakati wa kupakia, kwa hivyo kosa huonekana kwanza kwenye mashine yako mwenyewe. Kwenye ukurasa wa Programu kwenye dashibodi, chagua mteja wako, mfanye kuwa programu, na upakie faili.
Unda siri ya kutia sahihi
Kwenye ukurasa huohuo, unda siri ya kutia sahihi. Huonyeshwa mara moja tu. Ipe seva yako kupitia mazingira yake, na usiiweke kamwe kwenye udhibiti wa msimbo chanzo. Lingara hutia sahihi kila ombi kwa programu yako kwa siri hiyo, na kifurushi hukataa ombi ambalo sahihi yake hailingani. Programu inaweza kuwa na siri mbili kwa wakati mmoja, kwa hivyo unaweza kubadilisha moja bila kukatiza huduma: wakati siri mbili zinatumika, kila ombi hutiwa sahihi kwa zote mbili.
Andika kitendakazi cha render
Kitendakazi chako cha render hupokea ombi na kurudisha kadi. Ombi hutaja nafasi, lugha ya mwanafunzi, muktadha aliokubali kushiriki, na subject, thamani thabiti inayomtambulisha mwanafunzi kwa programu yako. Hifadhi chochote unachohifadhi kwa kila mwanafunzi chini ya subject. Kitufe kwenye kadi yako hutuma kitendo, na seva yako hukijibu kwa kadi mpya. Ukurasa wa kadi huorodhesha kila kitu ambacho kadi inaweza kubeba.
Weka adapta
Adapta ya kifurushi husoma ombi ghafi, hukagua sahihi yake, huita kitendakazi chako, na huandika jibu. Itoe kwenye anwani inayotajwa na render_url ya manifest yako. Lingara huita anwani za HTTPS pekee kwenye intaneti ya umma.
Isakinishe kwenye akaunti yako mwenyewe
Kwenye ukurasa wa Programu kwenye dashibodi, chagua kusakinisha programu kwenye akaunti yako, chagua kile inachoweza kuona, na uthibitishe. Fungua skrini ya mipango ya masomo kwenye skrini pana, au ukurasa wa Mwanzo kwenye wavuti, na kadi yako itaonekana.
Mteja wa OAuth wa programu yako pia hupokea matukio, kama vile mpango wa somo kuwa tayari. Mwongozo wa webhooks na matukio katika marejeo ya API unayaeleza.
Lugha ya mfano wa msimbo TypeScript Rust Go Java Kotlin Ruby PHP TypeScript
npm install @lingara/apps@0.1.0-alpha.6 Rust
cargo add lingara-apps@0.1.0-alpha.6 --features axum
cargo add axum@0.8
cargo add tokio --features macros,net,rt-multi-thread Go
go get github.com/Spinning-Cat-Studios/lingara_app_framework_sdk/go@v0.1.0-alpha.6 Java
< dependency >
< groupId >com.getlingara</ groupId >
< artifactId >lingara-apps-java</ artifactId >
< version >0.1.0-alpha.6</ version >
</ dependency > Kotlin
implementation ( "com.getlingara:lingara-apps-kotlin:0.1.0-alpha.6" ) Ruby
gem install lingara-apps -v 0.1.0-alpha.6 PHP
composer require spinningcatstudios/lingara-apps:0.1.0-alpha.6 spinningcatstudios/lingara:@alpha Lugha ya mfano wa msimbo TypeScript Rust Go Java Kotlin Ruby PHP TypeScript
import { manifest } from "@lingara/apps" ;
const json = manifest ()
. defaultLocale ( "en" )
. name ({ en: "Daily five" , "zh-Hans" : "每日五词" })
. description ( "Five words to review, picked from your plan." )
. renderUrl ( "https://apps.example.com/lingara/render" )
. slots ( "home.side" , "plans.empty_detail" )
. context ( "languages" , "plan_summary" )
// `.scopes(…)` lists the API scopes your client uses, for the learner's consent page. This app uses none.
. tutorNote ()
. toJson (); // Throws ManifestError naming the rule an upload would refuse.
writeFileSync ( "manifest.json" , json); // Upload it in the console. Rust
let manifest = manifest ()
. default_locale ( "en" )
. name ( "Daily five" )
. description ( "Five words to review, picked from your plan." )
. render_url ( "https://apps.example.com/lingara/render" )
. slots ([ AppSlotName :: HomeSide , AppSlotName :: PlansEmptyDetail ])
. context ([ ContextSliceKind :: Languages , ContextSliceKind :: PlanSummary ])
// `.scopes(…)` lists the API scopes your client uses, for the learner's consent page. This app uses none.
. tutor_note ( true )
. build () ? ;
std :: fs :: write ( "manifest.json" , manifest . to_json ()) ? ; Go
manifest, err := lingaraapps. NewManifest ().
DefaultLocale ( "en" ).
Names ( map [ string ] string { "en" : "Daily five" , "zh-Hans" : "每日五词" }).
Description ( "Five words to review, picked from your plan." ).
RenderURL ( "https://apps.example.com/lingara" ).
Slots (lingaraapps.AppSlotNameHomeSide, lingaraapps.AppSlotNamePlansEmptyDetail).
Context (lingaraapps.ContextSliceKindLanguages, lingaraapps.ContextSliceKindPlanSummary).
// `.Scopes(…)` lists the API scopes your client uses, for the learner's consent page. This app uses none.
TutorNote ( true ).
Build ()
if err != nil {
return err // a *lingaraapps.ManifestError names the broken rule
}
data, err := manifest. ToJSON ()
if err != nil {
return err
}
// Upload manifest.json in the console.
err = os. WriteFile ( "manifest.json" , data, 0o 644 ) Java
import com.getlingara.apps.Manifest;
import com.getlingara.apps.model.AppSlotName;
import com.getlingara.apps.model.ContextSliceKind;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
String json =
Manifest. manifest ()
. defaultLocale ( "en" )
. name ( "Daily five" )
. description ( "Five words to review, picked from your plan." )
. renderUrl ( "https://apps.example.com/lingara/render" )
. slots (AppSlotName.HOME_SIDE)
. context (ContextSliceKind.LANGUAGES, ContextSliceKind.PLAN_SUMMARY)
// `.scopes(…)` lists the API scopes your client uses, for the learner's consent
// page. This app uses none.
. tutorNote ( true )
. build () // refuses what the upload would, naming the rule
. toJson ();
// Upload manifest.json in the console, beside the app's icon.
Files. writeString (Path. of ( "manifest.json" ), json); Kotlin
import com.getlingara.apps.kotlin.manifest
import com.getlingara.apps.kotlin.model.AppSlotName
import com.getlingara.apps.kotlin.model.ContextSliceKind
import java.io.File
val json =
manifest {
defaultLocale = "en"
name ( "Daily five" )
description ( "Five words to review, picked from your plan." )
renderUrl = "https://apps.example.com/lingara/render"
slots (AppSlotName.HOME_PERIOD_SIDE)
context (ContextSliceKind.LANGUAGES, ContextSliceKind.PLAN_SUMMARY)
// `scopes(…)` lists the API scopes your client uses, for the learner's consent page.
// This app uses none.
tutorNote = true
}. toJson () // refuses what the upload would, naming the rule
// Upload manifest.json in the console, beside the app's icon.
File ( "manifest.json" ). writeText (json) Ruby
manifest = Lingara :: Apps . manifest
. default_locale ( "en" )
. name ( "Daily five" ) # A bare String is the default locale's; or {"en" => …, "zh-Hans" => …}.
. description ( "Five words to review, picked from your plan." )
. render_url ( "https://apps.example.com/lingara" )
. slots ( "home.side" , "plans.empty_detail" )
. context ( "languages" , "plan_summary" )
# `.scopes(…)` lists the API scopes your client uses, for the learner's consent page. This app uses none.
. tutor_note
# Raises Lingara::Apps::ManifestError naming the rule the upload would refuse.
File . write ( "manifest.json" , manifest. to_json ) PHP
use Lingara\Apps\Generated\AppSlotName ;
use Lingara\Apps\Manifest ;
$json = Manifest :: create ()
-> defaultLocale ( 'en' )
-> name ([ 'en' => 'Daily five' , 'zh-Hans' => '每日五词' ])
-> description ( 'Five words to review, picked from your plan.' )
-> renderUrl ( 'https://apps.example.com/lingara/render' )
-> slots ( AppSlotName :: HOME_SIDE , AppSlotName :: PLANS_EMPTY_DETAIL )
-> context ( 'languages' , 'plan_summary' )
// `->scopes(…)` lists the API scopes your client uses, for the learner's consent page. This app uses none.
-> tutorNote ()
-> toJson (); // Throws ManifestException naming the rule an upload would refuse.
file_put_contents ( 'manifest.json' , $json); // Upload it in the console. Lugha ya mfano wa msimbo TypeScript Rust Go Java Kotlin Ruby PHP TypeScript
const app = lingaraApp ({
// lgr_whsec_…, from the app's settings; pass two during a rotation.
secret: process.env. LINGARA_APP_SECRET ! ,
// Hold per-learner state on `subject`, the same value your events carry.
render : ( request ) => (request.slot === "home.side" ? withNote (request) : todayCard (streaks. get (request.subject) ?? 0 )),
actions: {
// The button's `action`; may arrive twice, so make it safe to repeat.
done : ( request ) => {
streaks. set (request.subject, (streaks. get (request.subject) ?? 0 ) + 1 );
return todayCard (streaks. get (request.subject) ! );
},
},
}); Rust
// The app's signing secret (lgr_whsec_…), from the environment.
let seen = Seen :: default ();
let (render_seen, action_seen) = ( Arc :: clone ( & seen), Arc :: clone ( & seen));
let app = App :: new ([ std :: env :: var ( "LINGARA_APP_SECRET" ) ? ], move | request : AppRenderRequest | {
render ( Arc :: clone ( & render_seen), request)
}) ?
// The button's `action` comes back as `action_id`. An action can arrive
// twice, so keep it safe to repeat.
. action ( "next" , move | request : AppActionRequest | {
let seen = Arc :: clone ( & action_seen);
async move {
* seen . lock () . map_err ( | _ | "poisoned" ) ?. entry (request . subject) . or_default () += 1 ;
today_card ( "Next word" , "zh" )
}
}); Go
// Per-learner state is keyed on Subject, the lgr_sub_… value the app's
// events carry too.
var mu sync . Mutex
streaks := map [ string ] int {}
render := func ( ctx context . Context , req lingaraapps . AppRenderRequest ) ( lingaraapps . Replier , error ) {
mu. Lock ()
days := streaks[req.Subject]
mu. Unlock ()
return lingaraapps. NewCard ().
Heading ( "Daily five" , 1 ).
Progress ( float64 (days % 5 ) / 5 , "This week" ).
Button ( "Done today" , "done" ).
Build ()
}
// An action may arrive twice: make it safe to run twice.
done := func ( ctx context . Context , req lingaraapps . AppActionRequest ) ( lingaraapps . Replier , error ) {
mu. Lock ()
streaks[req.Subject] ++
mu. Unlock ()
return lingaraapps. NewCard (). Heading ( "Nice work" , 1 ). Text ( "See you tomorrow." ). Build ()
}
app := lingaraapps . App {
Render: render,
Actions: map [ string ] lingaraapps . ActionFunc { "done" : done},
} Java
import com.getlingara.apps.Card;
import com.getlingara.apps.LingaraApp;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
// Per-learner state is keyed on subject, the value the app's events carry too.
Map< String , Integer > seen = new ConcurrentHashMap<>();
LingaraApp app =
LingaraApp. builder ()
// The app's signing secret (lgr_whsec_…); pass two during a rotation.
. secrets (System. getenv ( "LINGARA_APP_SECRET" ))
. render (
request -> {
int count = seen. getOrDefault (request. getSubject (), 0 );
return Card. card ()
. heading ( "Today's five" , 1 )
. text ( "Reviewed " + count + " so far." )
. button ( "Next" , "next" )
. build ();
})
// The button's action comes back as the request's action_id. It may arrive
// twice, so make it safe to repeat.
. action (
"next" ,
request -> {
int count = seen. merge (request. getSubject (), 1 , Integer :: sum);
return Card. card (). progress (Math. min ( 1.0 , count / 5.0 ), count + " of 5" ). build ();
})
. build (); Kotlin
import com.getlingara.apps.kotlin.LingaraApp
import com.getlingara.apps.kotlin.card
import com.getlingara.apps.kotlin.lingaraApp
import java.util.concurrent.ConcurrentHashMap
// Per-learner state is keyed on subject, the value the app's events carry too.
val seen = ConcurrentHashMap < String , Int >()
val app =
lingaraApp {
// The app's signing secret (lgr_whsec_…); pass two during a rotation.
secrets (System. getenv ( "LINGARA_APP_SECRET" ))
render { request ->
val count = seen. getOrDefault (request.subject, 0 )
card {
heading ( "Today's five" , 1 )
text ( "Reviewed $count so far." )
button ( "Next" , "next" )
}
}
// The button's action comes back as the request's action_id. It may arrive twice,
// so make it safe to repeat.
action ( "next" ) { request ->
val count = seen. merge (request.subject, 1 , Int:: plus ) ?: 1
card { progress ( minOf ( 1.0 , count / 5.0 ), " $count of 5" ) }
}
} Ruby
# lgr_whsec_…, from the app's settings; pass an Array of two during a rotation.
app = Lingara :: Apps :: App . new ( secret: ENV . fetch ( "LINGARA_APP_SECRET" ))
# Hold per-learner state on `subject`, the same value your events carry.
app. render do |request|
(request. slot == "home.side" ) ? with_note (request) : today_card ( STREAKS [request. subject ])
end
# The button's `action`; it may arrive twice, so make it safe to repeat.
app. action ( "done" ) do |request|
STREAKS [request. subject ] += 1
today_card ( STREAKS [request. subject ])
end PHP
use Lingara\Apps\App ;
use Lingara\Apps\Generated\AppActionRequest ;
use Lingara\Apps\Generated\AppRenderRequest ;
$app = new App (
// lgr_whsec_…, from the app's settings; pass a list of two during a rotation.
secret : ( string ) getenv ( 'LINGARA_APP_SECRET' ),
// Hold per-learner state on `subject`, the same value your events carry.
render : static fn ( AppRenderRequest $request) => $request -> getSlot () === AppSlotName :: HOME_SIDE
? withNote ($request)
: todayCard ($streaks[$request -> getSubject ()] ?? 0 ),
actions : [
// The button's `action`; may arrive twice, so make it safe to repeat.
'done' => static function ( AppActionRequest $request) use ( & $streaks) : CardModel {
$streaks[$request -> getSubject ()] = ($streaks[$request -> getSubject ()] ?? 0 ) + 1 ;
return todayCard ($streaks[$request -> getSubject ()]);
},
],
); Lugha ya mfano wa msimbo TypeScript Rust Go Java Kotlin Ruby PHP TypeScript
// A node:http request listener: it reads the raw body, verifies, dispatches and replies.
createServer ( nodeHandler (app)). listen ( 8787 ); Rust
// Nest the adapter at the path your manifest's render_url names.
let routes = axum :: Router :: new () . nest ( "/lingara/render" , lingara_apps :: axum :: router (app));
let listener = tokio :: net :: TcpListener :: bind ( "0.0.0.0:8080" ) .await? ;
axum :: serve (listener, routes) .await? ; Go
// The app's signing secret, lgr_whsec_…, or both during a rotation.
handler, err := lingaraapps. NewHandler (app, os. Getenv ( "LINGARA_APP_SECRET" ))
if err != nil {
return err // a malformed secret fails here, at startup
}
mux := http. NewServeMux ()
mux. Handle ( "/lingara" , handler)
err = http. ListenAndServe ( ":8080" , mux) Java
import com.getlingara.apps.JdkHttpHandler;
import com.getlingara.apps.LingaraApp;
import com.sun.net.httpserver.HttpServer;
import java.io.IOException;
import java.net.InetSocketAddress;
// The JDK's own server: the handler reads the raw body, verifies it, then dispatches.
HttpServer server = HttpServer. create ( new InetSocketAddress ( 8080 ), 0 );
server. createContext ( "/lingara/render" , JdkHttpHandler. of (app));
server. start (); Kotlin
import com.getlingara.apps.kotlin.LingaraApp
import com.getlingara.apps.kotlin.ktor.lingaraApp
import io.ktor.server.cio.CIO
import io.ktor.server.engine.embeddedServer
import io.ktor.server.routing.route
import io.ktor.server.routing.routing
// Route.lingaraApp reads the raw body itself, so content negotiation never parses it.
embeddedServer (CIO, port = 8080 ) {
routing {
route ( "/lingara/render" ) { lingaraApp (app) }
}
}. start (wait = true ) Ruby
# config.ru: any Rack server (Puma, Falcon, rackup) runs it. It reads the
# raw body itself, so mount it where nothing has parsed the JSON first.
run Lingara :: Apps :: RackApp . new (app) PHP
use Lingara\Apps\Psr15Handler ;
use Nyholm\Psr7\Factory\Psr17Factory ;
use Nyholm\Psr7Server\ServerRequestCreator ;
// A PSR-15 handler: mount it on your framework's route, or answer from a
// front controller as here. It reads the raw body, verifies, dispatches and replies.
$factory = new Psr17Factory ();
$handler = new Psr15Handler ($app, $factory, $factory);
$response = $handler -> handle (( new ServerRequestCreator ($factory, $factory, $factory, $factory)) -> fromGlobals ());
http_response_code ($response -> getStatusCode ());
foreach ($response -> getHeaders () as $name => $values) {
header ( "{ $name }: " . implode ( ', ' , $values));
}
echo $response -> getBody ();