Giter VIP home page Giter VIP logo

oapi-sdk-java's People

Contributors

keeperlibofan avatar maemual avatar magakireimu avatar maowenbo666 avatar zhailuxubyte avatar zhaoche27 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

oapi-sdk-java's Issues

获取部门信息列表设置fetch_child时报错

DepartmentListReqBody departmentListReqBody = new DepartmentListReqBody();
departmentListReqBody.setFetchChild(true);
Response<DepartmentListResult> execute = client.getContactService().getDepartments().list(departmentListReqBody).execute();

返回

java.lang.IllegalArgumentException: method GET must not have a request body.

list方法传null可获取列表,但无法设置fetch_child。

 Response<DepartmentListResult> execute = client.getContactService().getDepartments().list(null).execute();

这里不应该把setFetchChild放到body,而是放到queryParam才对。
例如:

 Response<DepartmentListResult> execute = client.getContactService().getDepartments().list().setFetchChild(true).execute();

审批任务推送的事件增加当前任务所处的节点id

审批任务推送的事件增加当前任务所处的节点id。

我目前想对节点的状态进行同步到自己的系统,但每个任务事件推送过来,我并不知道是哪个节点的任务,无法进行节点状态同步。
我目前的处理是:查询一遍审批定义接口才知道任务对应的节点id。这个过程增加了一次io操作和循环判断,处理麻烦。

accessToken获取失败

`` private static final String APP_ID = "xxxxxxxxxxxx";

private static final String APP_SECRET = "xxxxxxxxxxxxxxxx";
private static final String verificationToken = "xxxxxxxxxxxxxxxx";
private static final String encryptKey = "xxxxxxxxxxxxxxxx";

private static final AppSettings appSettings = Config.createInternalAppSettings(APP_ID,APP_SECRET,verificationToken,encryptKey);

public static Config getConfig(String domain) {

//redis存储方法
RedisStore redisStore = saveRedis(domain, appSettings.toString());
return new Config( domain,  appSettings,  redisStore);

}`
##########################################################

private static final Config config = Configs.getConfig("https://open.feishu.cn");

public static void main(String[] args) throws Exception {
testAccessToken();
//testFreshAccessToken();
//testUserInfo();
}

private static void testAccessToken() throws Exception {
AuthenService service = new AuthenService(config);
AuthenAccessTokenReqBody body = new AuthenAccessTokenReqBody();
body.setGrantType("authorization_code");
body.setCode("X1ijpLtwMERez2s3vQTQgh");
AuthenService.AuthenAccessTokenReqCall reqCall = service.getAuthens().accessToken(body);
Response response = reqCall.execute();
System.out.println(Jsons.DEFAULT_GSON.toJson(response));
System.out.println(Jsons.DEFAULT_GSON.toJson(response.getData()));
System.out.println(response.getRequestID());
}
#################################################################################

输出结果:
10:xxx:xxx.xxx [main] DEBUG com.larksuite.oapi.core.api.handler.subhandler.BuildSubHandler - [build]request:POST /open-apis/authen/v1/access_token app_access_token, body:{"grant_type":"authorization_code","code":"X1ijpLtwMERez2s3vQTQgh"}
Exception in thread "main" java.lang.NullPointerException
at com.larksuite.oapi.core.api.handler.Handler.complement(Handler.java:75)
at com.larksuite.oapi.core.api.handler.Handler.handle(Handler.java:56)
at com.larksuite.oapi.core.api.Api.send(Api.java:14)
at com.larksuite.oapi.service.authen.v1.AuthenService$AuthenAccessTokenReqCall.execute(AuthenService.java:130)
at czy.com.dldl.feishu.AuthenSample.testAccessToken(AuthenSample.java:40)
at czy.com.dldl.feishu.AuthenSample.main(AuthenSample.java:29)

Process finished with exit code 1

请问到底是什么null错误?

日历服务API怎么存放USER TOKEN

` private static final AppSettings appSettings = Config.createInternalAppSettings("", "",null,null);
private static final Config config = new Config(Domain.FeiShu, appSettings, new DefaultStore());

@Test
public void createCalendar () throws Exception {
    CalendarService calendarService = new CalendarService(config);

    Calendar calendar = new Calendar();
    calendar.setSummary("Test Calendar");
    calendar.setDescription("Test calendar create");
    calendar.setPermissions("public");
    Response<CalendarCreateResult> execute = calendarService.getCalendars().create(calendar).execute();
    System.out.println(Jsons.DEFAULT_GSON.toJson(execute));
}`

这个默认是应用级别的TOKEN,怎么放USER的TOKEN 来创建日历

新版SDK的一个疑问

目前的service下业务包命名带版本号v3、v4这样的。如果后期业务版本升级是否会出现更多带v的版本号。
最终提供给开发者的SDK是否会存在同个业务的不同版本号,使用时可能产生不确定性。
是否考虑使用不同的git分支来维护不同系列的版本号,像Spring那样?

关于ISV应用首次创建会话订阅问题

有两个问题
1.关于ISV应用上线的上线清单界面,这个首次会话订阅条件的刷新条件是什么?我曾尝试过刷新页面,但是我没有监听到任何有关于相关请求,并且我使用过不管是event_callback或者p2p_chat_create还是event_callback.p2p_chat_create,都不行我该如此测试这个订阅事件,毕竟我不可能每次测试都新建一个应用去测试首次会话创建。
1.后端服务创建机器人,使用SDK,如何才能监听到首次创建会话的事件,相关的接口文档上只有接口回调形式的监听,没有SDK形式的事件type。我曾尝试过刷新上线清单界面,但是我没有监听到任何有关于相关请求,并且我使用过不管是event_callback或者p2p_chat_create还是event_callback.p2p_chat_create,都不行,我该如何监听这一事件?

以上两个问题我无法从文档及人工客服那里寻找到答案,麻烦大佬的指导

com.lark.oapi.core.exception.IncorrectSignatureException: The result of signature verification failed

oapi-sdk调用报错:

com.lark.oapi.core.exception.IncorrectSignatureException: The result of signature verification failed
	at com.lark.oapi.event.EventDispatcher.handle(EventDispatcher.java:200) [oapi-sdk-2.0.2.jar:2.0.2]
	at com.lark.oapi.sdk.servlet.ext.ServletAdapter.handleEvent(ServletAdapter.java:31) [oapi-sdk-servlet-ext-1.0.0-rc2.jar:1.0.0-rc2]
	at cn.healthlink.devops.web.controller.EventController.event(EventController.java:73) [classes/:?]
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:1.8.0_221]
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[?:1.8.0_221]
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:1.8.0_221]
	at java.lang.reflect.Method.invoke(Method.java:498) ~[?:1.8.0_221]
	at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:205) [spring-web-5.3.23.jar:5.3.23]
	at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:150) [spring-web-5.3.23.jar:5.3.23]
	at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:117) [spring-webmvc-5.3.23.jar:5.3.23]
	at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:895) [spring-webmvc-5.3.23.jar:5.3.23]
	at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:808) [spring-webmvc-5.3.23.jar:5.3.23]
	at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87) [spring-webmvc-5.3.23.jar:5.3.23]
	at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1071) [spring-webmvc-5.3.23.jar:5.3.23]
	at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:964) [spring-webmvc-5.3.23.jar:5.3.23]
	at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006) [spring-webmvc-5.3.23.jar:5.3.23]
	at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:909) [spring-webmvc-5.3.23.jar:5.3.23]
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:517) [jakarta.servlet-api-4.0.4.jar:4.0.4]
	at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883) [spring-webmvc-5.3.23.jar:5.3.23]
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:584) [jakarta.servlet-api-4.0.4.jar:4.0.4]
	at io.undertow.servlet.handlers.ServletHandler.handleRequest(ServletHandler.java:74) [undertow-servlet-2.2.19.Final.jar:2.2.19.Final]
	at io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:129) [undertow-servlet-2.2.19.Final.jar:2.2.19.Final]
	at cn.springsoter.core.log4j2.filter.LogMDCFilter.doFilter(LogMDCFilter.java:24) [springsoter-core-log4j2-1.0.31.jar:1.0.31]
	at io.undertow.servlet.core.ManagedFilter.doFilter(ManagedFilter.java:61) [undertow-servlet-2.2.19.Final.jar:2.2.19.Final]
	at io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:131) [undertow-servlet-2.2.19.Final.jar:2.2.19.Final]
	at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) [spring-web-5.3.23.jar:5.3.23]
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117) [spring-web-5.3.23.jar:5.3.23]
	at io.undertow.servlet.core.ManagedFilter.doFilter(ManagedFilter.java:61) [undertow-servlet-2.2.19.Final.jar:2.2.19.Final]
	at io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:131) [undertow-servlet-2.2.19.Final.jar:2.2.19.Final]
	at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93) [spring-web-5.3.23.jar:5.3.23]
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117) [spring-web-5.3.23.jar:5.3.23]
	at io.undertow.servlet.core.ManagedFilter.doFilter(ManagedFilter.java:61) [undertow-servlet-2.2.19.Final.jar:2.2.19.Final]
	at io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:131) [undertow-servlet-2.2.19.Final.jar:2.2.19.Final]
	at org.springframework.boot.actuate.metrics.web.servlet.WebMvcMetricsFilter.doFilterInternal(WebMvcMetricsFilter.java:96) [spring-boot-actuator-2.6.12.jar:2.6.12]
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117) [spring-web-5.3.23.jar:5.3.23]
	at io.undertow.servlet.core.ManagedFilter.doFilter(ManagedFilter.java:61) [undertow-servlet-2.2.19.Final.jar:2.2.19.Final]
	at io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:131) [undertow-servlet-2.2.19.Final.jar:2.2.19.Final]
	at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) [spring-web-5.3.23.jar:5.3.23]
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117) [spring-web-5.3.23.jar:5.3.23]
	at io.undertow.servlet.core.ManagedFilter.doFilter(ManagedFilter.java:61) [undertow-servlet-2.2.19.Final.jar:2.2.19.Final]
	at io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:131) [undertow-servlet-2.2.19.Final.jar:2.2.19.Final]
	at io.undertow.servlet.handlers.FilterHandler.handleRequest(FilterHandler.java:84) [undertow-servlet-2.2.19.Final.jar:2.2.19.Final]
	at io.undertow.servlet.handlers.security.ServletSecurityRoleHandler.handleRequest(ServletSecurityRoleHandler.java:62) [undertow-servlet-2.2.19.Final.jar:2.2.19.Final]
	at io.undertow.servlet.handlers.ServletChain$1.handleRequest(ServletChain.java:68) [undertow-servlet-2.2.19.Final.jar:2.2.19.Final]
	at io.undertow.servlet.handlers.ServletDispatchingHandler.handleRequest(ServletDispatchingHandler.java:36) [undertow-servlet-2.2.19.Final.jar:2.2.19.Final]
	at io.undertow.servlet.handlers.RedirectDirHandler.handleRequest(RedirectDirHandler.java:68) [undertow-servlet-2.2.19.Final.jar:2.2.19.Final]
	at io.undertow.servlet.handlers.security.SSLInformationAssociationHandler.handleRequest(SSLInformationAssociationHandler.java:117) [undertow-servlet-2.2.19.Final.jar:2.2.19.Final]
	at io.undertow.servlet.handlers.security.ServletAuthenticationCallHandler.handleRequest(ServletAuthenticationCallHandler.java:57) [undertow-servlet-2.2.19.Final.jar:2.2.19.Final]
	at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43) [undertow-core-2.2.19.Final.jar:2.2.19.Final]
	at io.undertow.security.handlers.AbstractConfidentialityHandler.handleRequest(AbstractConfidentialityHandler.java:46) [undertow-core-2.2.19.Final.jar:2.2.19.Final]
	at io.undertow.servlet.handlers.security.ServletConfidentialityConstraintHandler.handleRequest(ServletConfidentialityConstraintHandler.java:64) [undertow-servlet-2.2.19.Final.jar:2.2.19.Final]
	at io.undertow.security.handlers.AuthenticationMechanismsHandler.handleRequest(AuthenticationMechanismsHandler.java:60) [undertow-core-2.2.19.Final.jar:2.2.19.Final]
	at io.undertow.servlet.handlers.security.CachedAuthenticatedSessionHandler.handleRequest(CachedAuthenticatedSessionHandler.java:77) [undertow-servlet-2.2.19.Final.jar:2.2.19.Final]
	at io.undertow.security.handlers.AbstractSecurityContextAssociationHandler.handleRequest(AbstractSecurityContextAssociationHandler.java:43) [undertow-core-2.2.19.Final.jar:2.2.19.Final]
	at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43) [undertow-core-2.2.19.Final.jar:2.2.19.Final]
	at io.undertow.servlet.handlers.SendErrorPageHandler.handleRequest(SendErrorPageHandler.java:52) [undertow-servlet-2.2.19.Final.jar:2.2.19.Final]
	at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43) [undertow-core-2.2.19.Final.jar:2.2.19.Final]
	at io.undertow.servlet.handlers.ServletInitialHandler.handleFirstRequest(ServletInitialHandler.java:275) [undertow-servlet-2.2.19.Final.jar:2.2.19.Final]
	at io.undertow.servlet.handlers.ServletInitialHandler.access$100(ServletInitialHandler.java:79) [undertow-servlet-2.2.19.Final.jar:2.2.19.Final]
	at io.undertow.servlet.handlers.ServletInitialHandler$2.call(ServletInitialHandler.java:134) [undertow-servlet-2.2.19.Final.jar:2.2.19.Final]
	at io.undertow.servlet.handlers.ServletInitialHandler$2.call(ServletInitialHandler.java:131) [undertow-servlet-2.2.19.Final.jar:2.2.19.Final]
	at io.undertow.servlet.core.ServletRequestContextThreadSetupAction$1.call(ServletRequestContextThreadSetupAction.java:48) [undertow-servlet-2.2.19.Final.jar:2.2.19.Final]
	at io.undertow.servlet.core.ContextClassLoaderSetupAction$1.call(ContextClassLoaderSetupAction.java:43) [undertow-servlet-2.2.19.Final.jar:2.2.19.Final]
	at io.undertow.servlet.handlers.ServletInitialHandler.dispatchRequest(ServletInitialHandler.java:255) [undertow-servlet-2.2.19.Final.jar:2.2.19.Final]
	at io.undertow.servlet.handlers.ServletInitialHandler.access$000(ServletInitialHandler.java:79) [undertow-servlet-2.2.19.Final.jar:2.2.19.Final]
	at io.undertow.servlet.handlers.ServletInitialHandler$1.handleRequest(ServletInitialHandler.java:100) [undertow-servlet-2.2.19.Final.jar:2.2.19.Final]
	at io.undertow.server.Connectors.executeRootHandler(Connectors.java:387) [undertow-core-2.2.19.Final.jar:2.2.19.Final]
	at io.undertow.server.HttpServerExchange$1.run(HttpServerExchange.java:852) [undertow-core-2.2.19.Final.jar:2.2.19.Final]
	at org.jboss.threads.ContextClassLoaderSavingRunnable.run(ContextClassLoaderSavingRunnable.java:35) [jboss-threads-3.1.0.Final.jar:3.1.0.Final]
	at org.jboss.threads.EnhancedQueueExecutor.safeRun(EnhancedQueueExecutor.java:2019) [jboss-threads-3.1.0.Final.jar:3.1.0.Final]
	at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.doRunTask(EnhancedQueueExecutor.java:1558) [jboss-threads-3.1.0.Final.jar:3.1.0.Final]
	at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.run(EnhancedQueueExecutor.java:1423) [jboss-threads-3.1.0.Final.jar:3.1.0.Final]
	at org.xnio.XnioWorker$WorkerThreadFactory$1$1.run(XnioWorker.java:1282) [xnio-api-3.8.7.Final.jar:3.8.7.Final]
	at java.lang.Thread.run(Thread.java:748) [?:1.8.0_221]

pom依赖:

<dependency>
  <groupId>com.larksuite.oapi</groupId>
  <artifactId>oapi-sdk</artifactId>
  <version>2.0.2</version>
</dependency>

原因:
com.lark.oapi.event.EventDispatcher类的verifySign(EventReq eventReq)方法,获取timestampnoncesourceSign的值为空。因为eventReq.getHeaderFirstValue(String name)方法参数name被转化成小写了,导致Map<String, List<String>> headers = new HashMap();headers里获取不到值。
feishu-bug-1
feishu-bug-3
feishu-bug-2
feishu-bug-4

希望能尽快解决这个bug,谢谢!

SDK日程操作疑问

1:SDK中创建飞书日程时没有能配置“更新日程是否给日程参与人发送bot通知”的参数,这个问题怎么解决;根据飞书服务端文档描述,OpenApi有相关字段:
image
但是在在SDK中日程对象模型中没有相关参数字段:
image

2:
image
按照飞书服务端文档所描述,这个为区域是通过下图中的字段控制的,
image
但是我在给赋值为Attendee”参与者区域“时,通知到飞书的提示框下方依旧是RSVP: 接受/拒绝/待定区域,是否是我参数有问题导致的还是其他原因。

user/department的delete接口报错。

以user的delete方法为例:

 UserDeleteReqBody userDeleteReq = new UserDeleteReqBody();
            Response<EmptyData> response = getClient(feishuApp).getContactService().getUsers().delete(userDeleteReq).setUserIdType("user_id").setUserId("testUserId").execute();
          

输出:

java.lang.NullPointerException
	at com.larksuite.oapi.core.api.handler.subhandler.UnmarshalRespSubHandler.unmarshalResp(UnmarshalRespSubHandler.java:56)
	at com.larksuite.oapi.core.api.handler.subhandler.UnmarshalRespSubHandler.handle(UnmarshalRespSubHandler.java:37)
	at com.larksuite.oapi.core.api.handler.Handler.handle(Handler.java:50)
	at com.larksuite.oapi.core.api.Api.send(Api.java:14)
	at com.larksuite.oapi.service.contact.v3.ContactService$UserDeleteReqCall.execute(ContactService.java:1010)

该用户实际上是删除成功的,只是回包解析有问题,department的delete方法也是同样。

搜索部门时,报错:user access token is empty

代码如下:
public static DepartmentSearchResult searchDepartments(String query) throws Exception { ContactService contactService = new ContactService(config); DepartmentSearchReqBody searchReqBody = new DepartmentSearchReqBody(); searchReqBody.setQuery(query); Response<DepartmentSearchResult> response = contactService.getDepartments().search(searchReqBody).execute(); System.out.println(Jsons.DEFAULT_GSON.toJson(response)); if (response.getCode() == 0) { return response.getData(); } return null; }
已经执行过testAccessToken方法。
执行searchDepartments报错:user access token is empty

获取用户user_id

使用sdk如何获取指定用户的open_id或者user_id
使用sdk发送卡片消息,现在有demo吗

事件多次推送

image

使用calendarService.setCalendarChangedEventHandler 会多次推送同一个事件

关于消息卡片的model

您好,昨天刚开始接触飞书发消息卡片这块,请问下是否能够提供下消息卡片的实体,这样的话方便使用,
现在都要自己造JSON,而且各种消息卡片类型也不太了解,上手比较费劲,如果有实体 方便了很多

富文本标题设置不生效

public class MessagePostContent {

@SerializedName("text")
private String title;
@SerializedName("content")
private MessagePostElement[][] content;

title这里设置成了text,没有标题样式了

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.